From 5e3d3c7c7dc73c9c8215243855b5fff771096c01 Mon Sep 17 00:00:00 2001 From: Leo Galambos Date: Mon, 10 Aug 2026 22:34:32 +0200 Subject: [PATCH] feat(python): add native distribution and release infrastructure - add the Rust-backed Python API with PyStemmer compatibility - distribute standard compiled models as a separate Python package - generate model artifacts during builds instead of storing them in Git - add GitHub release and Pages-backed package index workflows - add Python tests, benchmarks, documentation, and Gradle integration - refresh the documentation site, branding, and language benchmarks --- .github/RELEASING-PYTHON.md | 52 ++ .github/act/python-models-standard.json | 5 + .github/act/python-native.json | 5 + .github/workflows/pages.yml | 95 ++- .../python-models-standard-release.yml | 235 ++++++ .github/workflows/python-release.yml | 452 ++++++++++++ .gitignore | 21 + .project | 22 - README.md | 105 ++- Radixor.png | Bin 325433 -> 0 bytes build.gradle | 5 +- docs/architecture-and-reduction.md | 24 +- docs/architecture.md | 29 +- docs/assets/data/homepage-performance.json | 50 ++ docs/assets/images/banner.jpg | Bin 553374 -> 0 bytes docs/assets/images/flags/cs.svg | 1 + docs/assets/images/flags/da.svg | 1 + docs/assets/images/flags/de.svg | 1 + docs/assets/images/flags/en.svg | 1 + docs/assets/images/flags/es.svg | 1 + docs/assets/images/flags/fa.svg | 1 + docs/assets/images/flags/fi.svg | 1 + docs/assets/images/flags/fr.svg | 1 + docs/assets/images/flags/he.svg | 1 + docs/assets/images/flags/hu.svg | 1 + docs/assets/images/flags/it.svg | 1 + docs/assets/images/flags/nb.svg | 1 + docs/assets/images/flags/nl.svg | 1 + docs/assets/images/flags/nn.svg | 1 + docs/assets/images/flags/pl.svg | 1 + docs/assets/images/flags/pt.svg | 1 + docs/assets/images/flags/ru.svg | 1 + docs/assets/images/flags/sv.svg | 1 + docs/assets/images/flags/uk.svg | 1 + docs/assets/images/flags/yi.svg | 1 + docs/assets/images/hero-lines.svg | 1 + docs/assets/images/java-award.svg | 1 + docs/assets/images/radixor-logo.png | Bin 0 -> 75919 bytes docs/assets/javascripts/landing-v2.js | 1 + docs/assets/javascripts/mermaid.js | 14 + docs/assets/stylesheets/landing-v2.css | 136 ++++ .../stylesheets/radixor-docs-safety.css | 146 ++++ docs/benchmarks/data/stemming-quality.csv | 22 +- docs/benchmarks/data/stemming-quality.sha256 | 2 +- docs/benchmarks/index.md | 50 +- docs/benchmarks/languages/czech.md | 46 +- docs/benchmarks/languages/danish.md | 58 +- docs/benchmarks/languages/dutch.md | 30 +- docs/benchmarks/languages/english.md | 60 +- docs/benchmarks/languages/finnish.md | 50 +- docs/benchmarks/languages/french.md | 34 +- docs/benchmarks/languages/german.md | 56 +- docs/benchmarks/languages/hebrew.md | 18 +- docs/benchmarks/languages/hungarian.md | 28 +- docs/benchmarks/languages/italian.md | 28 +- docs/benchmarks/languages/norwegian-bokmal.md | 30 +- .../benchmarks/languages/norwegian-nynorsk.md | 24 +- docs/benchmarks/languages/persian.md | 56 +- docs/benchmarks/languages/polish.md | 46 +- docs/benchmarks/languages/portuguese.md | 32 +- docs/benchmarks/languages/russian.md | 28 +- docs/benchmarks/languages/spanish.md | 34 +- docs/benchmarks/languages/swedish.md | 30 +- docs/benchmarks/languages/ukrainian.md | 26 +- docs/benchmarks/languages/yiddish.md | 24 +- docs/benchmarks/reference/english-coverage.md | 20 +- docs/benchmarks/reference/environment.md | 58 +- docs/benchmarks/reference/methodology.md | 6 +- docs/benchmarks/reference/reproducibility.md | 32 +- docs/benchmarks/reference/tested-stemmers.md | 4 +- docs/built-in-languages.md | 62 +- docs/cli-compilation.md | 6 +- docs/dictionary-format.md | 29 +- docs/fast-track.md | 5 +- docs/getting-started.md | 34 + docs/index.md | 58 +- docs/integration-deep-dive.md | 2 +- docs/overrides/landing.html | 133 ++++ docs/programmatic-loading-and-building.md | 5 +- docs/programmatic-usage.md | 7 +- docs/python/fast-track.md | 66 ++ docs/python/index.md | 96 +++ docs/python/installation.md | 215 ++++++ docs/python/model-compilation.md | 105 +++ docs/python/performance.md | 160 ++++ docs/python/quick-start.md | 130 ++++ docs/python/usage.md | 173 +++++ docs/quick-start.md | 7 +- docs/technology-lineage.md | 236 ++++++ docs/why-radixor-is-different.md | 161 +++++ gradle/python.gradle | 303 ++++++++ gradle/snowball-benchmarks.gradle | 28 +- mkdocs.yml | 50 +- python/.gitignore | 23 + python/Cargo.lock | 254 +++++++ python/Cargo.toml | 20 + python/LICENSE | 28 + python/README.md | 253 +++++++ python/benchmarks/README.md | 111 +++ python/benchmarks/corpus.py | 103 +++ python/benchmarks/engines.py | 360 +++++++++ python/benchmarks/requirements-bench.txt | 6 + python/benchmarks/run_benchmark.py | 436 +++++++++++ python/models-standard/LICENSE-MODEL-DATA.txt | 12 + python/models-standard/MANIFEST.in | 4 + python/models-standard/README.md | 26 + python/models-standard/pyproject.toml | 29 + .../radixor_models_standard/__init__.py | 37 + .../models/__init__.py | 32 + .../notices/__init__.py | 32 + python/pyproject.toml | 82 +++ python/radixor/__init__.py | 398 ++++++++++ python/radixor/py.typed | 0 python/scripts/assemble_release.py | 171 +++++ python/scripts/build_standard_distribution.py | 129 ++++ python/scripts/build_standard_models.py | 285 ++++++++ python/scripts/prepare_release_tree.py | 132 ++++ python/scripts/update_simple_index.py | 173 +++++ python/scripts/verify_distributions.py | 598 +++++++++++++++ python/src/builder.rs | 682 ++++++++++++++++++ python/src/dict.rs | 122 ++++ python/src/encoder.rs | 349 +++++++++ python/src/lib.rs | 346 +++++++++ python/src/patch.rs | 494 +++++++++++++ python/src/serial.rs | 450 ++++++++++++ python/src/trie.rs | 294 ++++++++ python/tests/conftest.py | 44 ++ python/tests/test_distribution_verifier.py | 93 +++ python/tests/test_radixor.py | 303 ++++++++ python/tests/test_standard_models.py | 179 +++++ .../benchmark/SnowballLanguageCase.java | 34 + ...allLanguageStemmerComparisonBenchmark.java | 51 +- .../StemmerComparisonBenchmarkQuality.java | 6 + ...anguageStemmerComparisonBenchmarkTest.java | 101 +++ .../quality/QualityStemmerMatrixTest.java | 9 +- tools/parse-python-release-tag.sh | 15 + tools/run-published-accuracy-benchmarks.sh | 78 ++ .../test_update_benchmark_documentation.py | 174 +++++ tools/update-benchmark-documentation.py | 187 +++-- 139 files changed, 11420 insertions(+), 747 deletions(-) create mode 100644 .github/RELEASING-PYTHON.md create mode 100644 .github/act/python-models-standard.json create mode 100644 .github/act/python-native.json create mode 100644 .github/workflows/python-models-standard-release.yml create mode 100644 .github/workflows/python-release.yml delete mode 100644 .project delete mode 100644 Radixor.png create mode 100644 docs/assets/data/homepage-performance.json delete mode 100644 docs/assets/images/banner.jpg create mode 100644 docs/assets/images/flags/cs.svg create mode 100644 docs/assets/images/flags/da.svg create mode 100644 docs/assets/images/flags/de.svg create mode 100644 docs/assets/images/flags/en.svg create mode 100644 docs/assets/images/flags/es.svg create mode 100644 docs/assets/images/flags/fa.svg create mode 100644 docs/assets/images/flags/fi.svg create mode 100644 docs/assets/images/flags/fr.svg create mode 100644 docs/assets/images/flags/he.svg create mode 100644 docs/assets/images/flags/hu.svg create mode 100644 docs/assets/images/flags/it.svg create mode 100644 docs/assets/images/flags/nb.svg create mode 100644 docs/assets/images/flags/nl.svg create mode 100644 docs/assets/images/flags/nn.svg create mode 100644 docs/assets/images/flags/pl.svg create mode 100644 docs/assets/images/flags/pt.svg create mode 100644 docs/assets/images/flags/ru.svg create mode 100644 docs/assets/images/flags/sv.svg create mode 100644 docs/assets/images/flags/uk.svg create mode 100644 docs/assets/images/flags/yi.svg create mode 100644 docs/assets/images/hero-lines.svg create mode 100644 docs/assets/images/java-award.svg create mode 100644 docs/assets/images/radixor-logo.png create mode 100644 docs/assets/javascripts/landing-v2.js create mode 100644 docs/assets/javascripts/mermaid.js create mode 100644 docs/assets/stylesheets/landing-v2.css create mode 100644 docs/assets/stylesheets/radixor-docs-safety.css create mode 100644 docs/getting-started.md create mode 100644 docs/overrides/landing.html create mode 100644 docs/python/fast-track.md create mode 100644 docs/python/index.md create mode 100644 docs/python/installation.md create mode 100644 docs/python/model-compilation.md create mode 100644 docs/python/performance.md create mode 100644 docs/python/quick-start.md create mode 100644 docs/python/usage.md create mode 100644 docs/technology-lineage.md create mode 100644 docs/why-radixor-is-different.md create mode 100644 gradle/python.gradle create mode 100644 python/.gitignore create mode 100644 python/Cargo.lock create mode 100644 python/Cargo.toml create mode 100644 python/LICENSE create mode 100644 python/README.md create mode 100644 python/benchmarks/README.md create mode 100644 python/benchmarks/corpus.py create mode 100644 python/benchmarks/engines.py create mode 100644 python/benchmarks/requirements-bench.txt create mode 100644 python/benchmarks/run_benchmark.py create mode 100644 python/models-standard/LICENSE-MODEL-DATA.txt create mode 100644 python/models-standard/MANIFEST.in create mode 100644 python/models-standard/README.md create mode 100644 python/models-standard/pyproject.toml create mode 100644 python/models-standard/radixor_models_standard/__init__.py create mode 100644 python/models-standard/radixor_models_standard/models/__init__.py create mode 100644 python/models-standard/radixor_models_standard/notices/__init__.py create mode 100644 python/pyproject.toml create mode 100644 python/radixor/__init__.py create mode 100644 python/radixor/py.typed create mode 100755 python/scripts/assemble_release.py create mode 100644 python/scripts/build_standard_distribution.py create mode 100644 python/scripts/build_standard_models.py create mode 100755 python/scripts/prepare_release_tree.py create mode 100755 python/scripts/update_simple_index.py create mode 100644 python/scripts/verify_distributions.py create mode 100644 python/src/builder.rs create mode 100644 python/src/dict.rs create mode 100644 python/src/encoder.rs create mode 100644 python/src/lib.rs create mode 100644 python/src/patch.rs create mode 100644 python/src/serial.rs create mode 100644 python/src/trie.rs create mode 100644 python/tests/conftest.py create mode 100644 python/tests/test_distribution_verifier.py create mode 100644 python/tests/test_radixor.py create mode 100644 python/tests/test_standard_models.py create mode 100644 src/test/java/org/egothor/stemmer/benchmark/SnowballLanguageStemmerComparisonBenchmarkTest.java create mode 100755 tools/parse-python-release-tag.sh create mode 100755 tools/run-published-accuracy-benchmarks.sh create mode 100644 tools/tests/test_update_benchmark_documentation.py diff --git a/.github/RELEASING-PYTHON.md b/.github/RELEASING-PYTHON.md new file mode 100644 index 0000000..5853038 --- /dev/null +++ b/.github/RELEASING-PYTHON.md @@ -0,0 +1,52 @@ +# Releasing the Python distributions + +This maintainer-only checklist is intentionally outside the public MkDocs site. + +The repository descriptors use `0.0.0` as a non-release placeholder. Release +workflows create isolated staging trees and inject the stable version selected +by the tag. Never commit a release-number rewrite of the descriptors. +Standard `.rxc` resources are also generated in that staging tree from the +canonical model sources. The workflow compiles every model twice and rejects +non-deterministic output; generated payload must never be added to Git. + +## Validate without publishing + +Run **Python Standard Models Release** manually with version `1.0.0`, then run +**Python Native Release** with version `4.1.0`. `workflow_dispatch` validates +artifacts but cannot publish. The native run must pass Linux x86-64, Linux +ARM64, macOS universal2, and Windows x86-64. + +For the Linux paths, maintainers can use `act` with rootless Podman and the +event files under `.github/act/`. Do not pass production secrets to `act`. + +## Publish in dependency order + +Both tags must point to a commit already contained in `main`. + +```bash +git tag -a 'python-models-standard@1.0.0' \ + -m 'Python standard models 1.0.0' +git push origin 'python-models-standard@1.0.0' +``` + +Wait until the models workflow has published its GitHub Release and Pages +index entry. Then publish the native distribution: + +```bash +git tag -a 'python@4.1.0' -m 'Python Radixor 4.1.0' +git push origin 'python@4.1.0' +``` + +Do not push both tags together: native publication requires the standard-model +release to exist first. + +## Artifact identity + +Python releases use `SHA256SUMS` and GitHub keyless build-provenance +attestations. They do not use the Java Maven OpenPGP key. Verify a downloaded +artifact with: + +```bash +sha256sum --check SHA256SUMS +gh attestation verify --repo leogalambos/Radixor +``` diff --git a/.github/act/python-models-standard.json b/.github/act/python-models-standard.json new file mode 100644 index 0000000..7dfd71e --- /dev/null +++ b/.github/act/python-models-standard.json @@ -0,0 +1,5 @@ +{ + "inputs": { + "version": "1.0.0" + } +} diff --git a/.github/act/python-native.json b/.github/act/python-native.json new file mode 100644 index 0000000..0dfd6f0 --- /dev/null +++ b/.github/act/python-native.json @@ -0,0 +1,5 @@ +{ + "inputs": { + "version": "4.1.0" + } +} diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 51bd56e..9c39517 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -26,42 +26,43 @@ on: workflow_dispatch: permissions: - contents: write + contents: read concurrency: - group: pages-${{ github.ref }} - cancel-in-progress: true + group: github-python-pages + cancel-in-progress: false jobs: - publish-pages: - name: Publish static reports + build-pages: + name: Build static reports runs-on: ubuntu-latest steps: - name: Check out source repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 + persist-credentials: false - name: Validate Gradle wrapper - uses: gradle/actions/wrapper-validation@v4 + uses: gradle/actions/wrapper-validation@0b6dd653ba04f4f93bf581ec31e66cbd7dcb644d # v4 - name: Set up Temurin JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4 with: distribution: temurin java-version: '21' - name: Set up Gradle caching and instrumentation - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@0b6dd653ba04f4f93bf581ec31e66cbd7dcb644d # v4 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: - python-version: '3.x' + python-version: '3.14.6' - name: Install MkDocs Material - run: python -m pip install --upgrade pip mkdocs-material + run: python -m pip install --disable-pip-version-check mkdocs-material==9.7.6 - name: Verify reproducibility inputs shell: bash @@ -326,18 +327,80 @@ jobs: run: | set -euo pipefail mkdocs build --strict --config-file build/mkdocs/mkdocs.yml - rsync -a --delete --exclude '.git' --exclude '.git/' --exclude 'builds/' build/mkdocs-site/ .gh-pages/ + rsync -a --delete --exclude '.git' --exclude '.git/' --exclude 'builds/' --exclude 'python/' build/mkdocs-site/ .gh-pages/ mkdir -p .gh-pages/builds cp build/mkdocs-site/builds/index.html .gh-pages/builds/index.html cat > .gh-pages/.nojekyll <&2 + exit 1 + fi + - name: Upload static-site candidate + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: pages-site-${{ github.run_id }} + path: .gh-pages + if-no-files-found: error + include-hidden-files: true + retention-days: 1 + + publish-pages: + name: Publish static reports + needs: build-pages + runs-on: ubuntu-latest + environment: python-github-pages + permissions: + contents: write + + steps: + - name: Check out repository for publication + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 + path: pages + + - name: Select or initialize gh-pages + shell: bash + run: | + set -euo pipefail + cd pages + if git ls-remote --exit-code --heads origin gh-pages >/dev/null 2>&1; then + git fetch origin gh-pages:refs/remotes/origin/gh-pages + git checkout -B gh-pages origin/gh-pages + else + git checkout --orphan gh-pages + git rm -rf . + fi + + - name: Download static-site candidate + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: pages-site-${{ github.run_id }} + path: candidate + + - name: Validate and publish static site + shell: bash + run: | + set -euo pipefail + test ! -e candidate/python + test ! -e candidate/.git + if find candidate -type l -print -quit | grep -q .; then + echo 'Publication candidate contains a symbolic link.' >&2 + exit 1 + fi + rsync -a --delete \ + --exclude '.git' --exclude '.git/' \ + --exclude 'python' --exclude 'python/' candidate/ pages/ + cd pages git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" @@ -348,4 +411,4 @@ jobs: fi git commit -m "Publish reports for run ${GITHUB_RUN_NUMBER}" - git push origin gh-pages + git push origin HEAD:gh-pages diff --git a/.github/workflows/python-models-standard-release.yml b/.github/workflows/python-models-standard-release.yml new file mode 100644 index 0000000..4912d86 --- /dev/null +++ b/.github/workflows/python-models-standard-release.yml @@ -0,0 +1,235 @@ +name: Python Standard Models Release + +on: + push: + tags: + - 'python-models-standard@*' + workflow_dispatch: + inputs: + version: + description: Stable distribution version to validate without publishing + required: true + default: '1.0.0' + type: string + +permissions: + contents: read + +concurrency: + group: github-python-pages + cancel-in-progress: false + +jobs: + build: + name: Build and verify standard models + runs-on: ubuntu-latest + outputs: + version: ${{ steps.release.outputs.version }} + tag: ${{ steps.release.outputs.tag }} + + steps: + - name: Check out repository + if: ${{ env.ACT != 'true' }} + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: '3.12.13' + + - name: Install pinned Rust toolchain + uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c + with: + toolchain: 1.88.0 + + - name: Select and validate release + id: release + shell: bash + env: + REQUESTED_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + if [[ "${GITHUB_EVENT_NAME}" == 'push' ]]; then + tag="${GITHUB_REF_NAME}" + else + tag="python-models-standard@${REQUESTED_VERSION}" + fi + eval "$(./tools/parse-python-release-tag.sh "${tag}")" + [[ "${PYTHON_DISTRIBUTION}" == 'radixor-models-standard' ]] + if [[ "${GITHUB_EVENT_NAME}" == 'push' ]]; then + [[ "$(git rev-parse "${tag}^{commit}")" == "${GITHUB_SHA}" ]] + git merge-base --is-ancestor "${GITHUB_SHA}" origin/main + fi + printf 'version=%s\ntag=%s\n' "${PYTHON_VERSION}" "${tag}" >> "${GITHUB_OUTPUT}" + + - name: Install pinned build tools + run: >- + python -m pip install --disable-pip-version-check + maturin==1.14.1 setuptools==80.9.0 wheel==0.45.1 + + - name: Compile models and build isolated release tree + shell: bash + run: | + set -euo pipefail + rm -rf build/python-release + mkdir -p build/python-release/compiler-wheel build/python-release/compiler-runtime + maturin build --release --locked --manifest-path python/Cargo.toml \ + --out build/python-release/compiler-wheel + python -c "from pathlib import Path; import zipfile; wheels=list(Path('build/python-release/compiler-wheel').glob('*.whl')); assert len(wheels) == 1; zipfile.ZipFile(wheels[0]).extractall('build/python-release/compiler-runtime')" + PYTHONPATH=build/python-release/compiler-runtime \ + python python/scripts/build_standard_models.py \ + --project build/python-release/models-standard \ + --distribution-version '${{ steps.release.outputs.version }}' + python python/scripts/build_standard_distribution.py \ + --project build/python-release/models-standard \ + --outdir build/python-release/artifacts + + - name: Verify archives and offline installation + shell: bash + run: | + set -euo pipefail + python python/scripts/verify_distributions.py \ + --standard-dir build/python-release/artifacts \ + --standard-version '${{ steps.release.outputs.version }}' + python python/scripts/assemble_release.py \ + models-standard '${{ steps.release.outputs.version }}' \ + build/python-release/artifacts build/python-release/release + python -m venv build/python-release/venv + build/python-release/venv/bin/python -m pip install \ + --no-index --find-links build/python-release/release \ + radixor-models-standard + build/python-release/venv/bin/python -c \ + "from importlib import resources; assert resources.files('radixor_models_standard').joinpath('manifest.json').is_file()" + + - name: Prepare complete PEP 503 index candidate + shell: bash + run: | + set -euo pipefail + index_root='build/python-release/index/python/simple' + if [[ "${ACT:-false}" != 'true' ]] && git ls-remote --exit-code --heads origin gh-pages >/dev/null 2>&1; then + git fetch origin gh-pages:refs/remotes/origin/gh-pages + git worktree add --detach build/python-release/pages origin/gh-pages + mkdir -p "$(dirname "${index_root}")" + if [[ -d build/python-release/pages/python/simple ]]; then + cp -R build/python-release/pages/python/simple "${index_root}" + fi + fi + python python/scripts/update_simple_index.py \ + --root "${index_root}" \ + --repository "${GITHUB_REPOSITORY}" \ + --package radixor-models-standard \ + --version '${{ steps.release.outputs.version }}' \ + --tag '${{ steps.release.outputs.tag }}' \ + --artifacts build/python-release/release + + - name: Upload verified release candidate + if: ${{ env.ACT != 'true' }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: python-models-standard-release-${{ steps.release.outputs.version }} + path: build/python-release/release/* + if-no-files-found: error + retention-days: 14 + + - name: Upload package-index candidate + if: ${{ env.ACT != 'true' }} + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: python-models-standard-index-${{ steps.release.outputs.version }} + path: build/python-release/index/python/simple + if-no-files-found: error + retention-days: 14 + + publish: + name: Publish immutable GitHub Release + if: github.event_name == 'push' + needs: build + runs-on: ubuntu-latest + environment: python-github-release + permissions: + contents: write + id-token: write + attestations: write + + steps: + - name: Download verified release candidate + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: python-models-standard-release-${{ needs.build.outputs.version }} + path: release + + - name: Verify release inventory + shell: bash + run: | + set -euo pipefail + cd release + sha256sum --check SHA256SUMS + awk '{print $2}' SHA256SUMS | LC_ALL=C sort > expected-files + find . -maxdepth 1 -type f \( -name '*.whl' -o -name '*.tar.gz' \) \ + -printf '%f\n' | LC_ALL=C sort > actual-files + diff -u expected-files actual-files + rm expected-files actual-files + + - name: Attest package artifacts + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 + with: + subject-path: | + release/*.whl + release/*.tar.gz + + - name: Create and publish draft release exactly once + shell: bash + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ needs.build.outputs.tag }} + RELEASE_VERSION: ${{ needs.build.outputs.version }} + run: | + set -euo pipefail + if gh release view "${RELEASE_TAG}" >/dev/null 2>&1; then + echo "Release already exists; refusing to replace its assets: ${RELEASE_TAG}" >&2 + exit 1 + fi + gh release create "${RELEASE_TAG}" \ + release/*.whl release/*.tar.gz release/SHA256SUMS \ + --verify-tag --draft --title "radixor-models-standard ${RELEASE_VERSION}" \ + --notes "Precompiled standard Radixor model distribution ${RELEASE_VERSION}." + gh release edit "${RELEASE_TAG}" --draft=false + + publish-index: + name: Publish Python package index + if: github.event_name == 'push' + needs: [build, publish] + runs-on: ubuntu-latest + environment: python-github-pages + permissions: + contents: write + + steps: + - name: Check out gh-pages only + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + ref: gh-pages + path: pages + + - name: Download validated index candidate + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: python-models-standard-index-${{ needs.build.outputs.version }} + path: candidate + + - name: Commit package index + shell: bash + run: | + set -euo pipefail + mkdir -p pages/python/simple + rsync -a --delete candidate/ pages/python/simple/ + cd pages + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add python/simple + git diff --cached --quiet && exit 0 + git commit -m 'Index radixor-models-standard ${{ needs.build.outputs.version }}' + git push origin HEAD:gh-pages diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml new file mode 100644 index 0000000..cebdd7a --- /dev/null +++ b/.github/workflows/python-release.yml @@ -0,0 +1,452 @@ +name: Python Native Release + +on: + push: + tags: + - 'python@*' + workflow_dispatch: + inputs: + version: + description: Stable distribution version to validate without publishing + required: true + default: '4.1.0' + type: string + +permissions: + contents: read + +concurrency: + group: github-python-pages + cancel-in-progress: false + +jobs: + prepare: + name: Prepare versioned sources and sdist + runs-on: ubuntu-latest + outputs: + version: ${{ steps.release.outputs.version }} + tag: ${{ steps.release.outputs.tag }} + + steps: + - name: Check out repository + if: ${{ env.ACT != 'true' }} + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: '3.12.13' + + - name: Install pinned Rust toolchain + uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c + with: + toolchain: 1.88.0 + + - name: Select and validate release + id: release + shell: bash + env: + REQUESTED_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + if [[ "${GITHUB_EVENT_NAME}" == 'push' ]]; then + tag="${GITHUB_REF_NAME}" + else + tag="python@${REQUESTED_VERSION}" + fi + eval "$(./tools/parse-python-release-tag.sh "${tag}")" + [[ "${PYTHON_DISTRIBUTION}" == 'radixor' ]] + if [[ "${GITHUB_EVENT_NAME}" == 'push' ]]; then + [[ "$(git rev-parse "${tag}^{commit}")" == "${GITHUB_SHA}" ]] + git merge-base --is-ancestor "${GITHUB_SHA}" origin/main + fi + printf 'version=%s\ntag=%s\n' "${PYTHON_VERSION}" "${tag}" >> "${GITHUB_OUTPUT}" + + - name: Install pinned source-build tools + run: >- + python -m pip install --disable-pip-version-check + maturin==1.14.1 setuptools==80.9.0 wheel==0.45.1 + + - name: Materialize versioned native source + shell: bash + run: | + set -euo pipefail + rm -rf build/python-release + python python/scripts/prepare_release_tree.py \ + native '${{ steps.release.outputs.version }}' \ + build/python-release/native-source + + - name: Build and verify source distributions + shell: bash + run: | + set -euo pipefail + mkdir -p build/python-release/compiler-wheel build/python-release/compiler-runtime \ + build/python-release/native-sdist build/python-release/models + maturin build --release --locked \ + --manifest-path build/python-release/native-source/Cargo.toml \ + --out build/python-release/compiler-wheel + python -c "from pathlib import Path; import zipfile; wheels=list(Path('build/python-release/compiler-wheel').glob('*.whl')); assert len(wheels) == 1; zipfile.ZipFile(wheels[0]).extractall('build/python-release/compiler-runtime')" + PYTHONPATH=build/python-release/compiler-runtime \ + python python/scripts/build_standard_models.py \ + --project build/python-release/models-source \ + --distribution-version 1.0.0 + maturin sdist --manifest-path build/python-release/native-source/Cargo.toml \ + --out build/python-release/native-sdist + python python/scripts/build_standard_distribution.py \ + --project build/python-release/models-source \ + --outdir build/python-release/models + python python/scripts/verify_distributions.py \ + --standard-dir build/python-release/models \ + --standard-version 1.0.0 + PYTHONPATH=python/scripts python -c \ + "from pathlib import Path; from verify_distributions import _verify_main_sdist; _verify_main_sdist(next(Path('build/python-release/native-sdist').glob('*.tar.gz')), '${{ steps.release.outputs.version }}')" + + - name: Upload versioned native source + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: native-source-${{ steps.release.outputs.version }} + path: | + build/python-release/native-source + !build/python-release/native-source/target/** + if-no-files-found: error + retention-days: 1 + + - name: Upload native sdist + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: native-sdist-${{ steps.release.outputs.version }} + path: build/python-release/native-sdist/*.tar.gz + if-no-files-found: error + retention-days: 14 + + - name: Upload verified model fixture + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: native-model-fixture-${{ steps.release.outputs.version }} + path: build/python-release/models/* + if-no-files-found: error + retention-days: 1 + + build-linux-x86-64: + name: Build Linux x86-64 wheel + needs: prepare + runs-on: ubuntu-latest + + steps: + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: '3.12.13' + + - name: Install pinned Rust toolchain + uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c + with: + toolchain: 1.88.0 + + - name: Download versioned native source + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: native-source-${{ needs.prepare.outputs.version }} + path: build/python-release/native-source + + - name: Download model fixture + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: native-model-fixture-${{ needs.prepare.outputs.version }} + path: build/python-release/models + + - name: Select Linux build isolation + id: linux-isolation + shell: bash + run: | + if [[ "${ACT:-}" == 'true' ]]; then + echo 'manylinux=off' >> "${GITHUB_OUTPUT}" + echo 'container=' >> "${GITHUB_OUTPUT}" + else + echo 'manylinux=auto' >> "${GITHUB_OUTPUT}" + echo 'container=quay.io/pypa/manylinux2014_x86_64@sha256:0a42cb7e5f4ba6bbfb8d0a86d1aab0c8876ba9c3be16bd99360ae42bf010ec77' >> "${GITHUB_OUTPUT}" + fi + + - name: Build manylinux wheel + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1 + with: + command: build + target: x86_64 + manylinux: ${{ steps.linux-isolation.outputs.manylinux }} + container: ${{ steps.linux-isolation.outputs.container }} + maturin-version: v1.14.1 + rust-toolchain: 1.88.0 + working-directory: build/python-release/native-source + args: --release --locked --out ../wheel + + - name: Smoke-test wheel with standard models + shell: bash + run: | + set -euo pipefail + python -m pip install --no-index \ + --find-links build/python-release/wheel \ + --find-links build/python-release/models radixor + python -c "from radixor import Stemmer; assert Stemmer('en').stem('running') == 'run'" + + - name: Upload Linux x86-64 wheel + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: native-wheel-linux-x86-64-${{ needs.prepare.outputs.version }} + path: build/python-release/wheel/*.whl + if-no-files-found: error + retention-days: 14 + + build-platform-wheels: + name: Build ${{ matrix.name }} wheel + needs: prepare + strategy: + fail-fast: false + matrix: + include: + - name: Linux aarch64 + os: ubuntu-24.04-arm + target: aarch64 + manylinux: auto + container: quay.io/pypa/manylinux2014_aarch64@sha256:63bfa74be47f0277e998cb7c1b571b27664ac848bb356b0f4588438f930285dd + artifact: linux-aarch64 + - name: macOS universal2 + os: macos-14 + target: universal2-apple-darwin + manylinux: 'off' + container: '' + artifact: macos-universal2 + - name: Windows x86-64 + os: windows-2022 + target: x86_64-pc-windows-msvc + manylinux: 'off' + container: '' + artifact: windows-x86-64 + runs-on: ${{ matrix.os }} + + steps: + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: '3.12.13' + + - name: Install pinned Rust toolchain + uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c + with: + toolchain: 1.88.0 + + - name: Download versioned native source + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: native-source-${{ needs.prepare.outputs.version }} + path: build/python-release/native-source + + - name: Download model fixture + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: native-model-fixture-${{ needs.prepare.outputs.version }} + path: build/python-release/models + + - name: Build platform wheel + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1 + with: + command: build + target: ${{ matrix.target }} + manylinux: ${{ matrix.manylinux }} + container: ${{ matrix.container }} + maturin-version: v1.14.1 + rust-toolchain: 1.88.0 + working-directory: build/python-release/native-source + args: --release --locked --out ../wheel + + - name: Smoke-test wheel with standard models + shell: bash + run: | + set -euo pipefail + python -m pip install --no-index \ + --find-links build/python-release/wheel \ + --find-links build/python-release/models radixor + python -c "from radixor import Stemmer; assert Stemmer('en').stem('running') == 'run'" + + - name: Upload platform wheel + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: native-wheel-${{ matrix.artifact }}-${{ needs.prepare.outputs.version }} + path: build/python-release/wheel/*.whl + if-no-files-found: error + retention-days: 14 + + assemble: + name: Assemble verified release + needs: [prepare, build-linux-x86-64, build-platform-wheels] + runs-on: ubuntu-latest + + steps: + - name: Check out repository + if: ${{ env.ACT != 'true' }} + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: '3.12.13' + + - name: Download native artifacts only + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + pattern: native-wheel-* + path: build/python-release/artifacts + merge-multiple: true + + - name: Download native sdist + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: native-sdist-${{ needs.prepare.outputs.version }} + path: build/python-release/artifacts + + - name: Enforce release allowlist and checksums + run: >- + python python/scripts/assemble_release.py native + '${{ needs.prepare.outputs.version }}' + build/python-release/artifacts build/python-release/release + + - name: Prepare complete PEP 503 index candidate + shell: bash + run: | + set -euo pipefail + index_root='build/python-release/index/python/simple' + if git ls-remote --exit-code --heads origin gh-pages >/dev/null 2>&1; then + git fetch origin gh-pages:refs/remotes/origin/gh-pages + git worktree add --detach build/python-release/pages origin/gh-pages + mkdir -p "$(dirname "${index_root}")" + if [[ -d build/python-release/pages/python/simple ]]; then + cp -R build/python-release/pages/python/simple "${index_root}" + fi + fi + python python/scripts/update_simple_index.py \ + --root "${index_root}" \ + --repository "${GITHUB_REPOSITORY}" \ + --package radixor \ + --version '${{ needs.prepare.outputs.version }}' \ + --tag '${{ needs.prepare.outputs.tag }}' \ + --artifacts build/python-release/release + + - name: Upload verified release candidate + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: python-native-release-${{ needs.prepare.outputs.version }} + path: build/python-release/release/* + if-no-files-found: error + retention-days: 14 + + - name: Upload package-index candidate + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: python-native-index-${{ needs.prepare.outputs.version }} + path: build/python-release/index/python/simple + if-no-files-found: error + retention-days: 14 + + publish: + name: Publish immutable GitHub Release + if: github.event_name == 'push' + needs: [prepare, assemble] + runs-on: ubuntu-latest + environment: python-github-release + permissions: + contents: write + id-token: write + attestations: write + + steps: + - name: Download verified release candidate + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: python-native-release-${{ needs.prepare.outputs.version }} + path: release + + - name: Verify release inventory + shell: bash + run: | + set -euo pipefail + cd release + sha256sum --check SHA256SUMS + awk '{print $2}' SHA256SUMS | LC_ALL=C sort > expected-files + find . -maxdepth 1 -type f \( -name '*.whl' -o -name '*.tar.gz' \) \ + -printf '%f\n' | LC_ALL=C sort > actual-files + diff -u expected-files actual-files + rm expected-files actual-files + + - name: Require published standard models + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + [[ "$(gh release view 'python-models-standard@1.0.0' --json isDraft --jq '.isDraft')" == 'false' ]] + + - name: Attest package artifacts + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 + with: + subject-path: | + release/*.whl + release/*.tar.gz + + - name: Create and publish draft release exactly once + shell: bash + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ needs.prepare.outputs.tag }} + RELEASE_VERSION: ${{ needs.prepare.outputs.version }} + run: | + set -euo pipefail + if gh release view "${RELEASE_TAG}" >/dev/null 2>&1; then + echo "Release already exists; refusing to replace its assets: ${RELEASE_TAG}" >&2 + exit 1 + fi + gh release create "${RELEASE_TAG}" \ + release/*.whl release/*.tar.gz release/SHA256SUMS \ + --verify-tag --draft --title "radixor ${RELEASE_VERSION}" \ + --notes "Native Rust/Python Radixor distribution ${RELEASE_VERSION}." + gh release edit "${RELEASE_TAG}" --draft=false + + publish-index: + name: Publish Python package index + if: github.event_name == 'push' + needs: [prepare, assemble, publish] + runs-on: ubuntu-latest + environment: python-github-pages + permissions: + contents: write + + steps: + - name: Check out gh-pages only + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + ref: gh-pages + path: pages + + - name: Download validated index candidate + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: python-native-index-${{ needs.prepare.outputs.version }} + path: candidate + + - name: Commit package index + shell: bash + run: | + set -euo pipefail + mkdir -p pages/python/simple + rsync -a --delete candidate/ pages/python/simple/ + cd pages + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add python/simple + git diff --cached --quiet && exit 0 + git commit -m 'Index radixor ${{ needs.prepare.outputs.version }}' + git push origin HEAD:gh-pages diff --git a/.gitignore b/.gitignore index fff4645..74bbfe2 100644 --- a/.gitignore +++ b/.gitignore @@ -88,6 +88,9 @@ local.properties # Typically, this file would be tracked if it contains build/dependency configurations: #.project +# Eclipse and Buildship create project descriptors during import. +**/.project + # PMD plugin conf .pmd @@ -109,3 +112,21 @@ gradle-app.setting # Gradle task-name cache .gradletasknamecache + +##---------------------------------------------------------------------------------------- Python tooling +__pycache__/ +*.py[cod] +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ +.coverage +.coverage.* +htmlcov/ + +# Workspace-local Rust/Python build and test scratch directories. +/.cargo-target/ +/pytest-of-*/ + +# tempfile.NamedTemporaryFile-style dictionary scratch files must not survive +# as repository candidates when a process is interrupted. +/tmp*.gz diff --git a/.project b/.project deleted file mode 100644 index 5da9344..0000000 --- a/.project +++ /dev/null @@ -1,22 +0,0 @@ - - - Radixor - - - - org.eclipse.jdt.core.javanature - org.eclipse.buildship.core.gradleprojectnature - - - - org.eclipse.jdt.core.javabuilder - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - diff --git a/README.md b/README.md index bec06d2..a693307 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ -Radixor banner +

+ Radixor logo +

[![License](https://img.shields.io/github/license/leogalambos/Radixor)](LICENSE) [![Java](https://img.shields.io/badge/Java-21%2B-brightgreen)](#) +[![Python](https://img.shields.io/badge/Python-3.9%2B-1769ef)](docs/python/fast-track.md) [![Maven Central](https://img.shields.io/maven-central/v/org.egothor/radixor)](https://central.sonatype.com/artifact/org.egothor/radixor) [![Published reports](https://img.shields.io/badge/reports-GitHub%20Pages-blue)](https://leogalambos.github.io/Radixor/builds/latest/) [![Quality gates](https://github.com/leogalambos/Radixor/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/leogalambos/Radixor/actions/workflows/build.yml) [![Coverage](https://img.shields.io/endpoint?url=https://leogalambos.github.io/Radixor/builds/latest/metrics/coverage-badge.json)](https://leogalambos.github.io/Radixor/builds/latest/coverage/) [![Mutation score](https://img.shields.io/endpoint?url=https://leogalambos.github.io/Radixor/builds/latest/metrics/pitest-badge.json)](https://leogalambos.github.io/Radixor/builds/latest/pitest/) -*Deterministic, multi-language stemming for Java, built around compact dictionary-derived patch-command tries with an explicit quality/speed trade-off.* +*Deterministic, multi-language stemming for Java and Python, built around compact dictionary-trained patch-command tries with an explicit quality/speed trade-off.* -**Radixor** is a modern multi-language stemming toolkit for Java in the tradition of the original **Egothor** approach. It learns compact word-to-stem transformations from dictionary data, stores them in compiled patch-command tries, and exposes a runtime model designed for speed, determinism, and operational simplicity. Unlike a closed-form dictionary lookup stemmer, Radixor can also generalize beyond explicitly listed word forms. +**Radixor** is a modern multi-language stemming toolkit for Java and Python in the tradition of the original **Egothor** approach. It learns compact word-to-stem transformations from dictionary data, stores them in compiled patch-command tries, and exposes native runtime implementations designed for speed, determinism, and operational simplicity. Unlike a closed-form dictionary lookup stemmer, Radixor can also generalize beyond explicitly listed word forms. It is particularly well suited to systems that need stemming which is: @@ -22,7 +25,35 @@ It is particularly well suited to systems that need stemming which is: It also retains the operational advantages of a compiled artifact model: predictable runtime behavior, direct binary loading, and clear separation between preparation-time compilation and live request processing. -## Add Radixor and a model +## Choose a runtime + +For Python, one installation provides the native runtime and the separate +standard package of 20 precompiled models: + +From PyPI, once publication is enabled: + +```bash +python -m pip install --only-binary=:all: radixor +``` + +Or from the GitHub Releases-backed index: + +```bash +python -m pip install --only-binary=:all: \ + --index-url https://leogalambos.github.io/Radixor/python/simple/ radixor +``` + +```python +from radixor import Stemmer + +english = Stemmer("en") +print(english.stemWord("running")) # run +``` + +Continue with the [Python Fast Track](docs/python/fast-track.md) or +[Python Quick Start](docs/python/quick-start.md). + +### Java dependencies The core artifact contains the algorithm and registry, but no language dictionary. Add either one minimal model or the optional standard default pack: @@ -87,16 +118,16 @@ Radixor performance is best read together with stemming quality. The English dic | Used rows | Actual row ratio | All exact | Changed exact | Root preserved | Speed ms/op | Error ms | ns/token | | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | -| 100% | 100.000% | 97.478% | 97.197% | 97.552% | 20.627 | 2.117 | 98.0 | -| 90% | 90.000% | 97.047% | 94.913% | 97.613% | 21.713 | 2.104 | 103.2 | -| 80% | 80.000% | 96.635% | 92.768% | 97.661% | 17.408 | 1.438 | 82.7 | -| 70% | 70.000% | 96.209% | 90.565% | 97.705% | 16.946 | 1.531 | 80.5 | -| 60% | 60.000% | 95.750% | 88.384% | 97.703% | 15.735 | 1.278 | 74.8 | -| 50% | 50.000% | 95.262% | 86.107% | 97.690% | 14.714 | 1.089 | 69.9 | -| 40% | 40.000% | 94.753% | 83.855% | 97.643% | 15.090 | 1.254 | 71.7 | -| 30% | 30.000% | 94.208% | 81.651% | 97.537% | 13.773 | 1.071 | 65.4 | -| 20% | 20.000% | 93.633% | 79.366% | 97.416% | 15.396 | 2.497 | 73.1 | -| 10% | 10.000% | 92.868% | 76.516% | 97.204% | 16.970 | 2.847 | 80.6 | +| 100% | 100.000% | 97.478% | 97.197% | 97.552% | 15.064 | 0.658 | 71.6 | +| 90% | 90.000% | 97.047% | 94.913% | 97.613% | 17.798 | 2.161 | 84.6 | +| 80% | 80.000% | 96.635% | 92.768% | 97.661% | 13.900 | 0.941 | 66.0 | +| 70% | 70.000% | 96.209% | 90.565% | 97.705% | 14.809 | 1.376 | 70.3 | +| 60% | 60.000% | 95.750% | 88.384% | 97.703% | 13.186 | 0.930 | 62.6 | +| 50% | 50.000% | 95.262% | 86.107% | 97.690% | 12.852 | 0.943 | 61.1 | +| 40% | 40.000% | 94.753% | 83.855% | 97.643% | 12.358 | 0.831 | 58.7 | +| 30% | 30.000% | 94.208% | 81.651% | 97.537% | 11.657 | 0.921 | 55.4 | +| 20% | 20.000% | 93.633% | 79.366% | 97.416% | 11.494 | 1.256 | 54.6 | +| 10% | 10.000% | 92.868% | 76.516% | 97.204% | 9.895 | 0.925 | 47.0 | Column meanings: @@ -109,7 +140,7 @@ Column meanings: - `Error ms` is the JMH score error converted to milliseconds. - `ns/token` is average nanoseconds per changed token in that operation. -The contracted trie result is materially stronger than the older uncontracted profile: full English coverage reaches 97.478% all-token exactness and 97.197% changed-token exactness at 98.0 ns/token, while even a 10% deterministic dictionary slice remains at 92.868% all-token exactness and 76.516% changed-token exactness at 80.6 ns/token. This is why Radixor benchmark results are documented with both speed and quality instead of a single Porter speed badge. +The contracted trie result is materially stronger than the older uncontracted profile: full English coverage reaches 97.478% all-token exactness and 97.197% changed-token exactness at 71.6 ns/token, while even a 10% deterministic dictionary slice remains at 92.868% all-token exactness and 76.516% changed-token exactness at 47.0 ns/token. This is why Radixor benchmark results are documented with both speed and quality instead of a single Porter speed badge. For benchmark scope, workload design, environment, commands, report locations, and interpretation guidance, see [Benchmarking](docs/benchmarking.md). @@ -182,13 +213,22 @@ The repository keeps the front page concise and places detailed documentation un ### Getting Started -- [Fast Track](docs/fast-track.md) - The shortest path from adding core plus a model artifact to getting a first stem. +- [Python Fast Track](docs/python/fast-track.md) + The shortest path from `pip install` to the first native Python stem. -- [Quick Start](docs/quick-start.md) - A broader developer walkthrough covering loading options, querying, extension, persistence, and metadata. +- [Java Fast Track](docs/fast-track.md) + The shortest Java path from adding core plus a model artifact to getting a first stem. -- [Integration Deep Dive](docs/integration-deep-dive.md) +- [Python Quick Start](docs/python/quick-start.md) + Installation, standard models, batch use, PyStemmer migration, and deployment guidance. + +- [Java Quick Start](docs/quick-start.md) + A broader Java walkthrough covering loading options, querying, extension, persistence, and metadata. + +- [Python Overview](docs/python/index.md) + Runtime architecture, model packaging, API capabilities, and Java interoperability. + +- [Java Integration Deep Dive](docs/integration-deep-dive.md) Dependency setup, model selection, production lifecycle, search-pipeline guidance, and operational checklist. - [Built-in Languages](docs/built-in-languages.md) @@ -197,10 +237,29 @@ The repository keeps the front page concise and places detailed documentation un - [Dictionary Format](docs/dictionary-format.md) How to write and normalize stemming dictionaries. -- [Compilation (CLI tool)](docs/cli-compilation.md) - How to compile dictionaries into deployable binary artifacts. +- [Java CLI Compilation](docs/cli-compilation.md) + How to compile dictionaries into deployable binary artifacts from Java. -### Programmatic Usage +### Python + +The Python installation installs the native package together with the pure +`radixor-models-standard` 1.x distribution of the 2026.1 catalog: 20 precompiled v7 models, excluding +the optional PoliMorf model. Python runtime distributions contain no textual +dictionaries. + +- [Installation and Builds](docs/python/installation.md) + Wheels, source builds, Gradle tasks, host builds, and cross-compilation requirements. + +- [Usage and API](docs/python/usage.md) + Single and batch stemming, caching, custom dictionaries, and compiled models. + +- [Dictionary Compilation](docs/python/model-compilation.md) + Compile a textual dictionary once, load it directly, or share its version 7 binary with Java. + +- [Python Benchmarks](docs/python/performance.md) + Batch methodology and comparisons with available Python stemmers. + +### Java Programmatic Usage - [Programmatic Usage Overview](docs/programmatic-usage.md) Entry point to the Java API and the overall usage model. diff --git a/Radixor.png b/Radixor.png deleted file mode 100644 index 0aad67bb4b7cad95c7e9204182ea411ed99d209d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 325433 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�wCz{5m&26JCiCcsx?m88FV$aOB?qJumBnDpH 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≺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

Y;?>J}%AX&^!OuCELk@oi zhaG+x`yR3%VoWjTIv8yciA1FusTEd+MUymIpi|HzG$)qx?F)WP=D|9NkU9p4tBMh3 zAv1Z+CAxIw!9_EOaPfa5$ZpMk_k>oq*hGL&GP%(Zeh+CbgPAxnvPMlCs*?IZ(YER zw{Il(kU65Ed@+I5L!GS!!{7U^*tkCuUFcBlDq@Gao5D5S3Gsiad-HhBuCmVeyN12r z_dRpXRY_Imi39=xk`NJs!~g+qD^3k9e3ZV5?G^M&tCzN~UKzR_Xw*goga{fH!3YFU znT0Sz1|S0&D)Uft)j4&Bci4Lk_m8#q^bV&^F?esEe9otaoI2-y_ugx*=Xrh;Ibq0i z1i1E}Kf?q40cLJ9YeJHH5K2cAo)8}Nul1Pnimk1L zeRm(>(;xg4o03gvn;@-17(t>EoC%DBAVmnKFo_~)O|s+kR}b*I-+UDpU$_NHFv4oJ zAdU3nst_O7@c9ilmM8k!YWf;=R#B5zcT}FF*%tDS zE+jRhD%E)3qA&%LRg8{9pj8Wp;-N<#X&R-~*Nbx__hI;~YFzgr4RQVYIz-rO6kPZB z*V7vabgPB2qg3!>y?GzZtb_|DIuaioPqRM#?h>zf=}VaH1VcEJS+;GKyyQh!(_J|h z-HT#GwI-&XvK4En;Wk1f* zgOA{{F5cxO-MTC)*Gc9mpA|e-K+>Kj^C{Qg@Gl5W$_1O6$tq_-xcpG`$bpi z^}Q@*1I7N~hXx~V=vp+^QjG1XBbkK(-7wpJSX-=wM8-%L>EWzXrg`iCcmspu2O#f} z2v_<8)PHt8O`}A+j48GDQY)O)SeeqA+sT{%{IB>QZ~FxAc<%@4%x}liLM1JngMPnX z$wg`!{lcUbAJQni3vT1W<(TdgNA^9y>t6fIoO9+v7#1+>g>vHadn|(tPnJ6blE?P1 z@UNfy3ez*25Gsw1*D}_FjHD(=KeRuX05&Oa5H4eN@gV1%v4j8e8?TNgZw85p$h?Vh zRleLe%NkFtzvk;hjp0H5NV-EbYbsD%5t%{C!;d}xY5eM|Ue3Ws?xmwZSc7-AW>(i^ zHq|9R#o$89kVKK^IWr5JS?)RBfBi@C(U9Hx@tW80d{^tH=u7H@pfa_fF&wH%UxXTa zZOXj+GQPAzfrTH}?9~Qb>j0bM*l@%3y0IpB-#B=|)z887*YGw+NdkC*E6pMs@`W}d(&3=(kp@5( z&cQLKF#-!hD};h?-1KdfY?qUEr4*LX{2CVo@D__xfv0YTrRVw_H*45w&Ft=NoOi{g z+_$*Gue{+edF`9u!5{t24ZQ!xZ}P?a5Afg`94w@CEREZ!3GOuEG zeml3^b}tVdeG(gTWlXN(??=W}Yat9q+v`A^GB)(sVP$9?Ac{sYV!tM9m#acc9 ztw3q4&^VD`)ik2Boj>@~ce6Tx%*%2@q=A)%7b1=1VB%;sak-hxNtA3j2 zKKoo!9rFf7m^x}D&{9>*0pZ3*YSdlDPt;@%U+<=4cl`5EArffY!8x{GSowJJc@aIR z<>aB@q~^Byccb*m`UH2#m^$jq6^-DHMoYrDqF8iWL$gBEsec zY0~D{(dE(~cbHKilx!G|jNFH)sVb3gA%Kjr8#P-+3)( z?Ac7#UyZu$rtVLUDWXe6r{?7Dg(P#H*@bO<^q)V&z=mfpupUARt7l_rlj96Xa2&5sCrXT)F4$-hb)o%D(|>)&__60dnV*Iuo0I zmFO776^szexGWLwv+x#T^Q*A56UWk#$GG}gm+*5h`%y|FM#JAll*Xi$SBIKNTW>s@ zUSMq4aXA#*OuI8C!tFM{9c#2qI-3t#HBkF=#;pvo2LRfArt~EA!I= zX|tF#Lezi9byQM%M@o4d0)3@WY6{b5XvT1zW z58O!O6b!Sf!HSa}nS}K;$URfjo4Ec%|IE>2IWF#*V^n;>!Pqwk%OakuWtMrFuWg6k zuPUGEGGeqqZi_h5LS95A?`@WXuB<6hab*qT%FM*Ek&$H`EBwZW=-zKghC49^X+7lj zyP-0l=*6y=@ieLna!rfH0*yhE9gkgxQ=G) z>ASe#vL|!aB^Pq)MdvWPXE%?p^;i@sY}rY=<8<_n(_qss%ya*WxZtuUk#3q|+4fkrJ$hvHg=N)e95<`1i7uyIcm_|t;;9Ve0D~#1 zmMT>t`%Ws9PVg#4=qWz?`7cvAJQi^d#jjcxhm@;at83_^^QJ5k^E_`B%+i<0OA$#R zqgL8EM{W%u>1T$e)wM<_$nZn9o=&c=_ZaU83N|E{w-lE$roD6yo>%fTwBU1GAHokS&BmBj6H;~K1-A{}ly(haaHp;qKPt$tR z1E^^Hk^$XFeSY8PzslDiehjyH3(H=SsW7=KbXSF2xDvszB9#hh9A+;vkcmD?FF5J7)DPiFn|m=?7J?0Z-^s71(YsGv zo9kw{*Ac`rQbg8rz~xZX)}%?SoH)1{=X@HPhbB!|0K0Z>FQe_K8TE~3TSp4B3kN}P z0}Xqth~_wpi^oq$C2t~BLyZz+o`o9C);SlLCZP`EA8+_L`;Q(+x2JvDm@J-tM-@GMNhn|}9oWZgwv)(d_Zu1XjvhS|fe zu8sPsr0r{3Q*%7@=ss@!)_pbPZDaZHGFF`920is(6-^0_q{wx3k!@$26xSh&^RTwN zzPC)O0h>~unz5jYS?#d_NQ5_IvUBv=*i}OVat>PZ!^5faaota@)34#o8y1J<&7i}y>zwM zm(fAziPv1yTLIEkV?F%SCq9En+6WbLioN$`OxW=AR39Q1EP$ZHb>K3V7Z32$KmJ0V z`HUxm8K5O-sYk09Lt*%aRL`@h4{0}r7wArj69 z2k!CyAWi*t@l1q$M3Q!9d34`lZv66B31ksD;WhY%p0>It&6ux}VG;QAL$2qtWG#&V zgGyGij$%v1kKK!!dg1XyyyltgYGh=uJ3R4yya#@T^x+Z z_%SafI%%=8)*~|;*pW`aLY{~?e!Y^1M zU0EV!&Elno0x5}ckQ>Ny$TE26yZ(jmeCH6`HlK~j5(X6YZG#S5=&*cdkj|sAOr?TjhwtZw&%caUz48SJ?@4qxgaa`m9LkjXe$jC3_1M~^ zQ#pUINXqy zx@i0hL`#*|(nbnzC?Mm|fu_sniOdY^Mn0AMXfO?tLXH$gst|d)<~fz7I_US}r=pm8 zM@>jQyEkfjg|8ZSA5mLl4E?;nE;_c}z@yM84YOLxBFa}Nc5VgTJVYO%+T_NQJ71Z* zME76ed?Vl6J5lcEID-}z>HEC=Wj_%#g)sv%=%j-<$ly*S8sU)6QJxdQWiVe-I>U%WG9+7mG-m7xDZWGP{e+;EGY;YeSd#)eIad3)6ybNXZFuLO{x@9r5SjtgEnN=v>kUeQOa%#(CRJ(a_FhN%kqSouB`hNT z8l1VMN{~s?U>-;rSQaiuO$tER9OW_wD+l?5H~e4h+R_4Jz-3`w*9Jqwn$BWB9*=}I z^Tvz(-N*N>a_4s*0GTj|*V_34ZLIde5bYtOQC`rSBV1+3CcvAF*|y|Yf8{@hJYFqJ zqtb#sG`c@l2@}OYWH=_zdt2t}4xb|~G6zQcg<&MT@HKJeN&U>r&*Ji@T!`(jGTlnc zW=z!@(WV(|h=Nl(DdV!VN-@?m$Sp#peE!C-VgO@ptiFc)R0aadjW6h!Zhy4KryQL@O2{9-YZ6Mypk=0@wTx{TxBEavEB(BnpfQtRdm znr?kcBaq9XyNQ^NQ51A16o z63nffROCMBbd2!i82aRN-GI|CI0rqQVk3>jMOuxJq2RPal1|O?@ZQjUfNE@=#WSl}d0V2`tKfIsY@4f@4C1`~ezTC8=3}StuRS1ap397X~ zI=_=Y_|t27>}XC_#DshgsPD;Snw^ZZ`q)iTgh2QVSObm&pZL~2eC(!M5nFe%rZfX2 znepY}-Oycm&XSwFeXQQn_4h&NwWnM|H|ErI3^pPcIlLfR<_}rE>wBp$- z4`6Regkz>7Idb3uo_N7-e&g5vJ*k9LxgZ3nxinTrGpeM{*O9nMg=&$_H|)Gv9n>4k zDgAz!{0(agzgx!5iP$^M|4;LP9)7pZjg77e+SO2977dGyy1lyL(?)bT!)9D{R7ar7 z24OwjbGVlUl17QH)dzb;MIGYpni?Xds&s&G@UKs=YDU3{i38)|_fr(YMX}_iLMCwI zmu~0Idmo}TJr^j^ZZtE!peWXVUNHYlW8?%8!Jcw}czg=@B zn-|*jSC50UBucMK8*8;%r0rIaba;Uig0wS>Zg1k9fB8O)Xzq1&6moJ?B^tl4oAM2x z?~UobtRlARRAcMKi2k*%2@Qh-Ii_fCA~NHKM`DOsGR*ck5%Z>q{~ASrnkbEwGB#Nu z&PPj+^6=W%yo$lvaq_HNo|DEH27^I4OtDlt!O9dX6v)|-WbL4Lb{%97-o*-ZI8)Fo_Nl9xarp0==B{s>0qrVx31im zoC6_ML!4XG)KbbIdy1NM=^ee*C7yfbGdSn;ts$2^x|G+iS&01^b-q?ojto~CzpoC2 zbzP0?>#6Rx-F#+T8SwS$U-~(g4nKx;rW~FR-`DtPkKGr8L99mU7M+;|zWlXY=ypT4 zvh^P8hYdJs_!1QHMD=umapz;1fh=Y7xh{t=s+4dPU!w#KNBl_$dVPN!M(9V8;AT|X zuw!O zgS&6gi3voj1!+o#qi1va5|(YB<9VMI-(}SgSW_8U%hB34&Fszv*2u{V#8Lc!?^I zSmz^x=gPPvm*prQoRcMjR;yJOv&A_nMW6}$nnR{nn#T?tjF#|X;=?)OI$WFV)O84= z7DG`MfL`NlC{jN5(OdcA=Wl1n=Ckmb#`YcBg!ieuBhi6Id+P2}n3P?8PJ6Z99ffE?pq`IX_qeXClNQ5LspiPGAcX{q*Ph(0+RF=~bg3T?(KmWs* zdF1XxOeOQ!zC&1v_9~*hBDi*jG6LZ|T6q$g(_7ik8GB}U)9<~8sT7nV(4M70ijaR9 zvKphVrABe}HT&VZrf{U`bTPlr+Y#+`L03BgZ+x8%Y&hUvu4_269`*SL`Oz$7eWfF+ zO=}zPsaBj8mq001x+B-YN->1c__5EqAJV^9ack9mtLV_>5Ju^e3E@1ftgPW`uB&w< zu_(&RLbs;LD>J5JLn)#_lbgsiDNvLU?G?!7j*!$Kl*TxZO55yx;4zLZkFLQ|Ga}OC2Hn1PU2IH6s8hd_>(%^=#P4-l!pX1xqb>v;>5$&QPciWR(yk zNfPd*&vAnlE;|2o-t>n50ygkzWYkzn;Y$X`xKs%5@pk|KAOJ~3K~#fAvm#}!)^If> zvJz}N0XNT_W7shF!B5$d`$A|l}pJ*o=Kl0?BTyz*yJ614OsBTGqz7e0Iz zj2rKx=2Vp^W@{GI_!a_$>c z;+XnCINI$Ltv4#nkQ)MuHd;H$??+t0529GB>uiSqTu%Oq;OF?Ob#Uu#cOcU?E=gF< zdUS>*=SGC4#;*7zsv(iG-D*@s)`VT4AN}S~Cy^qXoHyW!qy-=Bz14 zKk+czVqy1Ac3yA>=RW;OoPX)XJn@-N=LwfRh0`xQ2jA8h85*q2Su$_2c#L=00G&W$ zzrqES;K*t5{&Vx)k?0x7F@<^ZcCpiG|)hRMUEwk1Jx`q=t z17H2hLww{zpJ(Uxb5Xuc-gSt~kw}F%7HbAL(`WadT`bJaV)C3+w>fg~DEHoZAJT+| z*%?8eMS8YWWZvSm7PvGcR-y}j#arm@&as+(@y@>pN?mG|hx1ahv+3Xfp zWneovRLITr0jZUUGIBsVU-HuZ+8WP!#%1i;vKi4IFeOs9w%dH>W8dIgU%7)#oo&cW zk@pOFe^Am9^E}5{ixh%9&%oy>pEFoH1ojwjdi`tJz7@0vC0uQiIIInI>t>5ia%K)@ zF|nxjJ`M={35s1dWIFb}3=%hD6gX+{xSpQb4>=Nh9|}`j%QGxAwlEw)Vn?2-;k~&@ z(RO3Ag(sgGZX}Ph8t_!8M2R|Eix(nFMBw2^9w`S_MblfKq6;~CPYa~KEW&LVhHTxs zb;B%;NsaJuCUE&RqKvoZJNNA4i(k8y_S7b%N=q}UVmQMXGo0dQ>O^7&ee(Va3v(@g z>Lo7@`@aayePxyuXp7RrFa6>xNEm?aL!>JzAsX42bq@$?S(gF5mRP6h8F=?!y&qFF zz}Avm)1dBE!%Z;~nKaQAdYr4amyzjJo9IbdGm9DH>RHbb6!uCX>KIlS^_Gw#4j1LnnPS60r0x*kSOcz#aw&{|>J-Yl307$uS08{C^?P9%Is4L!*m2$-X7=pB&bR1l&#H2mG+`hd181-W z)zAs7H`RkHYDH3<>RN9|S_w|L__bjJDzrAmgi=UNo}1EirWEbR>E-OqOgJ_gmhvJ_ zpG9=5mAKp?Oo#yT7Ls-gsZ_ikqSRIu0r!CdDIrZbe0+uD8D$}a5F@U`lM050E+I~; z;+~6AI!30zefO>Lo_BwU+38(KrpX5mmmAtT#2O_+fXUgiX@PB9HevIe1kItx4|C6L z_mcQF*aS&}%^g{mV~oXwlye?Gc!0a_zXz1WikP<-ql~mp@vaJh#|N5Efsp6cvo^@- zTgAf8v$^3j-{3vhe~C38kYMVQ*g_lneWKF(5kaV{G8sKp?rhDyyKwQ%39oO-It~M$ zyXz6I`{d`y=C`sUG(9iLOr+W6A6@8NHzNiTn+Nx384vZ~R$AnoT2d)Uq$QO-2HpL<;s5?WIQR4<`ju9iPf=t` z7Me39YN%ZG8H$J6rXBOW1JdAv6MV$AV`w6Ug45$`hr#%!H@Ho+0iE#1_qpNXWs;%8 ziA+%@A#cNxSH>-#Q8&!+eHKn<87nWnEry^|Lwc)VVN3QDO0 z5&;)qu#;yz?Mc}FDoVOCr?bc-uDg!9h&V_^$z*U2G80hXPk-)fJotE*z5zpJd=iSK zh~|kC?{$T%bLH#dYDluL?)iu=wWjqPqRKTiZjIqraE&h-edA3D4i_dMwlUUMEF@RCnb-AoQp$k7 z>Tp)&#^Geb-uoU}SE4hifpESVJ%)EUlOxMm5?dQu_=W>KxW&Y?+^{Dggiemzk{xj7a_$`zRZdf{rT`BneK# z(G|Gsp~sm&bvLIy;XF3)-o-#e4~tQddsk$21j~8>20?~nAU%uyHIAD;%RVF7I?Gv? zT*zft{V-QN?@BIs+C}U*=QNUqDNN$&c}w4CWZtsc?}cOE+7RU;BqBaV#Wb+Mm&yrs zfx7rgV+j&#T=~9+bmH3Bhz_ zeqkXr;x#IdaltG}sU&0x3W-iS9Ped3^!TBY3b&r_cRhoj5z0p_cDk!@?O*%@Q|S(- zTU*Hco_t_vX%!~r!r|?J`T1FPpLz-=%W3O2#}}8`d*@!XYhg2sbddKmu%S>hQM35y zfqmTdz+OZvfutxFOyeL>X@ZXWgd)002tnFuqjgGd9F_zlrB9G950>bi65%O6#hFwYX=ND(WUD z>hL3b!FU;dMU>RP^>Rx20r?%&hgD-Wh3mRQPo~L_6cb)VW4tCxR&Um%FHB^H(Sjms zBoqhrW}`mAUJorjSq9KB>Q$ID85h_nM;0x2-*X>RojLmbeq@l?vG1**aMlsQ;$RO3 zFFBhxFHFuZM@+o7>)xN3dswW@gNyL7fB76U^IJfs!AUl4gAhoSMD4AwA>zX$%3_=A7OlDlEgZp0hxw^qki6+Rp^LpW`Ey@AM==ZC~<jKvFscR8YvaBN}uJN*F2rlhjXzt$o zz)1{PD_@Ul9ob-p=`B(X`4$vrNn(7HQXpRi3Z{mJ^CRnLj2?SEC;DbS`}jX=5t`Jr`8EGLOGiT9vSc`ZQ;yN5`yst^ldaNl_7!Rrx$_2>uW@AmgZ#=bzMPl-)K!Q) zCrtz@>h*{Bx?$!^IgyZ~&d}p>$%`-FfABC%$Jc^vq(&hfKd7nLYoT!u=w6bv!r%Ff zE1!8eN)j{IRn?Hy?p+nPCC9iTSI=%8DLj$3e%MrNlFz^}Q^4g0Z{dj-p3T-RvzY!` zNiQ$VqY$oi*e~vPovGpKImQHI)VuGypF9r(qT#cm(%*T=BO zLWr-65v|#PO$&41_eS(Yek#>^{r{2i)kZ&@Gav-Ufl~_O1i66kJbHxLQ_tY+r(A-Y zYSRomN6ew&Vv5nw{MHM4KcW#XXza$qJKp{w zR*pFqW_BW7g3k>trRlU1TB*d7)9EDa-m?qm3|c01SJt@q&U;Bnkx~Dg^#`Ge!{eMq zr<#LD53~1y`%4YBFP-&dHT^@9v=U@XBU?$JqSp>}ohGEZMK*8@tblZm>8)q-hkyPz zeDE{hrWcXc=tVo!2)hZNC!TyV(IwB7d9#IaOz{<&w+w`$3mkWF6!^r=ck%b1`4WTK zE%a53z7-6za9D&;P0C56%U#eaIgiM52FDjU_taB)=F^{q>*gry>Bxk~?>){(-~Vx@ z)dJeJ5k`^^vXC2Gqh6&dY?j{Sq|!1sEm>K7n4kH{=kTHzTv&?;C<>Wal8MS0)Qc)! zrZ8M_Q9G2oAT`LG4~?w|InG(6sP~cdkqo>b7m~gOg9;jb#^nUb(skZ9@|v5H;Hp%b zgnqe^+`Ww&T8)o}a1-!iA)xnoXUk01L@RvU4~4TxXK87{ zjxC!;+|qp5K(g_o}f(?>$|TvP4x;ouPrt0u9m}G6wOk2W@g~*bIxQi=$9ru zUKXymqK1OYlJag`v%oXnksF7QDGxoeZv$&E?k7b`v#Sh|N^Rp%Q&<(Z9*c`QPzOa0 zsTlGBFPJC9_q~q!<2b{v0x7TWS0_$Y2x{>nANJRO`Nz59Dd*B(J<3ccCDEY~Ek3L0 zBR51X4vmp;)u5=*JFA*5+t=hdT6w113BBdRT=lG{@VeK&0_C8U%2Gco@MS+Qe9gcy zrVNPMwH&5j*R!z;y(YC>F8KaO(L_q%So+0Y=IYOXNT%8mSdlKp)F-)93KoGK03_%%@z$Gq3qkF8$FT zVf#6!!&DN?uKN9m4hUvj(@d*`X;Ng*v$AiI$M1fCy*J&-ZC|*Vn?LgB1&0ou!q+ z&TR`ZH@Y&ADhS0pwr@r_TTT>|vEyVm7<4W%?nl$tROk=15!D@i0wpW=?)Tk>T~G-;*NYCPf{LV+u@)Mrz~<%Q1*?4P|N~ z5ycEV>eHkr&j;1ycUa^9T|^lSJ090{c4bj46hcc@2k__b`fJ+Ln{YD03Q?L2O>&KH zAWwxJ6h}Vjp|G5F<{t2s`{l_Fh&|(-?``}%whHh4+kd1rvl)@JF`htIl_6rq3t5@& zMMP;>6X$J?^ck;s`OnZvBU8fL=x{2>J|{}RR11FYQ z5u~x)YbUHH#-Wj7gN~!Jq!mu2P)A8q!K$jiW+g?)RTc2mOD=9o5hgg|j$1#*7TfC1c!Q>1iO!){w%@Vc&s6Audzt z((j^Pi@7mr1Vz2+U9~3jL639J*v*bD30j7IDKJc`&vI}XnP21eFOKzLM4y#%jZ3sbLvZW%xM0o*a&*8%D;*vOem51x}z;tyfS9D2~6;KX|F%Yi~KFr0DvoUVS1S4m5NXb*j^e zEZm#x$(7H6K;q+JW4*wLKz;66=qlLPh3h`{DZakK}81y1;ppQWOa8`gvY4bV6M-DQx^>nOP zSQpHVl$7|Sa409z=6w0w6tr%Ygk1RfO|v}fipwKn5g0@b$8KM9xb*><(+Ga{Wk11N z-}U$OO^+mPky#rlZXVmuLV?#xgA~TPkzs_vk;oL)n&J8z{)uazcUh?u9d2^P$hki* zDP68p^cJlFjmRutxYxSEUAQQ?WkBetJ)`vx>&ml7`ti+aT|3O1-}pv$%!lS%P|wQ^FVTx)a8zge$fmIuH1DCW-e=gqi)$1c zHV=o7ErzUBWZ_6u>frI!K&Za1$U+?;bZD@o0%Hf9clH@1vgR;dbJwq!FSY@2$oQ&< z*S%oekW`*v$fQa}kJw1$jjQ%wA>iEe&P1tTBAA=nI&i+m=8!QasT!0yZ_66r_#B64 zd3A+ux5wt0X@pc2(?g7*?bkAyOKFb~I4^P5h3x2fhJ=^mn)NphXA2!TA)~oOu9?s$ z`Lb?u&PA8*CJ(W?2QpsMDjrUi4(Wi<r0vM)5Y@l+ zLLh7%tqx6jnkER_WhzP7xoum6NhgM$2S2plPgF;pj4`wRK?sgFIyG-hS{mAhYRXX< zYy{1W!o;oED8XY(@Za8gEjQkBFP$yBv2u#O*K|!DiZiWID#7B&y<>IIC0Ur_NtayC zw$pb9M`&-cgC0^TI!Q`fEAo|94j(wgk;f0RvUnUj$k1NUQW_;RQ!E)wbj2lXK6NvTy%n66I07o^2No?AsT0g4Q?O#V>*m`@jYfNk&m7JK zLz67a(ca>1&g$Y}Ui`eLkQ4?%Lz4{&oU&^xf;>cB#L)N5ns6uy7tNeJEu~pGwnQow zeQ$8iu=A81Ja*@OSRV?eFwOL#nT2Rd13K+6NT&Gex9;T0&%c};fi8_zd@LB7g>=|- zmVb#kZKk4#X~u~b^A72tyXB_b;<(|JV4g%lc*Fe zbqM`287PDBeHND<=gQ|?#p#MQzC|qTps!NAOu`0M7^pyq zV-W!X4zxjoR)Rr)jV+s|x#sF0A%Qt_Edmes_BP%^}7dLCEa3bVdYbDWGc5I&Gj8nIB=|$)B#B)z!N(u(K zr>zAl>O-{%gQoQ~cPBcKH0^RB60Z|Yb!^&>PMf!uI(L^_ux&bG4=6h zkGMz2+IRbA#lL&;)m--vA4kX(e1Z}JYjd18ao)#9&)b0zE+T=hR$RFgn8C>nuvx-DwJ7YzA+h6PHX6^g7>@vi+P`T(y7A#7V{=K% zqH#hVClpPit7466gOd8k_AQ%3Bu;cP_a^?!VhA81zRHzSV4&#*47+(!Vf|Og74Q)mF72h`(oQ8XC z4XF-MXkk6P_<0xe;^+M^*ZvSY`#$~hf@kQ-em@}xs&Dk+99>E=R=mlaJ~uc1bV zEHenD*|lpI2w$=1BCF4F&`H{XSAo2NSIr$q7w%fQW-3 z9huFgVXcjHbyaC*T~Xo;dFPboQ#P=C3LS706etW#98Ht$nnV&Yk;PUG16(a`DS%T3 zumZkx%ftMizx4)0YYy3(#rXsi2i{&tq!zWIIb{PsEGg4c;ax_yw#->)Ze{0=Ey%)e zQM$4F(MGd=Pj>&-KM29`Mh`gQMdd)|U5Nll87Mq;u{=VQ5iSGZwg*;t-S5AZN0&2Z zPB|AV+t@r%AXVB5&WJ)_gl8q|V_J%{FMBd)J?VU0J7GB*rbq*j33ZSmYOkp6TCP0Wy2j`xS4Y=IiD@3?ZjF^ zUQW&giBU|+grx@$a@WnblN}o{qo?pW*gQBM3mY2I#9LYzjL&${)z7J#Uq&b0dv5Rr&!@|M>TI*2wsU&QSP9T*mZO%O~Fcv*C z&zEogHZOhd2$z4bW5@CvHdJBt7}Go@b@^Bg^TgsFClGZr0( z8pVgnEg6yw&BD%e`S=%a=h1yf`MH<=J1&3nZnTwn;n0z4qlAoZ#-g@yxzZhQ{Mssz zxF#LTc#jp5%sJMqqpu`O7C!ccTln}_Z)Qz*5c6B;qZ#B5uL6QCd@OR1NCA1C1>Ko* zNa0ytKF0QKn|RSRKT4hrpr0|XJNPdA-L?Nn@9=<`WF9S4(BAe3Wx*g~<7kb;8bcyI z_#Ulf!1B_gy#9Az!+EE7%I8@$vVAM;Zrr5b`hYkXLgPCS@Xfu4`0yt_$9<0T7MTjZ(2wTzdipUHV(FQM6oCsG2 zMT!LLLQx?^F!M`Ppx{?XaAhRaB*?n*U=~8w8j0Az3R$8eh1>i9c+1;9%%8mTZ%C$h zkh>O{^W+iIRjx8nDv&-`+VsWd*Eq$9YSf6-RUsw)fgv|J zHdDO!y6buK8()p_AZ3VIQL;GKi{V@i5x|%7g?a;f;sH8AHCt@5}QKQ%+bbPVVHD1)3H{nYie(C>Un^(5^ zOo%eoVsahnP9oLQmq{B{GhXaDBILUoc>VAGCZE3XCRSG$(djf!Hcdx;9Do)3X5H}| zHNm7+hCz+Vdt7$uQ~AYTcriAIcG6@5iNb(Q6wPR4f(cb(VTCDqMJe?s@CvCW7tTYk zI|#owHX_!pWbPD06ko<)MahZHkxEDF$a3K^J2N#phxX$dDmgj6LX4|g-3jDDqUI_u zYwN|2tjFPVwdf-pFgra(D@mJ-ASX&zh!-xrZ`b@e#Sl^m5hQj%o>{VNP)aij&}UMH zb@Ti!WG)5Q!&8V03q(4kWE6VgN<&;T!@!uzp&xJ2GP;JBv~JnV4;Q2N$;J9NI6X%s zxQLfUlKas7e&WWvc+GFVf#p0!b>=Z7;ki+P#vjrkB7R!{U@lh1qVm*G!lQKXDzZM~ zhc0^>Go83xrLVE+nhd+)=W3&k?(o+Q|M~wj4RA1H@imQ)7%}lErXGdyNgffckA2}T ze(AT~$m1)}*?bxU*&v%t$LDWi@qq*ArJPP*lCHqY!$-LF(_iJ5&wdTRoYN-7 ztmc?rj`Gk+QZN?N@8JeLx=V++`25p3>-2@v*>l_xuzibS^K2Vy`UpYD%yl9@@NomO z7KtM3Wh@;%hK`&4-24m+o97t}1`SO};hb7*D%L?VHOu~E$NAcA_XM3Q0z-2u6$&9L zgu>fsgYU7%WAad7z5UNVz_BBSPHQXJ6zO!iNf8U|PN&1v)KoB1N>h#=I?AB~hiI!- zK=fn?+DwE;(5Krw$SJ3``PJ9_Bn$J9DA>9HZ~wF3BNZzQR*#TMi!%e9%}J7^mWK+7 z*4W&zbnF<3ROES1yWM8%)~)ojtc1{f%r4YI(&==tP9SBQOmvur|WB@Kz_a&X^Wy!w}4%5#4B%p#0vgZ>8< z^|{{s?mKSzllOdtKfdmKfW^ay|O_V@C(>uzBGilZ-rE;!tyXyK1vKmMk%Ws7y2)=9BPBdDTtV|9}C z0&jSFKZ!=oP^8Qx{qVT&S2n34tOsi?UaH2x!wHEKGQ`aJ;3{ujpycH~Ac)3Uatp>5 zfA&}xQ$B>kA_LPWHoX~e>vsI#RD>{yL=qGS;A1i*Oi3VQp2suKJyloL-fMXO zSZfbe-FoVD##biJVuqGqx&T;-ZTe;-oQ)uWwe=Z5f_#=~+V?*Hd)7Eq0 zwyn$@-bbpznY^+WSu;RPF0x$f03M|j&DJ<$tJd*_Fa8_*4)t+SzUs=td%376F-VME zCgy8d8hFh+dNj?WVFk(3EbOJvhVyQPg6l{*;5=$n($U7*)uA;@$m-G7vYhw7xGpY| zV!aTENZrr#Ja`HTQ?NX*K@^A78N=7A=p#JZ^;zPzb+&x1gM{&6QhnW9EFbe{cQhcu z;pSAC#u2B(*23y(#cN*mO8ULUTB?RwB9R}Mun&3Ghu3vuGHjmFZjW*6t+(<0@85?? zLIZD@j$7p}`z7A>>hDyKt`PFef4|r3RSi;CrGgA+ln?sMRY@Ip&XscgX0tiqr(XVZ zS3hwa;G==r2L3#7Pr8-a8MSj&INt^Ml`*D*%hvcJ&Y5M^NyYkQy+_40d%!2W!X$cF zb+UxsE|v=U2kc5pEoq87=MgF?_uMF&#(FSbVdFX6#B-(b663)-iFJ^h@XyADNL7c% z3ROb3oKWOQ`as9RJKy&SUj6zvbExByBa;}JmX;n$X6m2?WF=Nfv8OD|&!Nb9@y|Xd zX!)EOU@g?+b)_h+l}P{p;~wCFHza*9P|Mg2DLRPJP-3F)-683_FyZ;=r@q1E zpZgN+^(UZ5#^{TbzHtaCQA*?Ry%MBiV~8_&FqH;Zme5RUHqmo|^tti4joh*4W`r0; zqOd*>2N53{63#jL29#7ZMptpo^*8afC!9))0Cp(iYecbd2~o-@eS|bgA@FD<@YR36 zoA3PRooqVhY+RnAgaR3SOvT-jrYR$%BY5vfbjre9hkbkY(a=dGCqkBIG!lt(ee(Vx zCMPVv`#Y~$8^f54g}E7I8ly(yHSPDq0n&O{?B+<}P-=qFH6#46a?{;YDZMc~^sFh?wIVqhStfLgmVwXz?N!X| zm}MfF#P%$C%mj4S;)Fs+z=tt8&KVl1#&&yP7dd?JUVigeU&6~?bRp5U7N>sdh@0!c zVJ&7F=fDr{+`~sd{ZGvKl#$h&v1p8twwSYcu}n5=JcP%e z_a!|!JfGGD-feM*C!Lsh2Q6a>%Mmv z-}?3sNG6WKdg6Mh$|76VvOvq+$vBlswIo#%Ed`4Ui@f3WuPGa#_5D*0KPPbt?QG$P z_9b!jb+35^-~RqP!DpeFsZui2Ma3py*q&weUZSs93NU$?ptnZG*?({sSAX%}c;m0W z6l(-pCE?o2LBDVCp~)rZf#q*sS356Mu$Ky;BI0Df(9H#Ma*-LmQhbiAO{=PjVEy3! zSc)$>IN2UJs1*lOe}smVTcL$r{V&fma5gq- z!}4G^px|i#2q3 z0`DG^`mj)t41ce%CRl69T>$qyytB&}{`p@>n(a^!2_0pxe#qk;RA{KmuPy4Hdl9DZ z-g~r=ay>$K@*Fe!g>;$O2RXcEACb(M#D5R z{9IAw)%VT%I$YezN-6RJW>{0=gmuZGwJw`cvbKE`a{gkbIv5+p5>~=s6Z_Gm(QaUv z5|kr#iL6-pzY}r)saXpG)Qo6bqn7U*uFJ3ur|@@|mq+?inG@`lIc9608T`J(p6}8wrvlVB_u<@KS&&2|B^6oDE)2LlWt@_5qGH;Gkb<&N z3oy3`|MHdZarwvpfm`o=gwe@O;1$*aa1Y)Hy6N1_Nf&Q+c0g@7FcnmDVEu;g+I9LlYHr$ zH!!j36!LUD9CXnL6&Co+8jQCrWQ(lcw4U>yb}?ckWxn49tr#B}Ls`##KfH~H@3;rw z>CsXtgy&s+F6V4ri|~XfBR?1+ zQ7k1IIPa{jeB;KiFfo1%Ha7u!V;w>#VIlVtEfh`NWXFTsIp^Z@5kk?+GBzE59Jl@G z$Mo|)scZ!EzX_j-mjaXJSQR{-qm!$+bNfTwwf!*T$4!wSLq=~+-n4v1p~P7@u-9|N zC$47Q>XVVKi9kmdl?*;q0XkKTjg6I*MbppNxpQZE@D)g$DT?1kyr0LpL;Ut{{~GHz ziemo=aUgMlyY%U&@awO8DIdGyON>k&k4(nMqc20t2)Ggwt-~RHer6V_U}AcT#l;1t zrlxS-(3zV@sUUk2qVhL;A<$`~?819B!o=t#{mxTHbRU2I z_n#+kOrj^ISrj8UrEw}uLcDj#hC)SIlL@$QbfkGWV@VoKyvYMpM@V{j+Uqy7Z)P9w z{maXF>+4_1wo{Hp0gc#XE3T37wSptG6RP4j>d-tZcSDclEnP7K^D)YPmQLCtHb}7S z^s$g1M@!Qn^)%8eofl-rlE`q_Tj)Bv7G`EGE*_YC?Lbu!z##yAl#b7gEIQrcKInD> zx%B=AAL7Oz-o$@i`+aV^`(d1J(H>iiNXM|UiL;fOrO2rb5<<$X+(K6EhNqfB1ht5_ z%pE?+h_?LVOJ5MPkNv=8CY%l;4O}uH-K&Z-u?&2tAmN7n>u>-5A8=lWrk~KESXp6D zEGJDx%AtfkivK)XhJl23l-pLpGCUdm_^3RWjV@$5wfEh&kyHL7&I za2%A8*A7HNEXlj9%lHEono_m%XnxT81k^cd){DCTRIUK$aYs~FJX*7=UZB-aa$<G* zxv*0X66Ncla~UPx|9!dZ^|jVG@G3+fW**-Djz40@o&!uwZ=$o<#hRc@(`oQ6*VQq> z*I1W%$M;z;T%8TitFd(ZInAWSeGlEsM?UsBe*eucrys2Kfi7K~ePaf#l4|*gud-70 zvFRoTA#e5dr^s-R#Z6MnXO~Kqa_3={S@HbHczu-$3ZFEo3Sws|J?JQ3EWh!G`njGg|Z37oPS|R|ML=^P<*#l;r@fWYffJE0<9~6v~P^oR1+;U%de+ zhQN~G0@KJ<@2%J)mrrg8YketOM$of?d(z9}G0Vk5N(-kVw9J)U58~O&xG;3f zon_|m9Jkzl2iJY~dcJr4&1~N@L!&*-#Olo<<)m*SrCLRQv>tf9E7f~}nPa0TJAiz# zJzn$5pQBNv7!+!X+B#e{vi-8wh+#ke@LIgwzZ?E%y)*~vzfN^t`TNzO5bwhTDh4ML z>3`LSq#&t;tYgfrN3)){2Vl9>!C z6l1c%%+9^se#4KL+jp2T-5`-I5+vFQT#RQ42MULl3XH*8gA^7e9GD#8GI|TMbWAsN z#dLyfjiH)jsHA}bTxRfEB84V5y>MXGDpEWHWzO|;eEhuyWW<>$IkO?FQd&!5`{eWc zIbqEhZ+iW!NT?ms{L<-8!Jj#i$DXcL#k;b5=gB*8jzFAz#&y(f2I`yQdk7~RmOlP&Uz zfBsLd`s#N$d+SMDaPH}xb@~=IAKN00HbYg;Z3ds9V%od+u;t!Ic5}lmcX0cC4{@NA zk&I1ZCpMBvg%t^1W3e`ilOkVcqB;nk-9i^9Lxrhue_I)xqw-}Pa-8c30a~p@6b8?Ft2&V&+uEXe|8}D zsv%OW^OeMO*-oE~@86RFw>&b(<)8l#1rowzPo3ZjS17mcVwE>)AE`Pjnnq83dv z)WIBe%jziNN8D(Vp zc$`<{USczgbcRA{=VKvLymzS-4jT^cMNuzgP))ch4^at8B0LM78D9Pi&*s<-ZFJ#x zMg$+L7ga8J`MJ4jN}${kAurZDax1y*-u-;_>(}8#O5fyYXYnOLH|CAjvSy7pc;QK9 z6X69?Dx}CT{dr#fieILsLAn4@th}!k6JYRZDIlM=e0r2uG_>S3uXq{n{lMk4TN7k) z88FU~7WcJtaZ(Wo%SB^>*i6X1XJmX8_dWa&U;f$;c=2Z2>AfF-=G3!( zrL4ODqPAd5>5AnlPJrbg_0$D_oV7M;#C`OuiwXpGgo0Jda)=p(>H`hs3bc3R=Po`Y zQVu|=R+?xJ$Z2Z_yJaD^Hk{g*0^))aMdD%}dNm6y50WYz$V0=}1hl;vjLMUR0-?;| zAWqH(<8g%Z2&wRxffp)?hyKDt=?57fPK0Mb4av{T+|&A-_Hsb2aWwwx6|YfL5mxGz z`1utuAt5mWKK;3G@s02Nn9<3NWEQMffJJH*;>iff7}lC)GlwhLaGVgU8adv3dc7W` zlF7B3_|V5b#YGpM$0Zk?8Yb+*lgQ98@=^X`eE~+RqIpWGOjH_qAEuYCfI=0|OL5*R z<J0)655s9Kr@#@mxDX+JvKL0Mz9DT-F{}6TbQSpXY{&;#cmH|%E;(AjfrD%LetGX z{oW!bx9CPnqn(ELF)+AvgX@CurBbnMu1Jp*4)6LrVcY3E=UGof3s54&jj3W&uPLtT z0LLQ@Q&&>{tQ1%FKRy9oPUctP|EaoAGWg3(gxt*Z;Z5)OOTK&CgRD7jD~lop)xcO^ zZqAwXbb4LVc7vy0@(iXnu4O*!fD=r%$8lZD?cceHhwr?HHk#>V6lLQ$-bZt`mm%8> zhj$i{FCv}6+b&t&)c$k;03ZNKL_t({j$VHeVRNQejdAAIjXYu7xtw$E+3eW6pUXb@ z5wgx4YIGWrwm>QJz7N@1QjnwyZ(=ls1B(lqcUN5)g+Mt^*2@sqAe})uOMhWMN%s)% zeCIn^Hz`Rx!3`}2VK0G@7mgenIDN};OpT?O#SWsO!Gg_lobYHB3UA#=Q8Gj~?YRG8 zwr)I+*{nm3~ZkmL%jT#F6I35*9{fF)glu>6@muvq4&Rw7ykSkvDqA6OT5u|t>V5DHYX*c zY2R87?%s_M>zP=+ip8wUy7lYPsbc@$eT=o*^(8pS&6N!DTH(THhX$<>y2<#|25h## z_wU@nwLiX#(G;eq+DuQ4vTk~k@v$++$H!+>wojdQ~mtOWve)ILu zj$LXSq__ZsKzzUT&~_nK$afq(`WOU@*J-v#+lGSNyF4B z^9HGdIzvi0W2m`C8e9Vm7R!g0tERd}IDTI*B`rHjM?0wp&^6}3K1$va$!Lz_7f>(>0d2B zBNyX>geUJWBC|g0rd#~po8LgHs!p^lM!ls(S5&Z`lJe}TgV{2m4eJR-gMyD=aV4F; zAZd@{YPr4xle-$JIyZqwV2mM26

-3v;X)O?lySpN;|&DV7}kmxF!yGMX;>I&A=v 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�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

Bw1tU{n;73XwOE~iFpm2hxQe9`25d~Ubb^J2 zV@yxBx%`q{Jnj0&aqVLt&8`d1W@^mUAW{mG@XE`=L&2pZR)gPU-DaG)O-=^hlUNaH zgAoB*Knxt2GyLZ_@8Gjv{13i=-+pFeO?zS^B5ac&APD`z62fSaNj=YB{plOnxj`~Y zOn@d3((|un^;ofzB!Sny>P;NjA2ZfIi>M=V^c_j7)neP$vop^?upB(J*F)SK6grBR z2}6NU5qs{xgV((F`8?~{j|MbSC8!_`TvVU+f;9qzz$A*rIr#No`(FfXM=-^51g-WM zVHoywrd$x=#H|p9sN?Jqgb*Y;LdXOw<~cdHhu8h)i+S{8&OwUGdeKmUd6NxuCH{E* zQ1vqdky7N{G!><2dJl#bNnDHXT+cnHsHm;2p=fq=I{XW(0*tCIY+mI)pRkmVcbz4E zy~mP`$G{+U@Up^go_OD;= z?{OS+KA?^OkA0Z9R+gFF2R8wrzJd zdH$YveXZ#VtF1E>u{JQz`Hq*-?^1n&w5uvprv#rQ=Vpw_RHwT> z!cM6>|GjIwHuV-9`j2UPI`o5*msH;%?YidAEisytCuTWz>=*|R9?S|(a|`oymX=A3 zqavle`SI~_Hg4R=^z<~FH*aR!wr$RvyWQ@!hVtu+?Sa+zcj$}pP7~kQm55}m+TXU~ zIoDYSt%d(>(`2!#vpQ@OpjcF`iyxX41W$L4n00_pwJ_9L1xf!k|Cmfn!5D)Tl2{9* z3WzKmorS-6(>rf2&MGNrbZ@6u)6%Zu`gJ5iOn&~auF@AYz1tD=~iNgmEAg%MRQ{K#70CC*GnuJh+&i=Rj@P=Zh{(G?19 z6^Ve3fg>kkZoBjQeE#MybL(BZnOTSk#x~HJn8LP3hy@54V5P`B7y&{ZksRC4v!D1V ze&fYI!8i~)V<;4&+cn9lUgr}mtKqKi%<`MB`4cuzJ%~6NA&!hIei-+j$qiF%-MWn= zahuT6(mV$b?R6^Gp%*m~iSNtKljwu|<(qzwvv-bwOpvm>!FFNTB2$sH#w$5%!B@U? z5C8j5-pBTB7qhq|2!e6KkrAvB1hQ+1l=1?#?b=>A=v$0b7NH`foX3h8{`}8g!{i3~ zx?t;B)LPxKPHh6*q(E1+B`5~|`PYY4{a?!s{dB|8%i!b1z(pywE(!Z;?qOM=n6j?gSdoMT+up& zN)-jLuAr2MkLFPlb;65v-LYX{qh(aPB2sXeVnf|MS!oiPpI>D(%c2!kzUMheoN2vv zJO)9k)AgsUFjgfJ*(70H4`1yEQUmd{B*4YCcM~G>Fddfl>Jpra2ULekZ*nceK&I=e zL&e3oD-REeu}&o!fswB3ch7w%c-P;2h!6bjKd`WD8QZv(_Si-e6jrv-#(TwuZP(3H z3asoAgGrJEp*(cSI=Gzi;t|4l9xVx>)O6;KaMdL{`0xk*h!JUMNic~lj(|>_Xq8DcfdVD07gg#)(S=Z1 z7a^-OXA=J|(bGb*Xe>Xt{}A85e}-G`+|9SYe>Vq?&axC)RJ)B0+lU|}K|&BBTdwn) zc9b$v7;6d9Opvg2@Lt~Zd#~Y94>_Arp9HI1ow^e0;@t=S@-IHl%{Se}&ND?Hl=oGtcR>R`hga%L;61659V{V@CD)7#BO(X~(PA?x{DZ&htpC#B=JpDs`Q! zs)V44ZCi01Bu2+ETQ_gu$xnO~&;Qw< zW7E_GyT5xU`|iJ&&eA-i?SMcUMn|0gZ5S$)@CuRA88Rg%@l`qF{LQ>Ka1ctQwOFH( z!m0I{XpZICgZ%D)e+8Fcyn|30CrTAM?y(W@Cx7-HzH-Z5jBVJ4kzU}6)GyH@g+$1J z*jP{j=j_^rl_3adx7)-^iyYj0KSoD{%JG1;*2GbSR1gM=@zEA7l^~KiP8`06M_hRx z@A|9%#VcO=OwQXm>8zkSiwF}TtR;{JbnI;oJnT<6VKRa6ujlYmSfsF651g`6qNU>h zd;2?i+uwYc`;RR#vn(*}DdgBjv>L^>Mv0MF89G?ElnANZ4-yqf|16c~g$b81SRKiq^M0INu}@fJvKA?|b{HcoAF8^WLtE`Nw?!&NNwZmE|l&a>QCU|i%nW(bQDpQRf zR5Y&JBbw}$P436w=R*&dII|MYu1$D108!}3R0Y_!BG4IZH)d1on3NToySG_~(-*nY z|BEakPrYA@dv)HD!}N5R@&${0fL=Ei2YFr(J4c$luS!6)>KyWF+xY~fEOF6Z7eiFo zlvJJHTF=3{93nmO+LGl0Ig~fXtELY zb5e?;2*&x^sUQ=|7#k7%=#N~=i+}#PJng#2vvEQ(bNGG^9k`FB`B@;M6-WXl2m^@_ z&Q~xD1C(?LpTHo6MM*)R1X#zj9UW=WQWhP}a`xsC-tgP6pdC0RW+k29TWsLI{R_PE z*WbX%_%^g^p}jDXl3tlwNThNc9p^AUy$LG>qwNud(d^%IFFINxl!8D>OyUz) z>CQWlmhm<$pWKgGI>hh(#!GnXU;HNLoij-&AQVo>N(q6I&K^E@wsSl!%0))-! zby}a?bqzQ(qkARM*7D%Xui~b!+=d+6gl%uYwnm9!=xB=-l1}7`391!lYS+epm$e{O z;8kzBoBh7~_OfmBb}qee2Vu&%wJB7nW>FE)YQglzv-sD~eV&Q&%^2(KrHpZ$?KqB6 zDr90}1BuautriGam_JF-auykx4VJOA+aug@=j~j3?c-^U2!u3V(NN?9WS0b!LhO9N zIW{KQuwj~;Zu&ACHf%zLZSOs7ysZq*Izo2&?OGV;!{~iEwbf{C5z-ZDzW4pRc-qq* z<;8A%jY7zb+g!fcik|ycmE5caH>cjAcm3d5W$Q^*uwE7{W(p@@19|MQChY($h4s9n{&V3a*=Y8<$xq^?yd>l3&r6c=mQI8=H{hoSZfR{fl)IZS_ltTpy~kDWsd{Ej>{ZJCUS{Rii!8A{ zmOgpQpt9sW?G!2{U7@)!->WR*@}ga073h>fOuM>usxDBkOJ>UEMcJC)q@h#vb**|{ zO~yyn8qg#;7_`rH$DJ)P3Kj#`l)4UiS!1XSIZBNsQIXr*f4`d{fa>ml*|iPY8wWYy zG~M5dua`9zSF5>H1*xxEd#bw5bykr@4TNC|eN}l;-7~10FKY!2FHL~o@P;?sP*>S3 z6Ga-d`3_S-JX5?l>)=YmY=&hUTCoCF1u<-zICs4d(*+}aO?ma?)Vjd?-CBdhGSZTq zwS9`~p70~Q;Q9ZJAA9Q4xcI_zNS5cAIl7PIhxal+dz6K_BiLjC^dclnNYkMeKp+xY z%Akm7DT}f(EtO!C1r}!Z@uKJbH=g|XhY=`?^xf1rF&HF&^{&6;-)_E*_J%D;H3}A_ zl4zZ{&L=K#S&Cv>V`J<%|9lbyVHglf$=-YKW_kW3fwf?rphp;n7?ThNl6EK{p5y5L z@3U=6@{#wyou7X8V`xd|_2tx@4buC{sX%$}PCSfT_>Qb!5PV`MQqLnS)}+!up*J=iEmp#T6N07*naREd=W<({Dtsf~}@ zqqH`}+A*FtOiXagSHHrKKlLe0wj7XF4h)XEf6hB^3tznX>l~k%Ck#jFM5%(a^Lt)c zTx4>?Bw?$CF_zJhQRWur&`FFHnGzTRNf?HlJUPqZqlb9H6CR58zMAPhk|KpOy3cgy zlN+ZOU33<=-*ylC4;*H6Y$MuRbXj3B-s;Ji#QAqhliBLn%zs!o@|wV_+1|nQrp-L$ zlAT$A++@RSO4B%%iPu^}(W($EWw6XD7*x~3U=@6?teewx1FNc5mwnfIagWM;i&xY6 zuG$}}f`F>vY*5jlF0rcm?m8%Ell#_m|6Yj~Y;_@|tA?Eqn!9^!_x1d~E2XNv|5brx zRp)=uylRpNRoP6{q4Dw^zhW|y`c&t;!Rg;+M(vTM!R?>`*EX1cO%s;7_vO3S!xW0D zkR)}~zOn?W4)R6(@A1NjcBt@KN(;#4<5-jpSG`Jb}a%6Pv;DnRe~ z(KvCdqa`#!&I}rtO^O@C>M;zP+%-WZ43n4CCG2H;!yw*uleJ?Q0j^0SQ#o09)wbwwgzMy_`5e^+b z$z9*On{VHKC-?5TkD1w%?Aw2s<1;5&T#i^?PMlgZns&R5Nt_3+urXsT!{2`39b9?Y zE|k>>5~K{!C>CP4=7~SUJ^Pm!-EbDFJq})p+Nn1xC(>jE9iut#!Iv>Ixe=os^m+d1 z5$?VFdx#`qtQBUyYeEQGVSqLrMpewrq1`;@st56&xBn?Swm35qRkk1%v9Lu83hzsp zS2^z4l)dqLR|HGa5g`i}e(iVvlCRvckFhQ1U}T#}chwXDWIH4r9YKVtCnm_$O2}#l z3)W6lY$C99j_&2TkGqQBdD%}f0fZ%-Jo;=TELsb6EcotS$9T=J{V|(1T}Wp+L~D>D zaK!@x#>XewdG@))u@mAl$uf7}^L>P{FW{DzXv|(CH8d z8AkEH$$c2~-lF)}N>o1A!f~MN`8KseYQjrfFRpvt7%LmURou8?#^9juYNCo-6=S92 zp{~$$s`zs&dHyR^vTf2a9#+7qsTE9

)sT}ixrrU?HH!$Ti5w~A2cL8yQ#Er~(N zP@Z?ZR_M}|I=-yt`4783HeDwN=@ks(qpz1`(Mllc>!Jc(rD8Yi10Lq4oaqyOB?4Ye zjyFhTY|x-9`ax!@bY2~>W|v#z`_#D&Mk6scAn!W z=TJ&8K0c1Mg0XfBYa+%*Le4&G8c9qTxI{$=g(lz&U;RGMdd^E|Z#;+g#1>}}ly+K` zbO!WDI@&Nky@_3yUe3aD#7H=bvWDBg`88~`M2N&<2&BktpvGE?K*b#1_g$X&m`nN4 zd;VXh$6VrDEQV%Q3WCggxyz+3`Y@V44@?UDK!HpcWh5ibWjPLK%NVSus;V{};Ddlzs2!+7nl|3p@*=<)>16W~*6 z#dD?>_Ex%S4}yuU<~&Y^m0ptsWTm1bO_HBF1l39cqo$m`VFIkCds35YU8{Y*X_2SN zcpD_vvJyeiL4~PdlguiSxiewSSPz@WK@FIuRxB$`DvS10RYGV@$MrA;zd=gD>tVgQ z7Hh&V0*qzzZP5BOsL;C}dJ*fT{JxsTSe58n)j6*8yM~>wO+AL!!(Nfqa)6t_0fylw zG)?kW1A$)48&b7M76m4T5v~~I0agcr8pttrWJ_eqhcY6|_GI1*F6CHR3pQ1|B=b-e zSVWdcTZ9!T;e@oLw+CtkFwurh8$)(%9p{2`H*x;XX?AX(;H)j!xuwO^u>N zf|Ran=){l&R!H9Rjt}$oZ~Xw#nn1S392CvNu~gcbMvz#^xw|eT7#+s~6Jw(synhcT zjvgSyT?1&WPLKiuX$WP);_Lw)cJbN#-TVH6%@c*>H?KCDj_V%RzY;hMsaHhqb2+9> zYABy|4LtNnNlAM|uxayI{M#39W@LN{WQazB^pG?mo!yko=)~FgI-fk3oC_%tNQ4xO zw%Xiv*PT4=NsmEE2>S%Ex?(SGych(I9BYl>^2;ygpa1V?(TQSYWE@gE3}L~5jx`e- zCQ+&dLNYow&WV$=?jA{t6oNR3uoeOpaP;U=q?A1TstZ91XW^9ce?6=@{Z07Rtn2K; zgD*aZ|NPo_n44cDXpdlVoL;0P(Gez7U^Rfi7iYnGQI5Q43nw{Y9HZ{@11 z9>$g}V|eOzuN{Pkue+C44ppn)5KM&=;7Z@OvW`hzQd!2`8YKAFgqt(0k8l~(uPM}a zJ@g;y&SzNth(SEjCNSSRc;;%v(dwY5)j^7A$j9y-*G@&+WS{SY8rS%(cDcJISKuv3 z$_+uW8Z_>jAl_C&k65W^aM?3&!c|_c0C6>1E@hpaVk}j+el>|d*FuD65I?&qB)U$} zbeO{8N}l6NKT}i*AM~87#%CSJvnr;}^J51=Z|ib~D#6&*Cd9+e;bHfkDtP!x^n;q_ zX-)R}Dx}AtwX`fDZ{jSpmh_@w|HxsKC|6<`ycU!DR8=|4tcI!}8ASnxve%06jWW@O zGO6-GinRUb*BkhEyJuY+gVF^g{lC=?HjT!b#0gyK7-hBlK_hjHA|bFb0QEo$zk!WW zCZ;VD6w8D%LfQ^WXaWh+i;q|lkZAbgm%mPXd?S_sW7B&fP$~o~F$g+QOfWLarfu7a zjG^6b5iKuraNm7|xO2orNUuCC(Aap1`4b0dG0)rI_(yEn;P}3x>>lLqSzf`gDiE?c zL6NLIobyIB#$ZfN7!E^VG-DE;{nSVBum_#P!ps3|vJ566NfN9xy(Nkw;!c82l8n=< zlMT z1Icn{o`Z+>drq}Q$bj?C-9;R04`EK>;1Vg?Y??ZokAC={xaaOUOd?&kSzC;u$AZYI zP79=RL9q&;H3F}F-AkF9+ec^l1ja^aTJCZmILho<{Z zS<|g*p5^!Tsy)2y`4!`WsxedMJAS(U$hrZ%TBzH4Uf5b5yNMt{aX{3mn>E>7>#Cgt z@3nQFwb`#Z?^zi8=EOall@yD zonX;@`{%iD-%&ae+2xhyZg{Fp?0U2+Q`-)t zB(yLl;P-#~*O&+r+C;RK6W-F=L6b2Sn`nqN7)_v*?=mZ_=`ISfw!{{eQH*Wg!QcMV z=h=OzLkCFGo{Pv^eVB}kYi#15MS`(0kALD7Jm#7!IX1JOKw3g6Qh+x~3FhWbaPs5~ zfs$Bb7#o{l)AUxfcFCtu(kH_L6^s$c4ZQQOKY%q5#V%nNxPk_@P*?LX@f3ZqYgfQ? zp8F)`=8h0Zr!4N&S)H$^6^=7)tP{Y}29FYO_8m$olEg5-uuRyVTkctj zJTF?h1X`3!f|SkAGSQ>K0k~}F^(%Y4Nb9=Fb(>q&+EDDqHd)K-0=q#XVO5E5T|h7_ z$Eis{<8&m=W!5Zpfk74LbQm$DCSp`o-@mdXwrMa|9RKC>p=+1c3_liO%wPsfwHnht*#hUcE0&_vMkXW z#yX}diCAfpwA$|Ppk%bH$WYbIuPbbnb%g7*BhsH{Yq`mEZH845KOM>Lu);_P!udcI6_vxneuf=fr^0l4tt)0_!QmkF%(C}2QQsW4dS0bm z*|XqVSqa_F|E{(63cLRE#$*XZI&t!lhWvdqsi>0_9`fwc+B8qJLSY4b^S190ClI#B zky4@K1f@cx3|t^66tU6BR+}B?pHHkUN`)w;*uQTd0X7SIq!b8gF-eT>EOFxK{ap9> zALT{Qdm6&vJ#2L@&~jEgWmUmqqEi;UhzyRadln~muXW$1Dy*|w3W1AvPV&kZ{Vbi6 zhmk}`(e)Hn!rdpMo#<71d?}^l+Gw$^NGs&%Bnvv^&)@ML7J)?zu@~2}x&Mqc zx$cs7{>Uu|19;tUy@Dt?K_@R0dNbD#Z|)2p(MPi_U3VijlB0Bt~14 zC9&EVG!_|z#0kW)Vsh#n-udp2a^L+OR|@cB)&(~jiRgKLO%j6QhP|HfLK;91t2Vy6 zif^5QWs~))Y(uURIBBXTJItC_^dRH`AeLB%8Ys8@D2Hj*K|-5)H?Ne_x7K_3upG!@ zV$u|f*wm(@tOL>XXHa52i2gJv`B_PzxE{&xS|Bv4y0cAGw+9t>h8Y)CL3*7J{2+_G zvbj7gQeiD(*s3+ZXgxGcL87R{T|^F5DV0}QMNwj*+C+(X*aC2!MOG89+*6UCR5jPC z#@(PMUEOi>%G^yX!m9SDrpVbrbD+5Hp6)`0wbmuTMdR@%FrqU>{d6S|sg-zeHGwHt z1u#t}L>;`S7=*eMz*PEmraad)X?5K>l?&?K?@9!oKPonMqoCX7WQ zk-jl1Js&o^9xu3sgs*?&7NiPDjFX+VR)7%*?=g!M1Yw(vo3}7FISGLx3`0)L9H+Ci zoON8&%-UM(REPC4Efw+m|Kqhz*_s4n60}Wx_s@W}NJN%A6%(|K|3X(7thWN`*u$yi zl$1eMGjiUv#& zgmpn>5dwXxiv*{$sM^FLVo-;%E|AR!Emd&oGDupnvr`u=)v2tWsvxoadCQDGvEH!v zDj0jy=Qa%gbS+`aYb`9c-d(V|LQoUPZBa0(Nf)vzh;Q1p8f0bD)USIjXad7{fY;r} zL1@Eb@?2bts-9g{hj-W_P+bA8EZ!crpjt;)T+2OV&>mfNUW4+mL7vBD=&rK$y=)I? zinMCtiCl*kI8)Xwhuo|%sy>sIPpiDBR?Bf|8W@&A&6-U5x&^jMRG_pPN!@YM^OAkM zm%a_d+YseD#c2ZT_?+qIRd(Ez=h;&swB3Zx8U| zpySx_h2t1)B+ChmO>gI~-~SN~&-o58f;iS$$1|@8ZZnT)iwq40!%sZ-@mzA*xh!>N zFg7MmI?jt(fl`7)M-MYQcY=1i4MMVUY73h;Z9^vlO?uq{O0{T(8#sQn!~5RDnmt-ZKP%0$TPJ=yAs%9 z6E0R0Ze*Fpz#tFEs`b9gq9>mi*L55Rb@J95o>n*B2U)8Wi@<{l^i7l2s=c;pLMn17 zlOlw%2^_Obu&&7(aHc1{gXUWq7qALxSA|fh>e^Pta7__5O>J4ugw7Q;6qu@R+#qqO zvhHbF$GeCIEb>3gINMFMa)uRwT1lKg$f|F>o>x<(O_@zplMeVG(LchFV(#KpeTE&MKk$fAW2Om-zz6diHB`533lsF@40Jh&^8q+ z@;ois^$)ftg-^>qzM>P&QmzXK2$c(#EeKnzro!wT?78nCQng9aBvkqPCUf^l1x#+* zOsp+oyM>Np=4Vf!3h)gnojq(I4ddf2Uj3`T66tUbobu~ zL;}LNj%O!I&?4mU$%w!Hz{goM5NXiDL4ysxBQ2e(H$DkA7=LU-5W=fp`%5fD$3S$P zzpwiwiFF;#{rmT3-n&)^&OQGER1kVDZ-A7Zt4zR_%{%$mfB7oky!AlVEmy+bw{ADs zICy$d($&JRzWjNNj983a#A%{guZAtqR*=~I2GYwVG6te3W~rlDSWFll-@%{1@!cFf zYA{BjjX-N(z!B2l3)?vGuO26+hhw~wb#jo(<*>p{WgFTcNM+MNU|7yBr?G>$8i?mg z1+%IO19=)rlk;c-i7zWytOh#EiXhCxBOitl*eXlt%Tz_1sJ5?!a?q52E2XR~Rt&nn zrUm^|4clA`h;x(qQ$((Pr)gf%U3_tt9w7 z-LSnu!En=S%`Z6J4Myb_zyKvxH<*wqd^yNGdDG8!j}v$+8=1Y{UDUciX|N|n;Nh!q z@W=uu<~s!K7RDHYR^Va1DW}${Bae)aF)_W7SSPLvdFUWINociNnL={9fG0`B!u%|c ze8d%8cInw*69>`G-!pnWX~h7^6=CUv#+C&(b`TI*Dimpe#YG8b#f#Pkk6{Kb0zo8Xsz`DZwKY!6y5V2sW>y)F^9 znXB7<>`&UAR|U%lTU5L~6kL94Z??G7}B8LxEyH&qN?&7ZRF z(=Z*aVGuVf;dri;LwKfcsDngb>bghkSu9wKXI^~v!wQiND_T=F7uP}^y=fP}2}iY# zrnC}Ek6{ZRr#g-q1`0kb-Ro3RBAUXSo2qp;LG7JM3Bp=&_}6;uol3i>rpf4f2`<*{ z5ofAU)UasaQ|(cH%50F%WDeJ`b|!vj}O>HX)8XSgUEbLk=F= z$I|i~s^$3G8z(ojW%JgoGp@px6TUJ5;(!c)zZJa$* z`hHi#$O)M_C+xHt-7j`RQ9Zi8^E<4ca2fZePLaQ9r*;smw5dvDKJhiibb0hOs-kNW z@Y&o3?5idAR1{DP54`XC3XRVqT+kHw_`(X0jKTldTKkVOSYXw&uY>gu6q}+-6}`W+ z+XHRPKIq4P^?`a5+=`zj;zy-HV9ZBIwpo{~ieH}BU1-9?E*<+=Cy-e4zWm>d^K+OYVQ;}k_rzzm z!lU|h!`J(+&4J)*?YfHB<6cVY<7kY*SoqHO?4A9)%PTNP*RH52v}m8!H?vy6}vesD@TOqd{Jc8TUAe zk=pXKCp?s=T>B`ZxuXy-f{rnX`%waYu-g4HQ}gSfjiURqqDU14;7sRb%7uTfg~oW@q@va{@JXUn5bKD0 z?%9n}0u@L^pt$J452Dp-Ap>Vw6sVRfuFzuZ=JWW($8X~6Uq6gee)BLOY#Jzc%};vn zr*I-?uYUC}vb1!BFx0f#3QITPbp;cP*6EKMH~9ooTZ<(?24ifP+QFOO`d&`Xfg!+H z(X;9HKD*OXRV{L=>H_p8$xsu4qbeoOGQ>$4FID)tGz^q#wSoER?21$+W`h!{VYskm z-&Lo)UB=rgQ`cNePuOPfTYJyhs^h2Ye9v>Na7C!I1Z&b=9q0oqY@=@Ts=C>yvqMtJ zw=H+as1wJj@=nBsPzR>SLpPP5n-lB)%;jh+vIW|v$eWAO*+d({eqR!Bg?#!nN_3>yX{qz-9mIR6f&o??zf1t7`yPQ1vc?vv-YLnBJ-cZci!c^ptxtq45`&57xnSoczxpdb$7l#GnJONuT({Wz_q%1wZI;&A`1l(q zyu}bWiD-ey-O%nGcF&~uj`s^C_14lwmTWt(V5OX&@WZZp1fTfCr_eG)st~0DEEXvQ zI?>p~qT`s>NE<0YDuIq-jMi9V2!zw#)=7fZnpUgL?cctQAG`iKMnXj(EmkMq!#O`b z{WpR;o#hYlU*uJ7z-bZpXjGkfo2DHWZlLwlsn)W(g@FE~_;jVt@E44qY$#-sW+JG z{$^Z&)jJy2WY#sY85y)5)%k`NMMWy@io86!kY#J8?G`L@b3+Va+tNLUtoB~Ie)X+q z{d#F@zSfln2BJGQQyVe&7Jr@o-z)bK3dw+=5B@)_a%|Z>t`gX4>RnpApZR^Mc&CfS zs!|9M<(h%n`J@?aPAE`RXes2!NgmQ)7c&YeGT-}5h*5y-+mEeZ3wuSyL~ftxf&STY zUZc%k7rD>ctmrtnKx9higU!80!Sgu1kD{^z*6k77bRR0b+ZK*n?e$xnh~2SKr*56j z^E9^UAIrV}-emnNyZ=I1?_SWwv}Fk~)t|$aV$QUd3_QJ#X2KC04OM4HL%| zi*04XQMNFKL~)I3aH9EfCh{5s*7oJeQvLt{AOJ~3K~&Fu^7B9c>}2iv*Qpk-te7ea zV^kK-!9TxrPWbM%P0v;M^@DO%@b3H;1%y&_BvX~`U6mEyAgjEx&(;+ls^W*bWV=|D z%?kBrKw++G95uDf%8*Sz49RaE`U5@~%6oXVD0PpsN@S z96EG_Rycz38%L5PJzz|?d|)R_g5piP355Qd5%klerLZj6m-wG~nd&f0l4TefXQTW3uq0(b9Q;W({e zBT+QQzyJGPeC}Uwzndc%WdF9UnqR0bbHGXjdi@^qSHlFCxf~IU1^uJq!2hr zV`JE&@A`HO3rRP1`*E>@+aUQJvST@nenwTnpsA2sQ=zz`Ce8yz+-lGolK=lHNr2aj zyHeKbFT3b6yP77F_jN|zrv{Q&uC}=z9g!7nSk__VD zl#oOi;B12}ouxUfcAma2Q4=n?k)Q-v9dYI5mypsul<%@)T*Y1q>0r0W91O}qpt}o( z?Y*y}ypmb|Jji&7{VSo&IKu^E5GdLsqgW}imaaflV(G+&@u_Wm;?pLY?1u^GTuKJgmY{*o)c{? z#w18(5JEFC0l)J*ui)guUM$PBS`w4Ej+cL9=*&Cm#rCtnCZ zY<&{E7W+xFXCzAl<2=tycnGYa1WuW$pZBW7*V%8;kQ+N(pxE^_FLK^eD<4;g7<^yz zT*~zNjQ?BB&CL2zqUQN!dpX?%w+vcY94~!)c&^HlJ{$LZx*89S?q9Bh-#bS=2Q>Sh z)PtbEvt6Yhk&dgLATx(C>AAk#*s@}&xUcPiX=294YG%=d=1wHs9E`Jvzd4^oY6!n^vf{%4qpln=T^j)UfcAZLMN z3@k2n2!a+;${rp>evpQ(k<3!59V!;*7r;6JEGYy@5)lLvj7FOdV7Tha%LoHdfqRBZ zg?(I)p6?N?(C(%pm~5yr+2y;qj`WDKj7oU@>t2bTKSFf!IK<1?Btcnx7Z^+wbK>X> zVwur@rPAdY!Y5vhdXFFXORSU&+an z`-x(w3N551(n;3?SeRZPYGZoF*HR|}8FJRn3;Dtqzs@H<^<_{BZRt@(KV22gGJfx{ zD$>I!#SY@IccD#oC_mLa7s)mhv}wHI+cQfnqlslpRfjR}U0MZ!E<0v=y!BEhO$&ro zJh{^6CCa<2#m-Z&2$mhb(?zuvQg!#eD>l|!L8_l>89aZ^)8=(XV9H=QF*Jy)wb=vC1FG;yv<@P<+SKGm^sab2!TP=`ZB zmMQbg{CD)?hGAQF; z>yBlU4#^-tzqPbmT4_b_fjD4#Reb%zD0|K64G?u;c{y^`cMn~4eym;fYGVaKE6lo@ zLI`ICl)=d@(mEepsT_xR>y~LQIPVGF>Zp%etbL(U!J>5u&p=8x=0M#}`&I(~EVIqUkUoR4NjD%@u83d^y!ury zV9T~qmO3*CWsrd+iDIM(P=R3gz4x%(>0qTG)|M?>cd%va4iXP5Rx-q(GgVHb-EplI z2qQ>hK@zoSwYJf2ox@w-{I`7YgI~m2=q#Iz->VaN?QgsU%q+&tBcw*>o=(>q$!03r z4vb*XCMJp^q9{TeSn61|ZQsS)-u3~G95KX+NAlXB7cSp$6KSgcxE{7=P2jC7uO6pW zebLi7tGa$yNtZpHbJs1gm8!XE2Q&?)M2&b*^@d@x0@UjElk$#iU57hW!L~i?M|T|& zwj@Eb!XpXFMd_Lr8v7GJQ&2M>R4*u=Z?^;1GY+iwe(7DvdtQk<<;uBdV0v8ydd7#% z&^!5rw*T>_Vu;3zNu?SD+AI4Ppueg5Y>6sy)eJ2Ya>-MoYDHl3$G6m~)b@53st^*k zuk)DEb!>@Pk?0lo%FoY!M}qvl>V@=nX+gpX6BYw;DS9T6(&OoIpjco%$o{`tQjnVw?=XBKoKNAXR8WET_Lbu z+E|scaQf`5C|6r9B2DsY(P9M<;GsK+q8b^IQW*@@MQ3*#tL^G+;rO=V`+D3r^2f-K zKs2IlHW6oQB6iqROmVIAl~aXKt)mh(E#S)^hU|VAZc`KW)FumV9ptgjDE)__+R}x5 z=&W*`CRXY7Ep-x>mLrr5OT`p&_21C0?AvbYg6j zAUqdX8JIbA1d}9aEaBJ)8#hgpcramN4Pm>5PyvyVOiXX*#`palx8HRTBc%H=^$LVp zpdd68lkkUs_*={`9mLuNgwzOOQPOd{m*(f#bKgDwS|zcuTzK(AAPCXc*-=Op61H1d zBT#9_RRtae(8A~dDJPJ21KYMgh`)KyC-~H-ZXpOHk%u`;1)Da(3t#wL_U+q^B|__n zPP|MU$35NNbSyb_aWaqUFbo+T9Y@e&bZnZr1$fh2{sti-)+w(}_7rW_((`vEwn%l* zra|4+)%d2?sdrWdPQ{LU8gLiUIQi$)z1L=_4#KHIz2q%Cs_t>VQ&-h(XaeV4lkv3% z#obzXWl55r3dK-i<6$Z9ilq?Srq&%2fs(4Pd!E-pD54H(sRKTt&y?_`Y@`Wcv8yhg z*L8-g1#Wt~>P^Jvs^~gJ-li36>v_FW&4Vf-xz$iitQ}DB!(y!Iv2yFB2;|o%QyBwT zs|Tu$uXm%TtT5J1xOx!<sA}WlwRJ+N5_uXF*LU}^-i~>x zT~T5FlzEw#Is{-FaiZ(G!BupSy7jBq(#e*J|E?#k^Gwi$8U&k`YmwofE=+hG_qS<) z(u7z(D5x0*b3WW;Z{~-#-kAI1#FIiGg+N%xx6J|z1kx8$qbS0n5eg6rYjT59s*E!i zhG7Pg?tw{5*AYmp0VxTB0Ig$ooV}ee=q{r9j*=~KfHGk+dbwHMj&D^*sZcpC0ZX`Z zO?f3I$2Zcs@uzdeP{(POI035Z4T9f!?aNp^zMmk8v7HV!PB5LMTUbpDCudGD61I>M z#x_n6j*eikC>bD-SmQWbs5Y^f;D5jIMwT^1v9|{DRctFjc{N8TfNiqwT^K3^aQWqv zJnv_p&fMZ5q(}%`f!h?cp%p5Q?BCDA+$=&^60NZ+;Lx1eiC@jv!BGyb2o8(WydYH&W-;1%njb4v^I*^Xbm>zY?Rd0_Rmt_w_P=<)Q~>lWtf4wMh_ah;eD?^~V&w zKIYkaD6pcCVK!u|Co z{&#H+NI{~l6ZXq-jg3vvItEPU1D_#7Y&TKH*lhjlOS}sEW^r6e3JSUvT<~=5?>=|u zKi(gt_U`9h>qU30F1Olu9t)i5KF*>4y{Efn>DhP6yiTjuf;=C*4(V6yJ{7Gb)3u>{ zKg+qlWnQp#S}udqUd&hV7e00Ij+^4Zrr8gFF$sg zB=m!z<^6uORQL^>o@Bg;C@9+-j4{sgFx_j7bvA*X_ie1}mO9Oc0@B0?tP$Py)BnAj zV5zLLE`;rJVDtXLwpVGqY#t7qPxOsf-|_8!t&4@y^&{PH(n6XsspgE$*7I~7&oo!G z_Txr$f2`M*&I_;Fegu(3IV4cwyvlNtLDqVM=10>!g#QAR&L%>6@_}GhTvcVOOHPZN zt)~92D;XdUR6pO9R98e1E-c)lC~(|LjZ3iNI40^O88i#!Y=?xBJ~?$saJ$_`YmM?> zk#r)0u>rLMY)m&wLD* zU$~Rwhxa1lWr(#eB1i(O5ZW?x=qS;02O$JOyTzvMThRg^fEvebHDFbn@u_Wm`Rljy zp^tn98MYi3*t+1c$aNB^-p;jjLFtQL@?-2e|16d|ClEq=G`b-O0b+JcETG@taK$c=)jn(7KHf6TI<_?;(zzag{5u zXu=Tw_z(X(hYsy#cJ>h3bTGyRA;k_*u`8*R%6x;Z2}o>Dg3z`0Q3fLMeClj`IV0UZcyIsJoKHH_uou|iS+xl)|s7^ zpx>Xn(z-y(T5sb)knbXdu;>SwK_q>#EdRavM4`yB%kyeWMN%phpa=19SE8UijChKr zoD-LxdmP7EV3-(Pn#7mE`nn08=<#3A(-?XZC~LArHS6}4+ymzdAp438`R9^RF|d6u z5M>?fCLZv09^1vg=hxOMsA``VDtU-`_u7p0MTp!_8o-wcl%>gEDgcyquc{J~A^kOpEa6i!#H8YB zUaurEIyct4p0dLzjSo*fEFvDd_qs~=q3Gk>Lk+a`D(_WV58Zi2V?o(6Rl|J8dZjJ} zrSrCgGL`nuxkOjWpnDtx zM!NvP=&nMQ^If!IbW&F(|C(vC$D}Wci=;v5ko&}-N zo{Q&r+yN&}oZ!HrgS19lXqzy%JkREHwsZCMPvDxTJc&m?`C2Y}#KRbwo@7bK#2AE< z9koDu<1WQdk6QciI9?xTGC6JH{VUGkxnLRiZcmu%vtFMckE58O>Fuucq32oRP; z$2s+9jTP39yKYdZf)H(?)3J<`w(#^@|yw2si-4Y*9wRXfXFr&6Kgs9RYo`qNDy zl0sK&g#}Dg2y}!i!o@o2b~KIX@+nP$>+d@ST05?LKG{pVjl$B`X~_Pb3%+XMYlXyH zTQd~Gr~jBGoaOxfT5QzdRP)n-F;s!07l;6??6D$CljHn-&_0p1R(Q)casqV1EA_h~ zoHec}>I9x#6;1}qtIjGEGV8fW>7YL{2tm4IrJejV(9MgD$v|boc|}fRm&a=QI-C6v z#`2DwF%ZYO>lNO8UMk|m zIShn2OY)TPJo2vMcTZd@>p)d%C zhA?D_QaJ~_I-W}lgn6qg<82>{9ax$3t4{r{Eh(C%KK$uH#98N@VH54UvQB6+Tb~k( z)p?BpUm)=F*r6LluPS_wj+P>R4B9=7bgs!>^hMr>y9`NG#yz)ISF1LASYRr8*R@(y z2qTap$O`}I>*;uRJb2~SBq7k&I*}QF|8?w8EW&Dp)mWolFl$Y>7%V7(*HZ}@Z<44z z5Gj*ug-ER{nyrY&SQ0A*4-}4Xm8ex+Kv`xUUWDKjd4NTe@&5`P-lhYmCm^=giBn0L zZSD?|EHS=PYLG}Hz{8m{U&X)$FR~z>rFGmB97-v{TAvUlpc6*hp%dNVbXq~l(&X4? zfVDCnnaE&u#u`GQu)=cHWoPrMUwQ#=dCy1Ku=N702{9VHxf6j1m_2%&jhm+#o0>u> z$=T0IQiZBeh@4kDP-nxnQ#0cT&2$NIesFrf6w6Sd6xs!`7xs;iMhxzt5Z)N82 zF~-|%SL?74#W8_up{=16w%9Pam5+Y(|MJXdT+7I)aXjfz5hw7PSG|DGeCCTBKem^N zjc0=tNE`IdQ9(BTq?DM{K1+LX9T{Q~Y~8+#_q_LGJnV{#c|-C}&O7hKTFdp*|%>W|MX9vCW<1qZQI6A z|MXAS^8CwuPK6L?WBA(FzQ&jS{mV!ZaK*zO%Htn@Ez*BZCtyADL2FGANRlM+*J2S; zA_Lh2J>9qW0QcOxn>~B>FgrVo)|!cl33lw*!9^Ec#I9YtXt&$0Sf!o1dRpWw+E^Vr zbcj3e`i>I>jAO>e#<=F{Yt{sjbjvNbu>arzLKUF3=9+7+VS0MHcFb67iK2+Ff8!Po z96I9A6*}hXtFLC;maVm2jC5WdICPM2+;S^Qg;|22lUn;DJXO`zJw2020KwlnyylgVqr ztXjo0&ppS$zyO{Xpp+t)%h5Zlhc~_HP3*Y+4v0k8D(U@RiN-qLbDK0qH_Jg>qpe{~zh zVi98u^XJd!EqlFXD~g0c3@QKZ1NZa73olUdeUwtX?>+CKIo~|tY!}ktRRV6k?U(qz zkCRRzjRaY%1z}%f??*>St8!9@_^MP&^z`(wWXYTD{T|Yxl$>mROujemx~|F@8STq=^zW|-MKn?;KjvC~dFk@tAmlAMafyq)k$> zP7NZAb&?9~4?gre`uh4vr_%s21ui-ht3_5DYqYeq(ACvNXGdp(ZpM^%cl76@qodq@ z`|VZ`)KU218j&(W^N6^i;Ydl~TLdFg5CmQK_Su(0p`2s^?CzVt62<;Gio$%c_Z z9F;;EC#($HgW2eS{xyBfYi`9zK`Ni8d+r?io>^_JprZHI5a80wj^Y8=z%TRwmuH!&pA$-0cC8-8Cm1phR7r1BT103?6w-5wnobUq8 zrX5Z{?RY-+iGL-XYa*4&VvJ`yhsvov2&F(L1xiFU*!3zJqaoMa#dps5A$$G9dE_&Y zQliQaEHgfaY73`@#J4P`}^lGqq~=4vBtlylunrR+D>N03v356u-?i2{ry~h)wSGx z&q`LWelhHBQB%B&5{RCj9`@dQZ$5D7ds(z-QOLtfCW4IrhYy}g}xynP?qS_=*LMY-&8 z#`n%fYbcEs>1c1~$3Olty|a2^epsXZ7zAET7hFok#TQ@9jW^#)p|yqbc!_h*J(n3h z`(bp10#Y^OOCsSy+S_5>`aZ6{`fBdD>n{2>^b>eK(oqN}gt_{}-CC(&`|TF;p$~nC zgAYEClw*q;Y1cst0XneAjtF*DPZJO()YS;$r0M3HZ|01%zDIj|J3`eahEhtDkVr?_ zd%A`eP>a@!c$H&g50FZ7f7&|8_ewHj*Vv$`cYyyO+A`v25sy@`!NTQLAP}TCQx<)F# z$F9y6UaGJOPR=IGR&OW z!|uE7#=!?4%;H5mO&!07&pR|S%(CUDTIUfZC!9UW1XhKjp+Z2~{%U&Qda?n63a3mo-H32OP56EN?lnkJ3 z@~3QalyNY=he|0N$FV_J0{(2@*GyV@t6n|&Rp1sK7y{^9!53~GQN2j1Ei!t zNEd;^7Xg`kH{bg1`CNC!Sy<$7Xk9WGp&#xhqnu*p`85SwQ^L>mmBi z&Yg)-VMV{Wf)?r7&Ot>_>FeoS# zx_S1Q-}AG}ZsCicKLoAAl_XwUL56KfqPJA#oKoF3eBidWHs;KkLu-4R?e+zJ%{qyK z{V34Q<(ug2?8Hf>7#|zslv7UOq6_D+aN$BQ)bOyA9k;kZp>1I!m&-G2?i_l1XR%>J zAHq=$pDp^zW}9ejX=molnMf(Q;)*L+xNsrwdC$9Ro@de^A%vs)L4e~p?6~8ONGHV$ zYhJA3QW_J~tfA<_SIP#<#bS|*FTR+ouf3jfxlD6&3mqLDvF?GdZ9 zBQf$?n^g_x^@8YhPk!Pj8L2P%^NL=3r!Ggx=m>x_Wx4R4TPT@UC0!Mg>XOv95tG zC*k9cxqM53LWk{Y2VPZxuip1nV}EvATN|lVhDxQv@a935f9qSEciwqK z&DyAIRNrxm5GR4rNEGR8mfl%2X>Dy~XlN+ZI+z5F5Osizg#r74&$;KH$5mHeNwHL+ zt*wpL)>a(H7DC4>Ws62gIVh#bX0rec3~c7~(@*CYzxV~8|NPPH|E~SeL3QoNzUkC= zs6z2MKd8dW!`F0ncGA(&Q6m~Aga|bt!aJHE?2M@}DOB;gkr6G&7z%~5-4nh~+HvD` zDW#;PrG;$1iII^JT-S{Y3Db$UV#u_Cmi9Jg&6-7bZ;##II;bg1+cBup4Vs&qX>Mr= zA=!1XNK@bMFLwL;U;p~ooPEv@cyaAZ0Cyer~(vR`%KV9em|0Ut#W?xitxV^fPQ>D@5lYS+QaT7hQA_ zYuEQtD74bq*%_|)h~sPbP$AtK|NM=cHu0m2E@H)s6@2cPV|ed---{nqkj4-M8Y%7G z@XB`GL`kg?hUVsGX3d>PE|((+okN5$H8x;T0vU~u)|!nQH*x)qH?m^I3ijQ1U%v9? zW9jbho)Yonx-RXVo#gV(6Es4i1h+=N!8ftpQ*?fLBaOlgII0JcEaM0qsA2m83erhfPdhi{ocy>$VO5!zzIL5=XqcZu9R%*>%%XN zTLnr-Fmu5?dKuf1x_lJ=%@r_ za?Pw>y`I5AgATbn$`J_ZV**%u(!Z0<8pg*4kuu~7Hc-tBf`CA4w6O}8#()m1`koIX zMV}cn7jo&(uIAYn#;9oU^eexArh6VWvQL=oed+~Pw)MP9`nIAZeqiu5I4L*rp0!@F z2#pR4JLBatUZBxPyuh&hTgw<88N}B?tlVo7!7kFoGzcjjlyqb0o102)Q49nmp65|6 zhu^-(%sI0;>-*>M=${`?hzC`l55vSWBXMsG3=A+hIEWG!;%pHs9#MJ}b-i^Yh$RJ2 zJh_UaKJiH|`=2XlZEdBer-!to*tBUQgM*uJUB!&9PCDD$5Joe!d4Q3TA*6)P&USiw zW^%_}zveTa`3!$}D3&*DBAxrARXp}h*eZ@(+!Me?}}juJRZgubo1%J&WVepXT1Z*ZPV%l`}S9N)JVZrOY@ zzyISSoO;@~kU~-_mEw|S#8FN9Jtw=Qk$9AnE=sz1o|j0Jg9*A3mV++X(7%CCe)`k= zgiTdxEA41Td(yEileMw*T?YiFoi;aS@Y-e+uuFN(MKQ6uUFocIQyzy(a6Ry z2n;8lbP}hY`Ypyv<1ARPfcExQN~IzL1O1dM<1{xn(caZgA)jMxY=pkPwTz4nlg{So znK=tz!?(ZvZO%UX>?(o-6`!@$WGyU;8DgljHiq%BA|pe?c;$+XGlBrWQlU~RQYwy8 z935u!=FJQb4~K}3PTD)WxqIbGzVO9=W&Qf~@i|``ONM;nUd2Xmy(l!DV{l~O*M%pUIFn3l z+Y?W0+vdc!ZQHhOCllMY?POxT_xG=QtI|>Rhg;pZ&)H|~XRR&9UCCAEj>%l=3w`02 zhX59cuJCWo7CxcZ|N0bzsxUD~fts?aNoK*wbfCZ;*EMZXl~8yCu1ui0KqfU7Yu`@} zi+3cR24XC#>0 z4_xi6XA$B;!CW|jfa+8Pc;jDeLavDE-7S-J&z~w?LVKs`n|miEFw5+W;Dd?>zl#L6E?kxk+e$=UXhTIUCjb&%m1cl5@g10NVZw> z(Anl*Oivx!XWEZZYI*MdG_$eD2{b?t|4w=YN_j&CBJ#W|YS(ua-S|hfxZS5f==dpy zYploXR1FxaHE{uMb#kpaeZkK3@=P9mU}fWh4NMHzHxXfA<(XPzv2eZVg>2gm##U>w z#ncTeh1jQAR)P>9+^!H`m_(gmB=l`-VY=jkTFng2 za2KSUS9e5bIj@DqcpnHh-y@^J;Fv|EknQ<|oKr&WR$yqB*0Hinod{7W8|_Kx>!0R4 zACRA}&rDBjt%^~ucDgYMU_7px;BP%tyVQL=t`BeOZP(Ci;OxH~>+S%n{Z6y)mJBfX zrl288i*8uf=auQdgPOaC$L!jGii#a*{7JRwIXAS^SBb25X8%uKoU*^Lp#E}|@qENM z9x^nV&5d)7l9oY$e=*CIt^9-JAwkoU;$B4$kW8b>1fnp*Z13Db-RbA_oHqP@gfCBN5`3# zBhutwx>Fg|Y63tp4V95gsct3mKJ4S2wQkdsmF8d^KWY5mY(p&^lWVW7DUa3C1M*MH?7tq&E#=kWwk6cCQD3v&!bo6T`tC%I7uXaKpA`@^X&%;CO*; zfA&tFT3YvxI5s7e@k0+D!DXy8+A+OUJA3MlQs z!`U9Oqu=*8d~Q1b5>hBrAZv|FR{i&{Vfs%jShvW|njuBt1vrL68Ood!rK)Emidde& zRH6umpA8K#nvhqGXtwY5rm3-sk)sv!7nHDIAZ-K~|2GBX=UV-bZx@XH-ubnHc?cL_ z_+KlWt>%L)ovq%)FUjmDe<7;Yo=;-Y$pVGgr~-F|wX)?V2J6HhTJdM*7y~%3y;D1` z+~YsC+n^XImphVSRV=FNfva#yHy~W+i)=>?p ze6W|7c2rC5sJoiVPD@$0AEsP18@j-=A0YV0-Ye@$tBft6=>EY-*ONF4`!=_G?4XUV z`vw#WqlSz?Ra5KvV&DDLb*)~Dkunbz;1B_7JdKz|#(zQPGA z#WwA0XmB5l|_qiWmzD`aNHnat?BMKA{!LlqfDxV5Y=Pt;g!;qa#+_Zl`dZ z7UbW7IWfm`_+RMXq@|_7`2UWMCp(;yro>LOEJh}RM6>M`&}g@#RC(P68GAboUwXbK zSwC`jOT@nOp0eRcS+u&h+G z0|zeueJ9f)b#@WAJTAkPR+SQDG2an&t!59bE!L)34gq=KTIfQOX5{!DZ8VS%Mvy0` zq-w^WqESk-L*PU8SSa))F;Y*hlze7M!+QH7(Xbx>MKtp z*J_PbrlnvObuMN+l_^ekD3znj9O(&eaLltPN%58g2yJ`-Lh-lsi#u4p7*Ku*I z<@K;Qk;#O4h^>+^jvps|5pBmn%d%w?w0@PwVyRMp*Wb_Zv{zO zwM=_^`-;nTmwJOST#3(9$NNj&u&KFqF>t)mwCPF$sK#ty`~2gNs@24wFBoJLg-w=L zmMF-YL7Fli7f)EIgS5^yna&JW;SpUrzjxe>FB3IW{uCdZ?;tp48DZ@%y2)$MIiVTCTY(lVZx+Ka# zrh7i!KK6<7G?9E6qh5_&*SM{D?aC;eWe%s+Y2o?n^W|Px3;$y*>!^NsS`?rPx`j53(-xr%9jK;`^UFl_@`{GGFaQo zu1SG4**4#;L1%Oj@OZ)rY6Wl!Mi1?kJZpwzQ4Lpul-R7(70L{cCsNsR<)aA>)!s_y zWjWz%8lm%s8J=CeSNnDaewxc_LOCM1nVvySo#i$06Z5xX#O1Xakpar1TOERpCSU>k-*L7Rmd>>G>T%r!|7aq8@cEfdf>K0OwC!^U8_51=)v%C%Lf(1g zyw;m+ZvrBLfMUs`(+xTT?NU$p8OKkF#B$Rb7YcN?AEq6R+Z{xI$NlvgE=K9Trb@z!u`nu{)hsS)huo zHEuSm75_dDWJM;!(zW_lXJZ2ECjkBRvV%lWu4naH;4m;gqm5dp0YAh!CSg0t5?T^)w;hsm5a zv1uR(EOi>cpD+Zc@8pDH)iU|9TE^RL-D+MyHjO?TVQm0mYvioDSo}|_-gT0aKn#Ud z-@+?}EUM&JR?gel8L#v(nteNK++EY&gLMPwZKJ0{OR^wm)AiLj%U}1lZZ+fhYpX*% z*<~^J`az@8a5*^>xhy5#%GX8}FS|HU{ER}xa2!lx8cT7_VbR0@#HL53TYjpA!q3Us zUbMGEX=j39&s8hT?XtEk#!g@53pY&4ajXe2Iyp}i`2xs(|(Br=hHlm*==^Bg+F)9Ms&z(msvvzrqn1OeJ+;yBLX!G!YW1c#LHC z3t=n0&0_Dp9ohK{?g`|^~Q2FDTOH zGz8^Pn<_N25H(Dp1Ir$&lk{tKsgh=}7}Ic~_iNwROBlFVImG?SU)%EeC?vSz!(6;N zWE7QSmjx85l4uM&l=?9GBe&At-|whfuhy^;#+u=KcI!sIMZ#*QvCRnH#(bFq!YK08vz3nJ>n{6J(Ea9Z-!+H*Ra zorS{^dJgm5d~I!})|aCUH}8jgLYRW|Mar`0n-KUCrTWd4NR3c}*}Qa@y~3eAXA?4)&0CfR7?HKCS61~@^_l~Z49{+UgpUwwhA`#e$c(YGTR-;mQrWmTdsArE$-T%l25P}&`;s1L za<(fHEV`v=(O$xsp}ci?j<&Npl)S1w5v~I9p1*vb!J1fM*=y7?ilyOVjXH*j_=lGCmEqIrUA&5-%A6ixnM*foirT|wVED*#KA zt#%8z!PfZTmR9+>$S$>7{L8+37&!lUTXsbLqlmoHmF~cR>cViEhJJgQ4~I!wIopE1 zR%*(}0pC}rY)?!=)Tt(?LLf@eZ(Wp@yOa`$x~!`3m7ZZ=E}eAYC{*6^AV)Wu9oUsTaWoU~WU$}9Scqz;tV4tdQW!XXFUuWK?aC&BNYR^5%2|NRzY3P<`Ll3f z*`KeqR0`YYeH`}vCLR^T$b{nVhg4r14oDkWMJSe&r@?vpjk;tJl0m;g>)hZoh)$b- zb>RDj9KCR0lG){|j1o#Ks;H>N5Uqa`rU6u=Xf8Jl?PO~>be5Cf5aJ_~_u+Ol$u`(v z9f(3T$&3zLvht_OC08IwPHn7N;a{fsZz+$}tM3OrHAlOm%ZLap8V4$ugIFMqfnS%& z?Oi_7U%Mi-jU-OJBn0~jmv1L`F)y#()5AQE3{*)-Kn}NthFX_9OG%b92N{1*YB`h6 z<;vNbAq!5=$#oPo#T(ub#@HdFWHL=&SsCRc5^$(EH54I@^X(XJGZvfyR$<8 zd;DvsDsS7|aFwh`nY&7~YU69?+SU3Y0xe#PrXG+9>h*d-XL#`0{(UG>6}fWNv|Y7_ zX~zd{iXAw06g}WMt&&NWV}Y|W_A(WMtdYCdXizk!>8%?GFO2~d!PF0U_&qjwKQuZ>pV45q!!K?!AiuI4N{M=!YN!DSy z>q*00c*KSGw9qEjxoT4>%DI<02*r#bog#22j68b>km?G6PN02Td&jKJu9Wmw2(HaWI+>4lK^mp7h zwVr2Q!WN%sA1fm8Z4Nm!{AjR5H9-O1Z??3Uh8xycaRF5h`VFCArqJv>ecb0`k3*#j z4{rA;>@Ow%8qiE4r03x;^*npQx`b3VQk^cbxooEbqxkVHs5#qbOldj#kye5)pE5eC z)@+_Ev2li}qLQi#rf$iUx!NF=yMZaNHm#h)c%cl+#Y_=GhV2SAG2%Oc1O+KTa-3-5 z^c@J+jGsKo+wYrg3k(+-RvsXxqF6U49G2uruC~CGlPQFw<~wf{mIloa=2YrD>({iF$SP{bW#l#5%DJ{FaQX-)$wOy~xK6jSkp zdBv_iyR}|t(j`8()cq^)Y<@@<>KnN-Usx0T#dEO51ejf(5M_{R*-g&ZEk4Z_V22hV zJvlqu8>>*UeMzDQZ1pCh(vY!K2b@=|!dO^jbs;FgEqMj-Na^Y6fIu4=6La4-?X$FX zT5k>lRS#jTj_N3Bdy-XNUx&n~q88%`S?&ceqL7M_?M{Fpn5l5wxgC}C5(VK@@VJ#lga1CD#pGttVZ6L zTcm%Ps4eNyudQ{K&mm`@_cPU=aN3gKkdRei4OFAI@eb(Y-+u>Xqmc@nYN@KW5m;QvW9CBIWKxwCpV7Gf1%h9Fm4>YUM3=J+}k?3jP zj!g5T*igqiw!e(&!ZN(u>rm_d zyjzUgsr0!fl5Of;phgptedI}S1OA9K6nJ2RfglRaP^y+TjQrsymnY}zc;n=|U)U$P z+%%1@-K{_p$dwtJp=#l(KHgI_)HB}+LlI0{wP+~K9?VBbhGQu12UgT4xwr!6s#4M~ zuKPwLc%DN{>IvmJ9saDgQ1qOm(e)v>=5Oq`r|J-tX9?io01s}y5MLc*oZesIt za&A}{Yx(XI8)Lf`r+Gxn*n)p3=O8=>6V$T++te z_=mKHAG52HunL)mpaUsa<{BB50wS)5a#{?3<#RHvbvLgGZ5frP|KL;ZTPB|?cc~LW ztAW#nmhUmj*YfEv`PxPn2dl6KR<2O?jh=&J?DOlHj^|(A&)MysY=YYGG~SO>*|yVS zO{|-Hi-2>I0~N@HsJdv*??a(jni&9o!okV;GG(@1+|!fu_kQ#+QW=D7gnv|Oq6TdYvk zoWACW3i|1FLr=nrX=-l1cRT#mR}k}2CPJL2bI^~`pCDVa=LhL6nkSd5MaN{J@(c3f zi%!!rp;#mHhZ*%KgPZ$e1J`rU&)H0y-g~a*ZEp|f^+}jpp-1PC&SSmza6m}Z2iPCo z&ok9R#naR~hSfCoLpIVW_kGAZ?|w%%UGbh%_b)SIq6+eL?#Lk;i9cSrf#%b}iSe18 z|1t`h&={>F^Gt@Mrnv5 z{uP4bNMF!T{O#c04bSt2|A#V)pncD&oF+d9%qa(frTij7t`ow#AdH|CY#F*?l@e8H zBDTY&!PqQ{=ZtALakw(gO>%8j;@BNzA=D|XlVensc)d9m{ug9onl9|XP2ug6N@UZ$ z26Fl!2@$2@FT4-NdzZGLr-T~n3?inojN^)smYi~PR2Oz9WpFGM5sSq5Jmqli+9Z1f zzXz-)^m-9J_MAXZk|M3n+&}sW^=JwxPJRUeU1~IgE}?{fO%)$rVIkT53B#&zRO=?O z6_;(zh(Ap&Q@3{Jkn#lb#5?JN&4Rv;3PQSrP*pESkS!oajX5yz8h8@=eDA>F{g3f< z2dy`=TtyV)!i767#|nBrX(fsf6n0^Ppg}?RXBOthCtWtC;aYeGAh)|pf(nKu+8&u` z*31YG?=Z4?t(DV@3mU#miD2{ZBvq{yEENh|8;kbI zeTk^>-aAUqkQBly?7iSgfGdp&Z#ABC*{_Pci7Z#X%%~(I_k0TT*ghZ2<@?K5E93nH z)#QRsa3BQp&Y#f}*ncIMF|F|z=Trh~beTHt#Q)6nc^~##wU&WbHOC>W;MxM~SrVcu zB9n>=`ti0}U(Kgb@y$$U*FQVe7`I{e#XvxLxsYwt@TSH+4xKWq_1>`J>88l%1N3~U zb3m}iCpEcQ#oc7`0AOl|-(T+C_OV^6-?)@zYM_%k_w8Tn`HJ zo?VIp9*rOIk}U!QktlGd9bt~9Xn@)c$8G>oKNkay4>Ts=k=M7PrXA1V$7Pl_Fx@=L z^SQQvq>w3B*c1ePnZm*h8ZJU(@u5(OR@Hkvoq$mZE?Vx_QVuJ04P*e6l&QK>lWqm# zi(TcDjiw27-8J#bbcaC?~lbP$@m5{ zU*%~oLDIlG^ax=m|x zg4|&uBOqk9so>rSdj7t=T(6?WkB-X(L3{8zt=W6lxy`&jw^I>&4G!i0QV-X}n-L^J ztF>;Xu_>=Q0&Vc!*Rz|OlN)Ug`_p=e#Ec>!ByrAhN{`84t@$;6ev_6b_yk!piqM`M z9BXQ|di>Ao=d{9$Dx2h-v|9`!Eo{;KfZ{A!r$&-s-~W_wi>m=Eyp-_w@G|w3AU(qN zJuqMC(e-Xrnc{GWXA=NK;et^1{{*7siFWP8#C)V-YU3`QulVgyqBY`QuEHp)Mb#%bF!teo>!oiY9;57ah4jJvV4#cLAlemqHx zo(~(Bcf&IDq7nSaCJx|1ChL7jDN2ONI)S%9MUpc@ilC$yP+t_)N7gA2{!u3*F^&y( z7#@P|TL3l}xHCsV80P;fwD?jZT*1s@!MjR`#*o=k9K?=^!yLRMAkud_-aYks{q^Ie znppw16Uf1X<(J@$tlS<+SDt1xxfKZSmOZZ2X+NIU-+5tyqO=XXv=uv4xYc5Op-c98 z@_bx>>Ny$nLkTC?)5q-H%Q|B6aQ+=27*MDHO3Bzd2F=Yvd%=_;bK7@6O_=y+nR`1@ z37)jZA7#mbp$Y{^;Og}IgnPf;{SwB2jl1*0=Y_fn_$DgOs19i`_9MpfAU1&4vJ0lg zmEIqT-p4w9+UvC-)9Dkn(fJo&hQx{L`NB?hvW z$9}?$h{jg>Bes&GQ@^CCWM+~G-kQ(l{nUcpDiL(_f-*W87K?s}4s zLHQ8%8?Drfro6)E1OjDXYO>qzt=9FHmB`I*ELyf8(r!S!gMJ7u+rxq)V8x3zc&uy# z6oa7v-vk+sFiE<$z{c~X6=-n3riThL*{BYM^W~ZrTMMha3|Mr!$OHuBdv_--rHJzO zIxoG6l*Xq_6r{Y3a#E#M_$b7t#1KQ4KNjC-z}m~bvpox1N|WRbGE$$Js#t)=a%42h z%TkLh@u3o@I|y0aui*1}EQ(=ANL=6jR%RBK!8eccKVi{9n6w;|_urGV-=*Iikd;7T z$KlDi0hHIQz6baAN+(K?YG|%uVq+SGfUg6#E@THwGAVV& zx-A8y*bSz*l3Fx+RvkkXo1bl3S4QZ6z-P9$tUo%2sm01Dq9Yjq`C$G>g|SW$*z%Qr z6-$xC;HOvSg$NyD69tJ?<>B7sZ%x?;0LcKvCZ_iG(~Ap|8NY|qY_LHBL5&r=VO(_Q zML2==;0WfxMh5JDbDE$sE}dQcClB&%E%!Yz=JBQIRY&{$9v_zG--pU%)7|D-TV(_+ zngJ!-jK@!h_WFk^Fxv~>HnhVo7M%4_?B(LsIn}!=QY9T7ECxKi>kJEs@QC6Jf0uQL z9$A6}s821KR1TIQLj9l@aiiejMk=mP&ucx7-50@G+s*H%z#eJ&0j6l(&jI4+&EM~d5&OG;A$ME&GyYa;WdrxN%e!#(vO zTsA&9Kab0|<)ANCgv<}u7IMsrVOALfGv7;#4cIjUJLgDHbfu7^pYP(eq``QYm36+! z%d&7V1aOEQ4vh7b?Lup^>dG}hdTz%YQzl3>03Zg=a2{YMw6WHAw9MS^wgV-dCWpGv zi9Y!j1p|>#W;yf}3~yzMl*Llz75y=XzIu#nW3WFIWm=WS>4S%^;|c9jqXnH8?p}zg zC*_N1oWGM2xgeP(lz*8g5VS3jCs&baoi7q;>KY@((j(7c+Nsrd&m|%vLZ&89d6>^~ z0r(LBj`Od*pLWQteVC?o8)-8$vi>fEf996wxfP0}5l+${0LAp&`Hr7kRAl6DD_hu* z`(d8%x*zqMk(oI#Lk97g2NK7YZ^SR#wZ`Wr06%hB8o;Igt6<;`?AlQh-JABHwCR;Q z5&A8PLQ#7X089NbIcknUxPRP|0-cIL^$?=bw7VK4|6iW#m5hRzPm`sGu0RC;;$pIF zAAJ^+OgjjFjk;RytsW5IbUEw z0H>#}DCOKK^W+O!`W-mX6j_x*mOdr3U*!URS&jVg;z(%l$(f8FGZuKG&1u{xkVZA; zVP{TVX&09WD^RGr_dPF`w}v?^wpB2jTO|X&6wZ`7|`exX$nttUDcA6~}U?^!Kb$1;;NTk!+9E5OS-xe&~K|47AxatI&q`YVi z2+9myz_*~=?7y&<{Gv?qC4*01V@A{MVi+AmT)efn5~R5bkF@S`92?%pS%By2cuv8z z-)YVgZ)Kq4Cs!z72@$4f5sIB;on2ZIv9O>HT4J@I1PADwbeKti>iM5b!kR2+dVXU= zfG5Ysw%I15YW7?m%)+9~Kf&Tqs)lnNswj>o(Tb9%+-1R48hNMepuA*%_Fu~WU|R*h@U-n-JblaNgQK0!t}N1MhiIWGX>9%5Abld0^=W?3je3 zhW8sycRZg@aVfE&N&0{X*71Gvf}bJ@iEFnr*`VTf@N7*PeuOxQLBw zp#SG*a+1qduUAiZ!rN7MhVHfoC0zr>st!|gcpi2B1 zFo^e?3^`W{y5@lyJH>|Ng!`{$A;Q`uBwz6)Ws6_Ts2A$8SV~I{-%O@62hB?8O!V}8 zXX6lo9-J#6ae(=$_xZdy^6d+_6lHx+8D z1l`W7OmIXauZ) z-x`~n?dNHKI}9pQ$4ad3=>>GS)*suLo0q#qyL)pK^ZZ?M0?}Xm6oIMu=e0bhdw)ah zvvcL4qCkU2et97C8>%KGcFDym!F&a*{Yd?cwyn|>8BTe*KtQlfD|lv$hTZzsBT(n2 z`dq^zcg7CzH^j=6+;abFwyi*G!W!m56xIAzG}F&I3h9hPesoI4B1Tm71#In^VBw|S z($eIFQ922uhPDEz4wR$@1^W!B1ZHr*)%Rvhy~!l$=gd+Zhv1Y`zz^-+);t{lyp!+! zkvjMxPnROU+W_VV%r*oK>b<)}cCcXv5bD-br3pwErLnaUN_1W9p>S01uaSaG*S#g_l7vypBA5$cfF*A#+U& znhI)au2aKz9DcF+w9K|=$jotKj_n;zMBLjGHr9|tzSxkmM-w&|+Z_(%$CPi>VmWnF z`u}0Dx~sJeEoQ0Vv)8ft8FvvcRmv6q$_L@OQ;zL3-1Bj4FIA4cEbXa68mBST?W_^p zZR__CwU|$JChq`k5r77Ic;LEQ6oupbJgfCua9#h!xeevlE5Ed(C4qSNhi011-n+56 zG2h$|VM-PZGRdw!UhXmq6aveZEy28a_S#~=pG-!^YEfcoDjEfjd|dufE@zfd&3UiR z^Chssbg~&?2MH?Z8XHG7Hi?q8Yi2n3SSR0MN-F%2hzWh_IH)9B-?E>8{S*|bXRjbI zAWnT~EIvV&GvcfLCL_`Vgfa}57p|)6rfnK`&}_Rq(9^-<tvT+p<@)I3+DBAdn4#PoTPiVg;TjYq`{!G? z7w6W~m5c2gcTB=~i+YvlcRg+n+T>Md`9cK3>)B7YZ(>IJ&pL7|RdJd>R&pS$C&Gt}W3y ztFI86+Y2N8ENxTU??cS6Ry~pTlNfb*KiTNIZ-_Kt-MFSR z^jr*=Xz~Rt0?;a3P4P_@%X7df{b=$# zj{Qt8aQVotNphy*N~kr&=NMIS{|mdQv)d;FjDCY7D-?je7&3KnUbDbZ>-!)}@K2uu zdOX(?7mPf!%aeou42L!z=Q^5hUngoG9BI!|5G7IHi9o5(j7Y}hSvuEABnYmYMT4jq z*rzwaK*$2Ef@e_2Hbdv3Z8QAmsj6d+25Jw%=w7O&GY3=XMLYsG^Cc z0ceFp6z!N8XIp;^W`Tm(4M}ZQ*E(;2ftA_Ylb03x=`~`~|w* zD#Ar;uE2lkK23Hk6P0jZ^ZB-@3MN2BeQ&ak-2bG{Tr4n7!*;~n#`f8Z5(FUXOqataCzcs8#gn@~_U{t^>y zjp%hm+iRmvzqbYtRja^?_UNGd2uN_epE=oY0M|EfEtYOfoJ7EJ(v2WUN~uFW(Ju*t zDiRrrwfv^0v$?z!TNDBavcFEG(H1kk2;N4>!?oo!(nm_ z$C;3&FkywxqS9dOaV&C~lc_G50iMrCD>^!OnMhLtrC*?v}l- zOnX1si%@H`CzYTC4!6y`?_FrFrF;0h+bzoqdMnZUsBkqee(IQIPxa|?j9hG;jDH3v=l`E>c3!yJGYDz zWK*7hyFdSc;va^xfy_uXrk6eAscLom12mSW)wa)UzVN??hq~&Tn5Ofk`Q_zdATvR& zr#t6AX>hGEY-&ozdFv>af$9HQ05=oBxB-L>aJ`SN1hmLYWEP9MOgK8fRJasIkTXIaYA7=(BP&F5h9ohfqhUtF5o(T+# zKbI2QIqz}auoKOk`TTWR>cBrZC(^cnKbU?nz4!(-34uNS7y zNL&4@Od$71p57~kTAZ5`P!+{yYUfCuT^06_q|UOsbZlyx-`bM#u%HI4NsUcSv%quc zQV5GI8PC`NA*6+jv!{HurP2-`=uQz`Qq-r>tU3l4TSiM!Nwa1VCQQvFj>e0dj2Jl> z&ZmjP{Ll=DuIw9z4FY8{p3t+j{4p#(3%pq6B{@;J48=U#lPZ^Wz-t8n+&9#yIK%ot z!CI2z?XJ+XFglyjE@C1qF`6LD^YXRganB}x_WJqt^5R+EalEz4?wq3i2%3x?mM`BX zEX?qkFM>!!A%rNz08_f-l_$UF#}AipZz>e&`GFOaYKk%_6aazS64BmBv%f zlQaDkBdhGB?4s;4eFbxrLK9=G=Um(Ya9I0Zo$#Kz1u#gwrS+bM>;OSanKuyI<4-T+ zldDD(jQeigY`r~|OuO#7;|B@2!b1LoaaNnHAQk85F)V=aS|dY4@FkdL%hf(0&?x8a zV%uS0H)I$s{=vTi`Z7@}p*uV=-p>ytSTwo8{g6tfT5Z%DNP6aA96K4IpcYlF{bQII zw~HteEQ+DSBUGohK`y8H33=~!4|6UK`8}KO`=-|)lLe}_u{8qC^{@-t5SQx@JI?A% zn%*bXMmH8*0!*7kmaEYLI)!!Qv|e>?xe1AGVA|Cv7)u&GNgD!dFZugtj8FghFK^x@ z&1fn(P+{}#hJCZZJSx(^Co|bZ%v8&PGimO=R)S*bEdJ>=en{LO#1pD0HC;FVaYZC+ zzB28&5<#f-Vc#<)AkMI=rE#F*?p&qqb+Lw64wQr?daEj_6+qHRi-^>p?icJ*h%Q;q z6P>>v1#LuM6)Cif30QO%!dR01QJ)nXxE|LbDT!_?P}@@;>c0rF%TBR|z+C5|e0D4< zX=^P_;Z!~UnLA4_+(ZqC1))M5Zj`xxy={%`hEx@HvPHum#eqjqvGroQ;Ojr}pm^Cy zq+?;XrbJsNf=1M6-%%G1g~JHY)Yi6UMf`GOk>xNaWjvhR*x_B-Scp0Yh!ue;wi zFvNSip0>t;{*$z-D*FBXJ&xDE+5a44(tg26GLbWZo2G=^|CNT`UvJy<^YbC`;O!mm zZD-{@`!!P@wCFnjAf`HYJf}P^!8;${mj`uqJ$hd{ADe0pfA_q=eRO@tumYm6j>mOB z?SJe#o)=md8CVAU>gnzGm`as4I{i?y3Ml>~sl+CIe~%l-w9v`DBOE`xme|2m;{*Gb0)sC22rSh+N z|J~tu+tb!|t;4Yzm75Ju4>4Srv5RIEi?>`9cdX{BRCpm*qjorq1xMOnj&RUarN~$< z-ci*}RW1Sig(oD+bG*%~E-g^vzNwlYagLxtDB)rqT_d0$HME`^rzuAEo_y6RMB!H5Wkf%X=>HXz~3Ic8np{29i9c413;bL zdTee1QZSYsM)ETLynY`d%T^4;YN$(h#W6{n$s$H1C*MCTv@M3d#Badi0tCXMlfZ;7 z6St}En^SG`24-cx>0FefLjt#j6t6|8q_;6n)^VjS)M`#zq?;ZXGmmjeD^Id2m>*Hy z=9h_i4Y`D)H6S-P1`VB8($~3U$?_+7L!or$YXeDM+fRbMIH;-u#9P4udc{AJ3m?S) zc~N%BwU+$-3j{)MvriAEbVsB^Sb+@%YnKTjXrg4s5z5YTxpW1Fd2PLl z@yHtdAW|CO()+TeYdcw_nYzlS04=<&%k$Ce+VaObv}4u4PtLCxCE*75dE*)!;~7ZKe0%prG^Wz2_h@1s5HHRGjel{N2ld zHosY2ad0iqT;FPqD<1YTJQH^pa+u_hh2J&`+iwVZd4U7SV2#`|v(cIGruVbupLUbz zU$bE;m|?})?<}uuqBE@Pwbq{mi<9ujM&qDGbx!m}tYyCG!SG6%sezvJp&7|im4C*O zs`jtcWnIF4dfx<~aF1?m;B<9$DOvE~cikD73SMlVy6H%K8v)OOd*7DJgdvT~9z%$z z(@&Y#9@nIEWAb@jD4}oi0L6AjwC;W}?*97lap=zQi{-W>%IF5KaPQV{mBu(hoT`S31k-VgmrpMA@`g z^Zw6}ponYlCl5RShbkK{SY-+ji0zuYJ@g(naF3L{J+}h)76gA_^qB<>wg%o?L$U-Z zF!Eps_^@W4$kmC}bx?nH6pEUcDcOS~A_G-l>wQYk0kDFZ?=gx-eGb`$yA_ck4TT|K z1s+F)p(ZE1LrU+xi>-V!tIEiZuw2n4otkD#cbRKvA`fE=^*O~R%O_>o*!MtgS?C@bFQC1JS{hcK*SAoy<_uV54iFMIYulOrfu0ES1PP}qKd zY*}fx(iGIs%jRphBUY)faq6LzB;Q$`_GUe_$JD0Gr4!hQs!^Mw9Igd} zc5arr{X681oQy=1;3#MY0T|V4&@>t-7#JG;)85`cbGnX>x$D9 zGaNQ-=Jz62FvRYCjt_o~`{8|Rx_hX(6;7Eay>jJ)HactP65kt`_oNq`dG$RG_ishv za7G6vO-i(*=L~APqt4~baMtMkrOH3n8Ys3}s2rFhP|Om&!#uBe=7(mMwZ5c$!P+(n zfjlAmxx_rVKaLCN^J=5`*Fnccmv8I-kEU~ssf)LBl#ql#wu8M|#Oyd(<+Wr5V1 z-KRV)=KFrNu=GWRvEkC>Eu%I3a>S4pH;fU9GFiU)XN$t0JuZ_zS~q@=4Q#yQ>+Am< zl{+7=_}h0mp8{4*$PqIX!$Zn{yrbCf`uWkB?{|e!I)#*9IApiPL7ocNeZau9P@%%W z&5qiS`wBexE0)`hVTue0m9x+=!=y zyqTyWKm}+Gu=PIU`~e$9pzN2HhAG2+&wj(@JWX+4pZZ^=GlF_1$6rj-%*(*vzc)XO z-=3O%O&vUvvkZ~nt`~CxN4zn@ zx@6l%I%}O|4z3i?s<95m^i7`>q|wL>o>$}AwYR+ej*TXs-!GX^Zk_l$*ymQDl-?4# ztXk`>+Yh~0u7hv%XwKD;T2<@-+{&bnK<*-R+_Zz)Vt8oG2Rt9k|dZs-8p-(G5t=h^O}D5&uj=l zJeKVHET@moRWpZldp-oRo&P{hOi%x6cYt8LbL_mYZ~3={Mz7v=lNeJIn_nR_WSJqc zEE^TAo=g%=pLZxo!pWNBqSMrVjYy{9i_LRMj>DPny7*STGI(=fGZG>9Q4_j=@wy3x zTo4iny0O_zXv90d&R;iAE)>j-X`hEnnBbZ8gv6__bymz;&@4S6gyj|!!?q)DL{P|p_ zD>G}bw%+5^0NYpA@w!-_41`yxwIZBbQfCm?n==59cAfZ5e&F~QB)-0-8e_~XBh8ep zsmY7H49|^O_ia&y`$w7R*KP5+ST3Qll4fylw(N$mHazFb!a)C8t_wAUavFM*1zE#?P%PQ(23?&`jJ83o)t zkyvhhqh}HAk2c!HoCoqLMGVJ|{6W&ro2SVVJFnQ2b<UQu^s(AP^yKNTls#VUm?Dx@(?&Ly;0%qd-9geft4VcQi&_16(I|cZtSR=tKzw z`;rv|q**^RJ^Jos4L{#i8Ch9Fj@*Hb;cXuEM=+WQ+1PYku=u>~;WyOE zi3Ew?!({tXdB1aqH3?a@(Vi2?}g3>xVivRUSzo{l@mZT59m?xI9<-g z&|hv3OB(IvGKJzu0+v95G-$dFzho2&-YVp)I4TOz!^}v_z9616!<=FbxJMn(O}nxC zKA4u&x!;*oHUTXQ89Dhtw6_~w{Uu}OX*2P%@8rkZknS4P`aV?P%@R)i2aj6b!CCX z>RORMk9HXl!;Z3#m$3xB_D#`p3G=Xn45l*~>yd=x0^ZE;8x$nkB}f6S5toTc@wIo( z&W^s%?cC=!a?TU@5P~_PdP~zE*IG3bQ`6@WQJ(&<@LYTf6tn`dYL@OB&*-(Stzrn^ zG`iiH|1A44zHfV!i4uRA>3HfF)oZ+WUa(s)wtP>|{eK4qK}@l~Ce{5`H{wjW_o6{eNv#fc=+IX)h#T1`}XBQZm>0IyoA&;bP z*GD_%xH|r4U@2W<^0!=c3?)i3!e+3P^@KpHL`xkSHA?x6>eN zfVE?lAZj=6_0rVEiuDhxQsho(2@mFqW`4dUfi)y7l34|X%g+MW6^qTk$&dEKHF$!S zr7wtDlzd{>Tx`472VOUKDfagzTYd7Pu!=hC6Z?Y!iRjHcuL+429cA-m>XlM*I?F@- z>nw++tcLe8*|&)cUZ)8ujzh1ylc&luS?pFs^A#^dvuVR*Z&lcA>v32OkVh%CkQFzH zo;BmN@6;@pJ&{8z;^3Edu$6tRg!tNl!sdLMXW$Nrk@_Y5Fzov}Va(WYhoUVTqpm%^ z=&Njv0a7^I^5oLO`Q}6$CrIqSE?7#gmmNOWGzY~Ect>|%Gw2Z*K+mE}8-dz>;Rrq$ z>Y^kGTcLlp2i?37WYpCp!l0N;yWfm6-9abkSZjW|Wwl;<|5b(!Q?7)W|KKBn^CFhW zjT_q`C?P@%D_^j%Qfo<&!@b7D z&aT#G3porPCMb#JGE&4vc~JxC!BG*=euadjINnWuj<9y$NWZ+i)~Ns^$w1EMGtAy@ zjNGq}ceAN)-3`<$U7q2Zw(n5XDpep1L0elo)6WBHwVqF`XpT3?XpUX6Xtr}ZxD_XQ z;C8OcSgZ_q@6yy$+FN&pduZy!!4A+r4 zToPnQ!h&tqFo951w|D^lVqt{{>LLAt2f)tdAqO-x7uORgBz z^W9y^t+6KEH?RYXqV9RcjqUb>bOisjGj=UgsnqfMXrdyY1Tz9 z9EmkuJkl?*ADR~`ghT^=;~1NF##ebCSNjtM_TxlEUCvpib2PUO^2_KO@2(Jei!@;= zxf_Vh*5QxOg81htxC18+)`g1@B8ZnTpCa0Gz*ixZAWc?1A+P7@W*C3EA^V;ND4mpv z;g#wgyt>b)OKy*^o70zN;s{`6(6A^`e>?H9vG=sE+~HgPA^mQXTkM8$U77kCixL65 zRRY^yBqPRJ8**%=8o)@i=}wc}dQkxu#ApQtQev)QI-~A#_m*C7<+2U{lC##!4ug_Z zQBg6|;m>HM-CLa=xI`;AIX#-`t-P&+&>5qIK|u-vOK}lO1Rf+mu!i$}osHQhMDWu; zIEMbX|IWL;&=|cH6 zK4O*Qe0o|oex#BYZTW8Vn3H=``s+&Ass**1RlPuooPbx2HR1eZOW>j zmACb_=F`DOjKk-^+Q}(ye<)ISGo|sQ7&G8kA}kao0B`!w5OID3l1iKjcrcGP`3XhoHhf6(4Zk}275;=KCW6mlEOB8%qr$*PFUoJ`HH#D zZ@+Gz+j!y$l`7V3$>^Q8c`Y5ARs{rfJ>K?!3L>i~{HxjJp{%b9^CdlMF|IQ>uZoAM z+xcp9-9V$mh|9mz7cME#36esRw4LtZ6&E)$Z^mGrpXu<0C}Pr~J~uhi7=!qs**;po zKAo3rfj-*~Gys_`SN2^dxnnQL=UdA2kDo2)J;j4@qSd?8pKocMuN$`86|C>LId98D zTz4nfv47m3ps@*x_Mk{Rh@(!|#c5rUb}_8qh@_D6Nm>bVJ0Mkkp0p>XFAG(&$*!rL zF>KZJrzE%%Hhipqig<(yuo73mZ6o_WLQpZUdnx6Q>X_-;zu?z!*UnzjNcbD>Bn9si zNYG>sDZgJ=c`BzDzn97V#aYsBuk%=2+w9Nbxi>#wuZ!|0H?;m;*+8rw7IWx#hPL$6 z)Cwb(qN;kO0%{aMyAQCr|4mQ4 zoEPr;`Q>v4Kbtq}lK$9dCqTc1;CWCC|GbfPO+w*=LLZCgv&dNFrHO!p=i@mtO`ix_b^hiWVW6FLv!Dxta z1T1y8UP`X$Sr{%kcrcR%NB8^KI}4Ap!`q`kOi2Yn2(*N-5vJmo4pjVeJPOPed7>M6 z6i+hW4o_%{`6*9mc9A}I0UAq;_A_E2!oSt9oX#=nDRJH8t4qMPjIS-esC6!>a_D?Ih=A9s^$I}p@xuStp^-Tw)MFrF+9R-WtCyjH-{E!97Twm)wI>4>n}wd4Ss17T*l^1uRlLs(H5`u%>(>W#b%saLa<$dn zJp|x}$b0K{d$64^op-JO(Eb3P5Ln)Kp6o_EU)cI)>~4rYdhGI#*pz@8u~z zk-b*A4qrC19JS^8Fz2QD+ui}xQ)9=ri@1Hk7td;g4W!nR&$;+56Iul8D#t%Q4J>oX z5>9M8j=a43)Heb6??0rWkSQVot3qA(4fPQeMPdNW7?EHBwR+trN8nNv3s~NOjUvAS zg|q!}!bf1%_>22?cE@$C7-^geM>RcKStdg^J~#?jjDh{{o4Sp+w}%e5*}IQ<>-NAf zTMMpG&ahaNjv3fHIhM#Csr!rgKKFzeqi9pDMMlegv=T3dD5#ws+Uq|czGXjzZ!FcC zQ`l3U5IHaXlTP>Ov-zPcSlWz`vOD5KZ`I-~pO=s4t|KBAezI4`kd`q#{y$weBB-dK z|2(gJ^MOswUuKkobz5*xxsH3(of_Bm!a?1d(T*j|X}%1h0k2$+HB&F**P7t!|Ee#gwMW&Jb_R^ zr!vuux0l)-!6lyvqb@PNx*BW|`pqI}PL?<872hk?_j6?$69Xf+qy>hQWT8CtIXEh_ zhGU*{JBaRyk!yNy5C4;`!LT7RvQqCN_+Cm{+J3T0cN=DDqxUqa0&x2dVGOMAg6(m- zGIY?()-4YmkD&r0>9hRMQXRXS`+V z|7iiV>FMj>i=mmJG(?c#cmas0W6rBq_w&WoKMU&MAW4hYJ9g(sKg=Jkzcaq+kPoRS z33s7+qodE5eRhiL`b72w;<;a~N=dW;$7583Z^ThbVx4ZyA_rV=K^r>w7iR0-X$b)* z>;1C|aEVnl8q0<7B(JBhS4Wd6T2eqQDN>-+pFRv4t0&sQ%+^`6Tsr@ktTYJ(1GA8k zkpcf1e=Ts^iNl$D;EdtQG&$sD%+Z?fkP zvj4TUX|}>&rxXP!zD}0`UWkKE#@CmZ z^r!B!erzhO44q0h9PlsW-&5{dfT)BrwL-2QIaT>4Cg-p5mXeghqtt^4>{9=Uu&OsP zdv>(n{6yv2e7$sSIO+YGn!PSP>$pVxli{;<0ne)H(7l8wrUnQg;5tfF#K2w~1=7%t zFE4@Qn}te^rf>qt<$7Zncw3+O=iP^gI)?)vCudhiPR{YgDoubuyFEtD+YeFW`BMk- z3+kW4h=4(Utt?C0qJ42uyrD4i%!!G@J-AZ>J)j3P{Ma7_+@<>Vd`F`Khu-3pZF|w2 z|4?_pWl~B;#$lN-#9>)Kr08eBs@wr+@o=7J(kn55XCWV%;1upZ26(Yq2%8@*Oq+2}A(2s&oJRrRzAr!Es`0ijkXf;QX8hs8BStw7*(fd)E!v zKhmCuAc{>)fIiCD!-GR4n*?Up=Bgo#$mWb0#aXe)Wuqh;$LVjpXUQDkDCk( z3=GH`>NlKIwAgHN_oFqsy@Z@MHsH@2#`BB>vOa*w9t=!OeS7=3e|ld)hvMom&HYHv z$SUdU%L}B6K@wu%EzQ(yHp-izmaff%l}bdS zU^-oolOiW)bOxV)PvWN%TknXD%k~PW9R$rnb69=(3N@NPz+KkWY5d?942<#N(NS84 zrk;(BUs)NZiOFFqaC`<{2=D;-`=<^0FT)_6359P4_Eol*xHoJh9bQx<7Yrko?KUzr`@ zL5Abinee}Wy6bCVWUi-cmbYH@5g}}$xx?T662f_E26oRBe)o`(P6(#B}s2dMKKynKZ|-$a3@k%#}+?z#R|lT-*Vjy{bmk}6o8bxiPU+Og^o-kV`vfKHs(y~ zy(Owfo-RCew5hu;POk~FketMb_5saNMEy`$N`M>SijtoEIA z_PhVx^O0}I+6u|gCHu}d%2S*T&?K*U3W0EGXSg_D4vy-W-roKIi%u%T1wAFqaE4gnw(3FV7!y5s6xT|VU4&IVVt9x zm>QzGme<3CqJSbSRjHVmn4srnlx*tcbJu5JHc=>N!vv&X`R}D29q5KQA$J>BUgXpw zBL8l3cMWqBQ__kNxiCLxWDt=IheqhHydOJSciPX`?%7N9{IOzqUg$NOtxN#3$^Rpw zfRj{%_p1tvtxq5>XG2zLp-KOhR0vh#G1P3ptrxaCgis;_I9t=@+2;fhpbncVt z`yYmLwh1qiF`Hsio7*neMw@bjE0MX2WmdIErSiyS<~cpR^jLWu+O6ABwItS$>CsadaC36BHEudk;s? z+Rkp!P`%}j?XJ^A>@=uI1RZ5cJ@tfq3V|8z3?_ha zk;a1g=YpoPIMY&7^;}#Q`Rj>|eSd%!e>V3*F$*X9-M)df9|FgmVvVZZB@3krWdH#J z0|UbV-mFk2U-6_g4k#RDn>0uve?ZhE-qOhSotqQFB|GT1)cwx*uU%5R^CX|uVGq4# zFVK`H;CTfT@8cb4#M((!!tt=M3;?<6(2#Jml~3ek442fbB2b7Mo^PX;?)uTWcxjR| z8g;$Z0_7Fj{Yy|X0TTSW*PL|cNtA72NySLCwdQiWGc{2CA!TRQ)-L{{P$`pvT4`54J(c-KnPc6w4d%ZWzA2phC5=LzqWpwwUArI`OIpwiGt{zI%tCSj*|4ybW zx!bLpook~0(0DdDkZx2lq}6asYo8P|4BTIO}QO{tn|uiv`saO<5_Vs*P_#R3Id1h`+{U8azX z>hyn_&6j#dgJ9pQPo(+9vElPO^JS2zvMT)-hJSX9#0F)XLEwvfI2J<_`Xh*7sT>H( zD&{G~!oKwmV?>Gip>ti8Wj?GMJ|E$4h}YRdh&Tl>Q$U+p-UJ%xzhXRYy<2|lFPAWC z(#~zIjWgyr|KfeV?5VFk-YYnH2q%Cx!zM6htyj}CTlj^y+8Nd3wV-=u?q_57H^$adWT%4n9HdMDcs^${zye2l3-X zm{e#50X?>(^P{`_&sXk`y{Ye%_g5i*CG7Pq`)M1Kh-4m|Fz7K6UEF6Osbh!mUXrhd zV{AGSENedD(6c`K*|KU~t{!V3{kv=*_*U#u?}O;P2!TD)F&N6$HMuDM1z1U}Y_!wq zuWY4x|KEx->_GwW+~B`~Z5|+6ynbm2BSas)Cy4t4^2-Tm#FXK@U;;u(C+8E-^nl=7 z7C5x{Fe`9=Y%H9^q#BM0Kqy8PVGP6{^tOA)$1=S1IcHlJANV&V-&-!-%*K4cwrqM( z`!N3tBuk=JJw!t&ct(Bi32^YV;<)~A^X9=-u1fiNbF~{hr+P-&Y zPJ6f26c4*Q2HkNh{i~vQo7Q1rg3RuIf-D*2PR204uU9$a!ZN)T7kXqFFZJUBL2M_G z4W|y+5f*ykJ3azrhFXG5UjQXaA7i$Ul~-4VN|l8G1_qySz7kZlDQ@+5U+-D$l{!b7 zP2G^-?`J~|gFM(5#5G)JGyu987a~{7(9Vv&8M=I6RxtfXIc4^&2658v?Ks_0=s+}Z zYYx}gZt%E2jh<1xceZ7Jn;B@`{fcko^$6b6euU=(^lVm}^+5nxGSctz(q{3z2Opco zYCF|1dI!K974_8cHvd#)m|qF2NTz-xCp^lODo`+UoBmz0WRf7=XOT=xjpNe|K0?+uIqioG2H!6Q}$Fo5KgSX0E|qGDEVi3c zebo9^y@P|`@9$k{ua9j2A$opaN~@I`B@wv_X6H0cN2)8f!cfOkZ-aYF@GVYS{};sx&Za;wU$)pQCJ<*S?=lO^4K){tx0Cw%u(8i*R#bfm|r<5 zhJ$UGteWB~6Bxb!7qbl{ELQK4K`TWWov8&O%X(9GfP)MuF7{F5b^RS-m^~*StVe+#IS>Wqyos0YWKMN=urFXkvfiX!aMoDehON zY|pVd3`{8sypX*7fGqa^Qt63Qpdh{$>%^hy)7uIg=$tZHNvS>Z)a};S)y;N+6k>xg zvaz0JM6NvvQ8YAvgl}=T_d-p3Q|8l4l-YuxIO|vTVnkdkO}skOcw_ro#P0Jn&043g{3XbGo1E4=$MP`veaC9mdRaUoph?KDLfl(@EV}aI_LA`o zM+}Xw9z?~Cwz#;EmylU_oF@8K<@BY+c{|77brp0r6UH~O)zS>blJd9K zsxue_rjA6E83;#?%sXt#n)WJJ^6E7KQ50QnhQ6+b>u$-C>3SleKXM0a5E!8lgyKM@ z;DpXX1IEH!$P5TQTHA6oX6SD@n$Qkn=Ncu1XwZixfvw=CN{s4cS2m__;AjR>h(rpn}zM1l1|+(LOl2RXld zcCX5R>mTDGPbbq?`STr_8vkj0ha{w=2&#+I{>|7$m*{bPh3&M#lNT!xBtWBoG{aHD z8-d(vUl)?6CO07^v#mO2G$k$cwvl4VN@Mq+nV9Aq_d|f*N-3t zReTLiuM01Q2pz&^m()$>;A^f2%@*E?>yZ-rD~5Q$5| zdGnHWrm>T2F1J;-zB!uRH{6$sHDN87A&CeDQ+O|Q zW0DyO57L$x4YVPu1_cw6HR`v=yKc0dM-o4~Emjc(jP+N*fglJe2TcsFAl~Sg-A81( z_zCs-7O-{=?X9-srw~dpg9c{(11SmkaL-aGL+1U)Lt2{5%t|xyvieTPb)oh>O~eSg ziWc|T;8E$`!u(8qKCg1L4a*rLp|)v+oLQsP{f_;>IUNy$NyR-O>noy1bSlP$fgwYC zBU^N+aM}7%SnWV%|4j;Rbjyzpn|kxC;qyB)QT;uVZ$ScF9RT@(q()j0Aic3eX*d7)_pH@R<`O4$8~0tKik=k6u~ z#ofVF(s5(v<*gXH@(KB;_W6nHKtzS1v(QkltIM0BWS0%d^Gb`p!$;poG0HyPi&F`2 zl-ol&XXP7j?%Pd`6s`ni?6T31m`RC=2>DYxKQf6r`J8ZKTV7t*dyuQJ!Snemr5i)* zDi<&e1vP>}SVuqf8UeY#OmaYP*d%6ER#qz93`Z{(`DMw#Rusf4!VT9{M~)1tCw2ri zk;qg;pqs(jR6XaSk%3Hyfs8^SO_-acOXY@-fx)cgfw(G7^uu^9mA0xxNdq{=`jK^p zbNyqOaUr4nQhcaRT~eIAi#Hi0JqT605z0CzQWoJ!=GP2H<*ZwF_=;M-{^yI~-J*86 zgHy#@jrQiOI3WR@$%yA-%u%a{FJ%AyCkgYBigvb2$OQ6E*un|35MEUOVue9mk9L=3 ziGjG=@bJ&p9M&&n*IHmS5EAYf)NzR9!kPqp6hXDsj`~e|C1D8Z#Fft3g?aeeUGZn- z(PWiC!j+wupk=3|{&GfIYMM@t zCYkL}o!oH?*Oj5co!Si|(HX%68|I1vfm;l$J*BG1x6OpG^t$HrhG6S`r{$SD?x_oz283I#e-35e;i8#lOqo5+SFdNMml$snEFDWzYHlhAovdF>|e|0OZ7 z&OlO#B|-LQQP~&%M@_oiYY;S?4?na?sB+Pgy)kFp9uAF7+M(Yf6X<3`+9l^Vzu{^` z`c-c3OFvcnH7}2E6x=;qyvokm$%)#OkNp zX+vqjhZ!S~I==!iGl;$r(YyVsBPvYcR6T2!FH`~;tnP@TOD0amXW~5)7j2W;QJ`HZ z?MV%Z1%1$hJAdc1j`V=$Nb^}6rM*npK@p4%(d@IdO!Zr|MVDZ*_@qVZ3_Ivp_m+3c1{aFd4@r-=H(kxs4z!HMj!49l_9Y~2rs5b>{>4CDe(2` zsrJW~g_{vOnBcZ5L!S3<=KE>+&}EL^8kJfONM$Bi{;E?Uq{1VxGx}xbJbHLFE>e1a zKDnwkT6h^|4rnmRJorK++SG@{Lf>3gMJ7rKr}(UNm%S~EXx4A#RoT*<-9l$US<55D zGP=t*wZCV+fJuHCN>bGV`EGtFfJ{Kg>dp8foM)7jq@*?bZa6}rS#!q@?jxk{nu0eoTc)M{d3OAE5H@M6e%PDvYgcahT4a&1S5 zstheyP6&>c3G_6UIQWv#bhPF3&jPexqEYk{_7cu#G%WYNj)o}&;!2Xv>Km9kVeE?s z$!RCH2U8?_)G6eC#f`WY`e=?q$fdwwU34cyhZd&yKQ(|*$JrLdf2EoBd2ptojzc~l z=@RJ@`r_GDPM7SIYg`@)bJ~|L=0;~)YG>lxbKk9^YZGt+oce?`5jprg>HG~$5w%R(1rU9*o4n_2193?`ik z8EXTbD1wwy*{sB9?`g=y+XwsrEy^q+@JXvU+}EupoHrc2%pEpN>4v02C{6%D$j}`7 z(Cp1ozHaug9IVBEu{j^sg$g0V^I`vZon$(i!(YV2Vtvq(q(rf+X2^rHDgVMq*J5bx zLa2ycmV|X&yEtpiom%2>^V;W;Gdo_6IWI$N6YK8%T*RR z@8;!;rZt7+2_NZm8e|0hy-a2uJx6_}f;=I25M2LRoD8m7uz2_)RvtJDWT`T>Tak?^ z9kK&6RDy!J&=V&ST7Ip-_EI^`6rEfm%cYZ%4Q^DNdcaMUY7Y@X#5a~kaCTJC_*tg`k9!0GUGIOg$`eSa__CR z>`)3L2p}fKgM(J0Ff2>WBkG_5uLat5drCn&jn0+bvOOFU4lyot;V6phT)2lqZd6#8 z_})#eb9eOK=E(frO`xQUA)dlKJG!3W!CI-^ZyF9`&9dtNC!$@Xui8fllA5A8B+!&1 zFo86B3J5Mf$~#_rhR)#5j&Sr`&DdL{d92+^-OerlNtq?jjP}=3+sNAw z%ejxT3%B@5PgP=WaT7B5%VEyntZOe;$0NQvB;IpaE!SK*Y@C}tDcvzLTpHJk zYdmiwR}AWhWI!gS%=iDMKaBnQUD79|(TDM&js~V)rZRl>0J97v7-8d4As~cC+C1mK zw0AG+qTqfJNS+oXIlbri&cL6N5T5Mr!l%A4BvY>D{Xh8J-mhUOj-!0$in8^en{ z78$pRJpHnO{o#x5M-g;2m*o>i8+j}+j@NiDtO&i;gT8a0A7z(ml83e3_{&GQnNb22 z=dopHg)_)3f&!(kJ|f#M>Fxc8DdWlGHM8?R?SX5h3iZM7(0uTjlTdZ=VYy3*t)TdK~t=k~CAs&Q5fabYK^ zIOdQ3lG~3g5Jx9X(B3QtS7=;@A5h&)A1lA(Li7XR#-(cup-)Lt?F|oE``#i!m7Rn2 zxE2IwZKmEP$=*s+QK0F9jcM{LV#y!UEHg~`{SDHChgC?E-uB%s^~htZF!9pKTxZ=X zMq`UdsPJN-UL#&oc?K+6sjjMySS`T z?jxAyWsxnanQnU`jQ!_)2zEO_%}u#%(8)TlqSA^ec8 zkLcKfTe_cd${RRiP5k`*4eCOV+>d+x(cDyKE}R?Kcm}w6Kj}btD*|7_uD(w!m6v|- zz;V5Z!MUF*Vr$Zh8R3kr1yZd$A#oOHD2QF{6Hp{`Ir8O~c zD(Pzw>+AQU_nwIpmF0;*a@mQ=Ly|l7(~dYWSL)BbC-1_useN>pr-H}eD<@lP99f6> z$lsn3tUlGnuRBXSy$Ro$i!=` zi)3xjY49s2l_b7B@`V%OG6q}@#nqUW7G z>aa2m&SX}_Rsjkt?4Y@u#I`4L8~2dlj)`2j?HC-WLZn>K^?c$K zR&Ahx$MtdB%Qw_wg`x^IAxmlL?{3^1mF#+7zw@4m<2cR7{is&+JHN;?$*W{WHKeeN zQpW3MenM-Eoo@ueGp(wD74TD4{w}X6g9z?rGgJ9V|HO~rNvlC3A_fN2)T)lS;f7sQe&U6K z1y$=*_uQDcMkUWTRLZhuS&? zJQHAAcYMuHGFwzAr<6Q^4TWP@1C&x_bxt0LklQOInIlWx&&Y*#hL~yrNPq;%B1yvC zY>z5D8|kSDnzNZ}VrGQu<$ExNi|f)5W#kYzPlDlIBoK@{^CcU)!%W4{NhuFPIW`)L z8mzEtc1dx2e=jFHeI|;9h(X;H-ak@>U$)}@4`q%lN%iF6^?UlE8VL>>6@=*6%m#$; ze_9^1lGi9+$9qc(0HVq5J(t_*c&p_7Kk!5@zj}zVy^iONa~k3bRfc*C(x7Ua#-^u5 zy}%--nc>rquMB?0&1%Ku|F2uQ^Er!1e9&Dt6LyJc;Ktg7g$Tg z;Wp#Bg1(}-WCnTnD*YV8g-`M2Q*+E;Qq_de3YfdgRC}tL%m`1rvq8m5#liuPj(;PN zcECG#EfW(fcpzhRKMx7sIqVkeV%3mm%63;jpzAuGsV zw(x~cgEF_|Jv3vgk(8uW67|Hg2r~hGsKq=g;M-t$k-9Y-=Vm?T#|Dewv4oRT7TTIl zm^+;oxzJ%Vn!u$4B8s^mN1PIrZM!J)YYq7jdO?E<69J77=>Bl1`69<0K)k!?^k=!{ zn1amm>v$6BoYJqVtQp_eAHj<^N8g=Z-c{OU$vq7=@fzu%b$w~*&1}sr9I7f9vmkZ2 zbr6nKHo9ma27y;(I`C6N+5T8h(Rzr~!c=9062Gcp%bbpaxGLZ{`{f*2jhUl&{|#mF zOi%*x)lj00UZ0%iMM{?AOW|l8u9Wn7af$DL(|3H({^`@ZyIelbegb`tqjXq7y}=)G z!2FUpn1F=*^yCvXcJ3nMxv0ptJA-VVmWQi?nztPX|6RoDq}^#dUHexiv_{P?6E<`(xyq@)<_$v>pS;9GMguyfH<;Cc6t~g z_yhUG!WMmSP~5UZn3)6PQ95l*Of3OU7#f_0tJ@FvXeCBNiX^?7Lv_ykAyBnFl1+{> zm-x5!$?+|dq=B_>1Z1L##JInUerjo4SYCKFJ`*DamGo)W*v+ z{9O`*Gb4wPjx>6iQ8Rri+p^=SK4E&g{$Mm<-DOo67?eGAE9Nc-Xb~=gUROz8U?JGo z@)T|qBEGnLs_VS1iThP7WaAEBz=m(;!+Phl_td_UsGg9JS#zynUm42^e{zHwrnJlM zzh7pbvu0Jl7hTHgkS`?3TtIM?^>b~~mvH<2Z1jg}4zOuLnjYhHn?w4%U`>)i_b#DY zm%f+7#>^XM%axQlrGTjW_U&~$Q?)Y^-uGPNn}`R_k>c-LXtlGacnM0c0(lWXComNa zO`7zfm9oU8vRe{uQh3UKqtSV5TOy1YD5j(#2w*WtX+hC20#V}*L1jN8S;TSJ({#4# zT5sQ|$fgy(ELKmP_(73TFFbbnBLd@_PU1GU+X+l|fB9?$`&E7mBTHLawQ&V~{rEyh z6j?1*FeLUDiT^%px+CHUHgwaG35oI!|kQ zt1~f~o>0fXN>^_%ojk{MP+aYp=JBkFajkmv&VKyA0FNec*?F#SaQUIHzH|WfQL9G!ftNROEF=UzY8CFQLta_9x6eSlGGK*rJ zm#Cd;UROviuzfA0R7#-SQduFS^3M0LWI}$L;MY1hg(g#st4*yEGDz~`++D?SqF}Ur! z+Kd%UW1A!OfSw^P+4{On45rd+-E|Re^xk=>13QI*nEgHUs>+3s6NA%N)P^>isJ+d- z!cNg-bunJl?CH?5wX&TzZ3NedFv{jUk?%oHKi!0^>fAN?Z95}S5+<_~2j8Y|^`w^b zCZokb;WwVkl97_`XcAnm?@V^>(`HYzV&%BvPorR3ON4EGySBM@S$o=A5#5X+;f&u6 z@XIKU%dGkiO_l9##TwtW)wZdny2$9xe4nDa>G#+Qt|qG^>i+TlnJxeWxAk|ldy{l( z#Y5N>>`KT)r;z6o%c97M>p1&5)j(U1l~0cYRT0z2kI{Bub1(>*0eJ?N1HbZ1Kabm< zb`Dpc{044VCpcWMkV7TDj4@kz8gj7604YJvPjzHcT-eOZD*top+7CaWET%H^?3XAj#4xy##FR*xgxapkQfX(nQ#h1Or;y!0>F^Ihc0oJ#-UMCW#l#qQwLLmjbO+m z!v{X}alHE1--HWK|8^ifiwrA3G6!+-lue9yBlVpUf2xEswkp|sRq-mbmdL-s_pE3|b=*hVOI z$7>mI&wKv`fApvC!lfgQgR?i|Xv39Xu;e&AJXF+Z#=v=6C9pyW2nL0$7Vn#+3E|FW&LWfcOGt06@Utfhj=1ohFYTinpYKV%?+!zPm zYJCt=E7DQ+AS)`ctb75>_ugd35ZN)#9;7&TgTOa0-H(6x;63=k@46K$t}MPd-}Wjp zth7Ca=EIab_avo1$JSd!rMcSZqptPe#cMs*f~gO6*@k}UV!N&1(?(sRdaU~N(slX1 zA$^wH%mV6~aZzV&B84_zOxOQ6G2!Y(H6J9%#0J~-9EIL5ly&PGS6}-rTk9C5tyg|VW9))EF*q#eh{Ig)gZO%CKK!|Q(YPw*Fi^Y=J+%k4-G&m-HY zA4w902uq=oD)3@l@~tOdNl`5ioJx&1SBM*z0(ma6UU6`FjH{Qw266o{yz}jE#a*ww z18WAP8g(1H|0X?<#CT~x73?$#aPx8rWD*bw@bF_2uludH;2;0_lSs}y1vg&26@ndN zwLSwOfa8-*MIq+Ati-cz`o?OW-6!!Ru_i$9x_R!Kr5a+s}hCEPf#kV8yUnr%IVOSKhrPEm}&6WVKC9I8Te-o~`%ob`EmzgkiNxz>z^_SHbct z_?^37gIB%c4kS|9-^sEsOyj3#qu@}e=wUa$X#8pWdv<-l%F(d&tt}3aJkKj{!;~rI zyW4JIc+b1uhSZraYSyDZug^ z$a5r+SV@Ug2&}l0v06jXqEo2q#qNmv`YKF9>8S;GZLUYue&sLi`i_fKwR;{CEGB8U{?`HcR#9hLuz(zx_x7^3-$F@ zkHtDCR1BjezpmDyat?e@_jY@MZ`%wdE>0mfDFDQ<`nu9jVj2*tjCTx_2j6PGZP7cE zH-~76TkNSs13B`-zcrUi2+`S02F|UjM6j{cG>SIsp<{s6{g#-QlF44`H|*@Qxx$ zdJ%|HoS{<0mZ%B@k^vG78wp%J0p4}bhw-*|{0+YT_z@1zUBtO_HzSt`atTPW6-vqT zrix;oZyLpsQc$M~t}LlOm9oj&(waF-i}jeR&&XF=Ro${6!b{s7)>Nb{Rj^CptoG+% zj6pCUWv5#kq}=v4@!D8cVX;7eRUfZnA|y$Ulj~Q&H`j1{^e{g3-ao^O?)*L^yf6lm zqI7C*?IqrtFVnm-FZI-2ukl^PRZNs&6w7;se2<|K%rO{FG2B!7ppAXb|EcYf*iojK zKVR2a9dgh2eUGl~KC#aKP^#-wY7>f*0=*CPn^0&sg@)s6RpfIRx;|&8K2(h_sI5;+ zh*~vCGUSlR!Eh{q-}{4i;BD`C56<8GOdOoKh+L+~M1rD2@|4P6>F?J@UN|zOEJMOF z99@0{w_I4^jd#Bmcm2$rSTkV70I;B!$}YmdcZ5*>>Cn z15Xp>8{HNhN%$#Sf>P|P-9slzG*>F={NjDHIG`pJz#ms0AK=1l0b;IGN-b);*2TJ*+S}C6!uuix9ZCZ@lBCK zHV#DIP-8BXs+mMm*lV>5F zrqJ7H>r3>to^vVL+Ri)b`%_fZ?EVR;Mn$CW3Hx-0NWOm z&GSzu1@012u>Bs}VQEuQ8#DRZs@@hJyB-&?62X;HD%&Df`co=IQAi+{z|nQ!-S7Vh z{`@cBkI#SUA*>H?!urg4r0X*fIYW}J3#H;BLCP~^n-hraI<6f(feYu>c=3-t53hOk zFXDyIzg^k;NR^a86iG>$ct~#bS@yMpinwm*k7Cy;@3l%tDaxb>yW6?j6hCn3N#Ha8 z@!$C5r#_8;`^*U%THd15Gfd60elUX@B+DIRWEM2K6Lv`iqam3BZm@~suxnq zCnuSzSC6WRXNrC<{rhJS!ORKdc0kEpTB9v$s5}=JP7nWSgx3&1(VoM?5r5~`! zaI(3EY;zSUj&Rq{z8r6P)7`lFe4%O%g%qbesYbf|Dt*K|d@j*cwNIO}qbat0L|Qr*t#*&6(oEw&y=LVMl-gt9cVtbY_jOQO&HI$4_qp= zx@5@5J2qT=ilac+RW#5wE>vMRSJBSr9z}i6F!L49{83_w>Gky@r%N^4_w3iN35Ka1 z9~379t6f#h39t`+*hig_YSz6kPniHiLz~q3c{VR+%1pXMawXeZ+b|@^rBYow-T?pf zub;&SKKK#*`+xoqzH#X)j;@tkKK zf(al^0VX%K&y-dP97A%dr)_OoB6&91972 z?U7^L_rSyW+~@Dd|Nh_S@cA!(84rK$5nSJ#;N&Dzc1c1ZxTW1up63Os%BlqTlvqZ> zi-6B+fw+d8CL9S%k;_c=<(7WYtad>b4B%`lgjMyI=Z*DT2}cx(bmbdd_6_Ii;1M!M znx>Fhj+@S(!FPY>vvJ2AKa7{W_($;l-+Mc5NR@b(gzB;8B@*2l-!2EeEr{DxolWm; zvR7HuYs1>SxDue{39E@gJ?ANu)vg69S7JAp`S($QZ(rW2?HWER#KRSQrbD6aqXgYG z`826$>wfQUX#DXcz|^(ewKbE9;!Z)M2}=)fxJ1|{XYXTKb5sTa#gxh&CBG!v$h zR;#B0=HNWVCeM&2DKe?@0>OZ*N5BIQK8gn){3@P!;tGyWPOw@hxOmeAJpE};#WQZZ z6&Ek0ND?5=fOQJ6TtbQrjHy1+x>!*2_v-|Pcq7qg%SG~(FKs!OYTkfcsw8`pOK_ed z%LJGr`GOZHzZAg9MtKWwPO?I6nkgG0mO|t?gvhqVkz6z>^SNlCzDljQM0~diGb`xX z9EnnF7Rs87gH^?ra!pjouu6f1DG%e63#D3}uAK!43E8lMY9k-*{@hbu+yh(Q#3gKH?j;%`-ud)h zo2v35^c|K9O1}aG#lzBv_T7&Th`khTq9WyS)4ivY0K4p+x&i@1%)uP@kFRDB#ZoT> zIJXpEY2z_j3Un8dm#%7UbFHY55%!ii)@dbMeo7^}d|jR^kJrs+gCtq0G$2u`y%j~Y z{mWRcROn&Cw&{Z`=&beRyRL|Q`g?I}BC_v604S3}UU}V0DUlUOD1@y3z6=+c z$dRxVjHh4;$0jelbD1iXWD=zTP}wSuw3ydYth@a6l=`h}#+YWkO3gxUuQ^#z^_AE0 z_H$Jt(4zR<0^kWC#HJ9rQfpEr-kiLDD9B+5p|okoCVK)Lf&#otksRMdY`L(6S6B@p zdN${I8M?iJQiwGb19v}1(?fu5G0->WxZ>%O9J}MZP29?tHPV&IILFqd(D&cjszAqN zNfF)n5E4NhqFxi zH6x6-)S1h~m^D8iud#{qqW8Q08o?SLxq6TC{qj}@%8J*NJ-^j@E_#V8)#i5kRb|UL zq|B>zo^U1PRv+)7Y+^#ZcNau#?uc+`h2TD8xzCC3uD-jCzP`^0`Vebf8 zDp>{~g-|qBp#+JP4=sntbI3yF7lKtir^;4IoJu5=xDjhA5Y`gl=9scV?Rl$mybqqG zwglL^{)EAlp37QKu$nKehTkjnKafXub2hf^~O0zF0*3&ZdI~{5Q=gwrCP5g zY%L{NNiEA!c@*zLgvjUMa#>$ddb}M6_*AU${JtyB%(`)upO`1I4 z@kOKQbIcrjSX64_-1_sU>Hp2557P*g_3e4<+P5{-Wo6jJ7CXIowtKK`%Kn|=l;@>! zp+AGB-07j!p^Ja*{`%z!s}M~U|M6{Im@fV65PHC_i)9M_(ZTM zxr)uIsGlvAZ_+Isf6piH3}mtmjPjeACd`JkxGaryUdKA4@47NARX(1g^g1_sM_V15 zwNYAKwM8=~HXE1v&o?%6>;2mZZ{@|>(cgM1T>*VQrzV5a` z=l6X%Y#xWYHEeo+7fm)7-RH=({wbIJ_V=LrF}h;uzC88yZHiCrIAU9qZP7By=NsIX4ouPbo<1W?6&8){ZC4QwGVs7H%V$IR;FSn zwGc!(OLTKbou|3Xq3+IU8vA-2s>eL)y;V2v^>fGMX}?cJxw6P$Vu8*^1v*XCY8(j^$}JB!$RzqfR=#S0fznA!t;t>ONI~NLmD|=DZ(VF@;*C_x0usKYnqF z!i^)#c^!+h8W-!!+(ORseO?xEN7h zSH;!z+Ip|GsVYdVw#PC~F(X-~Ickra>HoXp)IKA)wzW$^-@7mkY-m-RS=5eu`!-jm z_(Ekal(DJ{x@7vjTd~Fk*`=N{ltQNty)ylCpGC;}GP`svMG8u5+Cx4x+y9VzQfM#j%Hh{CPj1Y9LPOc(Zf`E zv+umb2(X1(XVE9XgQJ7GU_0>xYZsrtk1T)a#83W9-_W^dKYE7<)QbJ+WM_+y^f>Q zU)c0cKdJ(`tzVR)ENtu7TGWHPX%FcB-SisjelWEe>_&tKlhKH1YZ93Hu}r;mZDmc} z`}p?J@AJMWRXKf?--Qs?e&SH~gnjQft&g`E@x$II%T8(j>T~8-z~lR45!vBfY2T?# zP7FT1+shMH|HF4&C(#2;QJx)AM9UYUGF7a)P&OIizr}zEQ_+`pziF7tu;1%s?R9Jf z#FzSeY?LBOFR80?*}r;bLOd`lb-`Gwm2TwGuY!L%el-!4p;`ulBL$ucn^a|U_t?7- zKl{H+$2oz4d+5h1V*3cOczIfPT(4b`LD%_@$Xjk)=SJy^?ea?=4h^HNU(%&Miu_4o`d1@_{_32&m zZT^k@=9142&2mweDG02~>$)$6v+sp;dHm_LOU!dCKAP6M%X4=h{3&Dci}yJ*mN^*5 zRim6MTfurZiso$Ea~9(q$QkdEKKIxqj)kA2lJ!tmGN%@V7-R7uz*AD98yW$Qr3ZM| zYR%}3XtjHQI=-J_;CzvS(Bt37zjusz@ zHOGr#!}YrPiXyt><2i&_D+?VICl-=Gx`tu;>xJ11(`JV4QM$gRUiL(l}%ugH)2ns5;^qLGe5B199 z^2`hUTAsK(PZuz8?hoH<#L^Xy!aFQ1PnS64@=9Kw-R~3KJ=b@i?2Qlblw`j{BU_LlB0a!yDs~I<`7zk|5*c8<<7IU8@qRXHzPgG7!$GI zcjF)Hi1*Qev8G)>GC4eu?#i(uz$O*Q*@!PhigwtPx5w*2v8fJonmS>R!)>r$GtIgV zHO^!1lYL<<#2bq!`%3_l8xRk>ujUkdoA>*i5Q`f7t^*#MPkeLVw|rIqMPU4;>-x6$ zW_O<-M|?cxyBk}4+Ru1FbufvK8RW}gi3jl=LS4I$DmtG+MPN~c#7_FHZS>hV$DPl{ z_t!_{%2lykibT7q5Zfw`(@2n}Qx*0ZW7{EX^Za%BIWN!B`#Wpt+vSe6v+?YM0x!>Q z*7EW-^O>_wev@yEmg~QB=7=lUwT!;_x^UN*e`Q14sx;}TFW!rJ>ZBji&jA#{h~U?NSnRb7vWl--FwR~Pq+J% z5W>owbFmtI?^#{;Q7;5I1Y+A~v4!dl-q#BGRC}vkJ-%{aor};YRIF4R^Jng-Vi=RU zZ~O13un%bQO7!m8U691_&kwCmz(_hi(>WuCL3F9eWk|$06#8Lz2k6VZ+Ry&b$+Am2 z1}xtn6DZgV1I4HD5yW}A(Cqr02(j0Eey40>?p<@>5QuGvpPl|@Gb~Bb)5R#U+3VCp!>5; zblJ{m%nPbJEjDBOjz*ow(EG4~01t)en$&LFUw-U`I(0v+Ayn3;h>*qf2q795k9IZE zlu*YVxP5&C`y&ZMqU5?cF%^Ykmx=fTeV zxttsI`5o(f&2e0(?x3N2jqYwcbyt(IR^m^Rit%$-SPI}>zFlrisH+y~Ls zRpi?Lt^K8RRZ5*=?GnWMRa}w<$cDbtv%qP z*Z5SNXU+2+c>lQ2g=H^bP}Grn7TeC7_CA?&k{0TFx!5l{7J%6%nt(_&c*6l*8lSC(|EdaJRK6;W=diV1F3pu zPEqADsdU<^zcyvhsUX2IeMVDtWI@%3=~r+eSnWOJLNu3(V>fR;+qkta-(A%?CyU1K z$oIOBS2%SXDNX{q&oS!@DDiym(nCqnDsUCFZOQN4;$5ikqeD}4DysW>7Et%}&Sm^7h7J~^^RcER_YaFgb>o!i%9w4HjxYy<)BM-Z_0jSp?eLe zTzXSK%Dz{Z$41~?eT>WFM$Aom0+iko-9=?z$NiMrvq?t)O8kd|1ye+~^~<^Xg2qGc zsXg%6i7z|_1@F=~M`d@7zjt=4I!{$i@7$B`>3<#TnIS#ub+IDgLusH)jde5xBB(oe zyPhYe7^GvP*cd7?%tI`QFy#(QO>}+O5q;0DU^YmmP3rT3D3|-GZT`?UIAhZirW}BG z{kglY{pE@Kl0%_=&dVmr|Mk>FIg~XRrQODKJ`FoZ<0QbY^E1>%!8P{2<2?MljF$j6 zVnd(=xXWEB#<&zZ0ggdz$&@^WC@}4lXjotRc>Q&{InlLc)LC~i7awAxMEe#hLRcVC zljIO{=H&xCceG?mTznmh;zAp}ys{$hXW7L(g;GS{*JOFjM7B$u@(H@57qcQeAiER? zY_Dx!Rw#2h{MjUC-2?2>?g(*dnYyz=sEhM1Sqx($^ix5ALjf8T7RFTeNT>wWH<<}B zMlSZs6cb)seD3po`uwcU;Ut8b^DgIyyVTQk{k-ijtm{?TchyeY7>3~6`z$iwz@_VN z*~h*ulL}qeb#I&dx%C56X)XxZciYwyXdf0f^;DljT32|QVT-TVyx5W@my)xokKV_> z)HLUF=(D4qrM5Fi=fL2j{%liup4&NL>TU1S^KR23uoZ90`6|yEQ*ZY^+_SnC8>nY- zeT;3r*(RlPo5O&Q-)Ri@=&t?EKH3ew^jhkfWV*NMKGRhCg+uk#n$|H@1I1NTGr0n^ z@5=Q%^qAl(H(e5Ri^X;@lwZ;%^b=E{maRWsFP1J5DqFReYL7#?G}>NIy|n8J9CJ!= zsR5&XaOuXEb6&l!+~CwpQau3dW066(#weF+-DTc&>2Lo&(_GmSG)l0KEv^k^&7wPa zb#dNSNVAD0x?-7b&P_2m+=0qRrJlLsvdeA(9G>Rf+@MloGtt-7`CDjL;aucOz7vW}z{0LMzkLKm`4iMGjR(uJ0~lEkRh z>RSi7&%USLzif9gS6-sCGx9C4j(x6FB3NAyW*a>y_Uf>Vs29aV`=i)@xWujIR;i~_ zM*3J2bm_sO%)uhO6rKX(GFi6?(NQhhrnCWuqDXfcUtd}rwY9_cUQ-kd+NG5XwROfs zb(@=x#E^T0f&_->ck4Iyrk?3|?7w{5D|~FzsAo7;gJ3zg;U%UwGzB{L7-c-K2?u$| zd#x^~vlWbd_gokD>fi~msRx+K=l1<#Qxa=n1NO!#2m!S0HzU4pRy|?Rjjk^{y)RJ_Vv_`8;;-S?%LxO;!8va>(xMMbcMGz=pK9 z(Ow^PDc+t&mk3t>L~h8UipyWtb=R7^@3N`b@1quL>sM@_D<;O5Zp>Yd1p5-pD0`qj zX6>=pjk}J{T>p7fVM(`-Vw#6O2`{yWnkqilM;C%}uc8!_b}d>d+X-+VMK520h`A)d zHjx0ARfOqh>b6mR)SD?4wp}W%4IXfX1Q@F(jM{%g_3I5GTCx{~2;!WT2ceHLBSl)@ zR5%JT_S38O_fLRbbYvR>>(dU<7m2~bm>^kzxvKxRm>0^jWC{r|<;=Ae7<^(6l@+18 z8|>4*a9Q0^)~+x5?z4yXNh~%L%fwbdwShpUJDce>roA&3GHgwNSy*QP-|y)H1gHh; fE-Z^N65#&_aUZt4=fSr`00000NkvXXu0mjfYGCEb diff --git a/build.gradle b/build.gradle index 3d5e8fb..366c27b 100644 --- a/build.gradle +++ b/build.gradle @@ -762,7 +762,9 @@ tasks.register('prepareMkDocsSource', Sync) { buildsPage.setText('# Historical Builds\n\nThe Pages publication workflow replaces this staging placeholder with the retained build index.\n', 'UTF-8') File configuration = layout.buildDirectory.file('mkdocs/mkdocs.yml').get().asFile configuration.parentFile.mkdirs() - configuration.setText(layout.projectDirectory.file('mkdocs.yml').asFile.getText('UTF-8') + String configurationText = layout.projectDirectory.file('mkdocs.yml').asFile.getText('UTF-8') + .replace('custom_dir: docs/overrides', 'custom_dir: ../mkdocs-source/overrides') + configuration.setText(configurationText + '\ndocs_dir: ../mkdocs-source\nsite_dir: ../mkdocs-site\n', 'UTF-8') } } @@ -1133,6 +1135,7 @@ apply from: 'gradle/paicehusk-benchmarks.gradle' apply from: 'gradle/opennlp-benchmarks.gradle' apply from: 'gradle/hunspell-benchmarks.gradle' apply from: 'gradle/cistem-benchmarks.gradle' +apply from: 'gradle/python.gradle' gradle.taskGraph.whenReady { taskGraph -> def banner = """ diff --git a/docs/architecture-and-reduction.md b/docs/architecture-and-reduction.md index 09e45d4..c789034 100644 --- a/docs/architecture-and-reduction.md +++ b/docs/architecture-and-reduction.md @@ -13,11 +13,29 @@ Radixor does not keep a large flat table of final stems. Instead, it converts di The build-time flow is: -```text -Dictionary -> Mutable trie -> Reduced trie -> Compiled trie +```mermaid +flowchart TD + dictionary[Training dictionary] + mutable[Mutable trie] + reduced[Reduced trie] + compiled[Compiled trie] + + dictionary --> mutable --> reduced --> compiled ``` -For registered models, the dictionary is an independently versioned GZip resource discovered through a descriptor and verified before this flow begins. The model resource is input to trie construction, not a precompiled trie. See [Model Selection and Loading](model-selection-and-loading.md) for discovery and [Architecture](architecture.md) for component and release boundaries. +Both implementations follow this conceptual flow. Java materializes its +object-based compiled trie and exposes multiple reduction modes; the Python +extension implements the production dominant-result profile in Rust and stores +the runtime trie in flat arrays. Their persisted interoperability boundary is +the version 7 binary stream, not their in-memory representation. + +For registered Java models, the dictionary is an independently versioned GZip +resource discovered through a descriptor and verified before this flow begins. +For Python's standard models, this flow runs during package preparation and the +installed `radixor-models-standard` distribution already contains validated +compiled version 7 tries. See [Model Selection and Loading](model-selection-and-loading.md) +for Java discovery and [Architecture](architecture.md) for component and release +boundaries. Explicit descriptors and stable model IDs now use the same compiled-value path as language defaults. `loadCompiled(descriptor, ...)` and `loadCompiled(modelId, ...)` first build with serialized patch commands and then map those values to `CompiledPatchCommand` while preserving metadata, reduction semantics, and ranked `getAll` order. Very large inputs can have a high temporary construction peak; PoliMorf is verified in an isolated 6 GiB JVM rather than increasing ordinary test or Gradle daemon heaps. diff --git a/docs/architecture.md b/docs/architecture.md index 4829bb0..51c82df 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,7 +2,7 @@ This document explains the structural architecture of **Radixor**: what data is stored, how it flows through the build pipeline, and how runtime lookup works once a compiled trie has been produced. -## Component boundaries +## Java component boundaries | Component | Responsibility | |---|---| @@ -19,6 +19,23 @@ This document explains the structural architecture of **Radixor**: what data is Read [Model Selection and Loading](model-selection-and-loading.md) for executable application examples and [Stemmer Models](stemmer-models.md) for artifact maintenance. +## Python component boundaries + +The Python distribution is a separate native implementation rather than a JVM +wrapper. The `radixor` wheel contains the Rust/PyO3 runtime but no language +data. Its mandatory `radixor-models-standard` dependency supplies 20 validated, +precompiled, GZip-compressed version 7 `.rxc` tries. It does not use Java model +JARs, `ServiceLoader`, descriptors, or the Java registry. + +`Stemmer("")` resolves and synchronously loads a compiled standard model; +it does not parse a textual dictionary at application startup. +`radixor.compile(...)` remains available for application-owned textual +dictionaries, and `Stemmer(compiled=...)` loads the resulting version 7 +artifact. The Java and Python in-memory layouts are intentionally different; +the shared dictionary syntax and version 7 binary stream are their +interoperability boundaries. See [Radixor for Python](python/index.md) and +[Compiling Dictionaries in Python](python/model-compilation.md). + ## Runtime model discovery and loading The implemented sequence is: @@ -100,8 +117,14 @@ That matters because many words share similar transformation patterns. Once thos The full build-time flow is: -```text -Dictionary -> Mutable trie -> Reduced trie -> Compiled trie +```mermaid +flowchart TD + dictionary[Training dictionary] + mutable[Mutable trie] + reduced[Reduced trie] + compiled[Compiled trie] + + dictionary --> mutable --> reduced --> compiled ``` Each stage has a different purpose. diff --git a/docs/assets/data/homepage-performance.json b/docs/assets/data/homepage-performance.json new file mode 100644 index 0000000..ee7fb42 --- /dev/null +++ b/docs/assets/data/homepage-performance.json @@ -0,0 +1,50 @@ +{ + "source": "Python all-language batch benchmark, 2026-08-08", + "environment": { + "processor": "AMD Ryzen 5 5625U with Radeon Graphics", + "platform": "Linux-7.1.6-201.fc44.x86_64-x86_64-with-glibc2.43", + "python": "CPython 3.14.6", + "cpu_governor": "schedutil" + }, + "batch_size": 100, + "direct_pystemmer_comparisons": 18, + "direct_pystemmer_wins": 18, + "geometric_mean_speedup_vs_pystemmer": 1.665046904842523, + "maximum_speedup_vs_pystemmer": { + "language": "it", + "speedup": 3.027396185495421 + }, + "radixor_throughput_mwords_per_second": { + "minimum": { + "language": "ru", + "value": 3.6574304356731138 + }, + "maximum": { + "language": "pt", + "value": 5.99085795076713 + } + }, + "languages": { + "cs": {"radixor_ns_per_word": 224.3, "radixor_mwords_per_second": 4.46, "pystemmer_ns_per_word": 236.6, "speedup_vs_pystemmer": 1.05}, + "da": {"radixor_ns_per_word": 178.3, "radixor_mwords_per_second": 5.61, "pystemmer_ns_per_word": 267.6, "speedup_vs_pystemmer": 1.50}, + "de": {"radixor_ns_per_word": 230.9, "radixor_mwords_per_second": 4.33, "pystemmer_ns_per_word": 635.5, "speedup_vs_pystemmer": 2.75}, + "en": {"radixor_ns_per_word": 180.5, "radixor_mwords_per_second": 5.54, "pystemmer_ns_per_word": 331.9, "speedup_vs_pystemmer": 1.84}, + "es": {"radixor_ns_per_word": 184.2, "radixor_mwords_per_second": 5.43, "pystemmer_ns_per_word": 316.6, "speedup_vs_pystemmer": 1.72}, + "fa": {"radixor_ns_per_word": 210.1, "radixor_mwords_per_second": 4.76, "pystemmer_ns_per_word": 497.1, "speedup_vs_pystemmer": 2.37}, + "fi": {"radixor_ns_per_word": 227.8, "radixor_mwords_per_second": 4.39, "pystemmer_ns_per_word": 258.8, "speedup_vs_pystemmer": 1.14}, + "fr": {"radixor_ns_per_word": 234.2, "radixor_mwords_per_second": 4.27, "pystemmer_ns_per_word": 503.7, "speedup_vs_pystemmer": 2.15}, + "he": {"radixor_ns_per_word": 228.6, "radixor_mwords_per_second": 4.37, "pystemmer_ns_per_word": null, "speedup_vs_pystemmer": null}, + "hu": {"radixor_ns_per_word": 198.2, "radixor_mwords_per_second": 5.04, "pystemmer_ns_per_word": 264.7, "speedup_vs_pystemmer": 1.34}, + "it": {"radixor_ns_per_word": 170.8, "radixor_mwords_per_second": 5.86, "pystemmer_ns_per_word": 517.0, "speedup_vs_pystemmer": 3.03}, + "nb": {"radixor_ns_per_word": 187.1, "radixor_mwords_per_second": 5.34, "pystemmer_ns_per_word": 239.7, "speedup_vs_pystemmer": 1.28}, + "nl": {"radixor_ns_per_word": 187.1, "radixor_mwords_per_second": 5.35, "pystemmer_ns_per_word": 354.8, "speedup_vs_pystemmer": 1.90}, + "nn": {"radixor_ns_per_word": 168.7, "radixor_mwords_per_second": 5.93, "pystemmer_ns_per_word": 231.2, "speedup_vs_pystemmer": 1.37}, + "pl": {"radixor_ns_per_word": 194.6, "radixor_mwords_per_second": 5.14, "pystemmer_ns_per_word": 214.5, "speedup_vs_pystemmer": 1.10}, + "pt": {"radixor_ns_per_word": 166.9, "radixor_mwords_per_second": 5.99, "pystemmer_ns_per_word": 293.2, "speedup_vs_pystemmer": 1.76}, + "ru": {"radixor_ns_per_word": 273.4, "radixor_mwords_per_second": 3.66, "pystemmer_ns_per_word": 414.4, "speedup_vs_pystemmer": 1.52}, + "sv": {"radixor_ns_per_word": 189.3, "radixor_mwords_per_second": 5.28, "pystemmer_ns_per_word": 212.5, "speedup_vs_pystemmer": 1.12}, + "uk": {"radixor_ns_per_word": 221.5, "radixor_mwords_per_second": 4.51, "pystemmer_ns_per_word": null, "speedup_vs_pystemmer": null}, + "yi": {"radixor_ns_per_word": 227.5, "radixor_mwords_per_second": 4.39, "pystemmer_ns_per_word": 624.2, "speedup_vs_pystemmer": 2.74} + }, + "pystemmer_missing_for_radixor_languages": ["he", "uk"] +} diff --git a/docs/assets/images/banner.jpg b/docs/assets/images/banner.jpg deleted file mode 100644 index 7fe6599115194db36ad693e47ea99928e02ece56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 553374 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⪙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

X`HwGodw%VmyU@!h}dx~u)4qXYcQrLCe@8ZBJ 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 \ No newline at end of file diff --git a/docs/assets/images/flags/da.svg b/docs/assets/images/flags/da.svg new file mode 100644 index 0000000..99b37fc --- /dev/null +++ b/docs/assets/images/flags/da.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/de.svg b/docs/assets/images/flags/de.svg new file mode 100644 index 0000000..30d1788 --- /dev/null +++ b/docs/assets/images/flags/de.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/en.svg b/docs/assets/images/flags/en.svg new file mode 100644 index 0000000..347a40e --- /dev/null +++ b/docs/assets/images/flags/en.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/es.svg b/docs/assets/images/flags/es.svg new file mode 100644 index 0000000..84e89ca --- /dev/null +++ b/docs/assets/images/flags/es.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/fa.svg b/docs/assets/images/flags/fa.svg new file mode 100644 index 0000000..4154636 --- /dev/null +++ b/docs/assets/images/flags/fa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/fi.svg b/docs/assets/images/flags/fi.svg new file mode 100644 index 0000000..4667051 --- /dev/null +++ b/docs/assets/images/flags/fi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/fr.svg b/docs/assets/images/flags/fr.svg new file mode 100644 index 0000000..e4b1ee0 --- /dev/null +++ b/docs/assets/images/flags/fr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/he.svg b/docs/assets/images/flags/he.svg new file mode 100644 index 0000000..2d7624f --- /dev/null +++ b/docs/assets/images/flags/he.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/hu.svg b/docs/assets/images/flags/hu.svg new file mode 100644 index 0000000..634d8df --- /dev/null +++ b/docs/assets/images/flags/hu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/it.svg b/docs/assets/images/flags/it.svg new file mode 100644 index 0000000..8f5e3b8 --- /dev/null +++ b/docs/assets/images/flags/it.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/nb.svg b/docs/assets/images/flags/nb.svg new file mode 100644 index 0000000..80dcbaa --- /dev/null +++ b/docs/assets/images/flags/nb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/nl.svg b/docs/assets/images/flags/nl.svg new file mode 100644 index 0000000..4506b8b --- /dev/null +++ b/docs/assets/images/flags/nl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/nn.svg b/docs/assets/images/flags/nn.svg new file mode 100644 index 0000000..80dcbaa --- /dev/null +++ b/docs/assets/images/flags/nn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/pl.svg b/docs/assets/images/flags/pl.svg new file mode 100644 index 0000000..6646a4a --- /dev/null +++ b/docs/assets/images/flags/pl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/pt.svg b/docs/assets/images/flags/pt.svg new file mode 100644 index 0000000..7ec82c5 --- /dev/null +++ b/docs/assets/images/flags/pt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/ru.svg b/docs/assets/images/flags/ru.svg new file mode 100644 index 0000000..937a4d1 --- /dev/null +++ b/docs/assets/images/flags/ru.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/sv.svg b/docs/assets/images/flags/sv.svg new file mode 100644 index 0000000..b69c420 --- /dev/null +++ b/docs/assets/images/flags/sv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/uk.svg b/docs/assets/images/flags/uk.svg new file mode 100644 index 0000000..2e25bb3 --- /dev/null +++ b/docs/assets/images/flags/uk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/flags/yi.svg b/docs/assets/images/flags/yi.svg new file mode 100644 index 0000000..2d7624f --- /dev/null +++ b/docs/assets/images/flags/yi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/hero-lines.svg b/docs/assets/images/hero-lines.svg new file mode 100644 index 0000000..94f894f --- /dev/null +++ b/docs/assets/images/hero-lines.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/java-award.svg b/docs/assets/images/java-award.svg new file mode 100644 index 0000000..d455852 --- /dev/null +++ b/docs/assets/images/java-award.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/images/radixor-logo.png b/docs/assets/images/radixor-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4f11b5bba45e35e201d4f75d6b369b8442605abf GIT binary patch literal 75919 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 S;^VefV5 z&EJQEyHMw$I}#`O)VBha)_|@KWfe-rEUaX}huZ(L?yYI4%akg!kF3BdLuiq^;)m%` z?Fjre(s?(6W$lo&3)c|FP6hW#xcJFOWm(>S0YE|bPrzNv3{{8G53p$OBl8lpgQ1A& zrn->21*s=}i<(qg63MCdj~GB;O*094jdsH;M5tUCR5eZhI+trcVmWl>J;T-CdZBhY zK6FQg-ULh?gKRh+CblBolo$nv1QV}gr?`;(LvqWWeS!JKz*%>_Cl7z$@m%o7Z|2A) zH)Cd|V@AQc`c_j=*)ey5YJdQFj$I`3*{ zHT7bz9wEb(4K2EEUE>pC0BHe^I)n%55Nj~*4dOvMLU@;SH@;Xlo+5tlMFBfX7@vY- z4lX%5i=W#y02o1z=|kx}6APHFtMBpOKk0>fAUlJt>KI@uBFfOkIm$w%-@49+8)wD3pcG!jwE#V- zGJvYq_sxzT(|e7>DB5nbwQ7)%5blHlXlVTDN6X@CayA zi<_NXTZH85CD)J8Hd8Tx5LDHJ>5A}nHND>*x&A$@4W3X(MlFsU)y2>4834kUjz@lG z5gl0N@FwdIYax00VwU7HYDF0pl>wry+Fk?Efm5YTAc-`)3f0d5)sj{1BKj5j-G5?= zjDdr0t7#h?dhNF@-+n>CiNDfeuwT)hL@%!-2I3v;y@{~<2Eup$9nQW#<{l5nY&nil zKyWV3@&_f9G0V(589Qz$8LW1>$7}yLBU|3fwIBWpGQA1>AaT6Yg<~^VT0@F8FulqE zrUqh6dTTZbf*1n$wN$((^&w-0`9&V_=4a}0k2sreU*lxYvM~r!1aLlqn`y!0V;QAE z;$rtrLQw;IX=@uwOGA=LH>U2A3?((b1_VNEw>iR%=0lrVS+z6U9f@JaVC5y$ zgsi9mCe@gBA_>JVgh}0lP?bheQ%)s_*e*ujj2Zd;iQR8u`S@oTkbo*#_c+}7_dM$B z^7b7E&K7aHiD&$K&%f^%g4Hs7)^y6Z>8yPog&ok9440Vd?N(A~Z7Y@im6ZxYFIl3C z)gyEV&yu`^feA9(3t{sCU?puPJOSGoIBAIMj)bKJaYC&@5BM)T{1qx${&cDpa8?qVr8Li24EL09+ZVxk+qQtOeVJtlQMs>T0K- z^{&Z7fL%+SXV?UM_$>@~KdSn_l8sx}uTio;(Qp>a+n!+} zYGaUc7Poxa?Xo6s*8t$Yu^n^oc|P>S{n)XE&^za9RzCbQzbAKEo7P31qCEp$ifGtJ z($sHQDz%7ymU?RgudL8TIDjo4rDgAWLa-R|I<^d9HKl#?FgVy0h+>nfBZ>Cb@UkOn z>tkzhYzlQH{%ik?T=f+-k9#h9rw78oMPgXmJqi^%Jz;)Lx#SYhnZI)`t}M9WU7ti| zHb%rhT_9Q%u(kr7W1{|l5~Gy571&?_@jZ7F=m-AbK2CYe{q;V7^h^ru={3Y-yXt|Sy*0%!2t0-O;Bqq7~6Hd z_i7QeNB?Nm)3@aAH=}qo5n{bSr8HYqFVHjc-hvt+n2;5r=HUMN}k41xY-I2nKp0i$%Ke_xy8|-a9pPMdU)j1T>(<99;gJ zx7(V$-8BJ?UB$0Vpn|S_{~XDlaD%#UpZXgzy?T-M?O(~jj%i0BmqK$<@`_(u*{ue} zg^bmZCrRDU(=DG(=yki2i1X&z2!45m$y1KwA%FXOd}kkAKMUSK-w9u87_lsa$* zntHt=MX5#<+zZmRm`TuuCAjKhxazyGV+XppiiRwv(Rl~82I+KLbl6r0qt?=HP2RP@ zG^zU+!>hhp_w^ENc)3H%?*CSoH&|esaw{#uPpj=v)fiPTv0d+1(hyCHvc=_UGdt=h zlnUUiDO%F@{nOS<(l#BU6zHjN5DmW$*?9zSg(C^50xcG9*Tv5r5dhqU$HM&&W0yPe zbGdO-JX_xWo#~aI)iwT@6?q~sjTOj}T8e7g$pQo|k#N3rc#E!%e1eXL5Uhq#2Vi;* zE!R;dLy1b3Ms@|2TpChF)iZ91hR5c8n#^+0okF`a=odeWpPeKBwckf1#}_5iv5_+q zAhSX_5PopA=hWYQF6u0MKl){4dIrj}Zl|H~l5d87Bqcdga{^)-TP6%c_@%%5Jr<`Y zx%P^ZkQvq-47^q_MpO6K9h0W5RfBXcAiXGXA^UGa&pRLa-X(D8P;6awMxZwq@rssC zQG`-LLikN`QL1*tTcbpWCMd`)w*2^I9YluK%({M|uFD#lfKaE_Eft;ylhb+_sF8MQ ziL=(7ik9%Jh*t9Jf@qD2NHeM_Rc;^KOD&>SoBmwoBZ{bajg+n)qUVeFUs1CCsAi&b zPLr3nx<$C+8MpUxykmR;x}jt=TVQc;3<8=TH>+-8EK|o#>au5p+Bf#V_`0ss-Ft-Q!tyjUwWUNg)(VWu>WSLMn*mYgt_~$>1{%XnWznqUukE7)p z+V4ZhN3gDLK{pq@Nje#s$sp@e96G{1p7U779(*?6zN|#Ej6qNazDfNfcp7W|8sG~t z6R3FrS%z#f=MyEsYGB&Rd8rC?3!Jk8X&s`{chc^-7MO53z1=d zV3JCeR*F581xPz&)c4nt?zOag>r@Zn3{+vZB8s+3hE{=+YP5EnZ|Hg3EkJE03_}y4 z4d+8dVvA3un?Axf-|uIi{QDPP@DYd;C{E1`_*KNu!nhZw}{r zxP>v)WlnN$#Hy&1-5TV5IQu@9ZIqt^*bH=CQf5ts=J8B-;aLgU*H?(qiY_!@Mh44TK8Z* zFTY*ex7&QRVpjF{<3g!5kB5Bws$rg5vAJDNG=ZpL2M=q0=@8pchF*WXBvdL~pu(Km zBB4@0XCjwF{a&^1BD84DO(^iFT7fRY6(afq#eU^`7k+@mS9IW^m`C+#C~vSl6X38>d}W*zpUzSC3j(UFu4icxeMO;$7IiYBX)EQ?|nr2 zLlp#IwI3*UTSkBXCHUDFGr0J>WFw=5uA$qpbPe>=imEQ@l=S1&L3xOKzvu-ljE-}7 zZ;8(=t7)vFVe_>~JlYKH6amRI%oy<1&k;WTG4yaBCZ=IBYeCtz9zoKy_}f8Xs0CJi zob9$<{A%bQ+CW?zQD^%(O zP3=}+KdB8>fpb(+P&QdX2XuTRx@$k~?JvVV@2}9_C|VYfHBED4g>v9nHa$jP@*4b~ zK8p+o8D!G%&(MYkP`#?b+z~kASjHdsV6NL6kZ#7nL+LekMwO<&pX&XZtW$bqD#y(3 zN8j}>^rG*>=p-^7O)Is+I4ZBhmg-PtRVo!prkWM5Kg<>jt|e;I?DXq2_*y%3g2r2O zn$%ylb@O74KCGwN?#I^e-wPe zM>+`~`9EUhB^u<47@=(@(sQQ2pU{~^bp@`vV^aS0#~mI3s$=!at+#rA=gfWe*HxQF zydg)?Oep_f3%~y5A>)Ro7?rBlX9%2%3qFkqiXv+3xpGYsPvSj{&!9KojC<>gvCn@! zp*N0(63Mc35h*6Ogu<~lK0*Hc*ASegT!TSSyqeaGOtbt9VBCP?pM5R|JS-QUzKH=K zq}E>D>#s+0ld_TuvMEO|`Uc_M?|`{JOl@okJ{1_67Jof{l$STivW1P0(V7IGI0#Zfa_y8?8m?(!n5EN`mcTL(=O!~c<``m-clg?r8FsvENic3ttMve;&En!p_ z2c4J%{lo|0W1ot|rin=;xVZ2oOJz{XJ?ibVN^#pJs?u5ztONF16j@ofSkfb8h*;A~ z@sriju!eqHRy{fEG)nzVs$HE7S^v6P_)WF^Yifb4D`(p8jaFMyhDr*N_TR0aJ@!~c zIw_JOm~Ua^P3DIFr~Iis0HYd2&6q=`!0!MZpw~Q?d4Rol^r~u(pNIf(%AK#d)%$O} z>Q1-evs7)V;k2*i4b-6iP2ad)0F^Uv{2gtp=tRE$)G_GwI|y(2W9$oFPsq9i=MZZe z98CgMH-^;KhR`avg zFi<5bROLz1nyp{YGHCx=?e-lG#6(Q9|&-}Xw(@BKxj7JC=D4Aq`$Z2igSxsZv})5>V<7@O2j?gv;Y zV9W?k;{wn$r{7lZ(b6M%27V3y`q#quu7ZtQ5br8&Ary;55>x|bwia*2K4|*;H3V0; zOI2hYu$Que)#$qr%!kzI=fs;G*Z{!^k{f?)?h$+(ukNF!_=#Qk)$!9| zC3_njlB{_}EP@r4Xvv9~24ikFlPfdNp#@=T5{!yX3cv9_6&suK6C2PguO__hr5Idn zM^;lmO@#qQFeZ*P3|E(}syIODlL=DI_Nx|t;`SE~$up!_Mqm9WaPgHeGo6%e@H8y7-}0136oT+9OA+F1P~^NcIkp-B62k%?-FYs$#v%b zpl?Ijp&Bhz%#_j0jCoOmERBKfqqcyb@Ws#nqzph>q>-4y3ue z8`AD;w+U-sJ6a2{`dRgvnju<2Me4Z)*{A)yHI&zIHk%Ty@^_<^)tGj!Rs|Iun%|G? zqZAn#4nD$uL~cOjLJ|3-naD17vpPToPFqge;RLz^Z^s>ep@Wk-R2=O5tvmWs|B;UW zIRQY{wXxCwPQCG@g`Jd3>&L}^Og)+!M~4k6AvLpFbmD3_#5tmMn8+YNNx8xGfo03uabzeOqRzdWtr_AoVWa zdj8}{Y9wp2nm902|IG%aY;b`V=qZ?>A_oQ81N;DEE>)59C9_xQRfmso@-$?E4INyz zmZ?$5GA~j(4HnekRxFZv^onQQ(U@FYJL15wV+Y8^c5AKvVbBv?s(J4b7?K*EtpXV zuX{aQaU(jt5pgb+t5#HODgm-)lWPYziL?GHo%H&n7M^73$;3yZr0GOrvIi~7q-$WK zfM)IA*4SHPvRX~ImIoJ$8k^*^LlY>gEF?PN3}szn+HT8QYwWcGf#A&mMwjVyI~o$5 zG5COlfEb6^H4(Fl>WZTCB638PJ&4(XWw#i!10%ciiq9S9#QzojBWyY$c}(||K)JFO z=oL=v3K~WjnI6zz?#AgMZdMleqOzz|lV!iwl{nhyyZ$ce&vCYn`>K$gYH zzfKo3-i7H+gsrDwk39`Dvl(oMa!|lv6%mJ8joOP60a48|#M&45zC9dzm1#IT% zb^5wx%b@$QN0fu{*Tc+cOs8^_Tz_MbIdT8T*m1uP*i;{=dKcvMX~n8HrQq{2j!Vyv zKnMDZo%k90AMyhH2|j)z0>Du}D*A3MNu$H^H0=q_buB7s)s1!aTkFI(PC&z5=!Q0? z1jxqG555KS%D)3U6S)JGU}r0MM66mHmRuqnsMjc$?gjcDnatn||Av0*UtszeP=cCH zCMNfIcZ70wqo^-6`XHr`AOU@qCW0!WWu&dQ?am#rwv1v42P~qyfy*dIC&e7Q9dpK+ zs;As7wmmBi$ILW~(>4`GZHv&dO{^9*CjHE&t1!WCA8g-y=|T7Hi;;Vt6@^Nyq6-CFy@kP@|5i9@CcdJ9O99BJiYQ)n=OaF% zGM#(!nEjY#CG_Gs_E}oxMLu=dLwY?FyWuyOZJ;DhIt`iF_rFJi}EHS^+s9O`k``3 zDxup!ue=)G{XTSjql$*0*c_A3N_X^03>I$I(tK0VEr=;ZDuWc;bOe$MN=CShZSW!-MSIrat06`tYZ44>jgU5C5_6DdRnu=)tyG zM7Bb1JkBF`-ils-1Ww$F?mYyj9|ucoaP^M3Cw%J}L&{~hb|w9HI{s$_fC{m1YVBAQ zi>jmzw?@0Gan9CcUiDqA9947BV^<)qP-*Jjx*Khy3wCfYF$3Q?554CD$rkOc%}u+)z4}g<(=wPB;{N+Mj0Ci#-r}uWf#K_u0nq1VdM|_MZ|P*%V`)& znu-gfwegB-qj*$lfXI*!dUd`UO#`X42{p=@#S$ng9LbEaSX)R(pWK}bhs{eK@j?$a z|Jgf-{^l>iyQgAylG>({8Fa^NeDW9mJiPp~aMS+y{NH%`9etJFA;(b-0Ka#S{q@&T zL(kMP^z9cPbNju^TGOFMmQ6M4+Hl;47y0nKc&MqREk5f|X3+GRQ@c;wgGR}kNHW4_ zKZrZ+KH#BalAwfYO*@qaU5q+0E<_u%GQebpyyN}o&AVXZR+SLEnk*Az-bpd|4w#Ob zObNy1nCuCn{!5Bj5fcmi%9)s|>}f`2huWm|L|}{RD=pPvn@9O{1^xOrD6hYk{Bcjh zZa*Ej5L!KfIP0vY@<5L!ok*i`s>yY^7AUPe_FAQZ)HYS znCbkjd(5Vs?A+3x(#OB*n=y0p8bQp79voSNm;TGG3>y5;J8she@JILAO)v(pIsN5~ z@sSn!gDyqUAq1N|J{9SBo_^OOlqwhknFs?FK6?3vqw*%ry54o-&(YF((RK4{(8>ei zLvA^=3Qb0!?dQ;DfyM5DjcNYKBzpZd=+)nZ;~&6;DVu>KZK0D@0FZ>Bc|}G#aQXMq zFMI)JwpJ;;m8cZ_@wk<$XK~Kco5@kWf^?oE@Gcc`k#;0?LZgWhCpF#G+>#-$lrCZ>Rmg&v%xj)&Pzk!`4Pp(ycGpEd z{{6|@{7uFT-PD@!0ztLiiEr)qEi&owe0l2#ZB0BKatR(H#>zG%?8Y8u(d3&Iy9(ZpAK`I8`&;U(vNY|p8;Xn5&(R@Ff5_No;DN81;7y#5o*uPu5}mj^BG zdW+<$HAB!0xhT4YfM`}u^(06Fh625%_TyYIdOh$=$L?g+^ z&Y9liis?;7fUh$Rg9XS&gANu&z564ue-F}27=P>IXwhcdb^j16PHNJeN~2;Jby{Ok zi}L0kCO5(NE<->6Zp@fQsqjjoJOwjxIyA&|YmP(#vS-Z!##WDC!F;rBSW z&tSdU&R9^dxZ^j4@lGc^YGlm6Zu|D)U+#BY`5$K))3opsD@Q_Utnkl&c z0V~d9N(ak}U4rZKn6q!YAkdyMPh4?bCpA$o|4N{;nHg*`$aMHu>MtwafHbyH(#jx) z>h3H@7mmQLYdLcp>)bM`-;ckMryuJe;Q#fLsV49>QlD6Qg&&#lesns{B;>t{FrzZE z88dkrR{irpJ|{>|gtA%YQp?!Yj6`X$fO?o_h?O@ydfTSZiUye643}R`_{_Va@Mti# zlXG+_(#BN8>9E0wTNeb%XlLG7$7}RRsrW|)(3cn_4uBpEHiia{)h(UR=ifR!W)Jl} ztIik<*_qSung2fsw2vRV1jvk~IJAY%NXaMPe+FBoH8WP-)R;~$FS-|h@5Cpo8vce2e06y$`UdW_*$f(mfg3KcDd@1_7}Bmjp(|9qMGn)V8*yueBqd! zyOO(Xys)n=m3NCPJJG(fKl-@gF4=KJX6W+eDT;Hej^V|;5s6=ruu#3LU8SH?ES z$}ZzH55SYea6WK1kU@giD(0dlnsTjZpCUjb*IfmsdZfA;^sS{8AWk5T&A??>q2~R_ zBYz8);^!w~8X&Ri0E7l3P2z~jrdw-o5XtH;Zfp~#$Ew%*XXGIJ1q_+aFiTnjGcpUqj7E0@Wl=DmThC;pA`pw%+ujS=e={ zq5<9eF4+1k@FfC4jARO{D(063z6&w~QZxu8^=ohSMrvj4_*&MD@6rT>MjRnxEfPv} zVg|kRV(5+_4>}iJaHx%Q6wOdTOO37aE(jVs1ue2rJ(Aq&0%#?BUUdg5siJ)#85v_T z2!>yj{o!>?n?G`Abxn5OvgfeFoHkDQ|APShxFtZgHpSkXN15I}&yAOElF@NOZ>0R+ z3v<~QH;Qb3;(bj2>Ho{RyPjgK7Lv`)VGqu#J+z>9X@xA5WYaxkPTY*!x`9C^^2os; zyVP2Jpcr+XZpgMA2fEhbcdppT9{%g-t;*0_O;~h$a8(;=u38}={ciPJtvhP`V2EGb zQoc!lOvTjxt;)CN64>&7+_-SX2io$JqYh|Dr>^^{Ac^NV~85fVq5Xlf@5tAWe zWe@j;*>4X+ko(`&C?l&n5tE6?jTrk_kbc@TLo+JddaG4SvelDRSts$;sQRue4N{N@ z4#qd27hQl}^hL~AhB!^C#Z5U7*@VfV*sXIfPNhJ+B`S2n1jI!v`5}CuO!&YagudqL zDHVB%J>XxGE#mdQ$>(N8cI{GT7Xr&G%IZKVoKkuil*-B=ur|PSJCbkN zD1%7{Nlr}UT0X~A~S9}lV=OD`yeW79WrA23p zd;AGpcsZ5KR{M{s$wk?Eb*qQSLkV){Q5fsMzaBaItL!KE_|Z#%wHY!4Q(OAhWa>Lz|GnTqvs!`lU!eY1z3rrhyBBM+pdJ2|>uGC#5Ln=@zB@)8w?fdr{bx zl{1{IEl%qA=n@aQ%PPSr%M<-ueOZ@$bK`&dvZXzT9!+1;9$umRm6J=gn};gbkr1otyMg%Rosz7JnG4hM^4zx{(i}}ZdN(Sv=)%0R}5TxC1j&%e0g1@ zs&?JAT|y|dE;*`W|GQx8vr!wBhJ$J;8Zv=@0=cuIuMp9Js>z1UN$OFC;kt)P_MkyK z>e1d-8?>SuXw_HgO`u=?1Try-IrbdFQi*0Z8kd-a$k+tLrrMv-%nFEUL{Fuf)(aX3 zk!;gGth0v@laJ59i)3+QL&;qgB{#`M#C{74zV zOV7BL!SWVtuP?9LKjL4zXN0-A&YQ-q@YqL6=ytHXE>Ze@l|dN^ScQ*jHiegTi~Viq z1s~!?;+^6=+4vX(;IxIs>@SvA45c%6W-Ivdas0TU>J!Gn%>(DRgP|84qCDes%Xcx6faKs}7Tv0H-Wdpzb`%mO>fD(gmj}^DN2w<@N z0(S32yCaFJRLfZniDisF?*%I3qw&6-e?Rb_U|(^su12)Zi(DCo4zoXIRzaq)@lZz?bfq;*|B%(WyhR8YG+Oym9dSZI3vt#&$-W| zS5TC59&+ga`uZc$rjn1UQ}~+6Q)jx>k5fj_%Hq@4O(sA^1d7kl*li~(v1;SAc@GxX zc-o84W$Kiz99b#pw}mrXi@(wa=*%C4#aYO*I`q_HoOPXUc;@p3)rm&*a{c?>_l{_v zT}c^dB3OfLIq9i_d?k+8NLjy@7P+Az1lNI3Me9%S7Dw}0nc8hhGz|fHL-@@5VD$jx zS-Jo!`GH2{RFmA<`T$Vh$_gJy!Jxtm>Oc^!IPeMwfW24(g?hI55&5_5;^h%D>pW#J z5)^Z%apcoSfqU{(aQtWi;P499eBuUH<~zD`zQfw8`K^s(hI8&|Ou90t<5sZxq0sT;IsI-=RASympyC7vRp}wQxlYus#g3P zz1iI`SVpXA;cShTU%~?Py~q3toPKV^2>!D}Y(=RJ0+xe2Rg8H8a2=|>2*c@6MzoHg ztTl0V2YCkl68+r!Fku4Uups&I};p9>H)Z87t9&h*KM+yMt=5hRB zRHjc@3wJ+zb&GN4z9%0mNFOtQ7zP1W2QVl!EqbAFNa2yvA#vA_i{5+1hbVL1z5!V# zg0b?v(P=)qaE*SXzo_qDn-6atEbGhMfZue5KFAHi_U8iK5nQJ$-Yq-5=R=RruDyO> zF1=(oKmW){Cbv#=`a}0}>O*hB*lx>*UQE)f8RGx7k9w!C^`a}TU}%Gh>A}N-AQg#S z)nkm}Ru>rG08jnf-{Xi8$`Ez_oVI|op;{#U?ZqPzO-~yBF}1D$z*EqB|9tBS#nn zkw@BH;oXOyI)yvUiY$4N{T|si2LJp&D+v71Am9s6yqG+|YH5utt=B;~Loge+ZV5!i5@+b-+9k4yY zjDYEa>6qC^{+7Sar(*~_m z*IC!?bwfnpR=Hbw3Iv8Tg=*KQUie!7d5<;VivgCB=fC$AjGT2WtMiUtZgJ9zN9i|A z(gUiV)(wid$V~;VwFY&X5#GF$t>lUn-`h#Myz7QFQMv_|gZFM?4c2q9Rdzo;a#p2j@x$ z(WLCk%5cD`)myX$zbItQ<7%nLn?N$nbzF)d@;hcOT(3RzI)6qd%YJ8%D?fD{@|7O? zi6ijDjr>nIe&`H9j8Hlk)Am9r`*LD3)zQU8rSG6$!oam^eHUx}(p5FTG~ECP-V5r5 zib`ImcdEvyx-g*dYZS!_gZ_dHmX9!)-N)eYF5H0~y5Zm+?l*sgmkdhtO>`qG=~aMy>O+fvsEJ~3}9c)8K+UMC9D47Rr>Rnp>@DtnNo zM^HQaW;wnT3?;w&!I!Y*;dkfYoTJPveWHfOYEWE_OEe3oI-Ju+TBqjV4`&OCYN-a- zJ?)2*tI)k~jrDmox38rG%|yfJMdXFVrQ=&&fSS#iNQ$B@GH`qG*SS)v3zW71Xk#0u z!6BoQ=nt-juYX8nQY0BM*CHsD;b{d3)|!Z}MHx<3n~@?MOMtlhON|9kYNQQ&&^6F& zfn}@)d{B9R0d@V0A(C%ai!v%;#{@(Avh&?Z9tzC1X?AwwSvur%vjBg(JZrM)`*CTD^{eO zJ0QjWn{fwr>B%e0yeJ>7KgE(ub=aWlk+EsQr5B+We-<-g!8g8Z^+#Sg9=CK6weA+Q0Kf_} zaLvMxnx%r23WGFzumoJEA_GwZ8jQT3h42fqrX@FOuFGKVGzQ)Lj$WI$%ke`80Ph0J zVTsH*GEs5~eWS3ts?sl@jP-uJl@fRT5{k6wOD{MlDFDRQpHFpv#RwyS35rQ4T{O0a zj8D3gK`;pjG669LlUXuROn*__p`8p4>=L`YWS=6V-S741{Tm01Zo=fa2R?0^IsTkS z+PL;j<$|G7gKr@!_XO9v!otu3Z*J>^{e>tRl5jbHaF#7+ZQ`l_`p0x0cz1T~D{wX% zdlsq`0s3CCT}y7HDc!4HL|YQf*v7A~J&03b6oTZT&*K2@5DvU|A5jL5&1)1kjH8q%4LrrLQGRn&CiBABT_6 z-_h&ys2@K(2&fP!5%Fq`!&2O?jJOX z#Y)BXR#EX1@C5Y|a1w9?ym$=~qIRBWa*4ATYso@ESlF+F!@J0qSL6@7Crn=I2L4HA zlyK^O?_Y+SzJjsF5=sa@_30b^KP6@Z8tUj&Tk_O_qB4f9`}_g1LReWPEFR&`zxe&A{fUuGQtc*w7ETT5b63?1M zHR6$J4PX1DvinjsW2rsZ<^hCe_m|M}L=3Id(;6RczHYJtby`mtqM!$X8`Vmn(7;R> zHDBO}+ss;U^n;ZE_D++?M`!QoHF{K!A3gwx(hD<)gpl`E>5Z;l?Nw(N*2oHnt`*9l zh>O2;aTv|HsEl2u{PnIMv_5emx!sDU&T#~b14^cW4oXLmjwoFT+Lb^@0=WsQ#^a3Q zO+bwol^|G<+>nJny0DwU+&($Q`Rt!=`j-Fd{O&9^_?4dgX+~>!WxZ3-cM#nEo1ZTndSw$Wv zkx$o&!hqH1Qw6{p4@_X;5NW7UL}ERz(&!J=*6bokHi7^555wW>F(Y}Tj8^JDo*}5F z?U7J*4%*p)xYQafwKY$Wf>Fu1NKMDU>PI_g>=eU$y;O?LG|^{I6*j zkNS-wIOjAI)z2HFkCiya`VD37yW+`Bz?-1KG&_SBgX9^xU%@Tz zm2zd?JSgW(*h`cbUZ%ba{!% zGiJE+tDnxDKJZ%B?t4FW?r|&^o;49xy~b{c!b;%?Q#l7NznOnH_XRA@t>DB^RulQn zWN}(6HTh0%AsfcVbD)-{a(;ss8>Jm;guogrXJ#y?g^5OrUnZOU-2K2$;DHyZ!Fgk8FBEcB{ zFY{~J!xolAdznO@zsBHQR``s@wx3re1Z{?L6QE?Jp^sXD!l?;Cv6esgCExMAinS9- zHC}`sZfv0DEKwdUf!_@1$r`oi3M(Qi*bpLP-x^F%SUaM{>asCrB0PKYw1+-Bo1S!w z*MC#`2X_$$MH)?wZaJ6=aZm9vc+?l*264pJTJp^^j6dYwY87{BM;SsTeYu&88G z((PILDS7C`*MJtD&0BJoFTH_(d-5OQ4)n=R=}?FruTmCQWZicA!yS9xjBI}q;#^cH4z3iFeE5<~@PKoJm zP|E%wbgj`(_!a$*8Moam1RufSI?W6OSwZ+N%6CUJ{BVzJc0z#{rLV$CV2 z{vN7*ggl)_smG5+Dua`1fVgeNSL+Vn3RkG{L=fVK)*XSxW%MiWLmu_J5c1?zm`qtK zPL`?tilbjc(0Jdig}{noSy?c(Mn@ElxWWyBoT*mFaEiP_`r&4Bd0+VXu}n|IS-}5m zfDayqr~miS5%{44z*~0To!>k5Do&i;!mjbYy47yB(hrNBPWeYG1M{H+i^flnh=ei* z>xz#NG^y)X93tmA2APnI$>FwspTqy1Zg+}Mr%>gD&7w%98^)Lot7k#QPEiN=&B;uBIYb1jA&3V z@0GdG7jOSjt|ZeboH=zAt_tWs%%V>_Ittde_wgf9f#16K&g|H7P5j`Fx!LuXM$Ovd z_=jy)zVq-(=*}&x%aS6r)^$F0oiAFgT7waT6`_*}Hp4Bf_}qIX&v=^ebAWp z%!hTs-_|X-CR0!P#vbAmWW)}1f|N2qBx-$A4YyCeQTnpVJ z12tA~9TCk{$PsLPGh`}sL>RHcXh&2fxY?z$Q@B7U<2TjG7u88N@W!FwR}8@k6J=n^ zd$z6)nC=JiGF}ZK6E%oCc?ne^6VD(Bf1Zin)1c=`plgC=MsZmX&D2E4DHW_j79@U+ zkg1Y|K;{FT($gtDne!Ouli{o&)+BAa*oL$LtQRN)e&8t7z_f+1!t6Ug&h<}uIrHDR z5!rMcNEX|ksF2ij(LO;2Jd&q{JX}%qQfC+bn%` zx*9BnBFB#*@BQ!58TgR{z*`UAi3vIk`U8Rn{gbCZ$~^tgU%=dt|BXgb=RncJ!gn=?A@2`gJ`R>y?Wx&iUSj895q_AV(Bqf?FgPfn4CK!=-tsMPF#)pjGXdpw@a^76`dQ zM}@pHpoxD5Qw0QLur{XlnMnGXB0h>%7>$G_8x-RelV_O8oUpRM!UsOej&q;SuGjq| zZeWlN8&Ea1T{o)W`}k0>5`;by%LkDfVl%+#XqPr6F3{R-8&Qxjw(lFN-8)s#`)zaV z{%go;(-MzrM$mnT`IR(s(@Bhtp^ksuO>L_z-hxO~L7pvRlDhNJdQ5Q0$TVE?Ww`M| zq~ZwB`cuA5PpB5ZHUYsm3%}74kRbw5q!A}MF|X)BjU0j4yg5NPWS<_ty0CV|dz>0k zFsDw!dw%+Zz>lQ~IG-P)Fo#f|p}~e7H%&?a32NRnGSa_C5ciL>rCZvy>N>kuU4FQ5 z*<$IlMd!2G(q)I%Tz+_^%-4#rH#7XvDaYEo1TW_q(QI|T@PGanCvoz&0a>@{%?ESq zyW2(QXLKYm5)>oCy)Ro(n^6X^OF5##-}SO^=M4ReT-cq5(8+^j84@y-T!fAoItqCZ zY*4H!nL;i?Zqm<1=qh9`U`ih^wvHj6vW$!y%z+)Oz3(5GedY@}^12Vu-@S-TPedA{ z4~WU4MqhPPCGT_?Sm>)JjSC%;%ipkJB1WZ_D34S$p=Q_gE9GW5{}t=qkN)e&?!N|` zE)i9SYEIA#6#X@AkjR#CkPb~zYAw6g8k(y07G+&Hpgz}?=!&HFr1YlXqEDlTuTDXr zZS~%y{WUQteO(0usm6=z8^akuEkhpV$3=lXAiK!KP^j=TOLy&lzI<3t=bH-wry_-7 zPMCmq|8xg|A4?V3`x0w1LU%WzzYPiK$n+t~!9*5A7dzX_dtVM>*bH%xZ+urIKRA+yX_e zjv$bG<+8g?#a-Iq&kT-2B-0>A0=#m<7JuCj-6#1lcn;p(L+q&6hF3xe+VIb{QR%H1 zZ^l>{Gth%UC~>=Yuy)PG3@-T|E7$Bmmj*C0ifr7LT8El=?=)IMX}S+`dXrOh;*dJt zNUaX!5SD?li4m~v3BO_Io+u(J5HgrJGd|?bFNLdKd)r+hVv;wZ!wibj_qrMIX%Tse zs=gjEQDVIA)CQ^R`Gnj<<(*KyhlJd(BMuF{M`=EL0Bjfi+CL%>dloW!Jlc1yT0HIe z2@{Ree-cGVYBAF^Y*meLRNZCUcpi#v1t3xMW0)rAVt9j8^=bgAuz3f^(_Wt9@(zL$OfFnai2;F#= zjKWnTy`k50!8bZX-{1FPCC&)GD~Q3lQbi2*o1Y$8*l^an-?ez;;+u{;{{CM)_UxzW zAOx{01dR)@MvD~)0beL)JR_d~^{XsjcO^?#evhReTtl&UH@dtE`3Q`TBQqmS%Sp19 zU3fE5sFgse^#7f`CgvK0boYveZ6prs<>=1VEKRAC``Qp7vjA1>Wkh z`;Bn?iwV{c#(NOdn}9*}4Tzi!`dn4|LafH18F8*wQ=9)!{Sd8#0hNM!H9l0e71N<1 zI)RD<{p$NLk9r<-Hlanb9;-f#D(=t{eKE9Vm0A@>NY>*zY)cluPOr-fdIN|dXA^~r z{I*>PS7}GyN>wu@oC@v`0 zdQeM;I5U2?{wml1C$H_IFXcSuK?O@BH`+3&x8fe~==m<;(4ai!@%QYGj1e5_O(Z0e zHs&hh4&GzCId%+IXLoY&YhPgQs_!s3a3uNCb)hqcY?y#_k;WarMQ??Apr|U(@mZDTrWiEy`As}jR%^u(s{USG>^@^i%k?5{B!uA(~ z4^(?RV#&+3-*s8%R6!r1&<8rzVwa>BP^&97!%H=phnBwLkSK~awyaW3MXI8C7gpB@ z=e-~E(BA_)f_h)Kl(kkXN+;2@v;z!7t<+k}pz=MTodvYioeb#pAemSu#e3zi&6?gQ z8UM-!>E0*V3ViCz!!-v+avo`p5 zoPU}eSjpx3`AkmR(ASC4(vOS7M+wiTs$?W{d^vgUJ=LpyVEMgG%kV^%60;SfSfOUQMiay-_d7DN|le`RTG_{7ILZv7)i+=r`NUub&$(s-wl0iMH zTz3Sj(oWX55&bP?$103K%XEJs=yfWr#xx@!pI|jSfRd7V%~%6Dc?|4rYPNm59JfgT z0C?}*NxXON6pVDp=pY1sXL^p&?gF`4)lTr*v8&oKeR5Nh`4aV(CFx=`#P*xOKkggn zxM>qdgFW<8^dB$!bbR=sUdv1$MvdjnlMGY2WhH2yDWy?)ROaM^;ylI&wE@WjL(nL-+4SFZf{c zA58ZL9RdeqysV;1CIcA;k0M!z zaUu4C|C4L{-1|C02TncRurLR!Wyq~i2B#0RUOugW&BPHfA!!T*qj(n>>1Fg+_VNAy z_*;(byb5|V$oNaLz6K0*1h_lnSqvaQqgsg*Qlw8$==6Kzy!s7(KF3?fb zsbbo`gDr`%q#~jE}!2j|YF$T#E zIyVbThf3LcN)B9z$kPeoqiKPKnj%nT0?9C>?iQNZ4X6frs-pq~D}|)yNP|FPqmIDX z4Elp_!^i~Y^hXnxU6eX&Yq+%Ip-)`_iO((4QaaSuTtYpbP<1R4-Pj5o0j}4K)1*K- zBW&(`lr!b=+2_~1-`+7OL5`h7|N0R8;SaCUbVnUWdAs=Wj*1EQY&SwytKqeG*jpaY zxJOA(gkwg9g(cWO7jgp*rw`9O|0bT?3T1}aEC^XpY!Gms@lM8poBxAvfAv)q-eX2) zV#O*pT5Cxj`}ir<7qLlZ+@>_&w9Hzv8Nxe!^YdZd`Mp#hT zc+WGL*f@?aiv%#XsJ1B_>u4E3Q2D%z^?!|+(K zRCM9c))|mn_aUgH>dKVC><4bv3`2qPIJqkPgZqUM^i-=1g2=KW+oq5=9QrXo+1u~< zi3|WAx_*KwgpXu>M%BbA-5@o%yMBv%=3PrVB20G-u7s6A$Zep+>4P#SpI3)5NMTiM zB>5;-LA+$Kf?U*?7QDWB{m+y{mx(0~h)F2ptT$##?nf=Nv~@gy~KMdCHR9s+M@Y zk1hF}_BA&_3#-^nQ5%xJp!ZDoGH%}aAAIlf_aVIvh|ObdFIla>FJw^J)M~Mx~3@SXdii?t3@bx)oU~2?zzkfD{BYG6VYxD^8gQ&Ux(5f;UK;_)>3M z2zfwF5ubW_?dYGF9e;)H{Hq47PwHr7x$5W?3_O!c9mgPn6;IqE0n_1ulhFh7c-a(pc{|ibIW}P(vS9 z#G?d7ZU#Aw0ZLW#Qs%;M$cnn~*kf}p139w?GZXNtJ$K|X`cIFa=m2nXH|My#gH3}s zwdP#<`Sb2n(nAF-DKJShsbQvP;EplR1vZAOEG zMCBk?36LgJk;=i?CCPEuDl2YMk@+~FjFI=tAUr_4mh2|e6_8^`z>dJ5@4fxb%a3sU zLo`wKrX~KmHAz`mJV?+mZmqp( z-#6rISG*DO(I(X=s|R+fe!j}^g4ebl-w-QLZn5i2yB%hLm1XQ*PaxdoUN9epLi$ON z&Z#6(&XLroc!4P!b0MmH$nlZ2<4_txbbnY7Tt0O5K(r&2FdcOh7Y1- z;@z?s^LSuURl8}=t%*U+e8aj=tsUE{qg_HYE-Wf-X+l-7ISe3N@Bu`ZkSwc<32hsP zv~DpgkXml6Jws5LkV^Wx_N}P10;1=@jX`7uuS%vOpJ&zIRa|f^Yeorj$_!Cu?I-W} zi3tFIaq<#>aZ0+Kyu`ND)u|SIUqk(MhUHE+uUU2>)QkUYJK#JmnG$7Sl*|M~yow2m zalv;n`GKV!JHLGGn@jRB5y@)8P6g~~mHvxu_3)`(F_1Q|T^iF+X|(Q!aX>}IAUeRF z`^$t7TUp-2YDj56&<~O@fOwq^R2X1cT!LeM?P27{j70e|8nZ$XA!I9}qp|*%+cN`b zzr)>s37vS6Yc)8edL70*QIZ{-2w`|3H$tOT8KAW5gj))r4(EUjF%HlGc@LJB&~LvF z$%{c1IGprxw))tK8P%LKJ6-k?Dw=n0l~RJ7 zY6pCYk_{3VNFX;#8rc=HfXkGQD4+Y%jt@Rl^68G)5!DLh6PrqaA>Y0D;;NLs8E~yH z!v>3Q=Jj>x;wA1x3(I5=xI6jJ{yZ!NFc#HF0P1TT2ci@Rc|mT_jDi79j@OCKr?Y?9ZDpbK6yD$!Ic^$W@skwwE%h70$yQYc zqRrw@x6*p)qVB?XeN7(McT0zi?V%=DOx^o%X3R!`e2uzp14H0(Sw z8tDAalX0ufM{jhPa8`|^50ZrVvWLY|Sq_G|Bg*7&{Ssq$IhnAUCgC)IdTaY@lKB!5 zCEV-oXi?lTCv(@UV;3UNP(MJ%CY`Eggzz4aS4*UHcJBozC8C!?tXGPsj#`jP1{1T)!(tA((;k^Gn+zd(V?wqYm_x%*$8tY(sQl zgy`|6@g{-N2D+&K=4Hzl{=ylj{R1O*%mzY0a;bLzA=jN-qocO!{buA{Z>63c-bN%d zFgMG{GoL^>;Z)p$BdCB21Q*+WWi3)8jjNSL-N8kdOjZY;^09|n)WKGj;~6uSaJkpbQ5~x<>+<)29vhcQjn$ssW5y)ME(UW4#{Pt6A9>qXlRnx1yy6J=v7< zHFa!*xbs)QPE_!r%v8FlUhv)Wx`&>->4B@)?)y6iy5LXwWgBS(}JyxY={~qY2;cxGBS-`@eSDVZDhix*VM)W zYW3RG5s=pRw?-gpya*&^5K^@glaQnA1(_vz4VY)jtp5`^L$WgE@C%gg@BvHgXNtOnVKAI|SMr<@l__qit88VRD{h zGk(oxoU`Fn%|gJ5A-KNgD5dwDBm?vHP_VsD`|Ffnkszg2x zjTGW<4k{bE6y1deNt0-zAu_VZ#w=@A%d85AFOdG1BB?St$&~^j{iEMkO!i1ojECj74#( z<}YR0KUw#^-MFQHr-#r0h**`3z)X0RsK|Di_+{ zj14BqI!czSTzPO50iSik6Lx+lzqDL3@0#eChn&1!ChvSFTb^;IElxSb6x)xNa%zLv zPLDg|^(AguFEbtfQJkEoj*~So&>%S>4%4T_?_$lb*RJZ?!=`qwFmJl)4!f|bLwCOl$vPEK>Ij-i2;axphXHG%nj-|MNM16OP}PPoBJCo*`pjE) zZraF<)-#ALeZn{+(L*oz2o(E}Jd>IzL_6JYQMpx`7NRy_q1-}GKvQOr%7HSr2w4Vp zs9}Huj2in{mi^f>2&JxRh6mX`b+l*l{?p^A=ZWt+bq_M=(a9{nFml=6362LH9UZ)3 zFfcEkniS5ss~Jqpi1Y_)`cBMpA65%kEn&G-Rvlq=O~YVWb6<#HAn!_vV1sGZf(D1B z6D-%QnBoCln;j65%vj_0`~7@);qQ%z1cSYo)3xuagwUQu8y+``zJOBg?Dd3S1yD5? zqox^sv6iqf$H*C5+5WQMXDQ#t%A7-cHgZZrg?tO%mz;Fo+xX2lJcs4Z2@iAfh_*b&gJo7geA=}gm>Xe1= z9Kju2Mve^74IRQCpK`}sWhee6;+7(S79DzWkP9@Gq^g}_Fhsuqu|&S_;Go(jIEo;M4Cwzxy=?BO??}YaIZmLg_WC1>dxT zrC0wYK7&xWxXYxSN<^f8IE2}QW@Kg4^uG@l{BPUMxXyAJbj@dnUb_R+nPT$3+mYj@ zFrDu0esR^Gs(QrqV$!9G?)yZU+Jm1&?8^kPs?wmAl}Kj=^$oEChgF7}@2i$=g9-{V z)X7m4vJquz8NKlRNM{gLj_Nr985R|(EYH)-fYif7?ct)9icm!#PT4?hgt-9hQj4ch zCnto7?q|A}&SqxKdCFk$3R|Z!zkBOx1RM+1Xaxb0tl-K(A7Kdak?}$6;!zY=MW5tY}oU!FeTNycR zGxm-9u&>{DyI*9w^Ob~Me}>6BkmZ=;o{HFx*We7n{}p21A8p8`jK5InC)Q<_RA6Am zAGkG{(RRz0q259@P#!7!bVrrBead$~f*Fa+pdkYd%ZicsAfk<0^pJ3fw#xw>k{_#s z0>B)wpG*P+g4}=Yo|``APUtZHFIPsHai?(12Fx>WUr_L<1^}bV+ENEQ=}kU1aN{$B z?frRPw{_ZZzeiguVCHU6d~yvAA-TI&lUFTEXhQsZ_?Qt|D>xS@i!w+VI%qiHjDKM6 z>`lf=T>J`0)|M&4fS~<#*GttgmKy6{KeF0f1ll&}iQgN8dXFv~VB+-6obig6vGE~K zWbwK{x$Kc(2!+IWrH~N@w#V z*=z3@p8(>QUdWz=BLx@dO{eb1|&A!5FYN>`#Na^oJfZaDZBq^nl>CM{l|u48jH z666xK1xKdQeK(?)e;hN>&Kg?s4MQ1$G}DB28$w#`LmgrcN?^#!OjM9ViXPEip-|5+ z%ii#grKjB$-I9q6yqMV%*?bK48MhxOcvJ(xb9SD@=s5={7IIlQ&}CtN_LAJol7=Y5iCeb=8=tx)YY?>5O>p^z>ij%s+TF`O-mUO|#i^_cA)L%$&1{ z&g+_(14c&4o__lT>7!c$Y}rq^X-0aJ1Hbk7!7;vXe|_uL zjG1G^&fTmGT%d5^O7JCw`lbXgu?(n;Mty5QBY^RWjU8FM_kTrSt_g+j8ZXW}&s@1e z5eh!AYfJrlwBege?NyFyyXMn%edvags?oxHu!i;*IOT!&=YcQ%b2i=k39Q^8lt&z5 z<0}%^aK=L!FylFP$91?jy^OH9g7!L~jt~NiPFX2q5GZ|;+6!@Z%w|2XqmUV}qYPg0 z7lhA$8ky`u;hLQ#DcTxOR?guTuGeDukbPX3?tafNg%>Zpbvs!HxBcIawX!8MqsJPy zKY9wgX@cIL-u{dXH6(yx7Zu){EblN^lUI5E5_}S^!I&#!6*CsC9WzcmPgNr%wCIg3eR8CEm9uY7(At z><1gbsBYo4t$(Tsl|~=F9u-rMNjpYZnPt=VEj;wOf5_R7dp665j$!_~G6p0JQYxm@ z6a-@WpO5Fr_rAv9O)n)ZEg&O#dQ&Anl®{wsY<|3k%O4AfHNgn(v(bf?h2`5WXP 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 b=document.querySelector(".rx2-menu"),n=document.querySelector(".rx2-nav");if(b&&n)b.addEventListener("click",()=>{const o=n.classList.toggle("open");b.setAttribute("aria-expanded",String(o));});document.querySelectorAll("[data-copy]").forEach(btn=>btn.addEventListener("click",async()=>{const el=document.getElementById(btn.dataset.copy);if(!el)return;try{await navigator.clipboard.writeText(el.innerText);const t=btn.textContent;btn.textContent="Copied";setTimeout(()=>btn.textContent=t,1200);}catch(e){}}));}); \ No newline at end of file diff --git a/docs/assets/javascripts/mermaid.js b/docs/assets/javascripts/mermaid.js new file mode 100644 index 0000000..09328de --- /dev/null +++ b/docs/assets/javascripts/mermaid.js @@ -0,0 +1,14 @@ +const renderRadixorDiagrams = () => { + mermaid.initialize({ + startOnLoad: false, + theme: "neutral", + flowchart: { htmlLabels: true, useMaxWidth: true }, + }); + return mermaid.run({ querySelector: ".mermaid" }); +}; + +if (typeof document$ === "undefined") { + document.addEventListener("DOMContentLoaded", renderRadixorDiagrams); +} else { + document$.subscribe(renderRadixorDiagrams); +} diff --git a/docs/assets/stylesheets/landing-v2.css b/docs/assets/stylesheets/landing-v2.css new file mode 100644 index 0000000..3dc4188 --- /dev/null +++ b/docs/assets/stylesheets/landing-v2.css @@ -0,0 +1,136 @@ + +:root{--rx2-navy:#0b1537;--rx2-text:#172554;--rx2-muted:#53627b;--rx2-line:#dce6f2;--rx2-purple:#6544ef;--rx2-blue:#1769ef;--rx2-cyan:#0a97cb;--rx2-teal:#0aa98f;--rx2-green:#079868;--rx2-red:#ef2f35;--rx2-shadow:0 12px 30px rgba(33,64,112,.085);--rx2-max:1050px} +.rx2-header{height:66px;background:#fff;border-bottom:1px solid #e2eaf3;position:relative;z-index:20}.rx2-header-inner{height:100%;width:min(var(--rx2-max),calc(100% - 42px));margin:auto;display:flex;align-items:center}.rx2-brand{display:flex;align-items:center;gap:10px;color:#11182e;text-decoration:none;font:850 1.23rem/1 system-ui,-apple-system,"Segoe UI",sans-serif;letter-spacing:.12em}.rx2-brand img{width:38px;height:38px}.rx2-nav{margin-left:auto;display:flex;gap:30px;align-items:center}.rx2-nav a{font:650 .74rem/1 system-ui,-apple-system,"Segoe UI",sans-serif;color:#11182e;text-decoration:none}.rx2-nav a:hover{color:#1769ef}.rx2-menu{display:none;margin-left:auto;width:38px;height:38px;border:1px solid var(--rx2-line);border-radius:10px;background:#fff;color:#172554} +.rx2-page{background:#fff;color:var(--rx2-navy);font-family:system-ui,-apple-system,"Segoe UI",Roboto,Arial,sans-serif;overflow:hidden}.rx2-page *{box-sizing:border-box}.rx2-page svg{display:block}.rx2-shell{width:min(var(--rx2-max),calc(100% - 42px));margin:0 auto}.rx2-hero{position:relative;padding:30px 0 18px;background:#fff}.rx2-hero:after{content:"";position:absolute;top:-55px;left:33%;width:820px;height:680px;background:url('../images/hero-lines.svg') center/contain no-repeat;opacity:.75;pointer-events:none;z-index:0} +.rx2-hero-top-grid{position:relative;z-index:1;display:grid;grid-template-columns:minmax(0,1.42fr) minmax(330px,.78fr);gap:72px;align-items:center;min-height:390px} +.rx2-hero-copy{padding:8px 0 16px 5px}.rx2-kicker{display:inline-flex;align-items:center;gap:8px;padding:6px 12px;border:1px solid #bfe8df;border-radius:999px;background:#f0fbf8;color:#087966;font-size:.68rem;font-weight:680}.rx2-kicker span{width:7px;height:7px;background:#099f7b;border-radius:50%}.rx2-hero h1{margin:22px 0 15px;max-width:650px;font-size:3.34rem;line-height:.99;letter-spacing:-.05em;font-weight:860;color:#0a1434}.rx2-hero h1>span{display:inline-block;margin-top:9px}.rx2-hero h1 b{font-weight:860;color:#6044ef}.rx2-hero h1 em{font-style:normal;font-weight:860;color:#0b9e8e}.rx2-hero-copy>p{max-width:650px;margin:0;color:#4b5b74;font-size:1rem;line-height:1.56} +.rx2-hero-actions{position:relative;padding:23px 24px 21px;border:1px solid #dce6f2;border-radius:17px;background:rgba(255,255,255,.94);box-shadow:0 13px 32px rgba(33,64,112,.07);backdrop-filter:blur(3px)}.rx2-action-eyebrow{margin-bottom:12px;color:#66748b;font-size:.61rem;font-weight:800;text-transform:uppercase;letter-spacing:.13em}.rx2-cta-row{display:grid;grid-template-columns:1fr;gap:11px;margin:0}.rx2-btn{height:52px;width:100%;padding:0 17px;border-radius:9px;color:#fff;text-decoration:none;display:inline-flex;align-items:center;gap:10px;font-size:.83rem;font-weight:740;box-shadow:0 9px 21px rgba(47,73,143,.14)}.rx2-btn b{margin-left:auto;font-size:1.25rem;font-weight:450}.rx2-java{background:linear-gradient(100deg,#5b36e8,#7553f1)}.rx2-python{background:linear-gradient(100deg,#1195ad,#08a58f)}.rx2-btn-icon{font-size:1.25rem}.rx2-python-glyph{display:grid;place-items:center;width:24px;height:24px;border-radius:8px;background:#fff;color:#138e9d;font-size:.68rem;font-weight:900} +.rx2-assurances{display:grid;grid-template-columns:1fr;gap:0;margin-top:17px;border-top:1px solid #e4ecf5;border-bottom:1px solid #e4ecf5}.rx2-assurances>div{display:grid;grid-template-columns:33px 1fr;align-items:center;gap:10px;min-height:61px;padding:8px 2px;color:#42516b;border-bottom:1px solid #edf2f7}.rx2-assurances>div:last-child{border-bottom:0}.rx2-assurances svg{width:31px;height:31px;flex:none;color:#2459ff}.rx2-assurances>div:nth-child(2) svg{color:#5d48ef}.rx2-assurances>div:nth-child(3) svg{color:#0a9c8b}.rx2-assurances span{display:flex;flex-direction:column;line-height:1.2}.rx2-assurances b{color:#1f2d49;font-size:.70rem}.rx2-assurances small{margin-top:3px;color:#66748b;font-size:.57rem} +.rx2-hero-links{display:flex;flex-wrap:wrap;gap:7px 14px;margin-top:14px}.rx2-hero-links a{color:#1769ef;text-decoration:none;font-size:.61rem;font-weight:720} +.rx2-highlight-section{position:relative;z-index:1;margin-top:17px}.rx2-highlight-card{border:1px solid #dce6f2;border-radius:17px;background:rgba(255,255,255,.98);box-shadow:0 12px 30px rgba(33,64,112,.075);padding:21px 24px 16px}.rx2-highlight-card h2{margin:0 0 14px;font-size:1.16rem;letter-spacing:-.025em} +.rx2-highlight-stats{display:grid;grid-template-columns:repeat(4,1fr);border:1px solid #e5edf6;border-radius:13px;overflow:hidden;background:#fbfdff}.rx2-highlight-stat{min-height:116px;padding:13px 10px 11px;text-align:center;border-right:1px solid #e5edf6;display:flex;flex-direction:column;align-items:center;justify-content:center}.rx2-highlight-stat:last-child{border-right:0}.rx2-highlight-stat i{display:block;width:34px;height:34px;margin-bottom:5px}.rx2-highlight-stat i svg{width:100%;height:100%}.rx2-highlight-stat strong{font-size:1.9rem;line-height:1.1;letter-spacing:-.04em}.rx2-highlight-stat span{margin-top:5px;color:#4b5b74;font-size:.65rem;line-height:1.26}.rx2-highlight-stat.purple{color:#663df0}.rx2-highlight-stat.teal{color:#0aa18c}.rx2-highlight-stat.blue{color:#155ff7} +.rx2-chart-title{margin-top:20px;color:#11182e;font-size:.93rem}.rx2-chart-title span{font-weight:600;font-size:.74rem}.rx2-throughput{margin-top:3px;color:#3d4a64;font-size:.79rem}.rx2-throughput b{font-size:1.72rem;background:linear-gradient(90deg,#155ff7,#0aa18c);color:transparent;background-clip:text;-webkit-background-clip:text}.rx2-chart-wrap{display:grid;grid-template-columns:34px 1fr;gap:7px;margin-top:9px}.rx2-ylabels{display:flex;flex-direction:column;justify-content:space-between;height:178px;padding:2px 0 13px;color:#66748b;font-size:.62rem}.rx2-chart{height:178px;display:flex;align-items:flex-end;gap:6px;padding:6px 2px 13px;border-bottom:1px solid #cad6e4;background:repeating-linear-gradient(to top,transparent 0 47px,rgba(209,220,233,.55) 48px 49px)}.rx2-bar{flex:1;min-width:8px;height:var(--h);border-radius:3px 3px 0 0;background:linear-gradient(180deg,#08bf9e 0%,#0ca8c0 45%,#2465ed 79%,#543cf0 100%)}.rx2-chart-range{display:flex;justify-content:space-between;padding:7px 3px 0 41px}.rx2-chart-range span{display:flex;flex-direction:column;color:#202b43;font-size:.73rem}.rx2-chart-range span:last-child{text-align:right}.rx2-chart-range small{font-size:.61rem;color:#526177}.rx2-note{border-top:1px solid #e5edf6;margin:12px 0 0;padding:11px 2px 0;color:#40506b;font-size:.64rem;line-height:1.42}.rx2-note b{color:#284df5} +.rx2-java-banner{margin-top:0;border:1px solid #d9e4f1;border-radius:15px;background:linear-gradient(90deg,#fbfdff,#fff);box-shadow:0 7px 18px rgba(35,73,129,.045);display:grid;grid-template-columns:150px 1.25fr 200px .95fr;min-height:160px;align-items:center;overflow:hidden}.rx2-java-banner>img{width:132px;height:125px;margin:auto}.rx2-java-copy{padding:12px 12px 12px 0}.rx2-java-copy h2{margin:0;font-size:1.65rem;line-height:1.04;letter-spacing:-.03em}.rx2-java-copy p{margin:10px 0 0;color:#4d5b72;font-size:.75rem;line-height:1.4}.rx2-win-box{height:116px;background:linear-gradient(145deg,#7b64f5,#5d42e6);color:#fff;border-radius:9px;display:flex;flex-direction:column;align-items:center;justify-content:center;box-shadow:0 12px 25px rgba(82,61,214,.17)}.rx2-win-box strong{font-size:2.52rem;line-height:1}.rx2-win-box span{font-size:.94rem;margin-top:4px}.rx2-win-box small{font-size:.61rem;margin-top:2px}.rx2-verify{padding:10px 18px;border-left:1px solid #e5edf6;color:#16213e;font-size:.69rem}.rx2-verify p{color:#53617a;line-height:1.42}.rx2-verify a{color:#1769ef;font-weight:700;text-decoration:none}.rx2-case-heading{text-align:center;font-size:.96rem;margin:16px 0 6px;color:#0d1734}.rx2-case-table{border:1px solid #dce6f2;border-radius:14px;overflow:hidden;background:#fff;box-shadow:0 5px 15px rgba(30,60,100,.035)}.rx2-case-head,.rx2-case-row{display:grid;grid-template-columns:1.05fr 1.15fr 1.15fr 1.3fr;align-items:center}.rx2-case-head{height:43px;border-bottom:1px solid #dfe8f2;font-size:.75rem;font-weight:700}.rx2-case-head>span,.rx2-case-row>span,.rx2-case-row>strong,.rx2-case-row>b{height:100%;display:flex;align-items:center;padding:7px 18px;border-left:1px solid #e6edf6}.rx2-case-head>span:first-child,.rx2-case-row>span:first-child{border-left:0}.rx2-case-head img{width:26px;height:26px;margin-right:8px}.rx2-case-head .snow{font-size:.76rem}.rx2-case-head .adv{color:#087f58}.rx2-case-head .adv svg{width:25px;height:25px;margin-right:7px}.rx2-case-row{min-height:42px;border-bottom:1px solid #e4ecf5;font-size:.75rem}.rx2-case-row:last-child{border-bottom:0}.rx2-case-row>span:first-child{display:grid;grid-template-columns:26px auto;grid-template-rows:auto auto;align-content:center;column-gap:8px}.rx2-case-row>span:first-child i{grid-row:1/3;width:24px;height:24px;color:#3154f6;font-style:normal;display:grid;place-items:center}.rx2-case-row>span:first-child i svg{width:24px;height:24px}.rx2-case-row small{font-size:.59rem;font-weight:500;color:#52617a}.rx2-case-row>strong{color:#079868;font-size:1rem}.rx2-case-row .red{color:#ef2f35;font-size:.95rem}.rx2-case-row .green{color:#078d64;font-size:.91rem;gap:5px}.rx2-case-row .green small{color:#078d64}.rx2-languages-strip{min-height:132px;margin-top:12px;border:1px solid #dce6f2;border-radius:13px;display:grid;grid-template-columns:190px 1fr;align-items:center;background:#fff;padding:10px 0}.rx2-language-label{display:flex;align-items:center;gap:12px;padding-left:20px}.rx2-language-label svg{width:38px;height:38px;color:#1769ef}.rx2-language-label strong,.rx2-language-label span{display:block}.rx2-language-label strong{font-size:1rem}.rx2-language-label span{font-size:.62rem;color:#5c6980}.rx2-language-list{display:grid;grid-template-columns:repeat(10,minmax(58px,1fr));align-items:start;gap:12px 6px;padding:7px 16px 7px 4px}.rx2-language{text-align:center;min-width:0}.rx2-language img{width:32px;height:32px;margin:auto;display:block}.rx2-language span{display:block;margin-top:4px;font-size:.51rem;line-height:1.16;color:#27334b}.rx2-code-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:10px}.rx2-code-card{border:1px solid #dce6f2;border-radius:13px;background:#fff;padding:10px 12px 9px;box-shadow:0 5px 13px rgba(26,54,99,.025)}.rx2-code-title{display:flex;align-items:center;gap:7px;height:27px}.rx2-code-title b{font-size:.94rem}.rx2-code-title button{margin-left:auto;border:1px solid #dbe5ef;background:#fff;color:#657188;border-radius:6px;font-size:.55rem;padding:3px 7px}.java-symbol{color:#e24924;font-size:1.2rem}.python-symbol{width:22px;height:22px;border-radius:6px;background:linear-gradient(135deg,#1769ef,#0aa98f);color:#fff;display:grid;place-items:center;font-size:.58rem;font-weight:900}.rx2-code-card pre{margin:5px 0 4px;min-height:124px;border:1px solid #e0e8f2;border-radius:7px;background:#f8fbff;padding:10px 11px;overflow:auto;font:500 .57rem/1.48 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#203359;white-space:pre}.rx2-code-card a{font-size:.59rem;color:#1769ef;text-decoration:none;font-weight:700}.rx2-code-card .kw{color:#9a38a9}.rx2-code-card .type{color:#0b699a}.rx2-code-card .str{color:#168550}.rx2-benefits{min-height:78px;margin-top:10px;border:1px solid #dce6f2;border-radius:13px;background:#fff;display:grid;grid-template-columns:repeat(5,1fr);align-items:center;padding:7px 0}.rx2-benefits>div{min-height:57px;display:flex;gap:9px;align-items:flex-start;padding:8px 13px;border-right:1px solid #e4ecf5}.rx2-benefits>div:last-child{border-right:0}.rx2-benefits svg{width:28px;height:28px;flex:none;color:#3155f4}.rx2-benefits>div:nth-child(2) svg{color:#7752f5}.rx2-benefits>div:nth-child(3) svg{color:#2567ef}.rx2-benefits>div:nth-child(4) svg{color:#1467ee}.rx2-benefits>div:nth-child(5) svg{color:#5745ed}.rx2-benefits p{margin:0}.rx2-benefits b{display:block;font-size:.64rem}.rx2-benefits span{display:block;margin-top:3px;color:#657188;font-size:.52rem;line-height:1.35}.rx2-final-card{min-height:89px;margin:10px 0 24px;border:1px solid #dce6f2;border-radius:13px;background:linear-gradient(95deg,#fbfdff,#f4f8ff 55%,#f2fffc);display:grid;grid-template-columns:62px 1fr auto auto;gap:15px;align-items:center;padding:12px 18px}.rx2-final-card>img{width:49px;height:49px}.rx2-final-card h2{margin:0;font-size:1.08rem}.rx2-final-card p{margin:4px 0 0;color:#5a6780;font-size:.62rem}.rx2-final-card a{text-decoration:none;border-radius:7px;height:42px;padding:0 20px;display:flex;align-items:center;font-size:.71rem;font-weight:700}.rx2-gh{border:1px solid #cad7e6;color:#14203b;background:#fff}.rx2-doc{background:#0e5be8;color:#fff}.rx2-footer{height:62px;border-top:1px solid #e1e9f2;background:#fbfdff;color:#637086;font:550 .58rem/1 system-ui,-apple-system,"Segoe UI",sans-serif}.rx2-footer>.rx2-shell{height:100%;display:flex;align-items:center;justify-content:space-between}.rx2-footer a{color:#53617a;text-decoration:none;font-weight:650} +@media(max-width:900px){.rx2-header-inner,.rx2-shell{width:min(100% - 26px,var(--rx2-max))}.rx2-menu{display:block}.rx2-nav{display:none;position:absolute;top:61px;left:13px;right:13px;background:#fff;border:1px solid var(--rx2-line);border-radius:12px;box-shadow:var(--rx2-shadow);padding:16px;flex-direction:column;align-items:flex-start;gap:15px}.rx2-nav.open{display:flex}.rx2-hero-grid{grid-template-columns:1fr}.rx2-hero-copy{padding-top:0}.rx2-hero h1{max-width:670px}.rx2-highlight-card{max-width:620px}.rx2-java-banner{grid-template-columns:110px 1fr 170px}.rx2-verify{grid-column:1/-1;border-left:0;border-top:1px solid #e5edf6}.rx2-language-list{grid-template-columns:repeat(5,minmax(70px,1fr));padding:10px 14px}.rx2-languages-strip{grid-template-columns:170px 1fr}.rx2-benefits{grid-template-columns:repeat(2,1fr)}.rx2-benefits>div{border-bottom:1px solid #e4ecf5}.rx2-final-card{grid-template-columns:52px 1fr}.rx2-final-card a{grid-row:2}} +@media(max-width:650px){.rx2-hero{padding-top:16px}.rx2-hero h1{font-size:2.35rem}.rx2-cta-row{flex-direction:column}.rx2-btn{width:100%}.rx2-assurances{grid-template-columns:1fr}.rx2-highlight-stats{grid-template-columns:repeat(2,1fr)}.rx2-highlight-stat{border-bottom:1px solid #e5edf6}.rx2-java-banner{grid-template-columns:1fr;padding:14px}.rx2-java-banner>img{width:104px}.rx2-win-box{width:100%}.rx2-verify{padding:15px 0 4px}.rx2-case-head,.rx2-case-row{grid-template-columns:1.25fr 1fr 1fr}.rx2-case-head>*:last-child,.rx2-case-row>*:last-child{grid-column:1/-1;border-left:0;border-top:1px dashed #e5edf6}.rx2-languages-strip{grid-template-columns:1fr}.rx2-language-label{padding:14px 16px 6px}.rx2-language-list{grid-template-columns:repeat(4,minmax(62px,1fr));padding:8px 12px 14px}.rx2-code-grid{grid-template-columns:1fr}.rx2-benefits{grid-template-columns:1fr}.rx2-benefits>div{border-right:0}.rx2-final-card{grid-template-columns:1fr}.rx2-final-card a{grid-row:auto}.rx2-footer{height:auto;padding:16px 0}.rx2-footer>.rx2-shell{gap:10px;flex-direction:column;align-items:flex-start}} + + +/* Benchmark-linked language badges */ +.rx2-language { + text-decoration: none; + border-radius: 10px; + padding: 5px 2px 6px; + transition: transform 140ms ease, background-color 140ms ease, box-shadow 140ms ease; +} +.rx2-language:hover, +.rx2-language:focus-visible { + background: #f4f8ff; + box-shadow: 0 5px 16px rgba(38, 76, 145, .09); + transform: translateY(-2px); + outline: none; +} +.rx2-language:hover span, +.rx2-language:focus-visible span { + color: #145ff5; +} +.rx2-case-note { + margin: -2px 10px 13px; + color: #65738a; + font-size: .58rem; + line-height: 1.45; + text-align: right; +} +@media (max-width: 650px) { + .rx2-case-note { + margin: 4px 4px 12px; + text-align: left; + font-size: .62rem; + } +} + +/* Current Radixor mark and language-labelled performance chart. */ +.rx2-brand img, +.rx2-final-card > img, +.rx2-case-head img { + object-fit: cover; + border-radius: 22%; + box-shadow: 0 3px 10px rgba(24, 82, 176, .16); +} +.rx2-chart-wrap { + margin-top: 10px; +} +.rx2-ylabels, +.rx2-chart { + height: 206px; + padding-bottom: 38px; +} +.rx2-chart { + gap: clamp(3px, .65vw, 7px); + overflow: visible; + background: repeating-linear-gradient(to top, transparent 0 47px, rgba(209, 220, 233, .55) 48px 49px); +} +.rx2-bar { + position: relative; + min-width: 12px; +} +.rx2-bar img { + position: absolute; + left: 50%; + bottom: -32px; + width: 24px; + height: 24px; + max-width: none; + padding: 2px; + object-fit: cover; + border: 1px solid #cbd8e7; + border-radius: 50%; + background: #fff; + box-shadow: 0 2px 6px rgba(33, 64, 112, .14); + transform: translateX(-50%); +} +.rx2-bar:focus-visible { + outline: 2px solid #1769ef; + outline-offset: 2px; +} + +@media (max-width: 650px) { + .rx2-highlight-card { + padding-right: 14px; + padding-left: 14px; + } + .rx2-chart-wrap { + grid-template-columns: 27px minmax(360px, 1fr); + overflow-x: auto; + padding-bottom: 4px; + } + .rx2-ylabels { + position: sticky; + left: 0; + z-index: 2; + background: linear-gradient(90deg, #fff 80%, rgba(255, 255, 255, 0)); + } + .rx2-chart { + gap: 5px; + } + .rx2-bar { + min-width: 12px; + } + .rx2-bar img { + width: 17px; + height: 17px; + padding: 1px; + bottom: -26px; + } +} + +@media (max-width: 900px) { + .rx2-hero-top-grid { + grid-template-columns: minmax(0, 1fr); + gap: 22px; + min-height: 0; + } + .rx2-hero-copy, + .rx2-hero-copy > p { + max-width: 100%; + } +} diff --git a/docs/assets/stylesheets/radixor-docs-safety.css b/docs/assets/stylesheets/radixor-docs-safety.css new file mode 100644 index 0000000..cfd3166 --- /dev/null +++ b/docs/assets/stylesheets/radixor-docs-safety.css @@ -0,0 +1,146 @@ +/* + * Radixor documentation safety constraints. + * + * The landing page uses custom page chrome while standard documentation pages + * use Material for MkDocs. These size constraints ensure that a documentation + * logo can never inherit unconstrained intrinsic dimensions. The remaining + * rules carry the landing page's light blue/teal visual language into the + * standard Material documentation shell. + */ +:root, +[data-md-color-scheme="default"] { + --md-primary-fg-color: #ffffff; + --md-primary-fg-color--light: #ffffff; + --md-primary-fg-color--dark: #f7faff; + --md-primary-bg-color: #11182e; + --md-primary-bg-color--light: #4b5b74; + --md-accent-fg-color: #1769ef; + --md-accent-fg-color--transparent: rgba(23, 105, 239, 0.1); + --md-typeset-a-color: #0e62df; + --rx-doc-line: #dce6f2; + --rx-doc-soft: #f7faff; + --rx-doc-text: #172554; + --rx-doc-muted: #53627b; + --rx-doc-teal: #078d7a; + --rx-doc-purple: #6544ef; +} + +.md-header { + color: var(--rx-doc-text); + background: rgba(255, 255, 255, 0.97); + border-bottom: 1px solid var(--rx-doc-line); + box-shadow: 0 5px 18px rgba(33, 64, 112, 0.06); + backdrop-filter: blur(8px); +} + +.md-header__title, +.md-header__button, +.md-header__topic, +.md-source { + color: var(--rx-doc-text); +} + +.md-search__form { + background: #f3f7fc; + border: 1px solid #d8e4f0; +} + +.md-search__input, +.md-search__icon { + color: var(--rx-doc-text); +} + +.md-search__input::placeholder { + color: #69778d; +} + +.md-tabs { + color: var(--rx-doc-text); + background: #fbfdff; + border-bottom: 1px solid var(--rx-doc-line); +} + +.md-nav__link--active, +.md-nav__link:focus, +.md-nav__link:hover { + color: #145ff5; +} + +.md-nav__item--section > .md-nav__link { + color: #263653; +} + +.md-typeset h1, +.md-typeset h2, +.md-typeset h3 { + color: #0b1537; +} + +.md-typeset a:hover { + color: var(--rx-doc-purple); +} + +.md-typeset code { + border-radius: 0.22rem; + background: #f3f7fc; +} + +.md-typeset .admonition, +.md-typeset details { + border-color: #8bb6f2; + box-shadow: 0 3px 12px rgba(33, 64, 112, 0.06); +} + +.md-typeset table:not([class]) { + border-color: var(--rx-doc-line); + box-shadow: 0 2px 10px rgba(33, 64, 112, 0.04); +} + +.md-footer { + --md-footer-fg-color: var(--rx-doc-text); + --md-footer-fg-color--light: #31516f; + --md-footer-fg-color--lighter: var(--rx-doc-muted); + --md-footer-bg-color: #fbfdff; + --md-footer-bg-color--dark: #f5f9fe; + color: var(--rx-doc-muted); + background: #fbfdff; + border-top: 1px solid var(--rx-doc-line); +} + +.md-footer-meta { + color: var(--rx-doc-muted); + background: #f5f9fe; +} + +.md-footer a, +.md-footer-meta a { + color: #31516f; +} + +.md-header__button.md-logo img, +.md-header__button.md-logo svg, +.md-nav__button.md-logo img, +.md-nav__button.md-logo svg { + width: 1.6rem; + height: 1.6rem; + max-width: 1.6rem; + max-height: 1.6rem; + object-fit: cover; + border-radius: 0.38rem; + box-shadow: 0 2px 8px rgba(24, 82, 176, 0.18); +} + +.md-typeset .mermaid { + width: 100%; + max-width: 100%; + overflow-x: auto; + text-align: center; +} + +.md-typeset .mermaid svg { + display: block; + width: auto; + max-width: 100%; + height: auto; + margin-inline: auto; +} diff --git a/docs/benchmarks/data/stemming-quality.csv b/docs/benchmarks/data/stemming-quality.csv index 89675bc..e4febe8 100644 --- a/docs/benchmarks/data/stemming-quality.csv +++ b/docs/benchmarks/data/stemming-quality.csv @@ -35,8 +35,8 @@ Stemmer,Language,Dictionary model ID,Dictionary model version,Dictionary model S "ENGLISH_RADIXOR","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","374384","568441","228735","555084","13357","1355","584042","374506","311382","15","0","161561989623","15","161561989638","0.000000","0","311382","0.000000","0.999951829979","1.000000000000","0.999999999907","0.999999999907","0.999999999954","0.999961463612","0.999975914409","0.999990365625","0.999951829979","0.999975914699","0.999975914653","0.000000000093","","","","","" "ENGLISH_SNOWBALL_ORIGINAL_PORTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","PRIMARY_OUTPUT","396939","591946","250964","591946","0","1","591946","321092","284940","360538","28415","175199063592","360538","175199424130","0.000206","28415","313355","9.067990","0.441440296958","0.909320100206","0.999997942128","0.999997779945","0.954659021167","0.492078968883","0.594347503684","0.750277266077","0.422826769235","0.633569676567","0.633568843266","0.000002220055","","","","","" "ENGLISH_SNOWBALL_ORIGINAL_PORTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","299877","283312","357325","28070","161561632313","357325","161561989638","0.000221","28070","311382","9.014651","0.442234838138","0.909853491852","0.999997788310","0.999997614573","0.954925640081","0.492899966248","0.595181398691","0.751026553880","0.423671353822","0.634325556555","0.634324660984","0.000002385427","","","","","" -"ENGLISH_SNOWBALL_PORTER2","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","PRIMARY_OUTPUT","396939","591946","250964","591946","0","1","591946","318385","284971","371381","28384","175199052749","371381","175199424130","0.000212","28384","313355","9.058097","0.434174040759","0.909419029535","0.999997880238","0.999997718233","0.954708454887","0.484848557029","0.587746607996","0.746086443827","0.416176453407","0.628367833992","0.628366984426","0.000002281767","","","","","" -"ENGLISH_SNOWBALL_PORTER2","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","297220","283368","368027","28014","161561621611","368027","161561989638","0.000228","28014","311382","8.996666","0.435017155489","0.910033335260","0.999997722069","0.999997548679","0.955015528665","0.485724531207","0.588647215295","0.746914872138","0.417080138768","0.629190045142","0.629189132274","0.000002451321","","","","","" +"ENGLISH_SNOWBALL_PORTER2","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","PRIMARY_OUTPUT","396939","591946","250964","591946","0","1","591946","318389","284986","371197","28369","175199052933","371197","175199424130","0.000212","28369","313355","9.053310","0.434308721805","0.909466898566","0.999997881289","0.999997719369","0.954732389927","0.484985638615","0.587880000578","0.746191747709","0.416310229172","0.628481826499","0.628480977278","0.000002280631","","","","","" +"ENGLISH_SNOWBALL_PORTER2","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","297224","283383","367843","27999","161561621795","367843","161561989638","0.000228","27999","311382","8.991849","0.435153080497","0.910081507602","0.999997723208","0.999997549911","0.955039615405","0.485862840613","0.588781726310","0.747020963182","0.417215208510","0.629304990872","0.629304078379","0.000002450089","","","","","" "FINNISH_LUCENE_FINNISH_LIGHT_STEM_FILTER","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","ALL_WORDS","PRIMARY_OUTPUT","57027","1788784","292","1788784","0","1","1788784","439975","12317229","1508153","19148370","1599840231184","1508153","1599841739337","0.000094","19148370","31465599","60.854936","0.890914189568","0.391450644242","0.999999057311","0.999987088650","0.695724850776","0.709786610775","0.543915310644","0.440884276934","0.373546480243","0.590549687554","0.590545009664","0.000012911350","","","","","" "FINNISH_LUCENE_FINNISH_LIGHT_STEM_FILTER","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","54762","1734784","274","1734784","0","1","1734784","431848","11954192","1155011","18806691","1504704980042","1155011","1504706135053","0.000077","18806691","30760883","61.138333","0.911893118140","0.388616672675","0.999999232401","0.999986734091","0.694307952538","0.718420864905","0.544981470973","0.438999334093","0.374552942180","0.595295615141","0.595290947645","0.000013265909","","","","","" "FINNISH_RADIXOR","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","ALL_WORDS","PRIMARY_OUTPUT","57027","1788784","292","1788784","0","1","1788784","69091","30511413","804","954186","1599841738533","804","1599841739337","0.000000","954186","31465599","3.032474","0.999973649899","0.969675263452","0.999999999497","0.999999403084","0.984837631475","0.993763441201","0.984591422195","0.975587162604","0.969650487220","0.984707932542","0.984707638627","0.000000596916","","","","","" @@ -205,24 +205,26 @@ Stemmer,Language,Dictionary model ID,Dictionary model version,Dictionary model S "RUSSIAN_RADIXOR","RU_RU","ru-ru-default","1.0.0","df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","37297","758584","10","758584","0","1","758584","37282","12780071","0","255156","287711428009","0","287711428009","0.000000","255156","13035227","1.957434","1.000000000000","0.980425657336","1.000000000000","0.999999113193","0.990212828668","0.996022851412","0.990116093179","0.984278980143","0.980425657336","0.990164459742","0.990164020680","0.000000886807","","","","","" "RUSSIAN_RADIXOR","RU_RU","ru-ru-default","1.0.0","df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","37297","758584","10","749142","9442","4","768163","37306","","","","","0","287711428009","0.000000","0","13035227","0.000000","","","","","","","","","","","","","","","","","" "RUSSIAN_RADIXOR","RU_RU","ru-ru-default","1.0.0","df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","37297","758584","10","749142","9442","4","768163","37306","13035227","0","0","287711428009","0","287711428009","0.000000","0","13035227","0.000000","1.000000000000","1.000000000000","1.000000000000","1.000000000000","1.000000000000","1.000000000000","1.000000000000","1.000000000000","1.000000000000","1.000000000000","1.000000000000","0.000000000000","","","","","" -"SNOWBALL_DANISH_DIRECT","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","ALL_WORDS","PRIMARY_OUTPUT","4179","27921","32","27921","0","1","27921","5553","78545","4795","11150","389682670","4795","389687465","0.001230","11150","89695","12.431016","0.942464602832","0.875689837784","0.999987695268","0.999959092010","0.937838766526","0.928307194100","0.907851012801","0.888276938388","0.831251984337","0.908463909669","0.908443737019","0.000040907990","","","","","" -"SNOWBALL_DANISH_DIRECT","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4173","27875","32","27875","0","1","27875","5539","78440","4795","11100","388399540","4795","388404335","0.001235","11100","89540","12.396694","0.942392022587","0.876033057851","0.999987654618","0.999959085584","0.938010356234","0.928327968188","0.908001736362","0.888546539947","0.831504743732","0.908606936602","0.908586757624","0.000040914416","","","","","" +"SNOWBALL_CZECH_DIRECT","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","ALL_WORDS","PRIMARY_OUTPUT","5113","51401","2","51401","0","1","51401","10932","172114","11935","128395","1320693256","11935","1320705191","0.000904","128395","300509","42.725842","0.935153138566","0.572741581783","0.999990963161","0.999893770330","0.786366272472","0.830101137739","0.710395865923","0.620863799839","0.550863514742","0.731847721723","0.731803909891","0.000106229670","","","","","" +"SNOWBALL_CZECH_DIRECT","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","5038","50697","2","50697","0","1","50697","10817","169464","11863","128023","1284758206","11863","1284770069","0.000923","128023","297487","43.034822","0.934576759115","0.569651783103","0.999990766441","0.999891145022","0.784821274772","0.828435805807","0.707848976847","0.617906692677","0.547806691450","0.729646021901","0.729601212980","0.000108854978","","","","","" +"SNOWBALL_DANISH_DIRECT","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","ALL_WORDS","PRIMARY_OUTPUT","4179","27921","32","27921","0","1","27921","5409","79378","4816","10317","389682649","4816","389687465","0.001236","10317","89695","11.502313","0.942798774259","0.884976866046","0.999987641378","0.999961175252","0.942482253712","0.930637722143","0.912973218547","0.895966806178","0.839881072045","0.913430404878","0.913411201871","0.000038824748","","","","","" +"SNOWBALL_DANISH_DIRECT","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4173","27875","32","27875","0","1","27875","5403","79223","4816","10317","388399519","4816","388404335","0.001240","10317","89540","11.522225","0.942693273361","0.884777752960","0.999987600550","0.999961047005","0.942382676755","0.930511444787","0.912817794779","0.895784477125","0.839618042308","0.913276538697","0.913257272617","0.000038952995","","","","","" "SNOWBALL_DANISH_LUCENE_FILTER","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","ALL_WORDS","PRIMARY_OUTPUT","4179","27921","32","27921","0","1","27921","5546","78557","4961","11138","389682504","4961","389687465","0.001273","11138","89695","12.417638","0.940599631217","0.875823624505","0.999987269285","0.999958696913","0.937905446895","0.926889068757","0.907056629699","0.888055112164","0.829920977011","0.907633945058","0.907613558620","0.000041303087","","","","","" "SNOWBALL_DANISH_LUCENE_FILTER","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4173","27875","32","27875","0","1","27875","5539","78440","4795","11100","388399540","4795","388404335","0.001235","11100","89540","12.396694","0.942392022587","0.876033057851","0.999987654618","0.999959085584","0.938010356234","0.928327968188","0.908001736362","0.888546539947","0.831504743732","0.908606936602","0.908586757624","0.000040914416","","","","","" "SNOWBALL_DUTCH_DIRECT","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","ALL_WORDS","PRIMARY_OUTPUT","4992","26201","85","26201","0","1","26201","12051","29267","2987","35170","343165676","2987","343168663","0.000870","35170","64437","54.580443","0.907391331308","0.454195570868","0.999991295825","0.999888830652","0.727093433346","0.756436964017","0.605371751249","0.504599968276","0.434073920266","0.641975952605","0.641933549660","0.000111169348","","","","","" "SNOWBALL_DUTCH_DIRECT","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4796","25402","84","25402","0","1","25402","11466","29053","2987","33965","322552096","2987","322555083","0.000926","33965","63018","53.897299","0.906772784020","0.461027008156","0.999990739566","0.999885462099","0.730508873861","0.759841613575","0.611268909508","0.511294841471","0.440163623968","0.646565343716","0.646521311443","0.000114537901","","","","","" "SNOWBALL_DUTCH_LUCENE_FILTER","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","ALL_WORDS","PRIMARY_OUTPUT","4992","26201","85","26201","0","1","26201","14573","15204","759","49233","343167904","759","343168663","0.000221","49233","64437","76.404861","0.952452546514","0.235951394385","0.999997788260","0.999854349712","0.617974591322","0.592568341791","0.378208955224","0.277738198319","0.233204491073","0.474059602198","0.474021680915","0.000145650288","","","","","" "SNOWBALL_DUTCH_LUCENE_FILTER","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4796","25402","84","25402","0","1","25402","14116","14874","715","48144","322554368","715","322555083","0.000222","48144","63018","76.397220","0.954134325486","0.236027801581","0.999997783324","0.999848554685","0.618012792452","0.593185189912","0.378439579172","0.277851461363","0.233379881694","0.474554767395","0.474515425112","0.000151445315","","","","","" -"SNOWBALL_FINNISH_DIRECT","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","ALL_WORDS","PRIMARY_OUTPUT","57027","1788784","292","1788784","0","1","1788784","381483","15082807","952306","16382792","1599840787031","952306","1599841739337","0.000060","16382792","31465599","52.065724","0.940611207417","0.479342757784","0.999999404750","0.999989164705","0.739671081267","0.788799811426","0.635056038739","0.531468350160","0.465261620083","0.671472389727","0.671468317739","0.000010835295","","","","","" -"SNOWBALL_FINNISH_DIRECT","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","54762","1734784","274","1734784","0","1","1734784","372232","14663371","936765","16097512","1504705198288","936765","1504706135053","0.000062","16097512","30760883","52.331112","0.939951485038","0.476688884386","0.999999377443","0.999988679564","0.738344130915","0.786987247415","0.632573283171","0.528815026735","0.462601231486","0.669376145960","0.669371899615","0.000011320436","","","","","" +"SNOWBALL_FINNISH_DIRECT","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","ALL_WORDS","PRIMARY_OUTPUT","57027","1788784","292","1788784","0","1","1788784","381016","15095314","952479","16370285","1599840786858","952479","1599841739337","0.000060","16370285","31465599","52.025976","0.940647352567","0.479740239491","0.999999404642","0.999989172414","0.739869822067","0.789035310423","0.635413022080","0.531861528280","0.465644940456","0.671763638639","0.671759568086","0.000010827586","","","","","" +"SNOWBALL_FINNISH_DIRECT","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","54762","1734784","274","1734784","0","1","1734784","371779","14675605","936938","16085278","1504705198115","936938","1504706135053","0.000062","16085278","30760883","52.291340","0.939988123652","0.477086597287","0.999999377328","0.999988687580","0.738542987307","0.787224487482","0.632931670824","0.529208871663","0.462984663835","0.669668377186","0.669664132316","0.000011312420","","","","","" "SNOWBALL_FINNISH_LUCENE_FILTER","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","ALL_WORDS","PRIMARY_OUTPUT","57027","1788784","292","1788784","0","1","1788784","377778","15121052","1288634","16344547","1599840450703","1288634","1599841739337","0.000081","16344547","31465599","51.944179","0.921471136011","0.480558212161","0.999999194524","0.999988978388","0.740278703342","0.778598131291","0.631685095974","0.531413183368","0.461651842069","0.665447610018","0.665443363449","0.000011021612","","","","","" "SNOWBALL_FINNISH_LUCENE_FILTER","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","54762","1734784","274","1734784","0","1","1734784","372232","14663371","936765","16097512","1504705198288","936765","1504706135053","0.000062","16097512","30760883","52.331112","0.939951485038","0.476688884386","0.999999377443","0.999988679564","0.738344130915","0.786987247415","0.632573283171","0.528815026735","0.462601231486","0.669376145960","0.669371899615","0.000011320436","","","","","" "SNOWBALL_FRENCH_DIRECT","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","ALL_WORDS","PRIMARY_OUTPUT","59240","404011","2301","404011","0","1","404011","85627","3744838","1092238","1625361","81605779618","1092238","81606871856","0.001338","1625361","5370199","30.266309","0.774194575401","0.697336914330","0.999986615858","0.999966701086","0.848661765094","0.757496924470","0.733758618240","0.711462917671","0.579477680015","0.734761496202","0.734744993787","0.000033298914","","","","","" "SNOWBALL_FRENCH_DIRECT","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","57698","400712","2133","400712","0","1","400712","84526","3736871","1088903","1619380","80278407962","1088903","80279496865","0.001356","1619380","5356251","30.233460","0.774356818202","0.697665400669","0.999986436101","0.999966266576","0.848825918385","0.757698693319","0.734013322497","0.711763857056","0.579795455624","0.735011537210","0.734994818860","0.000033733424","","","","","" "SNOWBALL_FRENCH_LUCENE_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","ALL_WORDS","PRIMARY_OUTPUT","59240","404011","2301","404011","0","1","404011","85202","3742072","1097843","1628127","81605774013","1097843","81606871856","0.001345","1628127","5370199","30.317815","0.773168950281","0.696821849619","0.999986547175","0.999966598516","0.848404198397","0.756589837411","0.733012775372","0.710860736247","0.578547882033","0.734003418250","0.733986862867","0.000033401484","","","","","" "SNOWBALL_FRENCH_LUCENE_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","57698","400712","2133","400712","0","1","400712","84810","3734232","1086494","1622019","80278410371","1086494","80279496865","0.001353","1622019","5356251","30.282729","0.774620254294","0.697172705312","0.999986466109","0.999966263711","0.848579585710","0.757784104203","0.733858787339","0.711398006457","0.579602638316","0.734876927298","0.734860210439","0.000033736289","","","","","" -"SNOWBALL_GERMAN_DIRECT","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","PRIMARY_OUTPUT","54092","277266","1474","277266","0","1","277266","81649","742376","65811","602476","38436668082","65811","38436733893","0.000171","602476","1344852","44.798684","0.918569588474","0.552013158325","0.999998287810","0.999982613933","0.776005723068","0.810879063265","0.689607573295","0.599890587538","0.526260347085","0.712083211201","0.712076031428","0.000017386067","","","","","" -"SNOWBALL_GERMAN_DIRECT","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","16007","145574","228","145574","0","1","145574","37843","506459","41477","351958","10594922057","41477","10594963534","0.000391","351958","858417","41.000819","0.924303203294","0.589991810507","0.999996085215","0.999962868855","0.794993947861","0.830216831177","0.720244490537","0.635998708058","0.562798507380","0.738465517386","0.738449797528","0.000037131145","","","","","" +"SNOWBALL_GERMAN_DIRECT","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","PRIMARY_OUTPUT","54092","277266","1474","277266","0","1","277266","81641","742393","65811","602459","38436668082","65811","38436733893","0.000171","602459","1344852","44.797420","0.918571301305","0.552025799121","0.999998287810","0.999982614376","0.776012043466","0.810885586285","0.689617919831","0.599902676509","0.526272398156","0.712092028219","0.712084848565","0.000017385624","","","","","" +"SNOWBALL_GERMAN_DIRECT","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","16007","145574","228","145574","0","1","145574","37842","506464","41477","351953","10594922057","41477","10594963534","0.000391","351953","858417","41.000236","0.924303894032","0.589997635182","0.999996085215","0.999962869327","0.794996860199","0.830219583690","0.720249040429","0.636004188257","0.562804063590","0.738469438547","0.738453718829","0.000037130673","","","","","" "SNOWBALL_GERMAN_LUCENE_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","PRIMARY_OUTPUT","54092","277266","1474","277266","0","1","277266","86669","723725","142783","621127","38436591110","142783","38436733893","0.000371","621127","1344852","46.185528","0.835220217240","0.538144717783","0.999996285246","0.999980126218","0.769070501515","0.752174652309","0.654551949931","0.579358576068","0.486493662760","0.670424751999","0.670415952491","0.000019873782","","","","","" "SNOWBALL_GERMAN_LUCENE_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","16007","145574","228","145574","0","1","145574","46077","471644","34482","386773","10594929052","34482","10594963534","0.000325","386773","858417","45.056540","0.931870719939","0.549434598802","0.999996745435","0.999960243292","0.774715672118","0.817996747049","0.691284921032","0.598564290417","0.528216517210","0.715543160924","0.715527026594","0.000039756708","","","","","" "SNOWBALL_HUNGARIAN_DIRECT","HU_HU","hu-hu-default","1.0.0","359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e","ALL_WORDS","PRIMARY_OUTPUT","19406","910688","1","910688","0","1","910688","116105","14275129","1281527","7842726","414652461946","1281527","414653743473","0.000309","7842726","22117855","35.458800","0.917621949087","0.645411998587","0.999996909404","0.999977996662","0.822704453996","0.846239680964","0.757813631609","0.686119053091","0.610064359819","0.769574048489","0.769564274829","0.000022003338","","","","","" @@ -241,6 +243,10 @@ Stemmer,Language,Dictionary model ID,Dictionary model version,Dictionary model S "SNOWBALL_NORWEGIAN_NYNORSK_DIRECT","NN_NO","nn-no-default","1.0.0","900cf2005605aea2a3d8d731ec0b0c1f47fb4469b4ba6b9134145d4d026a0398","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4681","16906","23","16906","0","1","16906","6120","20847","1201","7458","142868459","1201","142869660","0.000841","7458","28305","26.348702","0.945527939042","0.736512983572","0.999991593737","0.999939404316","0.868252288654","0.894744070663","0.828034079399","0.770581364403","0.706534264217","0.834502009245","0.834474211808","0.000060595684","","","","","" "SNOWBALL_NORWEGIAN_NYNORSK_LUCENE_FILTER","NN_NO","nn-no-default","1.0.0","900cf2005605aea2a3d8d731ec0b0c1f47fb4469b4ba6b9134145d4d026a0398","ALL_WORDS","PRIMARY_OUTPUT","4688","16937","23","16937","0","1","16937","6144","20854","1222","7508","143392932","1222","143394154","0.000852","7508","28362","26.472040","0.944645769161","0.735279599464","0.999991478035","0.999939130896","0.867635538749","0.893747964274","0.826916213966","0.769384020542","0.704908058410","0.833413920441","0.833385994629","0.000060869104","","","","","" "SNOWBALL_NORWEGIAN_NYNORSK_LUCENE_FILTER","NN_NO","nn-no-default","1.0.0","900cf2005605aea2a3d8d731ec0b0c1f47fb4469b4ba6b9134145d4d026a0398","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4681","16906","23","16906","0","1","16906","6130","20824","1201","7481","142868459","1201","142869660","0.000841","7481","28305","26.429959","0.945471055619","0.735700406289","0.999991593737","0.999939243362","0.867846000013","0.894463296250","0.827498509835","0.769862102111","0.705754761743","0.834016450529","0.833988591624","0.000060756638","","","","","" +"SNOWBALL_PERSIAN_DIRECT","FA_IR","fa-ir-default","1.0.0","b29a0d168a6a97f980666aa40b74a0edd8b6be4ab3320a7abfbb76b3529f4ea1","ALL_WORDS","PRIMARY_OUTPUT","69","3544","0","3544","0","1","3544","2029","6748","79","89296","6182073","79","6182152","0.001278","89296","96044","92.974054","0.988428299399","0.070259464412","0.999987221278","0.985764222716","0.535123342845","0.273526169012","0.131193436440","0.086290898024","0.070201720712","0.263526930171","0.261598150185","0.014235777284","","","","","" +"SNOWBALL_PERSIAN_DIRECT","FA_IR","fa-ir-default","1.0.0","b29a0d168a6a97f980666aa40b74a0edd8b6be4ab3320a7abfbb76b3529f4ea1","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","69","3544","0","3544","0","1","3544","2029","6748","79","89296","6182073","79","6182152","0.001278","89296","96044","92.974054","0.988428299399","0.070259464412","0.999987221278","0.985764222716","0.535123342845","0.273526169012","0.131193436440","0.086290898024","0.070201720712","0.263526930171","0.261598150185","0.014235777284","","","","","" +"SNOWBALL_POLISH_DIRECT","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","ALL_WORDS","PRIMARY_OUTPUT","9990","120867","1","120867","0","1","120867","19265","723037","70656","394036","7303167682","70656","7303238338","0.000967","394036","1117073","35.273970","0.910978174181","0.647260295433","0.999990325388","0.999936381518","0.823625310410","0.842338201869","0.756803292502","0.687038256475","0.608755869394","0.767880200391","0.767851723211","0.000063618482","","","","","" +"SNOWBALL_POLISH_DIRECT","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","9846","119451","1","119451","0","1","119451","18927","718592","70647","392165","7133029571","70647","7133100218","0.000990","392165","1110757","35.306102","0.910487190826","0.646938979453","0.999990095891","0.999935127795","0.823464537672","0.841893538764","0.756414224030","0.686692785364","0.608252553741","0.767482673444","0.767453627968","0.000064872205","","","","","" "SNOWBALL_PORTUGUESE_DIRECT","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","ALL_WORDS","PRIMARY_OUTPUT","4001","211091","0","211091","0","1","211091","11315","4816198","146201","670154","22273967042","146201","22274113243","0.000656","670154","5486352","12.214929","0.970538241685","0.877850710272","0.999993436282","0.999963358632","0.938922073277","0.950467296507","0.921870566157","0.894944355740","0.855064834721","0.923031789707","0.923014032222","0.000036641368","","","","","" "SNOWBALL_PORTUGUESE_DIRECT","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4001","211091","0","211091","0","1","211091","11315","4816198","146201","670154","22273967042","146201","22274113243","0.000656","670154","5486352","12.214929","0.970538241685","0.877850710272","0.999993436282","0.999963358632","0.938922073277","0.950467296507","0.921870566157","0.894944355740","0.855064834721","0.923031789707","0.923014032222","0.000036641368","","","","","" "SNOWBALL_PORTUGUESE_LUCENE_FILTER","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","ALL_WORDS","PRIMARY_OUTPUT","4001","211091","0","211091","0","1","211091","11315","4816198","146201","670154","22273967042","146201","22274113243","0.000656","670154","5486352","12.214929","0.970538241685","0.877850710272","0.999993436282","0.999963358632","0.938922073277","0.950467296507","0.921870566157","0.894944355740","0.855064834721","0.923031789707","0.923014032222","0.000036641368","","","","","" diff --git a/docs/benchmarks/data/stemming-quality.sha256 b/docs/benchmarks/data/stemming-quality.sha256 index 731eca6..3e853bb 100644 --- a/docs/benchmarks/data/stemming-quality.sha256 +++ b/docs/benchmarks/data/stemming-quality.sha256 @@ -1 +1 @@ -edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8 stemming-quality.csv +d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5 stemming-quality.csv diff --git a/docs/benchmarks/index.md b/docs/benchmarks/index.md index 5b5ed44..f035e62 100644 --- a/docs/benchmarks/index.md +++ b/docs/benchmarks/index.md @@ -52,8 +52,8 @@ Open [Language Benchmark Pages](languages/index.md) for the complete language li The English dictionary coverage benchmark shows the current contracted-trie operating curve. With the full English dictionary, Radixor reaches `97.478%` all-token exactness and `97.197%` -changed-token exactness at `98.0 ns/token`. Even with a deterministic 10% dictionary slice, it -keeps `92.868%` all-token exactness and `76.516%` changed-token exactness at `80.6 ns/token`. +changed-token exactness at `71.6 ns/token`. Even with a deterministic 10% dictionary slice, it +keeps `92.868%` all-token exactness and `76.516%` changed-token exactness at `47.0 ns/token`. Those figures should not be reduced to a single speed badge. The professional interpretation is a quality/speed envelope: the amount and quality of dictionary knowledge affect stemming precision, @@ -61,7 +61,17 @@ while contracted tries reduce lookup cost in uniform regions of the compiled gra ## Quality versus performance -Each language page keeps exact-root accuracy, JMH latency, and pairwise linguistic-quality results in separate tables. No undocumented scalar combines them. The 2026-07-23 language tables are generated from the unrounded JMH comparison report produced on the environment documented for this refresh. Readers should inspect the quality and speed dimensions side by side; no cross-language Pareto ranking is inferred from workloads with different dictionaries and token counts. +Each language page keeps exact-root accuracy, JMH latency, and pairwise linguistic-quality results in separate tables. No undocumented scalar combines them. The 2026-08-10 language tables are generated exclusively from the current unrounded JMH comparison report produced on the environment documented for this refresh. The Snowball 3.1.0 matrix adds direct Czech, Persian, and Polish stemmers; all previously published Java stemmers were measured again in the same run. Readers should inspect the quality and speed dimensions side by side; no cross-language Pareto ranking is inferred from workloads with different dictionaries and token counts. + +### New Snowball 3.1.0 rows + +| New direct stemmer | All exact | Changed exact | Root preserved | Speed | Relative to same-language Radixor | +| --- | ---: | ---: | ---: | ---: | ---: | +| Czech | 19.865% | 18.186% | 27.645% | 82.4 ns/token | 1.187× | +| Persian | 3.660% | 0.000% | 100.000% | 298.1 ns/token | 6.486× | +| Polish | 22.315% | 20.225% | 34.078% | 86.5 ns/token | 1.196× | + +These rows describe exact agreement with each Radixor model dictionary and the measured direct API workload; they are not a universal linguistic ranking. In this dataset the three new Snowball stemmers are both less exact and slower than their same-language Radixor baseline. Lucene 10.5.0 does not expose the three new algorithms through `SnowballFilter`, so no synthetic Lucene wrapper rows were added. @@ -76,16 +86,16 @@ The validated snapshot is a broad multilingual comparison covering the complete | Language | Dictionary mode | Winner | Balanced accuracy | Runner-up | Difference | Exact tie | Deterministic stemmers | |---|---|---|---:|---|---:|---|---:| -|Czech (`CS_CZ`)|ALL_WORDS|Radixor|0.996617|HUNSPELL CZECH LUCENE FILTER|0.142485045|no|3| -|Czech (`CS_CZ`)|LOWERCASE_GROUPS_ONLY|Radixor|0.997195|HUNSPELL CZECH LUCENE FILTER|0.144045088|no|3| -|Danish (`DA_DK`)|ALL_WORDS|Radixor|0.996243|SNOWBALL DANISH LUCENE FILTER|0.058337376|no|3| -|Danish (`DA_DK`)|LOWERCASE_GROUPS_ONLY|Radixor|0.996482|SNOWBALL DANISH DIRECT|0.058471663|no|3| +|Czech (`CS_CZ`)|ALL_WORDS|Radixor|0.996617|HUNSPELL CZECH LUCENE FILTER|0.142485045|no|4| +|Czech (`CS_CZ`)|LOWERCASE_GROUPS_ONLY|Radixor|0.997195|HUNSPELL CZECH LUCENE FILTER|0.144045088|no|4| +|Danish (`DA_DK`)|ALL_WORDS|Radixor|0.996243|SNOWBALL DANISH DIRECT|0.053760569|no|3| +|Danish (`DA_DK`)|LOWERCASE_GROUPS_ONLY|Radixor|0.996482|SNOWBALL DANISH DIRECT|0.054099342|no|3| |Dutch (`NL_NL`)|ALL_WORDS|Radixor|0.988733|SNOWBALL DUTCH DIRECT|0.261639748|no|4| |Dutch (`NL_NL`)|LOWERCASE_GROUPS_ONLY|Radixor|0.989114|SNOWBALL DUTCH DIRECT|0.258605347|no|4| |English (`US_UK`)|ALL_WORDS|Radixor|0.965537|ENGLISH LUCENE PORTER COPIED|0.010741250|no|11| |English (`US_UK`)|LOWERCASE_GROUPS_ONLY|Radixor|0.966202|ENGLISH LUCENE PORTER COPIED|0.011138557|no|11| |Finnish (`FI_FI`)|ALL_WORDS|Radixor|0.984838|SNOWBALL FINNISH LUCENE FILTER|0.244558928|no|4| -|Finnish (`FI_FI`)|LOWERCASE_GROUPS_ONLY|Radixor|0.988242|SNOWBALL FINNISH DIRECT|0.249897933|no|4| +|Finnish (`FI_FI`)|LOWERCASE_GROUPS_ONLY|Radixor|0.988242|SNOWBALL FINNISH DIRECT|0.249699076|no|4| |French (`FR_FR`)|ALL_WORDS|Radixor|0.958627|SNOWBALL FRENCH DIRECT|0.109964908|no|6| |French (`FR_FR`)|LOWERCASE_GROUPS_ONLY|Radixor|0.958856|SNOWBALL FRENCH DIRECT|0.110030565|no|6| |German (`DE_DE`)|ALL_WORDS|Radixor|0.910445|GERMAN CISTEM|0.031918024|no|8| @@ -100,10 +110,10 @@ The validated snapshot is a broad multilingual comparison covering the complete |Norwegian Bokmal (`NB_NO`)|LOWERCASE_GROUPS_ONLY|Radixor|0.976240|SNOWBALL NORWEGIAN BOKMAL DIRECT|0.101954266|no|5| |Norwegian Nynorsk (`NN_NO`)|ALL_WORDS|Radixor|0.950991|SNOWBALL NORWEGIAN NYNORSK DIRECT|0.082896791|no|3| |Norwegian Nynorsk (`NN_NO`)|LOWERCASE_GROUPS_ONLY|Radixor|0.951104|SNOWBALL NORWEGIAN NYNORSK DIRECT|0.082851757|no|3| -|Persian (`FA_IR`)|ALL_WORDS|Radixor|0.976360|PERSIAN LUCENE PERSIAN STEM FILTER|0.474147508|no|2| -|Persian (`FA_IR`)|LOWERCASE_GROUPS_ONLY|Radixor|0.976360|PERSIAN LUCENE PERSIAN STEM FILTER|0.474147508|no|2| -|Polish (`PL_PL`)|ALL_WORDS|Radixor|0.991105|POLISH LUCENE MORFOLOGIK FILTER|0.042712804|no|5| -|Polish (`PL_PL`)|LOWERCASE_GROUPS_ONLY|Radixor|0.991301|POLISH LUCENE MORFOLOGIK FILTER|0.042883749|no|5| +|Persian (`FA_IR`)|ALL_WORDS|Radixor|0.976360|SNOWBALL PERSIAN DIRECT|0.441236451|no|3| +|Persian (`FA_IR`)|LOWERCASE_GROUPS_ONLY|Radixor|0.976360|SNOWBALL PERSIAN DIRECT|0.441236451|no|3| +|Polish (`PL_PL`)|ALL_WORDS|Radixor|0.991105|POLISH LUCENE MORFOLOGIK FILTER|0.042712804|no|6| +|Polish (`PL_PL`)|LOWERCASE_GROUPS_ONLY|Radixor|0.991301|POLISH LUCENE MORFOLOGIK FILTER|0.042883749|no|6| |Portuguese (`PT_PT`)|ALL_WORDS|Radixor|0.998542|SNOWBALL PORTUGUESE DIRECT|0.059619854|no|6| |Portuguese (`PT_PT`)|LOWERCASE_GROUPS_ONLY|Radixor|0.998542|SNOWBALL PORTUGUESE DIRECT|0.059619854|no|6| |Russian (`RU_RU`)|ALL_WORDS|Radixor|0.990188|SNOWBALL RUSSIAN LUCENE FILTER|0.155623602|no|4| @@ -166,7 +176,7 @@ Counts use `PRIMARY_OUTPUT` only and retain each adapter configuration as a sepa |ITALIAN LUCENE ITALIAN LIGHT STEM FILTER|1|0|0|0|4.000|4.000| |NORWEGIAN BOKMAL LUCENE NORWEGIAN LIGHT STEM FILTER|1|0|0|0|4.000|4.000| |NORWEGIAN BOKMAL LUCENE NORWEGIAN MINIMAL STEM FILTER|1|0|0|0|5.000|5.000| -|PERSIAN LUCENE PERSIAN STEM FILTER|1|0|0|1|2.000|2.000| +|PERSIAN LUCENE PERSIAN STEM FILTER|1|0|0|1|3.000|3.000| |POLISH LUCENE MORFOLOGIK FILTER|1|0|0|1|2.000|2.000| |POLISH LUCENE STEMPEL DIRECT|1|0|0|0|4.000|4.000| |POLISH LUCENE STEMPEL FILTER|1|0|0|0|5.000|5.000| @@ -174,8 +184,9 @@ Counts use `PRIMARY_OUTPUT` only and retain each adapter configuration as a sepa |PORTUGUESE LUCENE PORTUGUESE MINIMAL STEM FILTER|1|0|0|0|6.000|6.000| |PORTUGUESE LUCENE PORTUGUESE STEM FILTER|1|0|0|0|4.000|4.000| |RUSSIAN LUCENE RUSSIAN LIGHT STEM FILTER|1|0|0|0|4.000|4.000| -|SNOWBALL DANISH DIRECT|1|0|0|1|3.000|3.000| -|SNOWBALL DANISH LUCENE FILTER|1|0|0|1|2.000|2.000| +|SNOWBALL CZECH DIRECT|1|0|0|0|4.000|4.000| +|SNOWBALL DANISH DIRECT|1|0|0|1|2.000|2.000| +|SNOWBALL DANISH LUCENE FILTER|1|0|0|1|3.000|3.000| |SNOWBALL DUTCH DIRECT|1|0|0|1|2.000|2.000| |SNOWBALL DUTCH LUCENE FILTER|1|0|0|0|4.000|4.000| |SNOWBALL FINNISH DIRECT|1|0|0|1|3.000|3.000| @@ -192,6 +203,8 @@ Counts use `PRIMARY_OUTPUT` only and retain each adapter configuration as a sepa |SNOWBALL NORWEGIAN BOKMAL LUCENE FILTER|1|0|0|1|3.000|3.000| |SNOWBALL NORWEGIAN NYNORSK DIRECT|1|0|0|1|2.000|2.000| |SNOWBALL NORWEGIAN NYNORSK LUCENE FILTER|1|0|0|1|3.000|3.000| +|SNOWBALL PERSIAN DIRECT|1|0|0|1|2.000|2.000| +|SNOWBALL POLISH DIRECT|1|0|0|0|6.000|6.000| |SNOWBALL PORTUGUESE DIRECT|1|0|0|1|2.000|2.000| |SNOWBALL PORTUGUESE LUCENE FILTER|1|0|0|1|3.000|3.000| |SNOWBALL RUSSIAN DIRECT|1|0|0|1|3.000|3.000| @@ -246,7 +259,7 @@ Counts use `PRIMARY_OUTPUT` only and retain each adapter configuration as a sepa |ITALIAN LUCENE ITALIAN LIGHT STEM FILTER|1|0|0|0|4.000|4.000| |NORWEGIAN BOKMAL LUCENE NORWEGIAN LIGHT STEM FILTER|1|0|0|0|4.000|4.000| |NORWEGIAN BOKMAL LUCENE NORWEGIAN MINIMAL STEM FILTER|1|0|0|0|5.000|5.000| -|PERSIAN LUCENE PERSIAN STEM FILTER|1|0|0|1|2.000|2.000| +|PERSIAN LUCENE PERSIAN STEM FILTER|1|0|0|1|3.000|3.000| |POLISH LUCENE MORFOLOGIK FILTER|1|0|0|1|2.000|2.000| |POLISH LUCENE STEMPEL DIRECT|1|0|0|0|4.000|4.000| |POLISH LUCENE STEMPEL FILTER|1|0|0|0|5.000|5.000| @@ -254,6 +267,7 @@ Counts use `PRIMARY_OUTPUT` only and retain each adapter configuration as a sepa |PORTUGUESE LUCENE PORTUGUESE MINIMAL STEM FILTER|1|0|0|0|6.000|6.000| |PORTUGUESE LUCENE PORTUGUESE STEM FILTER|1|0|0|0|4.000|4.000| |RUSSIAN LUCENE RUSSIAN LIGHT STEM FILTER|1|0|0|0|4.000|4.000| +|SNOWBALL CZECH DIRECT|1|0|0|0|4.000|4.000| |SNOWBALL DANISH DIRECT|1|0|0|1|2.000|2.000| |SNOWBALL DANISH LUCENE FILTER|1|0|0|1|3.000|3.000| |SNOWBALL DUTCH DIRECT|1|0|0|1|2.000|2.000| @@ -272,6 +286,8 @@ Counts use `PRIMARY_OUTPUT` only and retain each adapter configuration as a sepa |SNOWBALL NORWEGIAN BOKMAL LUCENE FILTER|1|0|0|1|3.000|3.000| |SNOWBALL NORWEGIAN NYNORSK DIRECT|1|0|0|1|2.000|2.000| |SNOWBALL NORWEGIAN NYNORSK LUCENE FILTER|1|0|0|1|3.000|3.000| +|SNOWBALL PERSIAN DIRECT|1|0|0|1|2.000|2.000| +|SNOWBALL POLISH DIRECT|1|0|0|0|6.000|6.000| |SNOWBALL PORTUGUESE DIRECT|1|0|0|1|2.000|2.000| |SNOWBALL PORTUGUESE LUCENE FILTER|1|0|0|1|3.000|3.000| |SNOWBALL RUSSIAN DIRECT|1|0|0|1|2.000|2.000| @@ -305,7 +321,7 @@ These aggregates cover all 20 documented languages. Macro balanced accuracy give ### Reproducible data - [Machine-readable quality snapshot](data/stemming-quality.csv) -- SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - [Linguistic quality methodology](reference/linguistic-quality.md) - [Tested stemmer inventory](reference/tested-stemmers.md) - [Reproducibility and raw data](reference/reproducibility.md) diff --git a/docs/benchmarks/languages/czech.md b/docs/benchmarks/languages/czech.md index 26be2fd..169d128 100644 --- a/docs/benchmarks/languages/czech.md +++ b/docs/benchmarks/languages/czech.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `cs-cz-default` | `1.0.0` | `CS_CZ` | 5,113 | 56,612 | 10,049 | 46,563 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `cs-cz-default` | `1.0.0` | `CS_CZ` | 5,113 | 56,612 | 10,049 | 46,563 | 46,563 | ## Radixor Patch Command Distribution @@ -30,14 +30,10 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 99.465% | 99.439% | 99.582% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 99.465% | 99.439% | 99.582% | Radixor dictionary-trained patch-command stemmer. | | Lucene HunspellStemFilter | 84.850% | 82.269% | 96.806% | Benchmark-only Czech Hunspell dictionary compared via Lucene HunspellStemFilter. | | Lucene CzechStemFilter | 16.784% | 15.538% | 22.559% | Lucene Czech suffix stemmer implemented as a TokenFilter. | - - - - - +| Official Snowball direct | 19.865% | 18.186% | 27.645% | Official Snowball 3.1.0 generated Java stemmer; rule-based suffix algorithm. | ## Speed @@ -45,18 +41,14 @@ Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 i | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `czechRadixor` | 3.395 | 0.066 | 72.9 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 381.189 | 32.563 | 8186.5 | 112.265 | Benchmark-only Czech Hunspell dictionary compared via Lucene HunspellStemFilter. | -| Lucene CzechStemFilter | `czechLuceneCzechStemFilter` | 3.125 | 0.042 | 67.1 | 0.920 | Czech suffix stemmer implemented as a Lucene TokenFilter. | - - - - - +| Radixor | `czechRadixor` | 3.230 | 0.050 | 69.4 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 349.111 | 24.459 | 7497.6 | 108.091 | Benchmark-only Czech Hunspell dictionary compared via Lucene HunspellStemFilter. | +| Lucene CzechStemFilter | `czechLuceneCzechStemFilter` | 2.927 | 0.032 | 62.9 | 0.906 | Czech suffix stemmer implemented as a Lucene TokenFilter. | +| Official Snowball direct | `snowballDirect[CZECH]` | 3.835 | 0.320 | 82.4 | 1.187 | Official Snowball 3.1.0 generated Java stemmer; direct API. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -74,11 +66,11 @@ Runtime performance and linguistic grouping quality are independent dimensions. The default model is `cs-cz-default`, loaded from classpath resource `org/egothor/stemmer/models/cs-cz-default/stemmer.gz`. The following findings compare only deterministic `PRIMARY_OUTPUT` rows over identical included groups; candidate policies are reported separately as capability analyses. -- **ALL_WORDS:** `Radixor` ranks first by balanced accuracy at **0.996617** among 3 deterministic stemmers. The runner-up is `HUNSPELL CZECH LUCENE FILTER` at 0.854132, a difference of 0.142485. This rank does not imply leadership in throughput or every secondary metric. -- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.997195** among 3 deterministic stemmers. The runner-up is `HUNSPELL CZECH LUCENE FILTER` at 0.853150, a difference of 0.144045. This rank does not imply leadership in throughput or every secondary metric. +- **ALL_WORDS:** `Radixor` ranks first by balanced accuracy at **0.996617** among 4 deterministic stemmers. The runner-up is `HUNSPELL CZECH LUCENE FILTER` at 0.854132, a difference of 0.142485. This rank does not imply leadership in throughput or every secondary metric. +- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.997195** among 4 deterministic stemmers. The runner-up is `HUNSPELL CZECH LUCENE FILTER` at 0.853150, a difference of 0.144045. This rank does not imply leadership in throughput or every secondary metric. ### `ALL_WORDS` -This mode contains **7 result rows**, **3 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. +This mode contains **8 result rows**, **4 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. #### `PRIMARY_OUTPUT` ranking @@ -89,6 +81,7 @@ This mode contains **7 result rows**, **3 evaluated stemmers**, and **3 output p |1|Radixor|0.996617|0.000000%|0.676519%| |2|HUNSPELL CZECH LUCENE FILTER|0.854132|0.000691%|29.172837%| |3|CZECH LUCENE CZECH STEM FILTER|0.794343|0.000928%|41.130549%| +|4|SNOWBALL CZECH DIRECT|0.786366|0.000904%|42.725842%| @@ -99,6 +92,7 @@ This mode contains **7 result rows**, **3 evaluated stemmers**, and **3 output p |1|Radixor|PRIMARY_OUTPUT|1.000000|0.993235|1.000000|0.996617|0.999998|0.000002| |2|HUNSPELL CZECH LUCENE FILTER|PRIMARY_OUTPUT|0.958877|0.708272|0.999993|0.854132|0.999927|0.000073| |3|CZECH LUCENE CZECH STEM FILTER|PRIMARY_OUTPUT|0.935210|0.588695|0.999991|0.794343|0.999897|0.000103| +|4|SNOWBALL CZECH DIRECT|PRIMARY_OUTPUT|0.935153|0.572742|0.999991|0.786366|0.999894|0.000106| @@ -109,6 +103,7 @@ This mode contains **7 result rows**, **3 evaluated stemmers**, and **3 output p |1|Radixor|PRIMARY_OUTPUT|0.998640|0.996606|0.994581|0.993235|0.996612|0.996611| |2|HUNSPELL CZECH LUCENE FILTER|PRIMARY_OUTPUT|0.895506|0.814739|0.747335|0.687392|0.824103|0.824070| |3|CZECH LUCENE CZECH STEM FILTER|PRIMARY_OUTPUT|0.836710|0.722556|0.635811|0.565626|0.741992|0.741949| +|4|SNOWBALL CZECH DIRECT|PRIMARY_OUTPUT|0.830101|0.710396|0.620864|0.550864|0.731848|0.731804| @@ -119,6 +114,7 @@ This mode contains **7 result rows**, **3 evaluated stemmers**, and **3 output p |1|Radixor|PRIMARY_OUTPUT|298476|0|2033|1320705191|0 / 1320705191|2033 / 300509| |2|HUNSPELL CZECH LUCENE FILTER|PRIMARY_OUTPUT|212842|9128|87667|1320696063|9128 / 1320705191|87667 / 300509| |3|CZECH LUCENE CZECH STEM FILTER|PRIMARY_OUTPUT|176908|12256|123601|1320692935|12256 / 1320705191|123601 / 300509| +|4|SNOWBALL CZECH DIRECT|PRIMARY_OUTPUT|172114|11935|128395|1320693256|11935 / 1320705191|128395 / 300509| @@ -193,7 +189,7 @@ Alternative candidates are capability analyses, not replacements for the determi ### `LOWERCASE_GROUPS_ONLY` -This mode contains **7 result rows**, **3 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. +This mode contains **8 result rows**, **4 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. #### `PRIMARY_OUTPUT` ranking @@ -204,6 +200,7 @@ This mode contains **7 result rows**, **3 evaluated stemmers**, and **3 output p |1|Radixor|0.997195|0.000000%|0.561033%| |2|HUNSPELL CZECH LUCENE FILTER|0.853150|0.000700%|29.369351%| |3|CZECH LUCENE CZECH STEM FILTER|0.792522|0.000918%|41.494586%| +|4|SNOWBALL CZECH DIRECT|0.784821|0.000923%|43.034822%| @@ -214,6 +211,7 @@ This mode contains **7 result rows**, **3 evaluated stemmers**, and **3 output p |1|Radixor|PRIMARY_OUTPUT|1.000000|0.994390|1.000000|0.997195|0.999999|0.000001| |2|HUNSPELL CZECH LUCENE FILTER|PRIMARY_OUTPUT|0.958957|0.706306|0.999993|0.853150|0.999925|0.000075| |3|CZECH LUCENE CZECH STEM FILTER|PRIMARY_OUTPUT|0.936557|0.585054|0.999991|0.792522|0.999895|0.000105| +|4|SNOWBALL CZECH DIRECT|PRIMARY_OUTPUT|0.934577|0.569652|0.999991|0.784821|0.999891|0.000109| @@ -224,6 +222,7 @@ This mode contains **7 result rows**, **3 evaluated stemmers**, and **3 output p |1|Radixor|PRIMARY_OUTPUT|0.998873|0.997187|0.995507|0.994390|0.997191|0.997190| |2|HUNSPELL CZECH LUCENE FILTER|PRIMARY_OUTPUT|0.894932|0.813466|0.745594|0.685581|0.822993|0.822960| |3|CZECH LUCENE CZECH STEM FILTER|PRIMARY_OUTPUT|0.836092|0.720206|0.632534|0.562751|0.740227|0.740184| +|4|SNOWBALL CZECH DIRECT|PRIMARY_OUTPUT|0.828436|0.707849|0.617907|0.547807|0.729646|0.729601| @@ -234,6 +233,7 @@ This mode contains **7 result rows**, **3 evaluated stemmers**, and **3 output p |1|Radixor|PRIMARY_OUTPUT|295818|0|1669|1284770069|0 / 1284770069|1669 / 297487| |2|HUNSPELL CZECH LUCENE FILTER|PRIMARY_OUTPUT|210117|8993|87370|1284761076|8993 / 1284770069|87370 / 297487| |3|CZECH LUCENE CZECH STEM FILTER|PRIMARY_OUTPUT|174046|11790|123441|1284758279|11790 / 1284770069|123441 / 297487| +|4|SNOWBALL CZECH DIRECT|PRIMARY_OUTPUT|169464|11863|128023|1284758206|11863 / 1284770069|128023 / 297487| @@ -330,7 +330,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `CS_CZ` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/danish.md b/docs/benchmarks/languages/danish.md index ad74bfe..554d575 100644 --- a/docs/benchmarks/languages/danish.md +++ b/docs/benchmarks/languages/danish.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `da-dk-default` | `1.0.0` | `DA_DK` | 4,179 | 32,256 | 8,356 | 23,900 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `da-dk-default` | `1.0.0` | `DA_DK` | 4,179 | 32,256 | 8,356 | 23,900 | 23,900 | ## Radixor Patch Command Distribution @@ -30,14 +30,9 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 99.371% | 99.527% | 98.923% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 99.371% | 99.527% | 98.923% | Radixor dictionary-trained patch-command stemmer. | | Lucene SnowballFilter | 55.509% | 54.159% | 59.371% | Lucene TokenFilter integration path around the Snowball algorithm. | -| Official Snowball direct | 55.509% | 54.159% | 59.371% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | - - - - - +| Official Snowball direct | 55.971% | 54.791% | 59.347% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | ## Speed @@ -45,18 +40,13 @@ Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 i | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `radixor[DANISH]` | 1.206 | 0.134 | 50.5 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Official Snowball direct | `snowballDirect[DANISH]` | 2.326 | 0.205 | 97.3 | 1.928 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[DANISH]` | 3.275 | 0.335 | 137.0 | 2.716 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | - - - - - +| Radixor | `radixor[DANISH]` | 1.146 | 0.122 | 47.9 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Official Snowball direct | `snowballDirect[DANISH]` | 2.542 | 0.179 | 106.4 | 2.219 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[DANISH]` | 2.879 | 0.239 | 120.4 | 2.512 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -74,8 +64,8 @@ Runtime performance and linguistic grouping quality are independent dimensions. The default model is `da-dk-default`, loaded from classpath resource `org/egothor/stemmer/models/da-dk-default/stemmer.gz`. The following findings compare only deterministic `PRIMARY_OUTPUT` rows over identical included groups; candidate policies are reported separately as capability analyses. -- **ALL_WORDS:** `Radixor` ranks first by balanced accuracy at **0.996243** among 3 deterministic stemmers. The runner-up is `SNOWBALL DANISH LUCENE FILTER` at 0.937905, a difference of 0.058337. This rank does not imply leadership in throughput or every secondary metric. -- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.996482** among 3 deterministic stemmers. The runner-up is `SNOWBALL DANISH DIRECT` at 0.938010, a difference of 0.058472. This rank does not imply leadership in throughput or every secondary metric. +- **ALL_WORDS:** `Radixor` ranks first by balanced accuracy at **0.996243** among 3 deterministic stemmers. The runner-up is `SNOWBALL DANISH DIRECT` at 0.942482, a difference of 0.053761. This rank does not imply leadership in throughput or every secondary metric. +- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.996482** among 3 deterministic stemmers. The runner-up is `SNOWBALL DANISH DIRECT` at 0.942383, a difference of 0.054099. This rank does not imply leadership in throughput or every secondary metric. ### `ALL_WORDS` This mode contains **5 result rows**, **3 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. @@ -87,8 +77,8 @@ This mode contains **5 result rows**, **3 evaluated stemmers**, and **3 output p | Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) | |---:|---|---:|---:|---:| |1|Radixor|0.996243|0.000000%|0.751435%| -|2|SNOWBALL DANISH LUCENE FILTER|0.937905|0.001273%|12.417638%| -|3|SNOWBALL DANISH DIRECT|0.937839|0.001230%|12.431016%| +|2|SNOWBALL DANISH DIRECT|0.942482|0.001236%|11.502313%| +|3|SNOWBALL DANISH LUCENE FILTER|0.937905|0.001273%|12.417638%| @@ -97,8 +87,8 @@ This mode contains **5 result rows**, **3 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|1.000000|0.992486|1.000000|0.996243|0.999998|0.000002| -|2|SNOWBALL DANISH LUCENE FILTER|PRIMARY_OUTPUT|0.940600|0.875824|0.999987|0.937905|0.999959|0.000041| -|3|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|0.942465|0.875690|0.999988|0.937839|0.999959|0.000041| +|2|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|0.942799|0.884977|0.999988|0.942482|0.999961|0.000039| +|3|SNOWBALL DANISH LUCENE FILTER|PRIMARY_OUTPUT|0.940600|0.875824|0.999987|0.937905|0.999959|0.000041| @@ -107,8 +97,8 @@ This mode contains **5 result rows**, **3 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|0.998488|0.996229|0.993979|0.992486|0.996236|0.996235| -|2|SNOWBALL DANISH LUCENE FILTER|PRIMARY_OUTPUT|0.926889|0.907057|0.888055|0.829921|0.907634|0.907614| -|3|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|0.928307|0.907851|0.888277|0.831252|0.908464|0.908444| +|2|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|0.930638|0.912973|0.895967|0.839881|0.913430|0.913411| +|3|SNOWBALL DANISH LUCENE FILTER|PRIMARY_OUTPUT|0.926889|0.907057|0.888055|0.829921|0.907634|0.907614| @@ -117,8 +107,8 @@ This mode contains **5 result rows**, **3 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|89021|0|674|389687465|0 / 389687465|674 / 89695| -|2|SNOWBALL DANISH LUCENE FILTER|PRIMARY_OUTPUT|78557|4961|11138|389682504|4961 / 389687465|11138 / 89695| -|3|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|78545|4795|11150|389682670|4795 / 389687465|11150 / 89695| +|2|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|79378|4816|10317|389682649|4816 / 389687465|10317 / 89695| +|3|SNOWBALL DANISH LUCENE FILTER|PRIMARY_OUTPUT|78557|4961|11138|389682504|4961 / 389687465|11138 / 89695| @@ -195,7 +185,7 @@ This mode contains **5 result rows**, **3 evaluated stemmers**, and **3 output p | Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) | |---:|---|---:|---:|---:| |1|Radixor|0.996482|0.000000%|0.703596%| -|2|SNOWBALL DANISH DIRECT|0.938010|0.001235%|12.396694%| +|2|SNOWBALL DANISH DIRECT|0.942383|0.001240%|11.522225%| |3|SNOWBALL DANISH LUCENE FILTER|0.938010|0.001235%|12.396694%| @@ -205,7 +195,7 @@ This mode contains **5 result rows**, **3 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|1.000000|0.992964|1.000000|0.996482|0.999998|0.000002| -|2|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|0.942392|0.876033|0.999988|0.938010|0.999959|0.000041| +|2|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|0.942693|0.884778|0.999988|0.942383|0.999961|0.000039| |3|SNOWBALL DANISH LUCENE FILTER|PRIMARY_OUTPUT|0.942392|0.876033|0.999988|0.938010|0.999959|0.000041| @@ -215,7 +205,7 @@ This mode contains **5 result rows**, **3 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|0.998585|0.996470|0.994363|0.992964|0.996476|0.996475| -|2|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|0.928328|0.908002|0.888547|0.831505|0.908607|0.908587| +|2|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|0.930511|0.912818|0.895784|0.839618|0.913277|0.913257| |3|SNOWBALL DANISH LUCENE FILTER|PRIMARY_OUTPUT|0.928328|0.908002|0.888547|0.831505|0.908607|0.908587| @@ -225,7 +215,7 @@ This mode contains **5 result rows**, **3 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|88910|0|630|388404335|0 / 388404335|630 / 89540| -|2|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|78440|4795|11100|388399540|4795 / 388404335|11100 / 89540| +|2|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|79223|4816|10317|388399519|4816 / 388404335|10317 / 89540| |3|SNOWBALL DANISH LUCENE FILTER|PRIMARY_OUTPUT|78440|4795|11100|388399540|4795 / 388404335|11100 / 89540| @@ -316,7 +306,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `DA_DK` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/dutch.md b/docs/benchmarks/languages/dutch.md index fc6a41a..658f1c7 100644 --- a/docs/benchmarks/languages/dutch.md +++ b/docs/benchmarks/languages/dutch.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `nl-nl-default` | `1.0.0` | `NL_NL` | 4,992 | 31,466 | 9,981 | 21,485 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `nl-nl-default` | `1.0.0` | `NL_NL` | 4,992 | 31,466 | 9,981 | 21,485 | 21,485 | ## Radixor Patch Command Distribution @@ -30,35 +30,25 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 99.120% | 98.711% | 100.000% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 99.120% | 98.711% | 100.000% | Radixor dictionary-trained patch-command stemmer. | | Lucene HunspellStemFilter | 46.590% | 22.718% | 97.976% | Benchmark-only Dutch Hunspell dictionary compared via Lucene HunspellStemFilter. | | Official Snowball direct | 15.954% | 8.992% | 30.939% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | | Lucene SnowballFilter | 12.620% | 5.441% | 28.073% | Lucene TokenFilter integration path around the Snowball algorithm. | - - - - - ## Speed Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower. | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `radixor[DUTCH]` | 1.410 | 0.139 | 65.6 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 24.183 | 2.889 | 1125.6 | 17.156 | Benchmark-only Dutch Hunspell dictionary compared via Lucene HunspellStemFilter. | -| Official Snowball direct | `snowballDirect[DUTCH]` | 4.560 | 0.205 | 212.2 | 3.235 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[DUTCH]` | 7.762 | 0.262 | 361.3 | 5.506 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | - - - - - +| Radixor | `radixor[DUTCH]` | 1.340 | 0.127 | 62.4 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 22.275 | 2.325 | 1036.8 | 16.621 | Benchmark-only Dutch Hunspell dictionary compared via Lucene HunspellStemFilter. | +| Official Snowball direct | `snowballDirect[DUTCH]` | 4.298 | 0.185 | 200.0 | 3.207 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[DUTCH]` | 7.317 | 0.255 | 340.6 | 5.460 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -340,7 +330,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `NL_NL` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/english.md b/docs/benchmarks/languages/english.md index 1e35a3f..ec0067d 100644 --- a/docs/benchmarks/languages/english.md +++ b/docs/benchmarks/languages/english.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `us-uk-default` | `1.0.0` | `US_UK` | 396,939 | 1,004,374 | 793,874 | 210,500 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `us-uk-default` | `1.0.0` | `US_UK` | 396,939 | 1,004,374 | 793,874 | 210,500 | 210,500 | ## Radixor Patch Command Distribution @@ -30,47 +30,39 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 97.478% | 97.197% | 97.552% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 97.478% | 97.197% | 97.552% | Radixor dictionary-trained patch-command stemmer. | | Lucene EnglishMinimalStemFilter | 90.981% | 65.189% | 97.820% | Minimal English plural reduction, not a full stemmer. | | Lucene KStemFilter | 80.076% | 76.608% | 80.996% | Krovetz-style English stemming TokenFilter; broader than minimal suffix reducers. | | Lucene HunspellStemFilter | 80.243% | 12.750% | 98.139% | Benchmark-only English Hunspell dictionary compared via Lucene HunspellStemFilter. | | Lucene EnglishPossessiveFilter | 79.032% | 0.003% | 99.987% | Possessive-ending remover only, not a full stemmer. | -| Snowball English / Porter2 | 40.342% | 46.296% | 38.763% | Porter2 rule-based suffix stemmer, distinct from original Porter. | +| Snowball English / Porter2 | 40.346% | 46.302% | 38.767% | Porter2 rule-based suffix stemmer, distinct from original Porter. | | Lucene PorterStemFilter | 39.538% | 46.201% | 37.772% | Lucene TokenFilter path for Porter suffix rules; not dictionary-root equivalent. | | Lucene PorterStemmer direct copy | 39.538% | 46.201% | 37.772% | Direct Porter suffix-rule implementation generated under build for benchmark-only use. | | OpenNLP PorterStemmer | 39.538% | 46.201% | 37.772% | Apache OpenNLP Porter suffix-rule implementation. | | Snowball original Porter | 39.529% | 46.179% | 37.766% | Classic Porter rule-based suffix stemmer. | | Paice/Husk Lancaster | 28.055% | 37.039% | 25.673% | Aggressive Paice/Husk rule stemmer that often produces shorter stems. | - - - - ## Speed Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower. | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `radixorUsUkProfiPreferredStem` | 17.489 | 1.380 | 83.1 | 1.000 | Full dictionary patch-command stemmer using compiled patch commands. | -| Lucene EnglishPossessiveFilter | `luceneEnglishPossessiveFilter` | 17.151 | 0.215 | 81.5 | 0.981 | Possessive-ending remover only; not a full stemmer. | -| Lucene EnglishMinimalStemFilter | `luceneEnglishMinimalStemFilter` | 18.522 | 0.152 | 88.0 | 1.059 | Narrow plural reduction filter; not a full stemmer. | -| Lucene PorterStemmer direct copy | `lucenePorterStemmerCopied` | 17.651 | 0.129 | 83.9 | 1.009 | Benchmark-only generated copy of Lucene package-private Porter implementation. | -| OpenNLP PorterStemmer | `opennlpPorterStemmer` | 17.681 | 0.139 | 84.0 | 1.011 | Apache OpenNLP Porter implementation. | -| Snowball original Porter | `snowballOriginalPorter` | 33.290 | 1.916 | 158.1 | 1.904 | Classic Porter suffix-rule stemmer; historical English baseline, not a dictionary-equivalent stemmer. | -| Lucene PorterStemFilter | `lucenePorterStemFilter` | 32.408 | 0.412 | 154.0 | 1.853 | Lucene TokenFilter integration path for Porter; includes TokenStream overhead. | -| Lucene KStemFilter | `luceneKStemFilter` | 45.877 | 0.425 | 217.9 | 2.623 | Krovetz-style English TokenFilter; broader than minimal suffix filters. | -| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 76.852 | 1.028 | 365.1 | 4.394 | Benchmark-only English Hunspell comparison using the benchmark Hunspell corpus. | -| Snowball English / Porter2 | `snowballEnglishPorter2` | 46.568 | 2.414 | 221.2 | 2.663 | Porter2 suffix-rule stemmer, distinct from original Porter. | -| Paice/Husk Lancaster | `paiceHuskLancaster` | 144.951 | 2.710 | 688.6 | 8.288 | Aggressive rule-based English stemmer. | - - - - +| Radixor | `radixorUsUkProfiPreferredStem` | 14.397 | 0.915 | 68.4 | 1.000 | Full dictionary patch-command stemmer using compiled patch commands. | +| Lucene EnglishPossessiveFilter | `luceneEnglishPossessiveFilter` | 15.034 | 0.322 | 71.4 | 1.044 | Possessive-ending remover only; not a full stemmer. | +| Lucene EnglishMinimalStemFilter | `luceneEnglishMinimalStemFilter` | 16.352 | 0.244 | 77.7 | 1.136 | Narrow plural reduction filter; not a full stemmer. | +| Lucene PorterStemmer direct copy | `lucenePorterStemmerCopied` | 16.491 | 0.149 | 78.3 | 1.145 | Benchmark-only generated copy of Lucene package-private Porter implementation. | +| OpenNLP PorterStemmer | `opennlpPorterStemmer` | 16.481 | 0.175 | 78.3 | 1.145 | Apache OpenNLP Porter implementation. | +| Snowball original Porter | `snowballOriginalPorter` | 30.634 | 1.620 | 145.5 | 2.128 | Classic Porter suffix-rule stemmer; historical English baseline, not a dictionary-equivalent stemmer. | +| Lucene PorterStemFilter | `lucenePorterStemFilter` | 29.666 | 0.536 | 140.9 | 2.061 | Lucene TokenFilter integration path for Porter; includes TokenStream overhead. | +| Lucene KStemFilter | `luceneKStemFilter` | 41.485 | 0.509 | 197.1 | 2.882 | Krovetz-style English TokenFilter; broader than minimal suffix filters. | +| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 74.399 | 1.223 | 353.4 | 5.168 | Benchmark-only English Hunspell comparison using the benchmark Hunspell corpus. | +| Snowball English / Porter2 | `snowballEnglishPorter2` | 43.117 | 1.983 | 204.8 | 2.995 | Porter2 suffix-rule stemmer, distinct from original Porter. | +| Paice/Husk Lancaster | `paiceHuskLancaster` | 137.952 | 2.443 | 655.4 | 9.582 | Aggressive rule-based English stemmer. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -104,7 +96,7 @@ This mode contains **15 result rows**, **11 evaluated stemmers**, and **3 output |2|ENGLISH LUCENE PORTER COPIED|0.954796|0.000207%|9.040545%| |3|ENGLISH LUCENE PORTER FILTER|0.954796|0.000207%|9.040545%| |4|ENGLISH OPENNLP PORTER|0.954796|0.000207%|9.040545%| -|5|ENGLISH SNOWBALL PORTER2|0.954708|0.000212%|9.058097%| +|5|ENGLISH SNOWBALL PORTER2|0.954732|0.000212%|9.053310%| |6|ENGLISH SNOWBALL ORIGINAL PORTER|0.954659|0.000206%|9.067990%| |7|ENGLISH PAICE HUSK LANCASTER|0.952535|0.000960%|9.492110%| |8|ENGLISH LUCENE KSTEM FILTER|0.878645|0.000110%|24.270875%| @@ -122,7 +114,7 @@ This mode contains **15 result rows**, **11 evaluated stemmers**, and **3 output |2|ENGLISH LUCENE PORTER COPIED|PRIMARY_OUTPUT|0.440121|0.909595|0.999998|0.954796|0.999998|0.000002| |3|ENGLISH LUCENE PORTER FILTER|PRIMARY_OUTPUT|0.440121|0.909595|0.999998|0.954796|0.999998|0.000002| |4|ENGLISH OPENNLP PORTER|PRIMARY_OUTPUT|0.440121|0.909595|0.999998|0.954796|0.999998|0.000002| -|5|ENGLISH SNOWBALL PORTER2|PRIMARY_OUTPUT|0.434174|0.909419|0.999998|0.954708|0.999998|0.000002| +|5|ENGLISH SNOWBALL PORTER2|PRIMARY_OUTPUT|0.434309|0.909467|0.999998|0.954732|0.999998|0.000002| |6|ENGLISH SNOWBALL ORIGINAL PORTER|PRIMARY_OUTPUT|0.441440|0.909320|0.999998|0.954659|0.999998|0.000002| |7|ENGLISH PAICE HUSK LANCASTER|PRIMARY_OUTPUT|0.144284|0.905079|0.999990|0.952535|0.999990|0.000010| |8|ENGLISH LUCENE KSTEM FILTER|PRIMARY_OUTPUT|0.551014|0.757291|0.999999|0.878645|0.999998|0.000002| @@ -140,7 +132,7 @@ This mode contains **15 result rows**, **11 evaluated stemmers**, and **3 output |2|ENGLISH LUCENE PORTER COPIED|PRIMARY_OUTPUT|0.490783|0.593208|0.749662|0.421675|0.632717|0.632716| |3|ENGLISH LUCENE PORTER FILTER|PRIMARY_OUTPUT|0.490783|0.593208|0.749662|0.421675|0.632717|0.632716| |4|ENGLISH OPENNLP PORTER|PRIMARY_OUTPUT|0.490783|0.593208|0.749662|0.421675|0.632717|0.632716| -|5|ENGLISH SNOWBALL PORTER2|PRIMARY_OUTPUT|0.484849|0.587747|0.746086|0.416176|0.628368|0.628367| +|5|ENGLISH SNOWBALL PORTER2|PRIMARY_OUTPUT|0.484986|0.587880|0.746192|0.416310|0.628482|0.628481| |6|ENGLISH SNOWBALL ORIGINAL PORTER|PRIMARY_OUTPUT|0.492079|0.594348|0.750277|0.422827|0.633570|0.633569| |7|ENGLISH PAICE HUSK LANCASTER|PRIMARY_OUTPUT|0.173443|0.248891|0.440518|0.142133|0.361370|0.361368| |8|ENGLISH LUCENE KSTEM FILTER|PRIMARY_OUTPUT|0.582762|0.637891|0.704541|0.468312|0.645971|0.645970| @@ -158,7 +150,7 @@ This mode contains **15 result rows**, **11 evaluated stemmers**, and **3 output |2|ENGLISH LUCENE PORTER COPIED|PRIMARY_OUTPUT|285026|362583|28329|175199061547|362583 / 175199424130|28329 / 313355| |3|ENGLISH LUCENE PORTER FILTER|PRIMARY_OUTPUT|285026|362583|28329|175199061547|362583 / 175199424130|28329 / 313355| |4|ENGLISH OPENNLP PORTER|PRIMARY_OUTPUT|285026|362583|28329|175199061547|362583 / 175199424130|28329 / 313355| -|5|ENGLISH SNOWBALL PORTER2|PRIMARY_OUTPUT|284971|371381|28384|175199052749|371381 / 175199424130|28384 / 313355| +|5|ENGLISH SNOWBALL PORTER2|PRIMARY_OUTPUT|284986|371197|28369|175199052933|371197 / 175199424130|28369 / 313355| |6|ENGLISH SNOWBALL ORIGINAL PORTER|PRIMARY_OUTPUT|284940|360538|28415|175199063592|360538 / 175199424130|28415 / 313355| |7|ENGLISH PAICE HUSK LANCASTER|PRIMARY_OUTPUT|283611|1682034|29744|175197742096|1682034 / 175199424130|29744 / 313355| |8|ENGLISH LUCENE KSTEM FILTER|PRIMARY_OUTPUT|237301|193361|76054|175199230769|193361 / 175199424130|76054 / 313355| @@ -251,7 +243,7 @@ This mode contains **15 result rows**, **11 evaluated stemmers**, and **3 output |2|ENGLISH LUCENE PORTER COPIED|0.955064|0.000222%|8.987032%| |3|ENGLISH LUCENE PORTER FILTER|0.955064|0.000222%|8.987032%| |4|ENGLISH OPENNLP PORTER|0.955064|0.000222%|8.987032%| -|5|ENGLISH SNOWBALL PORTER2|0.955016|0.000228%|8.996666%| +|5|ENGLISH SNOWBALL PORTER2|0.955040|0.000228%|8.991849%| |6|ENGLISH SNOWBALL ORIGINAL PORTER|0.954926|0.000221%|9.014651%| |7|ENGLISH PAICE HUSK LANCASTER|0.952850|0.001032%|9.428933%| |8|ENGLISH LUCENE KSTEM FILTER|0.881028|0.000120%|23.794246%| @@ -269,7 +261,7 @@ This mode contains **15 result rows**, **11 evaluated stemmers**, and **3 output |2|ENGLISH LUCENE PORTER COPIED|PRIMARY_OUTPUT|0.440920|0.910130|0.999998|0.955064|0.999998|0.000002| |3|ENGLISH LUCENE PORTER FILTER|PRIMARY_OUTPUT|0.440920|0.910130|0.999998|0.955064|0.999998|0.000002| |4|ENGLISH OPENNLP PORTER|PRIMARY_OUTPUT|0.440920|0.910130|0.999998|0.955064|0.999998|0.000002| -|5|ENGLISH SNOWBALL PORTER2|PRIMARY_OUTPUT|0.435017|0.910033|0.999998|0.955016|0.999998|0.000002| +|5|ENGLISH SNOWBALL PORTER2|PRIMARY_OUTPUT|0.435153|0.910082|0.999998|0.955040|0.999998|0.000002| |6|ENGLISH SNOWBALL ORIGINAL PORTER|PRIMARY_OUTPUT|0.442235|0.909853|0.999998|0.954926|0.999998|0.000002| |7|ENGLISH PAICE HUSK LANCASTER|PRIMARY_OUTPUT|0.144700|0.905711|0.999990|0.952850|0.999990|0.000010| |8|ENGLISH LUCENE KSTEM FILTER|PRIMARY_OUTPUT|0.551013|0.762058|0.999999|0.881028|0.999998|0.000002| @@ -287,7 +279,7 @@ This mode contains **15 result rows**, **11 evaluated stemmers**, and **3 output |2|ENGLISH LUCENE PORTER COPIED|PRIMARY_OUTPUT|0.491609|0.594049|0.750417|0.422524|0.633478|0.633477| |3|ENGLISH LUCENE PORTER FILTER|PRIMARY_OUTPUT|0.491609|0.594049|0.750417|0.422524|0.633478|0.633477| |4|ENGLISH OPENNLP PORTER|PRIMARY_OUTPUT|0.491609|0.594049|0.750417|0.422524|0.633478|0.633477| -|5|ENGLISH SNOWBALL PORTER2|PRIMARY_OUTPUT|0.485725|0.588647|0.746915|0.417080|0.629190|0.629189| +|5|ENGLISH SNOWBALL PORTER2|PRIMARY_OUTPUT|0.485863|0.588782|0.747021|0.417215|0.629305|0.629304| |6|ENGLISH SNOWBALL ORIGINAL PORTER|PRIMARY_OUTPUT|0.492900|0.595181|0.751027|0.423671|0.634326|0.634325| |7|ENGLISH PAICE HUSK LANCASTER|PRIMARY_OUTPUT|0.173928|0.249533|0.441413|0.142553|0.362017|0.362015| |8|ENGLISH LUCENE KSTEM FILTER|PRIMARY_OUTPUT|0.583322|0.639575|0.707836|0.470129|0.648000|0.647999| @@ -305,7 +297,7 @@ This mode contains **15 result rows**, **11 evaluated stemmers**, and **3 output |2|ENGLISH LUCENE PORTER COPIED|PRIMARY_OUTPUT|283398|359344|27984|161561630294|359344 / 161561989638|27984 / 311382| |3|ENGLISH LUCENE PORTER FILTER|PRIMARY_OUTPUT|283398|359344|27984|161561630294|359344 / 161561989638|27984 / 311382| |4|ENGLISH OPENNLP PORTER|PRIMARY_OUTPUT|283398|359344|27984|161561630294|359344 / 161561989638|27984 / 311382| -|5|ENGLISH SNOWBALL PORTER2|PRIMARY_OUTPUT|283368|368027|28014|161561621611|368027 / 161561989638|28014 / 311382| +|5|ENGLISH SNOWBALL PORTER2|PRIMARY_OUTPUT|283383|367843|27999|161561621795|367843 / 161561989638|27999 / 311382| |6|ENGLISH SNOWBALL ORIGINAL PORTER|PRIMARY_OUTPUT|283312|357325|28070|161561632313|357325 / 161561989638|28070 / 311382| |7|ENGLISH PAICE HUSK LANCASTER|PRIMARY_OUTPUT|282022|1666990|29360|161560322648|1666990 / 161561989638|29360 / 311382| |8|ENGLISH LUCENE KSTEM FILTER|PRIMARY_OUTPUT|237291|193354|74091|161561796284|193354 / 161561989638|74091 / 311382| @@ -408,7 +400,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `US_UK` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/finnish.md b/docs/benchmarks/languages/finnish.md index d4c7401..ffca962 100644 --- a/docs/benchmarks/languages/finnish.md +++ b/docs/benchmarks/languages/finnish.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `fi-fi-default` | `1.0.0` | `FI_FI` | 57,027 | 1,865,215 | 110,525 | 1,754,690 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `fi-fi-default` | `1.0.0` | `FI_FI` | 57,027 | 1,865,215 | 110,525 | 1,754,690 | 1,754,690 | ## Radixor Patch Command Distribution @@ -30,14 +30,10 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 98.661% | 98.803% | 96.408% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 98.661% | 98.803% | 96.408% | Radixor dictionary-trained patch-command stemmer. | | Lucene SnowballFilter | 10.991% | 10.268% | 22.471% | Lucene TokenFilter integration path around the Snowball algorithm. | -| Official Snowball direct | 10.991% | 10.268% | 22.471% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | -| Lucene FinnishLightStemFilter | 4.351% | 4.294% | 5.264% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. | - - - - +| Official Snowball direct | 10.995% | 10.272% | 22.462% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | +| Lucene FinnishLightStemFilter | 4.351% | 4.294% | 5.264% | Light suffix stemmer; intentionally narrower than Radixor's dictionary-trained transformation model. | ## Speed @@ -45,18 +41,14 @@ Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 i | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `finnishRadixor` | 289.539 | 4.136 | 165.0 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene FinnishLightStemFilter | `finnishLuceneFinnishLightStemFilter` | 175.789 | 4.827 | 100.2 | 0.607 | Light Finnish suffix stemmer. | -| Official Snowball direct | `snowballDirect[FINNISH]` | 259.889 | 8.924 | 148.1 | 0.898 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[FINNISH]` | 332.524 | 9.490 | 189.5 | 1.148 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | - - - - +| Radixor | `finnishRadixor` | 225.954 | 2.940 | 128.8 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene FinnishLightStemFilter | `finnishLuceneFinnishLightStemFilter` | 168.756 | 6.027 | 96.2 | 0.747 | Light Finnish suffix stemmer. | +| Official Snowball direct | `snowballDirect[FINNISH]` | 247.984 | 16.083 | 141.3 | 1.097 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[FINNISH]` | 321.331 | 9.790 | 183.1 | 1.422 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -75,7 +67,7 @@ Runtime performance and linguistic grouping quality are independent dimensions. The default model is `fi-fi-default`, loaded from classpath resource `org/egothor/stemmer/models/fi-fi-default/stemmer.gz`. The following findings compare only deterministic `PRIMARY_OUTPUT` rows over identical included groups; candidate policies are reported separately as capability analyses. - **ALL_WORDS:** `Radixor` ranks first by balanced accuracy at **0.984838** among 4 deterministic stemmers. The runner-up is `SNOWBALL FINNISH LUCENE FILTER` at 0.740279, a difference of 0.244559. This rank does not imply leadership in throughput or every secondary metric. -- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.988242** among 4 deterministic stemmers. The runner-up is `SNOWBALL FINNISH DIRECT` at 0.738344, a difference of 0.249898. This rank does not imply leadership in throughput or every secondary metric. +- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.988242** among 4 deterministic stemmers. The runner-up is `SNOWBALL FINNISH DIRECT` at 0.738543, a difference of 0.249699. This rank does not imply leadership in throughput or every secondary metric. ### `ALL_WORDS` This mode contains **6 result rows**, **4 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. @@ -88,7 +80,7 @@ This mode contains **6 result rows**, **4 evaluated stemmers**, and **3 output p |---:|---|---:|---:|---:| |1|Radixor|0.984838|<0.000001%|3.032474%| |2|SNOWBALL FINNISH LUCENE FILTER|0.740279|0.000081%|51.944179%| -|3|SNOWBALL FINNISH DIRECT|0.739671|0.000060%|52.065724%| +|3|SNOWBALL FINNISH DIRECT|0.739870|0.000060%|52.025976%| |4|FINNISH LUCENE FINNISH LIGHT STEM FILTER|0.695725|0.000094%|60.854936%| @@ -99,7 +91,7 @@ This mode contains **6 result rows**, **4 evaluated stemmers**, and **3 output p |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|0.999974|0.969675|1.000000|0.984838|0.999999|0.000001| |2|SNOWBALL FINNISH LUCENE FILTER|PRIMARY_OUTPUT|0.921471|0.480558|0.999999|0.740279|0.999989|0.000011| -|3|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|0.940611|0.479343|0.999999|0.739671|0.999989|0.000011| +|3|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|0.940647|0.479740|0.999999|0.739870|0.999989|0.000011| |4|FINNISH LUCENE FINNISH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.890914|0.391451|0.999999|0.695725|0.999987|0.000013| @@ -110,7 +102,7 @@ This mode contains **6 result rows**, **4 evaluated stemmers**, and **3 output p |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|0.993763|0.984591|0.975587|0.969650|0.984708|0.984708| |2|SNOWBALL FINNISH LUCENE FILTER|PRIMARY_OUTPUT|0.778598|0.631685|0.531413|0.461652|0.665448|0.665443| -|3|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|0.788800|0.635056|0.531468|0.465262|0.671472|0.671468| +|3|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|0.789035|0.635413|0.531862|0.465645|0.671764|0.671760| |4|FINNISH LUCENE FINNISH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.709787|0.543915|0.440884|0.373546|0.590550|0.590545| @@ -121,7 +113,7 @@ This mode contains **6 result rows**, **4 evaluated stemmers**, and **3 output p |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|30511413|804|954186|1599841738533|804 / 1599841739337|954186 / 31465599| |2|SNOWBALL FINNISH LUCENE FILTER|PRIMARY_OUTPUT|15121052|1288634|16344547|1599840450703|1288634 / 1599841739337|16344547 / 31465599| -|3|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|15082807|952306|16382792|1599840787031|952306 / 1599841739337|16382792 / 31465599| +|3|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|15095314|952479|16370285|1599840786858|952479 / 1599841739337|16370285 / 31465599| |4|FINNISH LUCENE FINNISH LIGHT STEM FILTER|PRIMARY_OUTPUT|12317229|1508153|19148370|1599840231184|1508153 / 1599841739337|19148370 / 31465599| @@ -199,7 +191,7 @@ This mode contains **6 result rows**, **4 evaluated stemmers**, and **3 output p | Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) | |---:|---|---:|---:|---:| |1|Radixor|0.988242|<0.000001%|2.351587%| -|2|SNOWBALL FINNISH DIRECT|0.738344|0.000062%|52.331112%| +|2|SNOWBALL FINNISH DIRECT|0.738543|0.000062%|52.291340%| |3|SNOWBALL FINNISH LUCENE FILTER|0.738344|0.000062%|52.331112%| |4|FINNISH LUCENE FINNISH LIGHT STEM FILTER|0.694308|0.000077%|61.138333%| @@ -210,7 +202,7 @@ This mode contains **6 result rows**, **4 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|0.999973|0.976484|1.000000|0.988242|1.000000|0.000000| -|2|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|0.939951|0.476689|0.999999|0.738344|0.999989|0.000011| +|2|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|0.939988|0.477087|0.999999|0.738543|0.999989|0.000011| |3|SNOWBALL FINNISH LUCENE FILTER|PRIMARY_OUTPUT|0.939951|0.476689|0.999999|0.738344|0.999989|0.000011| |4|FINNISH LUCENE FINNISH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.911893|0.388617|0.999999|0.694308|0.999987|0.000013| @@ -221,7 +213,7 @@ This mode contains **6 result rows**, **4 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|0.995185|0.988089|0.981093|0.976459|0.988159|0.988159| -|2|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|0.786987|0.632573|0.528815|0.462601|0.669376|0.669372| +|2|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|0.787224|0.632932|0.529209|0.462985|0.669668|0.669664| |3|SNOWBALL FINNISH LUCENE FILTER|PRIMARY_OUTPUT|0.786987|0.632573|0.528815|0.462601|0.669376|0.669372| |4|FINNISH LUCENE FINNISH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.718421|0.544981|0.438999|0.374553|0.595296|0.595291| @@ -232,7 +224,7 @@ This mode contains **6 result rows**, **4 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|30037514|804|723369|1504706134249|804 / 1504706135053|723369 / 30760883| -|2|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|14663371|936765|16097512|1504705198288|936765 / 1504706135053|16097512 / 30760883| +|2|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|14675605|936938|16085278|1504705198115|936938 / 1504706135053|16085278 / 30760883| |3|SNOWBALL FINNISH LUCENE FILTER|PRIMARY_OUTPUT|14663371|936765|16097512|1504705198288|936765 / 1504706135053|16097512 / 30760883| |4|FINNISH LUCENE FINNISH LIGHT STEM FILTER|PRIMARY_OUTPUT|11954192|1155011|18806691|1504704980042|1155011 / 1504706135053|18806691 / 30760883| @@ -324,7 +316,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `FI_FI` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/french.md b/docs/benchmarks/languages/french.md index 7056628..24ea717 100644 --- a/docs/benchmarks/languages/french.md +++ b/docs/benchmarks/languages/french.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `fr-fr-default` | `1.0.0` | `FR_FR` | 59,240 | 474,110 | 108,141 | 365,969 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `fr-fr-default` | `1.0.0` | `FR_FR` | 59,240 | 474,110 | 108,141 | 365,969 | 365,969 | ## Radixor Patch Command Distribution @@ -30,16 +30,12 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 94.831% | 94.859% | 94.734% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 94.831% | 94.859% | 94.734% | Radixor dictionary-trained patch-command stemmer. | | Lucene HunspellStemFilter | 68.923% | 63.617% | 86.876% | Benchmark-only French Hunspell dictionary compared via Lucene HunspellStemFilter. | | Lucene FrenchMinimalStemFilter | 11.472% | 6.236% | 29.192% | Minimal suffix reducer; narrow baseline, not a full stemmer. | | Lucene SnowballFilter | 8.551% | 5.183% | 19.952% | Lucene TokenFilter integration path around the Snowball algorithm. | | Official Snowball direct | 8.462% | 5.067% | 19.952% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | -| Lucene FrenchLightStemFilter | 6.377% | 3.965% | 14.540% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. | - - - - +| Lucene FrenchLightStemFilter | 6.377% | 3.965% | 14.540% | Light suffix stemmer; intentionally narrower than Radixor's dictionary-trained transformation model. | ## Speed @@ -47,20 +43,16 @@ Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 i | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `frenchRadixor` | 49.340 | 0.986 | 134.8 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 1781.070 | 43.544 | 4866.7 | 36.098 | Benchmark-only French Hunspell dictionary compared via Lucene HunspellStemFilter. | -| Lucene FrenchMinimalStemFilter | `frenchLuceneFrenchMinimalStemFilter` | 19.093 | 0.681 | 52.2 | 0.387 | Minimal French suffix reducer; narrow baseline. | -| Lucene FrenchLightStemFilter | `frenchLuceneFrenchLightStemFilter` | 29.553 | 0.465 | 80.8 | 0.599 | Light French suffix stemmer. | -| Official Snowball direct | `snowballDirect[FRENCH]` | 121.376 | 0.865 | 331.7 | 2.460 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[FRENCH]` | 126.574 | 4.671 | 345.9 | 2.565 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | - - - - +| Radixor | `frenchRadixor` | 37.443 | 0.520 | 102.3 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 1673.192 | 57.385 | 4572.0 | 44.686 | Benchmark-only French Hunspell dictionary compared via Lucene HunspellStemFilter. | +| Lucene FrenchMinimalStemFilter | `frenchLuceneFrenchMinimalStemFilter` | 18.034 | 0.181 | 49.3 | 0.482 | Minimal French suffix reducer; narrow baseline. | +| Lucene FrenchLightStemFilter | `frenchLuceneFrenchLightStemFilter` | 27.961 | 0.493 | 76.4 | 0.747 | Light French suffix stemmer. | +| Official Snowball direct | `snowballDirect[FRENCH]` | 112.255 | 4.045 | 306.7 | 2.998 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[FRENCH]` | 119.555 | 4.560 | 326.7 | 3.193 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -358,7 +350,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `FR_FR` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/german.md b/docs/benchmarks/languages/german.md index be10455..cf2805f 100644 --- a/docs/benchmarks/languages/german.md +++ b/docs/benchmarks/languages/german.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `de-de-default` | `1.0.0` | `DE_DE` | 54,092 | 333,036 | 90,535 | 242,501 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `de-de-default` | `1.0.0` | `DE_DE` | 54,092 | 333,036 | 90,535 | 242,501 | 242,501 | ## Radixor Patch Command Distribution @@ -30,41 +30,33 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 92.725% | 92.847% | 92.396% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 92.725% | 92.847% | 92.396% | Radixor dictionary-trained patch-command stemmer. | | Lucene HunspellStemFilter | 47.064% | 29.661% | 93.678% | Benchmark-only German Hunspell dictionary compared via Lucene HunspellStemFilter. | | CISTEM (German) | 24.675% | 23.724% | 27.222% | Benchmark-only CISTEM implementation. | -| Lucene GermanLightStemFilter | 37.434% | 35.465% | 42.707% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. | +| Lucene GermanLightStemFilter | 37.434% | 35.465% | 42.707% | Light suffix stemmer; intentionally narrower than Radixor's lexicon-trained transformation model. | | Lucene GermanMinimalStemFilter | 27.640% | 24.951% | 34.844% | Minimal suffix reducer; narrow baseline, not a full stemmer. | | Lucene SnowballFilter | 30.956% | 28.853% | 36.589% | Lucene TokenFilter integration path around the Snowball algorithm. | -| Official Snowball direct | 30.481% | 29.027% | 34.376% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | +| Official Snowball direct | 30.483% | 29.030% | 34.376% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | | Lucene GermanStemFilter | 21.559% | 19.312% | 27.576% | German Lucene stemming TokenFilter; broader than minimal/light variants. | - - - - ## Speed Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower. | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `germanRadixor` | 40.571 | 1.647 | 167.3 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| CISTEM | `germanCistem` | 305.166 | 4.590 | 1258.4 | 7.522 | Benchmark-only CISTEM implementation. | -| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 291.791 | 21.769 | 1203.3 | 7.192 | Benchmark-only German Hunspell dictionary compared via Lucene HunspellStemFilter. | -| Lucene GermanMinimalStemFilter | `germanLuceneGermanMinimalStemFilter` | 23.903 | 0.208 | 98.6 | 0.589 | Minimal German suffix reduction; narrow baseline. | -| Lucene GermanLightStemFilter | `germanLuceneGermanLightStemFilter` | 24.695 | 0.322 | 101.8 | 0.609 | Light German suffix stemmer; narrower than a dictionary stemmer. | -| Lucene GermanStemFilter | `germanLuceneGermanStemFilter` | 72.140 | 1.544 | 297.5 | 1.778 | Older German stemming TokenFilter with normalization requirements. | -| Lucene SnowballFilter | `luceneSnowballFilter[GERMAN]` | 110.086 | 2.315 | 454.0 | 2.713 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | -| Official Snowball direct | `snowballDirect[GERMAN]` | 100.122 | 2.623 | 412.9 | 2.468 | Official Snowball generated Java stemmer; direct API. | - - - - +| Radixor | `germanRadixor` | 27.697 | 0.583 | 114.2 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| CISTEM | `germanCistem` | 289.568 | 8.761 | 1194.1 | 10.455 | Benchmark-only CISTEM implementation. | +| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 265.653 | 10.779 | 1095.5 | 9.591 | Benchmark-only German Hunspell dictionary compared via Lucene HunspellStemFilter. | +| Lucene GermanMinimalStemFilter | `germanLuceneGermanMinimalStemFilter` | 22.385 | 0.217 | 92.3 | 0.808 | Minimal German suffix reduction; narrow baseline. | +| Lucene GermanLightStemFilter | `germanLuceneGermanLightStemFilter` | 23.170 | 0.383 | 95.5 | 0.837 | Light German suffix stemmer; narrower than Radixor's lexicon-trained transformation model. | +| Lucene GermanStemFilter | `germanLuceneGermanStemFilter` | 67.453 | 0.967 | 278.2 | 2.435 | Older German stemming TokenFilter with normalization requirements. | +| Lucene SnowballFilter | `luceneSnowballFilter[GERMAN]` | 105.203 | 2.035 | 433.8 | 3.798 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | +| Official Snowball direct | `snowballDirect[GERMAN]` | 91.847 | 2.301 | 378.7 | 3.316 | Official Snowball generated Java stemmer; direct API. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -96,7 +88,7 @@ This mode contains **12 result rows**, **8 evaluated stemmers**, and **3 output |---:|---|---:|---:|---:| |1|Radixor|0.910445|0.000002%|17.910967%| |2|GERMAN CISTEM|0.878527|0.000674%|24.293900%| -|3|SNOWBALL GERMAN DIRECT|0.776006|0.000171%|44.798684%| +|3|SNOWBALL GERMAN DIRECT|0.776012|0.000171%|44.797420%| |4|SNOWBALL GERMAN LUCENE FILTER|0.769071|0.000371%|46.185528%| |5|GERMAN LUCENE GERMAN LIGHT STEM FILTER|0.753833|0.000191%|49.233299%| |6|GERMAN LUCENE GERMAN STEM FILTER|0.720992|0.000443%|55.801084%| @@ -111,7 +103,7 @@ This mode contains **12 result rows**, **8 evaluated stemmers**, and **3 output |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|0.999400|0.820890|1.000000|0.910445|0.999994|0.000006| |2|GERMAN CISTEM|PRIMARY_OUTPUT|0.797231|0.757061|0.999993|0.878527|0.999985|0.000015| -|3|SNOWBALL GERMAN DIRECT|PRIMARY_OUTPUT|0.918570|0.552013|0.999998|0.776006|0.999983|0.000017| +|3|SNOWBALL GERMAN DIRECT|PRIMARY_OUTPUT|0.918571|0.552026|0.999998|0.776012|0.999983|0.000017| |4|SNOWBALL GERMAN LUCENE FILTER|PRIMARY_OUTPUT|0.835220|0.538145|0.999996|0.769071|0.999980|0.000020| |5|GERMAN LUCENE GERMAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.902792|0.507667|0.999998|0.753833|0.999981|0.000019| |6|GERMAN LUCENE GERMAN STEM FILTER|PRIMARY_OUTPUT|0.777304|0.441989|0.999996|0.720992|0.999976|0.000024| @@ -126,7 +118,7 @@ This mode contains **12 result rows**, **8 evaluated stemmers**, and **3 output |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|0.957746|0.901392|0.851302|0.820486|0.905758|0.905755| |2|GERMAN CISTEM|PRIMARY_OUTPUT|0.788860|0.776627|0.764768|0.634824|0.776886|0.776879| -|3|SNOWBALL GERMAN DIRECT|PRIMARY_OUTPUT|0.810879|0.689608|0.599891|0.526260|0.712083|0.712076| +|3|SNOWBALL GERMAN DIRECT|PRIMARY_OUTPUT|0.810886|0.689618|0.599903|0.526272|0.712092|0.712085| |4|SNOWBALL GERMAN LUCENE FILTER|PRIMARY_OUTPUT|0.752175|0.654552|0.579359|0.486494|0.670425|0.670416| |5|GERMAN LUCENE GERMAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.781189|0.649884|0.556368|0.481355|0.676991|0.676984| |6|GERMAN LUCENE GERMAN STEM FILTER|PRIMARY_OUTPUT|0.674901|0.563540|0.483723|0.392311|0.586140|0.586130| @@ -141,7 +133,7 @@ This mode contains **12 result rows**, **8 evaluated stemmers**, and **3 output |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|1103976|663|240876|38436733230|663 / 38436733893|240876 / 1344852| |2|GERMAN CISTEM|PRIMARY_OUTPUT|1018135|258954|326717|38436474939|258954 / 38436733893|326717 / 1344852| -|3|SNOWBALL GERMAN DIRECT|PRIMARY_OUTPUT|742376|65811|602476|38436668082|65811 / 38436733893|602476 / 1344852| +|3|SNOWBALL GERMAN DIRECT|PRIMARY_OUTPUT|742393|65811|602459|38436668082|65811 / 38436733893|602459 / 1344852| |4|SNOWBALL GERMAN LUCENE FILTER|PRIMARY_OUTPUT|723725|142783|621127|38436591110|142783 / 38436733893|621127 / 1344852| |5|GERMAN LUCENE GERMAN LIGHT STEM FILTER|PRIMARY_OUTPUT|682737|73514|662115|38436660379|73514 / 38436733893|662115 / 1344852| |6|GERMAN LUCENE GERMAN STEM FILTER|PRIMARY_OUTPUT|594410|170297|750442|38436563596|170297 / 38436733893|750442 / 1344852| @@ -231,7 +223,7 @@ This mode contains **12 result rows**, **8 evaluated stemmers**, and **3 output |---:|---|---:|---:|---:| |1|Radixor|0.966959|0.000001%|6.608210%| |2|GERMAN CISTEM|0.914727|0.000812%|17.053716%| -|3|SNOWBALL GERMAN DIRECT|0.794994|0.000391%|41.000819%| +|3|SNOWBALL GERMAN DIRECT|0.794997|0.000391%|41.000236%| |4|SNOWBALL GERMAN LUCENE FILTER|0.774716|0.000325%|45.056540%| |5|GERMAN LUCENE GERMAN LIGHT STEM FILTER|0.768968|0.000130%|46.206331%| |6|GERMAN LUCENE GERMAN STEM FILTER|0.716147|0.000358%|56.770194%| @@ -246,7 +238,7 @@ This mode contains **12 result rows**, **8 evaluated stemmers**, and **3 output |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|0.999900|0.933918|1.000000|0.966959|0.999995|0.000005| |2|GERMAN CISTEM|PRIMARY_OUTPUT|0.892172|0.829463|0.999992|0.914727|0.999978|0.000022| -|3|SNOWBALL GERMAN DIRECT|PRIMARY_OUTPUT|0.924303|0.589992|0.999996|0.794994|0.999963|0.000037| +|3|SNOWBALL GERMAN DIRECT|PRIMARY_OUTPUT|0.924304|0.589998|0.999996|0.794997|0.999963|0.000037| |4|SNOWBALL GERMAN LUCENE FILTER|PRIMARY_OUTPUT|0.931871|0.549435|0.999997|0.774716|0.999960|0.000040| |5|GERMAN LUCENE GERMAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.971001|0.537937|0.999999|0.768968|0.999961|0.000039| |6|GERMAN LUCENE GERMAN STEM FILTER|PRIMARY_OUTPUT|0.907196|0.432298|0.999996|0.716147|0.999950|0.000050| @@ -261,7 +253,7 @@ This mode contains **12 result rows**, **8 evaluated stemmers**, and **3 output |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|0.985968|0.965783|0.946408|0.933831|0.966346|0.966343| |2|GERMAN CISTEM|PRIMARY_OUTPUT|0.878883|0.859676|0.841289|0.753887|0.860246|0.860236| -|3|SNOWBALL GERMAN DIRECT|PRIMARY_OUTPUT|0.830217|0.720244|0.635999|0.562799|0.738466|0.738450| +|3|SNOWBALL GERMAN DIRECT|PRIMARY_OUTPUT|0.830220|0.720249|0.636004|0.562804|0.738469|0.738454| |4|SNOWBALL GERMAN LUCENE FILTER|PRIMARY_OUTPUT|0.817997|0.691285|0.598564|0.528217|0.715543|0.715527| |5|GERMAN LUCENE GERMAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.836342|0.692324|0.590620|0.529431|0.722729|0.722714| |6|GERMAN LUCENE GERMAN STEM FILTER|PRIMARY_OUTPUT|0.743781|0.585563|0.482850|0.413990|0.626242|0.626223| @@ -276,7 +268,7 @@ This mode contains **12 result rows**, **8 evaluated stemmers**, and **3 output |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|801691|80|56726|10594963454|80 / 10594963534|56726 / 858417| |2|GERMAN CISTEM|PRIMARY_OUTPUT|712025|86055|146392|10594877479|86055 / 10594963534|146392 / 858417| -|3|SNOWBALL GERMAN DIRECT|PRIMARY_OUTPUT|506459|41477|351958|10594922057|41477 / 10594963534|351958 / 858417| +|3|SNOWBALL GERMAN DIRECT|PRIMARY_OUTPUT|506464|41477|351953|10594922057|41477 / 10594963534|351953 / 858417| |4|SNOWBALL GERMAN LUCENE FILTER|PRIMARY_OUTPUT|471644|34482|386773|10594929052|34482 / 10594963534|386773 / 858417| |5|GERMAN LUCENE GERMAN LIGHT STEM FILTER|PRIMARY_OUTPUT|461774|13791|396643|10594949743|13791 / 10594963534|396643 / 858417| |6|GERMAN LUCENE GERMAN STEM FILTER|PRIMARY_OUTPUT|371092|37962|487325|10594925572|37962 / 10594963534|487325 / 858417| @@ -378,7 +370,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `DE_DE` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/hebrew.md b/docs/benchmarks/languages/hebrew.md index d061353..6af6722 100644 --- a/docs/benchmarks/languages/hebrew.md +++ b/docs/benchmarks/languages/hebrew.md @@ -8,9 +8,9 @@ The default Hebrew model currently has no same-language third-party adapter in t ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `he-il-default` | `1.0.0` | `HE_IL` | 2,358 | 61,071 | 4,715 | 56,356 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `he-il-default` | `1.0.0` | `HE_IL` | 2,358 | 61,071 | 4,715 | 56,356 | 56,356 | ## Radixor Patch Command Distribution @@ -32,23 +32,17 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | --- | ---: | ---: | ---: | --- | | Radixor | 98.228% | 98.172% | 98.897% | Full default-model Radixor dictionary patch-command stemmer. | - - - ## Speed Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `hebrewRadixor` | 3.921 | 0.140 | 69.6 | 1.000 | Full default-model Radixor dictionary patch-command stemmer. | - - - +| Radixor | `hebrewRadixor` | 3.570 | 0.074 | 63.3 | 1.000 | Full default-model Radixor dictionary patch-command stemmer. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the default language model used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Hebrew patch commands use forward traversal as declared by the model metadata. - Results are environment-specific and should be compared only with rows from the same benchmark run. @@ -290,7 +284,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `HE_IL` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/hungarian.md b/docs/benchmarks/languages/hungarian.md index 764a97c..e3ad6f5 100644 --- a/docs/benchmarks/languages/hungarian.md +++ b/docs/benchmarks/languages/hungarian.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `hu-hu-default` | `1.0.0` | `HU_HU` | 19,406 | 935,713 | 38,775 | 896,938 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `hu-hu-default` | `1.0.0` | `HU_HU` | 19,406 | 935,713 | 38,775 | 896,938 | 896,938 | ## Radixor Patch Command Distribution @@ -30,13 +30,10 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 99.222% | 99.537% | 91.948% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 99.222% | 99.537% | 91.948% | Radixor dictionary-trained patch-command stemmer. | | Lucene SnowballFilter | 66.445% | 66.938% | 55.043% | Lucene TokenFilter integration path around the Snowball algorithm. | | Official Snowball direct | 66.445% | 66.938% | 55.043% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | -| Lucene HungarianLightStemFilter | 14.748% | 14.777% | 14.086% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. | - - - +| Lucene HungarianLightStemFilter | 14.748% | 14.777% | 14.086% | Light suffix stemmer; intentionally narrower than Radixor's dictionary-trained transformation model. | ## Speed @@ -44,17 +41,14 @@ Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 i | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `hungarianRadixor` | 61.205 | 0.944 | 68.2 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene HungarianLightStemFilter | `hungarianLuceneHungarianLightStemFilter` | 92.090 | 3.410 | 102.7 | 1.505 | Light Hungarian suffix stemmer. | -| Official Snowball direct | `snowballDirect[HUNGARIAN]` | 152.969 | 4.468 | 170.5 | 2.499 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[HUNGARIAN]` | 188.807 | 5.290 | 210.5 | 3.085 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | - - - +| Radixor | `hungarianRadixor` | 52.020 | 1.337 | 58.0 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene HungarianLightStemFilter | `hungarianLuceneHungarianLightStemFilter` | 87.362 | 3.444 | 97.4 | 1.679 | Light Hungarian suffix stemmer. | +| Official Snowball direct | `snowballDirect[HUNGARIAN]` | 158.081 | 9.757 | 176.2 | 3.039 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[HUNGARIAN]` | 179.398 | 8.041 | 200.0 | 3.449 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -322,7 +316,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `HU_HU` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/italian.md b/docs/benchmarks/languages/italian.md index 2d90f7f..84529da 100644 --- a/docs/benchmarks/languages/italian.md +++ b/docs/benchmarks/languages/italian.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `it-it-default` | `1.0.0` | `IT_IT` | 10,009 | 337,546 | 20,004 | 317,542 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `it-it-default` | `1.0.0` | `IT_IT` | 10,009 | 337,546 | 20,004 | 317,542 | 317,542 | ## Radixor Patch Command Distribution @@ -29,31 +29,25 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 99.056% | 98.997% | 100.000% | Full Radixor dictionary patch-command stemmer. | -| Lucene ItalianLightStemFilter | 0.466% | 0.479% | 0.270% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. | +| Radixor | 99.056% | 98.997% | 100.000% | Radixor dictionary-trained patch-command stemmer. | +| Lucene ItalianLightStemFilter | 0.466% | 0.479% | 0.270% | Light suffix stemmer; intentionally narrower than Radixor's dictionary-trained transformation model. | | Lucene SnowballFilter | 0.041% | 0.043% | 0.010% | Lucene TokenFilter integration path around the Snowball algorithm. | | Official Snowball direct | 0.041% | 0.043% | 0.010% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | - - - ## Speed Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower. | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `italianRadixor` | 25.073 | 0.534 | 79.0 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene ItalianLightStemFilter | `italianLuceneItalianLightStemFilter` | 15.956 | 0.184 | 50.2 | 0.636 | Light Italian suffix stemmer. | -| Official Snowball direct | `snowballDirect[ITALIAN]` | 115.818 | 3.174 | 364.7 | 4.619 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[ITALIAN]` | 123.974 | 4.405 | 390.4 | 4.944 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | - - - +| Radixor | `italianRadixor` | 22.503 | 0.434 | 70.9 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene ItalianLightStemFilter | `italianLuceneItalianLightStemFilter` | 15.008 | 0.278 | 47.3 | 0.667 | Light Italian suffix stemmer. | +| Official Snowball direct | `snowballDirect[ITALIAN]` | 109.401 | 2.983 | 344.5 | 4.862 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[ITALIAN]` | 116.392 | 3.271 | 366.5 | 5.172 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -321,7 +315,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `IT_IT` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/norwegian-bokmal.md b/docs/benchmarks/languages/norwegian-bokmal.md index 44554b1..878c1b8 100644 --- a/docs/benchmarks/languages/norwegian-bokmal.md +++ b/docs/benchmarks/languages/norwegian-bokmal.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `nb-no-default` | `1.0.0` | `NB_NO` | 17,929 | 90,757 | 33,376 | 57,381 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `nb-no-default` | `1.0.0` | `NB_NO` | 17,929 | 90,757 | 33,376 | 57,381 | 57,381 | ## Radixor Patch Command Distribution @@ -30,14 +30,11 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 96.852% | 97.637% | 95.503% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 96.852% | 97.637% | 95.503% | Radixor dictionary-trained patch-command stemmer. | | Lucene NorwegianMinimalStemFilter | 57.107% | 53.913% | 62.599% | Minimal suffix reducer; narrow baseline, not a full stemmer. | | Official Snowball direct | 54.824% | 51.791% | 60.040% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | | Lucene SnowballFilter | 54.803% | 51.780% | 60.001% | Lucene TokenFilter integration path around the Snowball algorithm. | -| Lucene NorwegianLightStemFilter | 52.136% | 50.616% | 54.749% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. | - - - +| Lucene NorwegianLightStemFilter | 52.136% | 50.616% | 54.749% | Light suffix stemmer; intentionally narrower than Radixor's dictionary-trained transformation model. | ## Speed @@ -45,18 +42,15 @@ Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 i | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `norwegianBokmalRadixor` | 3.401 | 0.055 | 59.3 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene NorwegianMinimalStemFilter | `norwegianBokmalLuceneNorwegianMinimalStemFilter` | 2.943 | 0.023 | 51.3 | 0.865 | Minimal Norwegian suffix reducer. | -| Lucene NorwegianLightStemFilter | `norwegianBokmalLuceneNorwegianLightStemFilter` | 3.358 | 0.036 | 58.5 | 0.987 | Light Norwegian suffix stemmer. | -| Official Snowball direct | `snowballDirect[NORWEGIAN_BOKMAL]` | 4.378 | 0.295 | 76.3 | 1.287 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[NORWEGIAN_BOKMAL]` | 6.114 | 0.436 | 106.5 | 1.797 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | - - - +| Radixor | `norwegianBokmalRadixor` | 3.240 | 0.087 | 56.5 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene NorwegianMinimalStemFilter | `norwegianBokmalLuceneNorwegianMinimalStemFilter` | 2.726 | 0.022 | 47.5 | 0.841 | Minimal Norwegian suffix reducer. | +| Lucene NorwegianLightStemFilter | `norwegianBokmalLuceneNorwegianLightStemFilter` | 3.136 | 0.028 | 54.6 | 0.968 | Light Norwegian suffix stemmer. | +| Official Snowball direct | `snowballDirect[NORWEGIAN_BOKMAL]` | 4.711 | 0.410 | 82.1 | 1.454 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[NORWEGIAN_BOKMAL]` | 5.681 | 0.206 | 99.0 | 1.753 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -332,7 +326,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `NB_NO` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/norwegian-nynorsk.md b/docs/benchmarks/languages/norwegian-nynorsk.md index 87f4386..645a59a 100644 --- a/docs/benchmarks/languages/norwegian-nynorsk.md +++ b/docs/benchmarks/languages/norwegian-nynorsk.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `nn-no-default` | `1.0.0` | `NN_NO` | 4,688 | 19,651 | 6,089 | 13,562 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `nn-no-default` | `1.0.0` | `NN_NO` | 4,688 | 19,651 | 6,089 | 13,562 | 13,562 | ## Radixor Patch Command Distribution @@ -30,29 +30,23 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 93.089% | 91.395% | 96.863% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 93.089% | 91.395% | 96.863% | Radixor dictionary-trained patch-command stemmer. | | Official Snowball direct | 60.974% | 60.212% | 62.670% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | | Lucene SnowballFilter | 60.918% | 60.146% | 62.638% | Lucene TokenFilter integration path around the Snowball algorithm. | - - - ## Speed Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower. | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `radixor[NORWEGIAN_NYNORSK]` | 0.617 | 0.062 | 45.5 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Official Snowball direct | `snowballDirect[NORWEGIAN_NYNORSK]` | 0.955 | 0.076 | 70.4 | 1.548 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[NORWEGIAN_NYNORSK]` | 1.352 | 0.106 | 99.7 | 2.191 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | - - - +| Radixor | `radixor[NORWEGIAN_NYNORSK]` | 0.584 | 0.057 | 43.1 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Official Snowball direct | `snowballDirect[NORWEGIAN_NYNORSK]` | 1.087 | 0.097 | 80.2 | 1.861 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[NORWEGIAN_NYNORSK]` | 1.256 | 0.095 | 92.6 | 2.149 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -312,7 +306,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `NN_NO` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/persian.md b/docs/benchmarks/languages/persian.md index 8d45620..f3cc5af 100644 --- a/docs/benchmarks/languages/persian.md +++ b/docs/benchmarks/languages/persian.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `fa-ir-default` | `1.0.0` | `FA_IR` | 69 | 3,770 | 138 | 3,632 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `fa-ir-default` | `1.0.0` | `FA_IR` | 69 | 3,770 | 138 | 3,632 | 5,000 | ## Radixor Patch Command Distribution @@ -28,11 +28,9 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 95.836% | 95.677% | 100.000% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 95.836% | 95.677% | 100.000% | Radixor dictionary-trained patch-command stemmer. | | Lucene PersianStemFilter | 1.485% | 0.000% | 40.580% | Lucene Persian suffix stemmer with required normalization in the measured path. | - - - +| Official Snowball direct | 3.660% | 0.000% | 100.000% | Official Snowball 3.1.0 generated Java stemmer; rule-based suffix algorithm. | ## Speed @@ -40,15 +38,13 @@ Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 i | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `persianRadixor` | 0.243 | 0.004 | 66.9 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene PersianStemFilter | `persianLucenePersianStemFilter` | 0.469 | 0.007 | 129.1 | 1.930 | Persian suffix stemmer with Lucene normalization in the measured path. | - - - +| Radixor | `persianRadixor` | 0.230 | 0.003 | 46.0 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene PersianStemFilter | `persianLucenePersianStemFilter` | 0.448 | 0.009 | 89.5 | 1.948 | Persian suffix stemmer with Lucene normalization in the measured path. | +| Official Snowball direct | `snowballDirect[PERSIAN]` | 1.490 | 0.055 | 298.1 | 6.486 | Official Snowball 3.1.0 generated Java stemmer; direct API. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -66,11 +62,11 @@ Runtime performance and linguistic grouping quality are independent dimensions. The default model is `fa-ir-default`, loaded from classpath resource `org/egothor/stemmer/models/fa-ir-default/stemmer.gz`. The following findings compare only deterministic `PRIMARY_OUTPUT` rows over identical included groups; candidate policies are reported separately as capability analyses. -- **ALL_WORDS:** `Radixor` ranks first by balanced accuracy at **0.976360** among 2 deterministic stemmers. The runner-up is `PERSIAN LUCENE PERSIAN STEM FILTER` at 0.502212, a difference of 0.474148. This rank does not imply leadership in throughput or every secondary metric. -- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.976360** among 2 deterministic stemmers. The runner-up is `PERSIAN LUCENE PERSIAN STEM FILTER` at 0.502212, a difference of 0.474148. This rank does not imply leadership in throughput or every secondary metric. +- **ALL_WORDS:** `Radixor` ranks first by balanced accuracy at **0.976360** among 3 deterministic stemmers. The runner-up is `SNOWBALL PERSIAN DIRECT` at 0.535123, a difference of 0.441236. This rank does not imply leadership in throughput or every secondary metric. +- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.976360** among 3 deterministic stemmers. The runner-up is `SNOWBALL PERSIAN DIRECT` at 0.535123, a difference of 0.441236. This rank does not imply leadership in throughput or every secondary metric. ### `ALL_WORDS` -This mode contains **4 result rows**, **2 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. +This mode contains **5 result rows**, **3 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. #### `PRIMARY_OUTPUT` ranking @@ -79,7 +75,8 @@ This mode contains **4 result rows**, **2 evaluated stemmers**, and **3 output p | Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) | |---:|---|---:|---:|---:| |1|Radixor|0.976360|0.000000%|4.728041%| -|2|PERSIAN LUCENE PERSIAN STEM FILTER|0.502212|0.000049%|99.557494%| +|2|SNOWBALL PERSIAN DIRECT|0.535123|0.001278%|92.974054%| +|3|PERSIAN LUCENE PERSIAN STEM FILTER|0.502212|0.000049%|99.557494%| @@ -88,7 +85,8 @@ This mode contains **4 result rows**, **2 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|1.000000|0.952720|1.000000|0.976360|0.999277|0.000723| -|2|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|0.992991|0.004425|1.000000|0.502212|0.984769|0.015231| +|2|SNOWBALL PERSIAN DIRECT|PRIMARY_OUTPUT|0.988428|0.070259|0.999987|0.535123|0.985764|0.014236| +|3|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|0.992991|0.004425|1.000000|0.502212|0.984769|0.015231| @@ -97,7 +95,8 @@ This mode contains **4 result rows**, **2 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|0.990172|0.975787|0.961815|0.952720|0.976074|0.975715| -|2|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|0.021738|0.008811|0.005525|0.004425|0.066288|0.065774| +|2|SNOWBALL PERSIAN DIRECT|PRIMARY_OUTPUT|0.273526|0.131193|0.086291|0.070202|0.263527|0.261598| +|3|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|0.021738|0.008811|0.005525|0.004425|0.066288|0.065774| @@ -106,7 +105,8 @@ This mode contains **4 result rows**, **2 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|91503|0|4541|6182152|0 / 6182152|4541 / 96044| -|2|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|425|3|95619|6182149|3 / 6182152|95619 / 96044| +|2|SNOWBALL PERSIAN DIRECT|PRIMARY_OUTPUT|6748|79|89296|6182073|79 / 6182152|89296 / 96044| +|3|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|425|3|95619|6182149|3 / 6182152|95619 / 96044| @@ -174,7 +174,7 @@ Alternative candidates are capability analyses, not replacements for the determi ### `LOWERCASE_GROUPS_ONLY` -This mode contains **4 result rows**, **2 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. +This mode contains **5 result rows**, **3 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. #### `PRIMARY_OUTPUT` ranking @@ -183,7 +183,8 @@ This mode contains **4 result rows**, **2 evaluated stemmers**, and **3 output p | Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) | |---:|---|---:|---:|---:| |1|Radixor|0.976360|0.000000%|4.728041%| -|2|PERSIAN LUCENE PERSIAN STEM FILTER|0.502212|0.000049%|99.557494%| +|2|SNOWBALL PERSIAN DIRECT|0.535123|0.001278%|92.974054%| +|3|PERSIAN LUCENE PERSIAN STEM FILTER|0.502212|0.000049%|99.557494%| @@ -192,7 +193,8 @@ This mode contains **4 result rows**, **2 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|1.000000|0.952720|1.000000|0.976360|0.999277|0.000723| -|2|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|0.992991|0.004425|1.000000|0.502212|0.984769|0.015231| +|2|SNOWBALL PERSIAN DIRECT|PRIMARY_OUTPUT|0.988428|0.070259|0.999987|0.535123|0.985764|0.014236| +|3|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|0.992991|0.004425|1.000000|0.502212|0.984769|0.015231| @@ -201,7 +203,8 @@ This mode contains **4 result rows**, **2 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|0.990172|0.975787|0.961815|0.952720|0.976074|0.975715| -|2|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|0.021738|0.008811|0.005525|0.004425|0.066288|0.065774| +|2|SNOWBALL PERSIAN DIRECT|PRIMARY_OUTPUT|0.273526|0.131193|0.086291|0.070202|0.263527|0.261598| +|3|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|0.021738|0.008811|0.005525|0.004425|0.066288|0.065774| @@ -210,7 +213,8 @@ This mode contains **4 result rows**, **2 evaluated stemmers**, and **3 output p | Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible | |---:|---|---|---:|---:|---:|---:|---:|---:| |1|Radixor|PRIMARY_OUTPUT|91503|0|4541|6182152|0 / 6182152|4541 / 96044| -|2|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|425|3|95619|6182149|3 / 6182152|95619 / 96044| +|2|SNOWBALL PERSIAN DIRECT|PRIMARY_OUTPUT|6748|79|89296|6182073|79 / 6182152|89296 / 96044| +|3|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|425|3|95619|6182149|3 / 6182152|95619 / 96044| @@ -300,7 +304,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `FA_IR` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/polish.md b/docs/benchmarks/languages/polish.md index 383cac9..f60cf12 100644 --- a/docs/benchmarks/languages/polish.md +++ b/docs/benchmarks/languages/polish.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `pl-pl-unimorph` | `1.0.0` | `PL_PL` | 9,990 | 132,308 | 19,957 | 112,351 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `pl-pl-unimorph` | `1.0.0` | `PL_PL` | 9,990 | 132,308 | 19,957 | 112,351 | 112,351 | ## Radixor Patch Command Distribution @@ -30,14 +30,12 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 98.837% | 98.744% | 99.359% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 98.837% | 98.744% | 99.359% | Radixor dictionary-trained patch-command stemmer. | | Lucene HunspellStemFilter | 89.545% | 88.272% | 96.713% | Benchmark-only Polish Hunspell dictionary compared via Lucene HunspellStemFilter. | | Lucene MorfologikFilter | 87.729% | 86.606% | 94.047% | Dictionary-based path; Morfologik can emit multiple terms. | | Lucene StempelFilter | 70.009% | 69.262% | 74.220% | Lucene TokenFilter integration path for table-driven Polish Stempel. | | Lucene StempelStemmer direct | 70.009% | 69.262% | 74.220% | Direct table-driven Polish Stempel stemmer API. | - - - +| Official Snowball direct | 22.315% | 20.225% | 34.078% | Official Snowball 3.1.0 generated Java stemmer; rule-based suffix algorithm. | ## Speed @@ -45,18 +43,16 @@ Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 i | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `polishRadixor` | 8.972 | 0.203 | 79.9 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 524.081 | 35.121 | 4664.7 | 58.412 | Benchmark-only Polish Hunspell dictionary compared via Lucene HunspellStemFilter. | -| Lucene StempelStemmer direct | `polishLuceneStempelStemmerDirect` | 37.947 | 0.335 | 337.8 | 4.229 | Direct table-driven Polish Stempel stemmer API. | -| Lucene StempelFilter | `polishLuceneStempelFilter` | 43.090 | 0.411 | 383.5 | 4.803 | Lucene TokenFilter integration path for table-driven Polish Stempel. | -| Lucene MorfologikFilter | `polishLuceneMorfologikFilter` | 143.527 | 1.176 | 1277.5 | 15.997 | Dictionary-based Morfologik TokenFilter; may emit multiple terms. | - - - +| Radixor | `polishRadixor` | 8.122 | 0.146 | 72.3 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 471.669 | 26.993 | 4198.2 | 58.070 | Benchmark-only Polish Hunspell dictionary compared via Lucene HunspellStemFilter. | +| Lucene StempelStemmer direct | `polishLuceneStempelStemmerDirect` | 31.524 | 0.189 | 280.6 | 3.881 | Direct table-driven Polish Stempel stemmer API. | +| Lucene StempelFilter | `polishLuceneStempelFilter` | 39.180 | 0.362 | 348.7 | 4.824 | Lucene TokenFilter integration path for table-driven Polish Stempel. | +| Lucene MorfologikFilter | `polishLuceneMorfologikFilter` | 138.971 | 1.429 | 1236.9 | 17.110 | Dictionary-based Morfologik TokenFilter; may emit multiple terms. | +| Official Snowball direct | `snowballDirect[POLISH]` | 9.715 | 0.858 | 86.5 | 1.196 | Official Snowball 3.1.0 generated Java stemmer; direct API. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -74,11 +70,11 @@ Runtime performance and linguistic grouping quality are independent dimensions. The default model is `pl-pl-unimorph`, loaded from classpath resource `org/egothor/stemmer/models/pl-pl-unimorph/stemmer.gz`. The following findings compare only deterministic `PRIMARY_OUTPUT` rows over identical included groups; candidate policies are reported separately as capability analyses. -- **ALL_WORDS:** `Radixor` ranks first by balanced accuracy at **0.991105** among 5 deterministic stemmers. The runner-up is `POLISH LUCENE MORFOLOGIK FILTER` at 0.948392, a difference of 0.042713. This rank does not imply leadership in throughput or every secondary metric. -- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.991301** among 5 deterministic stemmers. The runner-up is `POLISH LUCENE MORFOLOGIK FILTER` at 0.948417, a difference of 0.042884. This rank does not imply leadership in throughput or every secondary metric. +- **ALL_WORDS:** `Radixor` ranks first by balanced accuracy at **0.991105** among 6 deterministic stemmers. The runner-up is `POLISH LUCENE MORFOLOGIK FILTER` at 0.948392, a difference of 0.042713. This rank does not imply leadership in throughput or every secondary metric. +- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.991301** among 6 deterministic stemmers. The runner-up is `POLISH LUCENE MORFOLOGIK FILTER` at 0.948417, a difference of 0.042884. This rank does not imply leadership in throughput or every secondary metric. ### `ALL_WORDS` -This mode contains **11 result rows**, **5 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. +This mode contains **12 result rows**, **6 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. #### `PRIMARY_OUTPUT` ranking @@ -91,6 +87,7 @@ This mode contains **11 result rows**, **5 evaluated stemmers**, and **3 output |3|HUNSPELL POLISH LUCENE FILTER|0.933457|0.000383%|13.308172%| |4|POLISH LUCENE STEMPEL DIRECT|0.855699|0.000602%|28.859618%| |5|POLISH LUCENE STEMPEL FILTER|0.855699|0.000602%|28.859618%| +|6|SNOWBALL POLISH DIRECT|0.823625|0.000967%|35.273970%| @@ -103,6 +100,7 @@ This mode contains **11 result rows**, **5 evaluated stemmers**, and **3 output |3|HUNSPELL POLISH LUCENE FILTER|PRIMARY_OUTPUT|0.971931|0.866918|0.999996|0.933457|0.999976|0.000024| |4|POLISH LUCENE STEMPEL DIRECT|PRIMARY_OUTPUT|0.947549|0.711404|0.999994|0.855699|0.999950|0.000050| |5|POLISH LUCENE STEMPEL FILTER|PRIMARY_OUTPUT|0.947549|0.711404|0.999994|0.855699|0.999950|0.000050| +|6|SNOWBALL POLISH DIRECT|PRIMARY_OUTPUT|0.910978|0.647260|0.999990|0.823625|0.999936|0.000064| @@ -115,6 +113,7 @@ This mode contains **11 result rows**, **5 evaluated stemmers**, and **3 output |3|HUNSPELL POLISH LUCENE FILTER|PRIMARY_OUTPUT|0.948942|0.916426|0.886065|0.845744|0.917924|0.917913| |4|POLISH LUCENE STEMPEL DIRECT|PRIMARY_OUTPUT|0.888559|0.812669|0.748723|0.684450|0.821030|0.821007| |5|POLISH LUCENE STEMPEL FILTER|PRIMARY_OUTPUT|0.888559|0.812669|0.748723|0.684450|0.821030|0.821007| +|6|SNOWBALL POLISH DIRECT|PRIMARY_OUTPUT|0.842338|0.756803|0.687038|0.608756|0.767880|0.767852| @@ -127,6 +126,7 @@ This mode contains **11 result rows**, **5 evaluated stemmers**, and **3 output |3|HUNSPELL POLISH LUCENE FILTER|PRIMARY_OUTPUT|968411|27967|148662|7303210371|27967 / 7303238338|148662 / 1117073| |4|POLISH LUCENE STEMPEL DIRECT|PRIMARY_OUTPUT|794690|43990|322383|7303194348|43990 / 7303238338|322383 / 1117073| |5|POLISH LUCENE STEMPEL FILTER|PRIMARY_OUTPUT|794690|43990|322383|7303194348|43990 / 7303238338|322383 / 1117073| +|6|SNOWBALL POLISH DIRECT|PRIMARY_OUTPUT|723037|70656|394036|7303167682|70656 / 7303238338|394036 / 1117073| @@ -208,7 +208,7 @@ Alternative candidates are capability analyses, not replacements for the determi ### `LOWERCASE_GROUPS_ONLY` -This mode contains **11 result rows**, **5 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. +This mode contains **12 result rows**, **6 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score. #### `PRIMARY_OUTPUT` ranking @@ -221,6 +221,7 @@ This mode contains **11 result rows**, **5 evaluated stemmers**, and **3 output |3|HUNSPELL POLISH LUCENE FILTER|0.933546|0.000382%|13.290396%| |4|POLISH LUCENE STEMPEL DIRECT|0.856335|0.000611%|28.732387%| |5|POLISH LUCENE STEMPEL FILTER|0.856335|0.000611%|28.732387%| +|6|SNOWBALL POLISH DIRECT|0.823465|0.000990%|35.306102%| @@ -233,6 +234,7 @@ This mode contains **11 result rows**, **5 evaluated stemmers**, and **3 output |3|HUNSPELL POLISH LUCENE FILTER|PRIMARY_OUTPUT|0.972469|0.867096|0.999996|0.933546|0.999975|0.000025| |4|POLISH LUCENE STEMPEL DIRECT|PRIMARY_OUTPUT|0.947796|0.712676|0.999994|0.856335|0.999949|0.000051| |5|POLISH LUCENE STEMPEL FILTER|PRIMARY_OUTPUT|0.947796|0.712676|0.999994|0.856335|0.999949|0.000051| +|6|SNOWBALL POLISH DIRECT|PRIMARY_OUTPUT|0.910487|0.646939|0.999990|0.823465|0.999935|0.000065| @@ -245,6 +247,7 @@ This mode contains **11 result rows**, **5 evaluated stemmers**, and **3 output |3|HUNSPELL POLISH LUCENE FILTER|PRIMARY_OUTPUT|0.949394|0.916764|0.886303|0.846320|0.918272|0.918260| |4|POLISH LUCENE STEMPEL DIRECT|PRIMARY_OUTPUT|0.889130|0.813590|0.749881|0.685758|0.821871|0.821848| |5|POLISH LUCENE STEMPEL FILTER|PRIMARY_OUTPUT|0.889130|0.813590|0.749881|0.685758|0.821871|0.821848| +|6|SNOWBALL POLISH DIRECT|PRIMARY_OUTPUT|0.841894|0.756414|0.686693|0.608253|0.767483|0.767454| @@ -257,6 +260,7 @@ This mode contains **11 result rows**, **5 evaluated stemmers**, and **3 output |3|HUNSPELL POLISH LUCENE FILTER|PRIMARY_OUTPUT|963133|27267|147624|7133072951|27267 / 7133100218|147624 / 1110757| |4|POLISH LUCENE STEMPEL DIRECT|PRIMARY_OUTPUT|791610|43601|319147|7133056617|43601 / 7133100218|319147 / 1110757| |5|POLISH LUCENE STEMPEL FILTER|PRIMARY_OUTPUT|791610|43601|319147|7133056617|43601 / 7133100218|319147 / 1110757| +|6|SNOWBALL POLISH DIRECT|PRIMARY_OUTPUT|718592|70647|392165|7133029571|70647 / 7133100218|392165 / 1110757| @@ -360,7 +364,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `PL_PL` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/portuguese.md b/docs/benchmarks/languages/portuguese.md index a1e2dd5..c1d440e 100644 --- a/docs/benchmarks/languages/portuguese.md +++ b/docs/benchmarks/languages/portuguese.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `pt-pt-default` | `1.0.0` | `PT_PT` | 4,001 | 215,490 | 8,002 | 207,488 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `pt-pt-default` | `1.0.0` | `PT_PT` | 4,001 | 215,490 | 8,002 | 207,488 | 207,488 | ## Radixor Patch Command Distribution @@ -30,35 +30,29 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 99.815% | 99.808% | 100.000% | Full Radixor dictionary patch-command stemmer. | -| Lucene PortugueseLightStemFilter | 8.966% | 5.558% | 97.326% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. | +| Radixor | 99.815% | 99.808% | 100.000% | Radixor dictionary-trained patch-command stemmer. | +| Lucene PortugueseLightStemFilter | 8.966% | 5.558% | 97.326% | Light suffix stemmer; intentionally narrower than Radixor's dictionary-trained transformation model. | | Lucene PortugueseMinimalStemFilter | 5.539% | 1.896% | 100.000% | Minimal suffix reducer; narrow baseline, not a full stemmer. | | Lucene SnowballFilter | 0.625% | 0.558% | 2.374% | Lucene TokenFilter integration path around the Snowball algorithm. | | Official Snowball direct | 0.625% | 0.558% | 2.374% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | | Lucene PortugueseStemFilter | 0.312% | 0.308% | 0.425% | Portuguese RSLP-style Lucene TokenFilter stemmer. | - - - ## Speed Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower. | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `portugueseRadixor` | 12.301 | 0.252 | 59.3 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene PortugueseLightStemFilter | `portugueseLucenePortugueseLightStemFilter` | 11.409 | 0.151 | 55.0 | 0.927 | Light Portuguese suffix stemmer. | -| Lucene PortugueseMinimalStemFilter | `portugueseLucenePortugueseMinimalStemFilter` | 15.619 | 0.084 | 75.3 | 1.270 | Minimal Portuguese suffix reducer. | -| Official Snowball direct | `snowballDirect[PORTUGUESE]` | 57.577 | 1.591 | 277.5 | 4.681 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[PORTUGUESE]` | 63.403 | 2.720 | 305.6 | 5.154 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | -| Lucene PortugueseStemFilter | `portugueseLucenePortugueseStemFilter` | 158.014 | 5.150 | 761.6 | 12.845 | Portuguese RSLP-style Lucene TokenFilter. | - - - +| Radixor | `portugueseRadixor` | 10.902 | 0.166 | 52.5 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene PortugueseLightStemFilter | `portugueseLucenePortugueseLightStemFilter` | 10.125 | 0.161 | 48.8 | 0.929 | Light Portuguese suffix stemmer. | +| Lucene PortugueseMinimalStemFilter | `portugueseLucenePortugueseMinimalStemFilter` | 14.338 | 0.156 | 69.1 | 1.315 | Minimal Portuguese suffix reducer. | +| Official Snowball direct | `snowballDirect[PORTUGUESE]` | 52.193 | 1.905 | 251.5 | 4.788 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[PORTUGUESE]` | 58.991 | 2.457 | 284.3 | 5.411 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | +| Lucene PortugueseStemFilter | `portugueseLucenePortugueseStemFilter` | 164.457 | 8.688 | 792.6 | 15.085 | Portuguese RSLP-style Lucene TokenFilter. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -342,7 +336,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `PT_PT` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/russian.md b/docs/benchmarks/languages/russian.md index 058d3f3..a2366ae 100644 --- a/docs/benchmarks/languages/russian.md +++ b/docs/benchmarks/languages/russian.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `ru-ru-default` | `1.0.0` | `RU_RU` | 37,410 | 806,279 | 74,808 | 731,471 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `ru-ru-default` | `1.0.0` | `RU_RU` | 37,410 | 806,279 | 74,808 | 731,471 | 731,471 | ## Radixor Patch Command Distribution @@ -30,31 +30,25 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 98.807% | 98.696% | 99.896% | Full Radixor dictionary patch-command stemmer. | -| Lucene RussianLightStemFilter | 9.658% | 8.452% | 21.447% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. | +| Radixor | 98.807% | 98.696% | 99.896% | Radixor dictionary-trained patch-command stemmer. | +| Lucene RussianLightStemFilter | 9.658% | 8.452% | 21.447% | Light suffix stemmer; intentionally narrower than Radixor's dictionary-trained transformation model. | | Lucene SnowballFilter | 9.162% | 8.162% | 18.936% | Lucene TokenFilter integration path around the Snowball algorithm. | | Official Snowball direct | 9.162% | 8.162% | 18.936% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | - - - ## Speed Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower. | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `russianRadixor` | 90.151 | 1.796 | 123.2 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene RussianLightStemFilter | `russianLuceneRussianLightStemFilter` | 59.456 | 2.102 | 81.3 | 0.660 | Light Russian suffix stemmer. | -| Official Snowball direct | `snowballDirect[RUSSIAN]` | 102.353 | 1.669 | 139.9 | 1.135 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[RUSSIAN]` | 138.597 | 4.727 | 189.5 | 1.537 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | - - - +| Radixor | `russianRadixor` | 72.723 | 1.809 | 99.4 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene RussianLightStemFilter | `russianLuceneRussianLightStemFilter` | 58.844 | 3.217 | 80.4 | 0.809 | Light Russian suffix stemmer. | +| Official Snowball direct | `snowballDirect[RUSSIAN]` | 103.471 | 8.136 | 141.5 | 1.423 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[RUSSIAN]` | 130.783 | 3.979 | 178.8 | 1.798 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -322,7 +316,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `RU_RU` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/spanish.md b/docs/benchmarks/languages/spanish.md index 5f820af..77f3212 100644 --- a/docs/benchmarks/languages/spanish.md +++ b/docs/benchmarks/languages/spanish.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `es-es-default` | `1.0.0` | `ES_ES` | 65,059 | 926,393 | 120,121 | 806,272 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `es-es-default` | `1.0.0` | `ES_ES` | 65,059 | 926,393 | 120,121 | 806,272 | 806,272 | ## Radixor Patch Command Distribution @@ -30,37 +30,31 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 97.459% | 97.544% | 96.891% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 97.459% | 97.544% | 96.891% | Radixor dictionary-trained patch-command stemmer. | | Lucene HunspellStemFilter | 49.074% | 42.656% | 92.154% | Benchmark-only Spanish Hunspell dictionary compared via Lucene HunspellStemFilter. | | Lucene SpanishMinimalStemFilter | 17.284% | 5.347% | 97.403% | Minimal suffix reducer; narrow baseline, not a full stemmer. | | Lucene SpanishPluralStemFilter | 15.140% | 5.802% | 77.820% | Plural-focused suffix reducer; narrow baseline. | -| Lucene SpanishLightStemFilter | 9.577% | 7.088% | 26.279% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. | +| Lucene SpanishLightStemFilter | 9.577% | 7.088% | 26.279% | Light suffix stemmer; intentionally narrower than Radixor's dictionary-trained transformation model. | | Lucene SnowballFilter | 4.889% | 4.287% | 8.932% | Lucene TokenFilter integration path around the Snowball algorithm. | | Official Snowball direct | 4.889% | 4.287% | 8.930% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | - - - ## Speed Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower. | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `spanishRadixor` | 81.605 | 1.347 | 101.2 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 2033.430 | 14.863 | 2522.0 | 24.918 | Benchmark-only Spanish Hunspell dictionary compared via Lucene HunspellStemFilter. | -| Lucene SpanishMinimalStemFilter | `spanishLuceneSpanishMinimalStemFilter` | 42.144 | 1.556 | 52.3 | 0.516 | Minimal Spanish suffix reducer; narrow baseline. | -| Lucene SpanishLightStemFilter | `spanishLuceneSpanishLightStemFilter` | 44.479 | 1.291 | 55.2 | 0.545 | Light Spanish suffix stemmer. | -| Lucene SpanishPluralStemFilter | `spanishLuceneSpanishPluralStemFilter` | 96.537 | 3.418 | 119.7 | 1.183 | Plural-oriented Spanish suffix reducer. | -| Official Snowball direct | `snowballDirect[SPANISH]` | 172.151 | 7.261 | 213.5 | 2.110 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[SPANISH]` | 201.697 | 9.363 | 250.2 | 2.472 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | - - - +| Radixor | `spanishRadixor` | 63.164 | 1.885 | 78.3 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 1936.800 | 18.685 | 2402.2 | 30.663 | Benchmark-only Spanish Hunspell dictionary compared via Lucene HunspellStemFilter. | +| Lucene SpanishMinimalStemFilter | `spanishLuceneSpanishMinimalStemFilter` | 40.414 | 1.475 | 50.1 | 0.640 | Minimal Spanish suffix reducer; narrow baseline. | +| Lucene SpanishLightStemFilter | `spanishLuceneSpanishLightStemFilter` | 43.922 | 1.497 | 54.5 | 0.695 | Light Spanish suffix stemmer. | +| Lucene SpanishPluralStemFilter | `spanishLuceneSpanishPluralStemFilter` | 89.799 | 3.474 | 111.4 | 1.422 | Plural-oriented Spanish suffix reducer. | +| Official Snowball direct | `snowballDirect[SPANISH]` | 192.868 | 11.684 | 239.2 | 3.053 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[SPANISH]` | 182.719 | 6.957 | 226.6 | 2.893 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -366,7 +360,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `ES_ES` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/swedish.md b/docs/benchmarks/languages/swedish.md index d686779..750126c 100644 --- a/docs/benchmarks/languages/swedish.md +++ b/docs/benchmarks/languages/swedish.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `sv-se-default` | `1.0.0` | `SV_SE` | 12,371 | 110,468 | 24,731 | 85,737 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `sv-se-default` | `1.0.0` | `SV_SE` | 12,371 | 110,468 | 24,731 | 85,737 | 85,737 | ## Radixor Patch Command Distribution @@ -30,33 +30,27 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 96.713% | 97.407% | 94.307% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 96.713% | 97.407% | 94.307% | Radixor dictionary-trained patch-command stemmer. | | Lucene SwedishMinimalStemFilter | 49.532% | 49.186% | 50.730% | Minimal suffix reducer; narrow baseline, not a full stemmer. | -| Lucene SwedishLightStemFilter | 45.672% | 46.383% | 43.209% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. | +| Lucene SwedishLightStemFilter | 45.672% | 46.383% | 43.209% | Light suffix stemmer; intentionally narrower than Radixor's dictionary-trained transformation model. | | Official Snowball direct | 40.068% | 37.512% | 48.926% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | | Lucene SnowballFilter | 38.785% | 35.839% | 48.999% | Lucene TokenFilter integration path around the Snowball algorithm. | - - - ## Speed Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower. | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `swedishRadixor` | 5.476 | 0.081 | 63.9 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene SwedishMinimalStemFilter | `swedishLuceneSwedishMinimalStemFilter` | 4.741 | 0.086 | 55.3 | 0.866 | Minimal Swedish suffix reducer. | -| Lucene SwedishLightStemFilter | `swedishLuceneSwedishLightStemFilter` | 4.893 | 0.053 | 57.1 | 0.893 | Light Swedish suffix stemmer. | -| Official Snowball direct | `snowballDirect[SWEDISH]` | 7.606 | 0.555 | 88.7 | 1.389 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[SWEDISH]` | 10.295 | 0.749 | 120.1 | 1.880 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | - - - +| Radixor | `swedishRadixor` | 5.078 | 0.104 | 59.2 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene SwedishMinimalStemFilter | `swedishLuceneSwedishMinimalStemFilter` | 4.417 | 0.061 | 51.5 | 0.870 | Minimal Swedish suffix reducer. | +| Lucene SwedishLightStemFilter | `swedishLuceneSwedishLightStemFilter` | 5.090 | 0.373 | 59.4 | 1.002 | Light Swedish suffix stemmer. | +| Official Snowball direct | `snowballDirect[SWEDISH]` | 7.497 | 0.653 | 87.4 | 1.476 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[SWEDISH]` | 9.831 | 0.648 | 114.7 | 1.936 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -332,7 +326,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `SV_SE` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/ukrainian.md b/docs/benchmarks/languages/ukrainian.md index a6dee0b..d362bce 100644 --- a/docs/benchmarks/languages/ukrainian.md +++ b/docs/benchmarks/languages/ukrainian.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `uk-ua-default` | `1.0.0` | `UK_UA` | 1,493 | 15,737 | 2,985 | 12,752 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `uk-ua-default` | `1.0.0` | `UK_UA` | 1,493 | 15,737 | 2,985 | 12,752 | 12,752 | ## Radixor Patch Command Distribution @@ -30,31 +30,25 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 99.307% | 99.365% | 99.062% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 99.307% | 99.365% | 99.062% | Radixor dictionary-trained patch-command stemmer. | | Lucene HunspellStemFilter | 86.815% | 83.759% | 99.866% | Benchmark-only Ukrainian Hunspell dictionary compared via Lucene HunspellStemFilter. | | Lucene MorfologikFilter | 92.362% | 90.637% | 99.732% | Dictionary-based path; Morfologik can emit multiple terms. | | Morfologik direct | 92.362% | 90.637% | 99.732% | Direct dictionary lookup; first returned stem is used for quality when no ranking weight is exposed. | - - - ## Speed Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower. | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `ukrainianRadixor` | 0.639 | 0.009 | 50.1 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 47.919 | 5.308 | 3757.8 | 74.957 | Benchmark-only Ukrainian Hunspell dictionary compared via Lucene HunspellStemFilter. | -| Morfologik direct | `ukrainianMorfologikDirect` | 8.662 | 0.105 | 679.3 | 13.550 | Direct Morfologik dictionary lookup; first returned stem is used for quality. | -| Lucene MorfologikFilter | `ukrainianLuceneMorfologikFilter` | 15.367 | 0.219 | 1205.1 | 24.038 | Dictionary-based Morfologik TokenFilter; may emit multiple terms. | - - - +| Radixor | `ukrainianRadixor` | 0.594 | 0.010 | 46.6 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 39.820 | 3.772 | 3122.6 | 67.067 | Benchmark-only Ukrainian Hunspell dictionary compared via Lucene HunspellStemFilter. | +| Morfologik direct | `ukrainianMorfologikDirect` | 8.231 | 0.121 | 645.5 | 13.863 | Direct Morfologik dictionary lookup; first returned stem is used for quality. | +| Lucene MorfologikFilter | `ukrainianLuceneMorfologikFilter` | 14.700 | 0.176 | 1152.8 | 24.758 | Dictionary-based Morfologik TokenFilter; may emit multiple terms. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -364,7 +358,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `UK_UA` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/languages/yiddish.md b/docs/benchmarks/languages/yiddish.md index 3647d1b..c477b81 100644 --- a/docs/benchmarks/languages/yiddish.md +++ b/docs/benchmarks/languages/yiddish.md @@ -8,9 +8,9 @@ Radixor must not be read as simply "slower" when a narrow competitor has a lower ## Dictionary Corpus -| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens | -| --- | --- | --- | ---: | ---: | ---: | ---: | -| `yi-default` | `1.0.0` | `YI` | 802 | 4,300 | 1,524 | 2,776 | +| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens | +| --- | --- | --- | ---: | ---: | ---: | ---: | ---: | +| `yi-default` | `1.0.0` | `YI` | 802 | 4,300 | 1,524 | 2,776 | 5,000 | ## Radixor Patch Command Distribution @@ -29,29 +29,23 @@ Accuracy is computed from JMH auxiliary counters in the current report. The coun | Stemmer | All exact | Changed exact | Root preserved | Note | | --- | ---: | ---: | ---: | --- | -| Radixor | 98.930% | 98.343% | 100.000% | Full Radixor dictionary patch-command stemmer. | +| Radixor | 98.930% | 98.343% | 100.000% | Radixor dictionary-trained patch-command stemmer. | | Lucene SnowballFilter | 2.837% | 2.558% | 3.346% | Lucene TokenFilter integration path around the Snowball algorithm. | | Official Snowball direct | 2.837% | 2.558% | 3.346% | Official Snowball generated Java stemmer; rule-based suffix algorithm. | - - - ## Speed Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower. | Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | | --- | --- | ---: | ---: | ---: | ---: | --- | -| Radixor | `radixor[YIDDISH]` | 0.249 | 0.001 | 89.6 | 1.000 | Full Radixor dictionary patch-command stemmer. | -| Official Snowball direct | `snowballDirect[YIDDISH]` | 1.574 | 0.066 | 567.1 | 6.330 | Official Snowball generated Java stemmer; direct API. | -| Lucene SnowballFilter | `luceneSnowballFilter[YIDDISH]` | 1.849 | 0.079 | 665.9 | 7.434 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | - - - +| Radixor | `radixor[YIDDISH]` | 0.234 | 0.001 | 46.8 | 1.000 | Radixor dictionary-trained patch-command stemmer. | +| Official Snowball direct | `snowballDirect[YIDDISH]` | 1.487 | 0.062 | 297.5 | 6.354 | Official Snowball generated Java stemmer; direct API. | +| Lucene SnowballFilter | `luceneSnowballFilter[YIDDISH]` | 1.754 | 0.070 | 350.8 | 7.492 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. | ## Interpretation Notes -- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie. +- Radixor is a dictionary-trained patch-command stemmer. Its learned transformations can generalize beyond the word forms listed in the training resource. - Light, minimal, plural, and possessive filters are narrow baselines. They can be fast because they intentionally perform less linguistic work. - Lucene TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs. - Morfologik rows are dictionary-based and can emit multiple terms for one input token. Quality rows use the first returned term when no ranking weight is available. @@ -311,7 +305,7 @@ Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: ### Provenance - Authoritative source: `docs/benchmarks/data/stemming-quality.csv` -- Source SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` +- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` - Evaluation command: `./gradlew stemmingQuality --no-daemon` - Dictionary language: `YI` - Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY` diff --git a/docs/benchmarks/reference/english-coverage.md b/docs/benchmarks/reference/english-coverage.md index 18dd028..2fc7f02 100644 --- a/docs/benchmarks/reference/english-coverage.md +++ b/docs/benchmarks/reference/english-coverage.md @@ -6,16 +6,16 @@ This benchmark is the clearest demonstration of the Radixor quality/speed envelo | Used rows | Actual row ratio | All exact | Changed exact | Root preserved | Speed ms/op | Error ms | ns/token | | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | -| 100% | 100.000% | 97.478% | 97.197% | 97.552% | 20.627 | 2.117 | 98.0 | -| 90% | 90.000% | 97.047% | 94.913% | 97.613% | 21.713 | 2.104 | 103.2 | -| 80% | 80.000% | 96.635% | 92.768% | 97.661% | 17.408 | 1.438 | 82.7 | -| 70% | 70.000% | 96.209% | 90.565% | 97.705% | 16.946 | 1.531 | 80.5 | -| 60% | 60.000% | 95.750% | 88.384% | 97.703% | 15.735 | 1.278 | 74.8 | -| 50% | 50.000% | 95.262% | 86.107% | 97.690% | 14.714 | 1.089 | 69.9 | -| 40% | 40.000% | 94.753% | 83.855% | 97.643% | 15.090 | 1.254 | 71.7 | -| 30% | 30.000% | 94.208% | 81.651% | 97.537% | 13.773 | 1.071 | 65.4 | -| 20% | 20.000% | 93.633% | 79.366% | 97.416% | 15.396 | 2.497 | 73.1 | -| 10% | 10.000% | 92.868% | 76.516% | 97.204% | 16.970 | 2.847 | 80.6 | +| 100% | 100.000% | 97.478% | 97.197% | 97.552% | 15.064 | 0.658 | 71.6 | +| 90% | 90.000% | 97.047% | 94.913% | 97.613% | 17.798 | 2.161 | 84.6 | +| 80% | 80.000% | 96.635% | 92.768% | 97.661% | 13.900 | 0.941 | 66.0 | +| 70% | 70.000% | 96.209% | 90.565% | 97.705% | 14.809 | 1.376 | 70.3 | +| 60% | 60.000% | 95.750% | 88.384% | 97.703% | 13.186 | 0.930 | 62.6 | +| 50% | 50.000% | 95.262% | 86.107% | 97.690% | 12.852 | 0.943 | 61.1 | +| 40% | 40.000% | 94.753% | 83.855% | 97.643% | 12.358 | 0.831 | 58.7 | +| 30% | 30.000% | 94.208% | 81.651% | 97.537% | 11.657 | 0.921 | 55.4 | +| 20% | 20.000% | 93.633% | 79.366% | 97.416% | 11.494 | 1.256 | 54.6 | +| 10% | 10.000% | 92.868% | 76.516% | 97.204% | 9.895 | 0.925 | 47.0 | ## Column Meanings diff --git a/docs/benchmarks/reference/environment.md b/docs/benchmarks/reference/environment.md index aec3799..5bc3323 100644 --- a/docs/benchmarks/reference/environment.md +++ b/docs/benchmarks/reference/environment.md @@ -4,25 +4,25 @@ The values below are environment-specific and must not be read as universal perf | Item | Value | | --- | --- | -| Benchmark date | 2026-07-23 (Europe/Prague) | +| Benchmark date | 2026-08-10 (Europe/Prague) | | Corpus command | `./gradlew benchmarkCorpusReport --no-daemon` | -| Exact-root accuracy command | Direct JMH execution of the four `*BenchmarkQuality` classes selected in `stemmer-accuracy-2026-07-23.txt`; timing scores are discarded | +| Exact-root accuracy command | `tools/run-published-accuracy-benchmarks.sh 2026-08-10`; all four `*BenchmarkQuality` classes are selected and timing scores are discarded | | Stemming-quality command | `./gradlew stemmingQuality --no-daemon` | -| Published speed command | `tools/run-published-speed-benchmarks.sh 2026-07-23` | -| Published speed run interval | 2026-07-23 12:58:50 to 15:15:43 Europe/Prague (2 h 16 min 53 s, including idle intervals and both JMH suites) | +| Published speed command | `tools/run-published-speed-benchmarks.sh 2026-08-10` | +| Published speed run interval | 2026-08-10 16:22:33 to 18:36:41 Europe/Prague (2 h 14 min 8 s, including idle intervals and both JMH suites) | | Stabilization intervals | 120 s before the main speed matrix; 60 s between the main matrix and coverage-speed suite | | Corpus and command report | `build/reports/jmh/benchmark-corpora.csv` | -| Exact-root reports | `build/reports/jmh/stemmer-accuracy-2026-07-23.csv` and `.txt` | -| Speed reports | `build/reports/jmh/stemmer-speed-2026-07-23.csv` and `.txt` | -| English coverage accuracy reports | `build/reports/jmh/english-coverage-accuracy-2026-07-23.csv` and `.txt` | -| English coverage speed reports | `build/reports/jmh/english-coverage-speed-2026-07-23.csv` and `.txt` | +| Exact-root reports | `build/reports/jmh/stemmer-accuracy-2026-08-10.csv` and `.txt` | +| Speed reports | `build/reports/jmh/stemmer-speed-2026-08-10.csv` and `.txt` | +| English coverage accuracy reports | `build/reports/jmh/english-coverage-accuracy-2026-08-10.csv` and `.txt` | +| English coverage speed reports | `build/reports/jmh/english-coverage-speed-2026-08-10.csv` and `.txt` | | Stemming-quality reports | `build/reports/stemming-quality/stemming-quality.csv` and `.md` | -| Environment report | `build/reports/jmh/performance-environment-2026-07-23.txt` | -| Selected speed methods | `build/reports/jmh/published-speed-benchmarks-2026-07-23.txt` | +| Environment report | `build/reports/jmh/performance-environment-2026-08-10.txt` | +| Selected speed methods | `build/reports/jmh/published-speed-benchmarks-2026-08-10.txt` | | Comparison scope | Same-language methods used by the 20 language pages; `PolishPolimorfStemmerComparisonBenchmark`, all quality methods, the separate CISTEM gold-standard experiment, and internal trie microbenchmarks are excluded | | Model scope | Exactly the 20 IDs declared by `Language.defaultModelId()`; Polish uses `pl-pl-unimorph`, and `pl-pl-polimorf` is not measured | -| Core base commit | `1f1b03c6a8d36a0918b92ebde698e5379a2a5946` | -| Measured source state | `release@4.0.0-dirty`; exact tracked changes and untracked-source checksums are retained as `measured-source-2026-07-23.patch` and `measured-untracked-2026-07-23.sha256` | +| Core base commit | `b45e143c8484c2ae1d6e31069a2e67232c4f7f29` | +| Measured source state | `release@4.1.0-dirty`; exact tracked changes and untracked-source checksums are retained as `measured-source-2026-08-10.patch` and `measured-untracked-2026-08-10.sha256` | | JMH version | 1.37 | | Speed benchmark mode | Average time, `time/op` | | Score unit | `ns/op`; language pages additionally derive `ms/op` and `ns/token` | @@ -32,21 +32,21 @@ The values below are environment-specific and must not be read as universal perf | Speed threads | 1 | | Speed fork heap | Fixed `-Xms6g -Xmx6g` | | Reported uncertainty | JMH `Score Error (99.9%)` over 30 measured samples | -| Observed relative uncertainty | Main speed matrix: maximum 11.945%, with 6 of 102 rows above 10%; coverage-speed curve: maximum 16.775%, with 3 of 10 rows above 10%; no published row exceeded 20% | +| Observed relative uncertainty | Main speed matrix: maximum 10.607%, with 2 of 105 rows above 10%; coverage-speed curve: maximum 12.142%, with 2 of 10 rows above 10%; no published row exceeded 20% | | Deterministic measurements | Corpus, patch-command distribution, exact-root counters, coverage accuracy, and pairwise stemming quality are evaluated without interpreting runtime scores; no warmup is required | -| JVM reported by JMH | JDK 25.0.3, OpenJDK 64-Bit Server VM, 25.0.3+9 | -| Java runtime | OpenJDK Runtime Environment, Red Hat build 25.0.3+9 | +| JVM reported by JMH | JDK 25.0.4, OpenJDK 64-Bit Server VM, 25.0.4+7 | +| Java runtime | OpenJDK Runtime Environment, Red Hat build 25.0.4+7 | | JVM invoker | `/usr/lib/jvm/java-25-openjdk/bin/java` | | Operating system | Fedora Linux 44 (MATE-Compiz) | -| Kernel | Linux 7.1.4-200.fc44.x86_64 | +| Kernel | Linux 7.1.7-200.fc44.x86_64 | | Architecture | x86_64 | -| CPU | AMD Ryzen 5 8600G w/ Radeon 760M Graphics | +| CPU | AMD Ryzen 5 7600 6-Core Processor | | Physical / logical CPUs | 6 / 12 | | CPU frequency policy | `amd-pstate-epp`; governor `performance` on every logical CPU; EPP `performance`; boost enabled | | CPU affinity | Scheduler default; no explicit pinning | -| Installed memory | 60 GiB reported by the operating system | -| Pre-run idle state | Load average 0.25 / 0.36 / 0.71 after the 120 s idle interval; CPU Tctl 40.2 degrees Celsius; swap unused | -| End-of-run state | Load average 1.16 / 1.28 / 1.32; CPU Tctl 60.5 degrees Celsius | +| Installed memory | 61 GiB reported by the operating system | +| Pre-run idle state | Load average 0.16 / 0.42 / 0.88 after the 120 s idle interval; CPU Tctl 57.4 degrees Celsius; swap unused | +| End-of-run state | Load average 1.59 / 1.49 / 1.36; CPU Tctl 78.1 degrees Celsius | | Power and idle policy | Developer workstation on stable power; screensaver, suspend, and hibernation disabled | | Concurrent project work | None during the published speed and coverage-speed run | @@ -67,15 +67,15 @@ The JMH runtime classpath contains the optional model artifact because it is a s Generated local report files for this benchmark update: - `build/reports/jmh/benchmark-corpora.csv` -- `build/reports/jmh/stemmer-accuracy-2026-07-23.csv` -- `build/reports/jmh/stemmer-accuracy-2026-07-23.txt` -- `build/reports/jmh/stemmer-speed-2026-07-23.csv` -- `build/reports/jmh/stemmer-speed-2026-07-23.txt` -- `build/reports/jmh/english-coverage-accuracy-2026-07-23.csv` -- `build/reports/jmh/english-coverage-accuracy-2026-07-23.txt` -- `build/reports/jmh/english-coverage-speed-2026-07-23.csv` -- `build/reports/jmh/english-coverage-speed-2026-07-23.txt` -- `build/reports/jmh/performance-environment-2026-07-23.txt` +- `build/reports/jmh/stemmer-accuracy-2026-08-10.csv` +- `build/reports/jmh/stemmer-accuracy-2026-08-10.txt` +- `build/reports/jmh/stemmer-speed-2026-08-10.csv` +- `build/reports/jmh/stemmer-speed-2026-08-10.txt` +- `build/reports/jmh/english-coverage-accuracy-2026-08-10.csv` +- `build/reports/jmh/english-coverage-accuracy-2026-08-10.txt` +- `build/reports/jmh/english-coverage-speed-2026-08-10.csv` +- `build/reports/jmh/english-coverage-speed-2026-08-10.txt` +- `build/reports/jmh/performance-environment-2026-08-10.txt` - `build/reports/stemming-quality/stemming-quality.csv` - `build/reports/stemming-quality/stemming-quality.md` - `build/reports/stemming-quality/metric-correlations-pearson.csv` diff --git a/docs/benchmarks/reference/methodology.md b/docs/benchmarks/reference/methodology.md index 130b5c2..02486d8 100644 --- a/docs/benchmarks/reference/methodology.md +++ b/docs/benchmarks/reference/methodology.md @@ -2,7 +2,7 @@ The stemmer comparison suite measures Radixor and Java stemmers on the same language and deterministic Radixor model dictionary-derived data. Published Radixor rows in this refresh use contracted compiled patch tries, where uniform preferred-command subtrees are collapsed into accepting leaves before the trie is frozen for lookup. For each language, the registered default model resource stores the expected root as the first tab-separated field on a line and its surface forms on the same line. Every single-token field on that line can therefore be paired with the same expected root. -Published speed results come only from the exact method selection retained in `published-speed-benchmarks-2026-07-23.txt`. Internal `FrequencyTrie*` microbenchmarks, quality methods, the CISTEM gold-standard experiment, and the optional `PolishPolimorfStemmerComparisonBenchmark` are not part of those results. +Published speed results come only from the exact method selection retained in `published-speed-benchmarks-2026-08-10.txt`. Internal `FrequencyTrie*` microbenchmarks, quality methods, the CISTEM gold-standard experiment, and the optional `PolishPolimorfStemmerComparisonBenchmark` are not part of those results. The Snowball 3.1.0 refresh adds direct Czech, Persian, and Polish workloads; the existing Radixor and Lucene workload domains are unchanged. ## Benchmark Passes @@ -41,8 +41,8 @@ For right-to-left Radixor languages, patch application uses the traversal direct The quality pass reports exact-root agreement against the expected root from the default-model dictionary line. External-stemmer counters are written to: -- `build/reports/jmh/stemmer-accuracy-2026-07-23.csv` -- `build/reports/jmh/stemmer-accuracy-2026-07-23.txt` +- `build/reports/jmh/stemmer-accuracy-2026-08-10.csv` +- `build/reports/jmh/stemmer-accuracy-2026-08-10.txt` Accuracy is computed from standard JMH secondary rows: diff --git a/docs/benchmarks/reference/reproducibility.md b/docs/benchmarks/reference/reproducibility.md index 2571e48..e1b4d8e 100644 --- a/docs/benchmarks/reference/reproducibility.md +++ b/docs/benchmarks/reference/reproducibility.md @@ -4,19 +4,28 @@ - Machine-readable CSV: [stemming-quality.csv](../data/stemming-quality.csv) - SHA-256 record: [stemming-quality.sha256](../data/stemming-quality.sha256) -- SHA-256: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8` -- Complete scenarios: 308 +- SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5` +- Complete scenarios: 314 - Authoritative language universe: 20 languages -- Language-page scenarios: 308 across 20 benchmark pages +- Language-page scenarios: 314 across 20 benchmark pages The CSV contains the model ID, independent model version, descriptor SHA-256, raw pair counts, raw over/under numerators and denominators, candidate statistics, and relation metrics. Reserved partition-metric columns remain empty because the gold standard is an overlapping cover. Documentation is regenerated from this file rather than manually transcribed. Publication fails when any row uses a model other than the language's registered default. ## Commands ```bash -./gradlew stemmingQuality -./gradlew publishStemmingQualityDocumentation -./gradlew verifyStemmingQualityDocumentation +./gradlew --no-daemon stemmingQuality \ + publishStemmingQualityDocumentation \ + verifyStemmingQualityDocumentation +./gradlew --no-daemon benchmarkCorpusReport writeJmhRuntimeClasspath +tools/run-published-accuracy-benchmarks.sh 2026-08-10 +tools/run-published-speed-benchmarks.sh 2026-08-10 +python3 tools/update-benchmark-documentation.py \ + --corpus build/reports/jmh/benchmark-corpora.csv \ + --accuracy build/reports/jmh/stemmer-accuracy-2026-08-10.csv \ + --speed build/reports/jmh/stemmer-speed-2026-08-10.csv \ + --coverage-accuracy build/reports/jmh/english-coverage-accuracy-2026-08-10.csv \ + --coverage-speed build/reports/jmh/english-coverage-speed-2026-08-10.csv ./gradlew test ./gradlew prepareMkDocsSource mkdocs build --strict --config-file build/mkdocs/mkdocs.yml @@ -59,18 +68,19 @@ The Pages workflow publishes that staged documentation together with Javadoc, JU ## Performance benchmark reproduction -The current speed and coverage-speed command is: +The current accuracy, speed, and coverage commands are: ```bash -./gradlew writeJmhRuntimeClasspath --no-daemon -tools/run-published-speed-benchmarks.sh 2026-07-23 +./gradlew --no-daemon benchmarkCorpusReport writeJmhRuntimeClasspath +tools/run-published-accuracy-benchmarks.sh 2026-08-10 +tools/run-published-speed-benchmarks.sh 2026-08-10 ``` -The runner refuses to start unless every CPU uses the `performance` governor, materializes the exact selected benchmark list, rejects quality/Polimorf/gold-standard methods, and requires the Hebrew speed path. It records hardware, JVM, source-state, JAR, classpath, corpus, quality, load, temperature, and governor provenance before running. The exact JMH configuration is listed in [Environment and reports](environment.md). Quality and performance reports are separate datasets and are not combined into an undocumented scalar. +The speed runner refuses to start unless every CPU uses the `performance` governor, materializes the exact selected benchmark list, rejects quality/Polimorf/gold-standard methods, and requires the Hebrew speed path. It records hardware, JVM, source-state, JAR, classpath, corpus, quality, load, temperature, and governor provenance before running. The accuracy runner evaluates all four exact-root benchmark classes and verifies that every new Snowball 3.1.0 candidate exposes all six accuracy counters. The exact JMH configuration is listed in [Environment and reports](environment.md). Quality and performance reports are separate datasets and are not combined into an undocumented scalar. ## Recorded and unavailable provenance -The performance documentation records its 2026-07-23 environment, JDK 25.0.3, operating system, hardware, base revision, exact dirty patch, untracked-source checksums, executable JMH JAR checksum, and model descriptor checksums. The quality CSV embeds model identity and checksum in every row; run date, core source state, JVM, OS, and hardware are shared provenance on the environment page. +The performance documentation records its 2026-08-10 environment, JDK, operating system, hardware, base revision, exact dirty patch, untracked-source checksums, executable JMH JAR checksum, and model descriptor checksums. The quality CSV embeds model identity and checksum in every row; run date, core source state, JVM, OS, and hardware are shared provenance on the environment page. Exact immutable upstream revisions were not recorded for every legacy UniMorph import. That limitation remains explicit in model descriptors and cannot be repaired from filesystem timestamps. Dependency versions reproducible from repository configuration include Apache Lucene 10.5.0, Morfologik 2.1.9, the Ukrainian dictionary artifact 4.9.1, and JMH 1.37. diff --git a/docs/benchmarks/reference/tested-stemmers.md b/docs/benchmarks/reference/tested-stemmers.md index 285f6fe..90100e6 100644 --- a/docs/benchmarks/reference/tested-stemmers.md +++ b/docs/benchmarks/reference/tested-stemmers.md @@ -4,10 +4,10 @@ The JMH adapter registry is authoritative for evaluated implementations and lang | Family or implementation | Upstream / attribution | Tested version or revision | Evaluated scope | Output capability and adapter behaviour | Interpretation notes | | --- | --- | --- | --- | --- | --- | -| Radixor | Egothor / Radixor project | Base commit and measured working-tree state recorded on the environment page | All 20 reconciled default model languages; all 20 have benchmark pages | Deterministic preferred patch via `get`; ranked distinct alternatives via `getAll`; primary is always included | Model-dictionary-derived compiled patch trie. Default rows use each language's stable default model ID. | +| Radixor | Egothor / Radixor project | Base commit and measured working-tree state recorded on the environment page | All 20 reconciled default model languages; all 20 have benchmark pages | Deterministic preferred patch via `get`; ranked distinct alternatives via `getAll`; primary is always included | Dictionary-trained compiled patch trie. Default rows use each language's stable default model ID. | | Apache Lucene language stem filters | Apache Lucene project | 10.5.0 | Adapter-declared language-specific subsets | TokenFilter lifecycle and language normalization match JMH; normally single-output | Light, minimal, possessive, and language stem filters deliberately implement different scopes. Narrow scope is not a defect. | | Apache Lucene SnowballFilter | Apache Lucene project using Snowball algorithms | Lucene 10.5.0 | Snowball-supported subset of Radixor languages | Single primary token emitted through the Lucene TokenFilter path | Includes TokenStream overhead and required normalization. | -| Official Snowball Java | Snowball project | Repository preparation downloads the configured upstream Java distribution; an immutable revision was not recorded in the quality CSV | Same-language adapter subset | Direct generated Java API; single output | Rule-based suffix algorithms provide broad baselines rather than dictionary-root guarantees. | +| Official Snowball Java | Snowball project | 3.1.0 source distribution; SHA-256 `5dab34d491f55f47b6e971569ffe6aadf5991512c648ddfe5d331b494cf6d655` | 17 same-language direct adapters, including the Czech, Persian, and Polish stemmers added in 3.1.0 | Direct generated Java API; single output | Rule-based suffix algorithms provide broad baselines rather than dictionary-root guarantees. Lucene 10.5.0 does not yet expose the three new algorithms through `SnowballFilter`, so those rows are direct-only. | | Lucene Stempel | Apache Lucene / Polish stemming tables | Lucene 10.5.0 | Polish | Direct and TokenFilter paths where registered; single primary output | Table-driven Polish implementation. | | Morfologik | Morfologik project; Lucene integration by Apache Lucene | Morfologik 2.1.9, Lucene integration 10.5.0; Ukrainian dictionary artifact 4.9.1 | Registered Polish and Ukrainian paths | Deterministic first lemma for primary comparison; all distinct lemma strings for candidate policies | Several analyses may share a lemma and are deduplicated by exact string equality. | | Hunspell via Lucene | Hunspell dictionaries from the `wooorm/dictionaries` repository; adapter by Apache Lucene | Lucene 10.5.0; dictionary repository revision was not recorded | Configured German, English, Spanish, French, Dutch, Polish, and Ukrainian dictionaries | First emitted stem is primary; all distinct stems at the token position are candidates | Dictionary content and affix rules differ by language. | diff --git a/docs/built-in-languages.md b/docs/built-in-languages.md index 3d6b261..40fa805 100644 --- a/docs/built-in-languages.md +++ b/docs/built-in-languages.md @@ -1,33 +1,42 @@ # Built-in Languages and Default Models -“Supported language” means that Radixor defines a language enum value and publishes a corresponding default model artifact. It does not mean that a dictionary is embedded in the core JAR. Applications add model artifacts explicitly or use the optional standard pack. +“Supported language” means that the repository maintains a default dictionary +and runtime mapping for that language. Packaging differs by runtime: Java keeps +the core dictionary-free and resolves external model artifacts, while the +Python installs a separate `radixor-models-standard` data package containing +the 20 default dictionaries in precompiled version 7 form. -The language enum carries language identity, writing direction, a legacy resource-directory name, and the stable default model ID. A model descriptor carries the independently versioned model identity and resource. See [Model Selection and Loading](model-selection-and-loading.md) for the API and the generated [model catalog](stemmer-model-catalog.md) for versions, provenance, checksums, and sizes. +The Java language enum carries language identity, writing direction, a legacy +resource-directory name, and the stable default model ID. A Java model +descriptor carries the independently versioned model identity and resource. +Python accepts the short alias or the same full model ID. See +[Model Selection and Loading](model-selection-and-loading.md) for Java and +[Python Usage and API](python/usage.md) for Python. ## Defaults and variants -| Language | Enum | Default model ID | Default artifact | Optional variants | -|---|---|---|---|---| -| Czech | `CS_CZ` | `cs-cz-default` | `org.egothor:radixor-model-cs-cz-default` | — | -| Danish | `DA_DK` | `da-dk-default` | `org.egothor:radixor-model-da-dk-default` | — | -| German | `DE_DE` | `de-de-default` | `org.egothor:radixor-model-de-de-default` | — | -| Spanish | `ES_ES` | `es-es-default` | `org.egothor:radixor-model-es-es-default` | — | -| Persian | `FA_IR` | `fa-ir-default` | `org.egothor:radixor-model-fa-ir-default` | — | -| Finnish | `FI_FI` | `fi-fi-default` | `org.egothor:radixor-model-fi-fi-default` | — | -| French | `FR_FR` | `fr-fr-default` | `org.egothor:radixor-model-fr-fr-default` | — | -| Hebrew | `HE_IL` | `he-il-default` | `org.egothor:radixor-model-he-il-default` | — | -| Hungarian | `HU_HU` | `hu-hu-default` | `org.egothor:radixor-model-hu-hu-default` | — | -| Italian | `IT_IT` | `it-it-default` | `org.egothor:radixor-model-it-it-default` | — | -| Norwegian Bokmål | `NB_NO` | `nb-no-default` | `org.egothor:radixor-model-nb-no-default` | — | -| Dutch | `NL_NL` | `nl-nl-default` | `org.egothor:radixor-model-nl-nl-default` | — | -| Norwegian Nynorsk | `NN_NO` | `nn-no-default` | `org.egothor:radixor-model-nn-no-default` | — | -| Polish | `PL_PL` | `pl-pl-unimorph` | `org.egothor:radixor-model-pl-pl-unimorph` | `pl-pl-polimorf` / `org.egothor:radixor-model-pl-pl-polimorf` | -| Portuguese | `PT_PT` | `pt-pt-default` | `org.egothor:radixor-model-pt-pt-default` | — | -| Russian | `RU_RU` | `ru-ru-default` | `org.egothor:radixor-model-ru-ru-default` | — | -| Swedish | `SV_SE` | `sv-se-default` | `org.egothor:radixor-model-sv-se-default` | — | -| Ukrainian | `UK_UA` | `uk-ua-default` | `org.egothor:radixor-model-uk-ua-default` | — | -| English | `US_UK` | `us-uk-default` | `org.egothor:radixor-model-us-uk-default` | — | -| Yiddish | `YI` | `yi-default` | `org.egothor:radixor-model-yi-default` | — | +| Language | Java enum | Python alias | Default model ID | Java default artifact | Optional variants | +|---|---|---|---|---|---| +| Czech | `CS_CZ` | `cs` | `cs-cz-default` | `org.egothor:radixor-model-cs-cz-default` | — | +| Danish | `DA_DK` | `da` | `da-dk-default` | `org.egothor:radixor-model-da-dk-default` | — | +| German | `DE_DE` | `de` | `de-de-default` | `org.egothor:radixor-model-de-de-default` | — | +| Spanish | `ES_ES` | `es` | `es-es-default` | `org.egothor:radixor-model-es-es-default` | — | +| Persian | `FA_IR` | `fa` | `fa-ir-default` | `org.egothor:radixor-model-fa-ir-default` | — | +| Finnish | `FI_FI` | `fi` | `fi-fi-default` | `org.egothor:radixor-model-fi-fi-default` | — | +| French | `FR_FR` | `fr` | `fr-fr-default` | `org.egothor:radixor-model-fr-fr-default` | — | +| Hebrew | `HE_IL` | `he` | `he-il-default` | `org.egothor:radixor-model-he-il-default` | — | +| Hungarian | `HU_HU` | `hu` | `hu-hu-default` | `org.egothor:radixor-model-hu-hu-default` | — | +| Italian | `IT_IT` | `it` | `it-it-default` | `org.egothor:radixor-model-it-it-default` | — | +| Norwegian Bokmål | `NB_NO` | `nb` | `nb-no-default` | `org.egothor:radixor-model-nb-no-default` | — | +| Dutch | `NL_NL` | `nl` | `nl-nl-default` | `org.egothor:radixor-model-nl-nl-default` | — | +| Norwegian Nynorsk | `NN_NO` | `nn` | `nn-no-default` | `org.egothor:radixor-model-nn-no-default` | — | +| Polish | `PL_PL` | `pl` | `pl-pl-unimorph` | `org.egothor:radixor-model-pl-pl-unimorph` | `pl-pl-polimorf` / `org.egothor:radixor-model-pl-pl-polimorf` | +| Portuguese | `PT_PT` | `pt` | `pt-pt-default` | `org.egothor:radixor-model-pt-pt-default` | — | +| Russian | `RU_RU` | `ru` | `ru-ru-default` | `org.egothor:radixor-model-ru-ru-default` | — | +| Swedish | `SV_SE` | `sv` | `sv-se-default` | `org.egothor:radixor-model-sv-se-default` | — | +| Ukrainian | `UK_UA` | `uk` | `uk-ua-default` | `org.egothor:radixor-model-uk-ua-default` | — | +| English | `US_UK` | `en` | `us-uk-default` | `org.egothor:radixor-model-us-uk-default` | — | +| Yiddish | `YI` | `yi` | `yi-default` | `org.egothor:radixor-model-yi-default` | — | The maintained table deliberately avoids duplicating mutable provenance and checksum fields. Those values come from module metadata and are generated into the model catalog. @@ -44,6 +53,11 @@ The maintained table deliberately avoids duplicating mutable provenance and chec UniMorph and PoliMorf have different lexical sources and provenance. Applications should compare outputs with application-specific regression tests before changing an explicit model choice. +In Python, `Stemmer("pl")` selects `pl-pl-unimorph`. The standard Python data +package does not include PoliMorf; applications that need it must compile and +load it explicitly as a trusted custom model. As in Java, it never changes the +Polish default implicitly. + ## Dependency patterns Minimal English: diff --git a/docs/cli-compilation.md b/docs/cli-compilation.md index 29c7ab0..a6800f9 100644 --- a/docs/cli-compilation.md +++ b/docs/cli-compilation.md @@ -1,7 +1,11 @@ -# CLI Compilation +# Java CLI Compilation Radixor provides a command-line compiler for turning line-oriented dictionary files into compact binary stemmer artifacts. +This page documents the Java CLI and its selectable reduction and normalization +controls. Python exposes its production compilation profile through +`radixor.compile(...)`; see [Compiling Dictionaries in Python](python/model-compilation.md). + The CLI output is not a model JAR. A model artifact contains a compressed textual dictionary, descriptor, index, checksum, and license so the runtime registry can discover and compile it. The CLI instead emits an already compiled binary trie for direct `loadBinaryCompiled(...)` use. Choose the model-module workflow when independently published classpath discovery is required; choose the CLI when the application owns a compiled binary asset. This is the preferred preparation workflow when stemming should run against an already compiled artifact rather than against raw dictionary input. The CLI reads the dictionary, derives patch commands, builds a mutable trie, applies the selected subtree reduction strategy, and writes the final compiled trie in the project binary format under GZip compression. The result is a deployment-ready `.radixor.gz` file that can be loaded directly by application code. diff --git a/docs/dictionary-format.md b/docs/dictionary-format.md index af707b8..a3cec9b 100644 --- a/docs/dictionary-format.md +++ b/docs/dictionary-format.md @@ -8,11 +8,16 @@ Three artifacts must not be confused: | Artifact | Representation | Consumer | |---|---|---| -| Source textual dictionary | Plain UTF-8 tab-separated rows | Authors, parser, CLI, or model preparation | -| Registered model resource | The same Radixor dictionary bytes under GZip, accompanied by index, descriptor, checksum, and license | `StemmerModelRegistry` and `StemmerPatchTrieLoader` | -| Persisted compiled trie | GZip-compressed Radixor binary format, commonly `.radixor.gz` | `loadBinaryCompiled(...)` | +| Source textual dictionary | Plain UTF-8 tab-separated rows, optionally GZip-compressed | Java and Python loaders or compilation tools | +| Registered Java model resource | The same dictionary bytes under GZip, accompanied by index, descriptor, checksum, and license | Java `StemmerModelRegistry` and `StemmerPatchTrieLoader` | +| Standard Python model resource | A precompiled version 7 `.rxc` artifact in the required `radixor-models-standard` data package | Python `Stemmer("")` | +| Persisted compiled trie | GZip-compressed Radixor version 7 binary, commonly `.radixor.gz` in Java or `.rxc` in Python | Java `loadBinaryCompiled(...)` and Python `Stemmer(compiled=...)` | -The model file named `stemmer.gz` is not Java serialization and is not a pre-instantiated or persisted trie. It is compressed textual dictionary input parsed when the model is loaded. +The Java model file named `stemmer.gz` is not Java serialization and is not a +pre-instantiated or persisted trie. It is compressed textual dictionary input +parsed when a Java model is loaded. Python's standard data distribution instead +contains generated, precompiled version 7 tries; neither Python distribution +ships the textual source dictionaries. Consequently, compressed size is not a construction-memory estimate. The PoliMorf resource is 12,624,997 bytes compressed and 68,093,680 bytes decompressed, while full parsing, trie construction, reduction, and patch compilation require a dedicated verification JVM with a 6 GiB maximum heap. @@ -25,6 +30,22 @@ generation are disclosed transformations; the in-memory trie is a Radixor runtim Each logical line describes one canonical stem and zero or more known word variants that should reduce to that stem. The format is intentionally lightweight, easy to maintain in source control, and directly consumable both by the programmatic loader and by the CLI compiler. +## Use the format from either runtime + +The dictionary semantics are shared; the integration entry points are not: + +| Task | Java | Python | +|---|---|---| +| Load and compile text now | `StemmerPatchTrieLoader.loadCompiled(...)` | `Stemmer(path=...)` | +| Compile a reusable binary | `org.egothor.stemmer.Compile` | `radixor.compile(...)` | +| Load a compiled binary | `StemmerPatchTrieLoader.loadBinaryCompiled(...)` | `Stemmer(compiled=...)` | +| Registered/standard language | External model JAR selected through the registry | Compiled model in `radixor-models-standard`, selected by alias or model ID | + +Python compilation writes the shared version 7 stream using its fixed +production reduction profile. Java exposes additional reduction and +normalization controls. See [Java CLI Compilation](cli-compilation.md) and +[Compiling Dictionaries in Python](python/model-compilation.md). + ## Core structure Each non-empty logical line has the following shape: diff --git a/docs/fast-track.md b/docs/fast-track.md index 49a1d87..2d66283 100644 --- a/docs/fast-track.md +++ b/docs/fast-track.md @@ -1,10 +1,13 @@ -# Fast Track +# Java Fast Track This page is the shortest path from an empty Java project to a working Radixor stemmer. It deliberately uses an external model artifact and the preferred compiled-command runtime API, so the first result does not require writing a dictionary, running the CLI compiler, or understanding reduction internals. +For the native Python package, start with the [Python Fast Track](python/fast-track.md) +instead; Python does not use Maven model artifacts or the Java loader API. + Use this page when the goal is: - add the dependency, diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..d2e604a --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,34 @@ +# Get Started with Radixor + +Radixor provides first-class Java and Python runtimes over the same learned +patch-command model. Choose the environment that matches the application; both +use the maintained language catalog and can exchange compiled version 7 tries. + +## Python + +Choose Python for native CPython integration, efficient batch calls, and a +PyStemmer-compatible migration surface. + +```bash +python -m pip install radixor +``` + +- [Python Fast Track](python/fast-track.md) — install and produce the first stem. +- [Python Quick Start](python/quick-start.md) — build an application workflow. +- [Python Usage and API](python/usage.md) — scalar, batch, cache, and custom-model APIs. + +## Java + +Choose Java for JVM integration, selectable reduction modes, registry-based +model discovery, and extending or persisting compiled tries. + +- [Java Fast Track](fast-track.md) — add the core/model dependencies and produce the first stem. +- [Java Quick Start](quick-start.md) — loading, querying, extension, and persistence. +- [Java Integration Deep Dive](integration-deep-dive.md) — lifecycle and production guidance. + +## Shared concepts + +- [Built-in Languages](built-in-languages.md) documents aliases, model IDs, and defaults. +- [Dictionary Format](dictionary-format.md) defines the common source semantics. +- [Benchmarks](benchmarks/index.md) separates runtime speed from linguistic quality. +- [Architecture](architecture.md) explains shared behavior and runtime-specific internals. diff --git a/docs/index.md b/docs/index.md index dd7d4e9..2d0caa0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,53 +1,9 @@ -

Home

-

- Radixor banner -

+--- +template: landing.html +title: Radixor +description: Learned transformation stemming for Java and Python using compact reduced tries of patch commands across 20 languages. +--- -**Radixor** is a high-performance, multi-language stemmer for Java, built for production-grade search and text-processing systems. +# Radixor -It modernizes the proven Egothor patch-command trie approach and extends it for deployment realities that classic stemming pipelines do not handle well. - -Traditional Egothor-style stemming workflows usually treat a compiled dictionary as a fixed artifact. Once built, its lexical knowledge is effectively closed unless the original source dictionary is recompiled. Radixor removes that constraint. An already compiled stemming structure can be extended with additional words and transformations, which makes it possible to evolve an existing dictionary for domain-specific, customer-specific, or deployment-specific vocabulary without rebuilding the entire lexical base from scratch. - -Radixor also improves how ambiguous reductions can be handled at runtime. Instead of always forcing a single result, it can return multiple plausible stems when the input token cannot be reduced unambiguously. This allows downstream systems to preserve linguistic ambiguity where that is operationally useful, whether for retrieval quality, ranking strategies, diagnostics, or domain-specific normalization policies. - -The project also has a clear research lineage. The historical idea behind this stemming family is described in Leo Galambos's paper *Lemmatizer for Document Information Retrieval Systems in JAVA* (SOFSEM 2001), which presents a semi-automatic stemming technique designed for Java-based information retrieval systems. In Radixor documentation, this reference serves as historical and algorithmic background rather than as technical documentation of the current implementation. - -> Unlike traditional Egothor-based deployments, Radixor can extend an already compiled stemmer dictionary and can return multiple stems when a word is not reducible to a single unambiguous form. - -Radixor delivers: - -- **Fast runtime stemming** with compact lookup structures -- **Multi-language adaptability** through dictionary-driven compilation -- **Extension of compiled stemmer structures** without full recompilation from source dictionaries -- **Incremental vocabulary growth** for deployment-specific lexical refinement -- **Support for multiple stemming results** when reduction is ambiguous -- **Deterministic behavior** suitable for reproducible processing pipelines -- **Flexible integration paths**, including CLI-based and programmatic workflows -- **Operational transparency** through continuously published quality and benchmark reports - -Radixor is intended for teams that require consistent stemming quality at scale, while retaining the ability to evolve lexical resources after compilation and to handle ambiguous reductions with greater precision than traditional single-stem pipelines allow. - -## Add the core and model data - -The core `org.egothor:radixor` JAR contains no language dictionary. A minimal application adds one model; broad deployments may use the optional standard pack: - -```groovy -dependencies { - implementation 'org.egothor:radixor:' - runtimeOnly 'org.egothor:radixor-model-pl-pl-unimorph:1.0.0' -} -``` - -`StemmerPatchTrieLoader.loadCompiled(Language.PL_PL, ...)` resolves the default `pl-pl-unimorph`. `pl-pl-polimorf` is a separate optional model selected by stable model ID. Follow [Model Selection and Loading](model-selection-and-loading.md) for runnable examples or choose artifacts from the generated [model catalog](stemmer-model-catalog.md). - -## Start here - -- Read [Fast Track](fast-track.md) when you want the shortest path to a working bundled stemmer. -- Use [Model Selection and Loading](model-selection-and-loading.md) for default, explicit, dual-model, and ClassLoader examples. -- Use [Integration Deep Dive](integration-deep-dive.md) when you are wiring Radixor into a real application or search pipeline. -- Read [Quick Start](quick-start.md) for the broader developer walkthrough after the first result works. -- Use [Built-in Languages](built-in-languages.md) to interpret language defaults and optional model variants. -- Review [Benchmarking](benchmarking.md) and [Benchmark Results](benchmarks/index.md) for reproducible performance and quality methodology. -- Open [CI Reports](reports.md) to inspect published build artifacts and quality metrics. -- See the historical paper: [*Lemmatizer for Document Information Retrieval Systems in JAVA*](https://www.researchgate.net/publication/221512865_Lemmatizer_for_Document_Information_Retrieval_Systems_in_JAVA). +Learned transformation stemming for Java and Python, using compact reduced tries of patch commands and reproducible quality/performance benchmarks across 20 languages. diff --git a/docs/integration-deep-dive.md b/docs/integration-deep-dive.md index 69efb5f..6abfc7d 100644 --- a/docs/integration-deep-dive.md +++ b/docs/integration-deep-dive.md @@ -1,4 +1,4 @@ -# Integration Deep Dive +# Java Integration Deep Dive This page explains how to integrate Radixor into a real Java application after the first fast-track experiment works. It covers dependencies, external model artifacts, runtime lifecycle, diff --git a/docs/overrides/landing.html b/docs/overrides/landing.html new file mode 100644 index 0000000..37ff756 --- /dev/null +++ b/docs/overrides/landing.html @@ -0,0 +1,133 @@ +{% extends "base.html" %} +{% block extrahead %}{{ super() }}{% endblock %} +{% block header %}
{% endblock %} +{% block tabs %}{% endblock %} +{% block site_nav %}{% endblock %} +{% block container %} +
+
+
+
+
Dictionary-trained. Not dictionary-bound.
+

High-quality multilingual stemming.
Built for Java and Python.

+

Radixor learns word-to-stem transformations from lexical evidence, compiles them into a reduced trie of patch commands, and applies those commands algorithmically at runtime. The training dictionary supplies evidence—it does not define a closed runtime vocabulary.

+
+ +
+
Choose your runtime
+ +
+
Learned transformationsTrie + compact patch commands
+
GeneralizesNot restricted to listed word forms
+
ReproduciblePublic quality and speed evidence
+
+ +
+
+ +
+ +
+
+ +
+
+ Java benchmark award +

Java: wins in all 20
benchmarked languages

In the current same-language Java matrix, Radixor records the lowest measured runtime against every configured comparator on all 20 language pages.

+
20 / 20Benchmark winsAcross all 20 languages
+
Reproducible · Transparent · Verifiable

Every result is public, verifiable, and reproducible.

View all benchmarks →
+
+ +

Quality and speed you can trust — Finnish case study

+
+
Radixor❄ Snowball Finnish Radixor advantage
+
Quality score(higher is better)0.9848~0.740+33% higher quality
+
Overstemming(lower is better)3.03%~52%94.2% lower
+
Speed(vs. PyStemmer)1.14× faster1.00× (baseline)1.14× faster
+
+

Quality comparator: published Snowball Finnish Lucene/Snowball results. Speed comparator: PyStemmer 3.1.0 at batch size N=100. The point is the combined quality/performance envelope, not wrapper identity.

+ +
+
20 languagesFull Unicode support
+ +
+ +
+
Java
import org.egothor.stemmer.StemmerPatchTrieLoader;
+import org.egothor.stemmer.FrequencyTrie;
+
+FrequencyTrie<CompiledPatchCommand> trie =
+    StemmerPatchTrieLoader.loadCompiled(
+        StemmerPatchTrieLoader.Language.US_UK, ...);
+
+String word = "running";
+CompiledPatchCommand patch = trie.get(word);
+String stem = patch == null ? word : patch.apply(word);
View Java docs →
+
PyPython
from radixor import Stemmer
+
+stemmer = Stemmer("en")
+stems = stemmer.stem_batch([
+    "running", "studies", "better", "cars"
+    ])
View Python docs →
+
+ +
+

Learned transformationsLexical evidence becomes
compact patch commands

+

Beyond the dictionaryRuntime is not a closed
word-to-lemma lookup

+

Native speedMulti-million words per
second in Python and Java

+

20 language modelsOne architecture, trained
from language-specific data

+

Production readyDeterministic behavior
and reproducible evidence

+
+ +
+ +

A different stemming architecture.

Radixor combines learned patch commands, trie-based structural sharing, subtree reduction, and deterministic runtime application. It is neither a flat dictionary lookup nor another fixed suffix-rule table.

+ Technology & lineage + ▣  Read the Docs +
+
+
+{% endblock %} +{% block footer %}{% endblock %} diff --git a/docs/programmatic-loading-and-building.md b/docs/programmatic-loading-and-building.md index e500479..5c57ef9 100644 --- a/docs/programmatic-loading-and-building.md +++ b/docs/programmatic-loading-and-building.md @@ -1,7 +1,10 @@ -# Loading and Building Stemmers +# Loading and Building Stemmers in Java This document explains how to acquire a compiled Radixor stemmer in Java. +For Python construction and binary preparation, use [Python Usage and API](python/usage.md) +and [Compiling Dictionaries in Python](python/model-compilation.md). + ## Load a registered default model Language-oriented entry points resolve a registered default model and compile its GZip textual dictionary into a `FrequencyTrie`. The corresponding model JAR must be on the runtime classpath; the core contains no dictionary. diff --git a/docs/programmatic-usage.md b/docs/programmatic-usage.md index f2c23ae..d87d620 100644 --- a/docs/programmatic-usage.md +++ b/docs/programmatic-usage.md @@ -1,7 +1,12 @@ -# Programmatic Usage +# Java Programmatic Usage Radixor code and model data are separate runtime components. Every example on this page requires `org.egothor:radixor:` as an `implementation` dependency and at least one model JAR as a runtime dependency. The core JAR contains no `stemmer.gz`. +The Python implementation has its own native API. `pip install radixor` also +installs the separate standard data package containing 20 precompiled models. +See the [Python Quick Start](python/quick-start.md) and +[Python Usage and API](python/usage.md). + For complete dependency patterns, lifecycle guidance, and troubleshooting, use [Model Selection and Loading](model-selection-and-loading.md). The generated [model catalog](stemmer-model-catalog.md) records the current artifacts, versions, checksums, and provenance. ## 1. Minimal use: the Polish default diff --git a/docs/python/fast-track.md b/docs/python/fast-track.md new file mode 100644 index 0000000..ce46ea7 --- /dev/null +++ b/docs/python/fast-track.md @@ -0,0 +1,66 @@ +# Python Fast Track + +This is the shortest path from an empty Python environment to a working +Radixor stemmer. The installation includes the native runtime and the separate +standard-model package with 20 precompiled language models. + +## 1. Install + +=== "PyPI" + + ```bash + python -m pip install --only-binary=:all: radixor + ``` + +=== "GitHub Releases" + + ```bash + python -m pip install --only-binary=:all: \ + --index-url https://leogalambos.github.io/Radixor/python/simple/ radixor + ``` + +PyPI publication is pending, and the GitHub index becomes live with the first +Python releases. Until then, follow the source-checkout procedure on +[Installation and Builds](installation.md). + +Radixor supports CPython 3.9 and newer. A JVM, Java dependency, and source +dictionary are not required. + +## 2. Stem words + +```python +from radixor import Stemmer + +stemmer = Stemmer("en") + +print(stemmer.stem("running")) +print(stemmer.stem_batch(["running", "studies", "cars"])) +``` + +Expected first output: + +```text +run +``` + +`stem()` and `stem_batch()` preserve Radixor's original API: a word for which +the trie finds no patch command produces `None`. + +## 3. Use PyStemmer-compatible fallback semantics + +For a low-friction migration from PyStemmer, use the compatible method names: + +```python +stemmer.stemWord("running") +stemmer.stemWords(["running", "unknown_word"]) +``` + +These methods return the original input whenever no patch command is found, so +their results are always strings rather than `None`. + +## Next + +- Continue with the [Python Quick Start](quick-start.md) for model selection, + batch processing, custom compiled models, and deployment guidance. +- Use [Python Usage and API](usage.md) as the method reference. +- Review the reproducible [Python performance results](performance.md). diff --git a/docs/python/index.md b/docs/python/index.md new file mode 100644 index 0000000..dfdb820 --- /dev/null +++ b/docs/python/index.md @@ -0,0 +1,96 @@ +# Radixor for Python + +The **`radixor`** package is Radixor's native Python implementation. It is not +a wrapper around the Java library and does not require a JVM: it is a +compiled extension (Rust, via [PyO3](https://pyo3.rs/) and +[maturin](https://www.maturin.rs/)) that loads precompiled patch-command tries +derived from the same canonical UniMorph data as the Java models. + +```python +from radixor import Stemmer + +s = Stemmer("en") +s.stem("running") # 'run' +s.stem_batch(["cats", "ran"]) # ['cat', 'run'] +``` + +- [Fast Track](fast-track.md) — install and produce the first stem. +- [Quick Start](quick-start.md) — the complete application-oriented learning path. +- [Installation and building](installation.md) — Linux, Windows, macOS. +- [Usage and examples](usage.md) — batch API, caching, and custom models. +- [Dictionary compilation](model-compilation.md) — prepare a version 7 binary + once and share it with Python or Java. +- [Performance](performance.md) — fair, reproducible comparisons vs PyStemmer, + snowballstemmer, NLTK Porter, and CISTEM. + +The language and model-ID mapping is shared with Java and maintained on the +[Built-in Languages](../built-in-languages.md) page. Installing `radixor` +also resolves the separate pure `radixor-models-standard` distribution containing +the 20 default compiled models; Java applications select independently +versioned model JARs. + +!!! note "Same models, same results, different runtime" + The standard Python models are compiled from the identical canonical + dictionaries with the identical production reduction configuration + (`MERGE_SUBTREES_WITH_EQUIVALENT_DOMINANT_GET_RESULTS`, 75 % / 3×, + uniform-subtree contraction, `LOWERCASE_WITH_LOCALE_ROOT`, `AS_IS` + diacritics, `storeOriginal=true`). For a word present in a model, both + implementations return the same dominant stem. The compiled **binary format + is shared** (see below), so a model compiled by one side loads in the other. + +## Java vs. Python: read this first + +The two implementations solve the same problem but make different runtime +trade-offs. Mixing their mental models causes confusion, so the differences are +stated explicitly. **Neither is “better”** — they target different runtimes. + +| Aspect | Java (`org.egothor:radixor`) | Python (`radixor`) | +|---|---|---| +| Runtime | JVM library | Compiled extension (Rust/PyO3), no JVM | +| Distribution | Maven JAR + model JARs | `abi3` wheel (one wheel per OS/arch, Python ≥ 3.9) | +| Hot-path data structure | `CompiledNode` graph; routines operate on caller-owned **`char[]`** with zero-copy normalized lookups and visitor sinks (`EntrySink`) | Flat **CSR arrays** (no per-node objects); reused UTF‑16 scratch buffers | +| Result cache | **None** — `get()` is stateless and re-stems every call | **Bounded**, 10,000 entries by default (matching PyStemmer); `Stemmer(cache_size=0)` disables it | +| Batch API | Not a batch call; you loop and reuse `char[]`/visitors to avoid allocation | **`stem_batch()` / `stem_all_batch()`** — one Python↔Rust crossing amortized over the whole list | +| Reduction modes | All three modes selectable at compile time | Fixed to the production `DOMINANT` mode | +| Extending a compiled trie | **Supported** — add words/transformations to an already-compiled trie without recompiling | **Not exposed** — compile from a dictionary (or load a compiled binary) | +| Model resolution | `ServiceLoader` registry, descriptors, SHA‑256 integrity checks | Separate standard data package; catalog/format/SHA‑256 validation before synchronous native load | +| Normalization control | Case and diacritic modes fully configurable | `lowercase` toggle; diacritics `AS_IS` (models are built this way) | +| Binary format | `StemmerPatchTrieBinaryIO` v7 read/write (versioned, fingerprinted) | v7 read/write, **inner stream byte-identical** to Java; **v7 only** (no legacy v1–v6) | +| Multiple stems | `getAll(...)` | `stem_all()` / `stem_all_batch()` | + +### Runtime capabilities that differ + +To avoid surprises, these Java capabilities are **not** in the Python package: + +- **Extending / incrementally growing a compiled trie.** Python compiles from a + source dictionary (or loads a compiled binary); it does not add words to an + existing compiled trie at runtime. +- **Selectable reduction modes.** Only the production `DOMINANT` mode is used. +- **Pluggable provider discovery.** Python currently resolves one known + standard provider directly; entry-point plugins are not yet exposed. +- **Legacy binary versions.** Only stream version 7 is read/written. +- **Diacritic-removal modes** beyond `AS_IS` (the bundled models are `AS_IS`). + +### Python-specific capabilities + +- A **batch API** (`stem_batch`) that amortizes the Python↔native boundary — the + single most important call for throughput from Python. +- A **bounded result cache** (`cache_size=10_000` by default) for workloads with + repeated tokens. It is shared by `stem()`, `stemWord()`, `stem_batch()`, and + `stemWords()`; pass `cache_size=0` to disable it. The `stem_all*()` methods are + not cached. +- A `lowercase=False` mode to skip per-lookup lowercasing when the caller + guarantees already-lowercased input. + +## Interoperability + +The compiled binary is Radixor's **v7 trie stream**, and the Python runtime writes +the *inner stream byte-for-byte identically to the Java* +`StemmerPatchTrieBinaryIO`. Consequently: + +- a model compiled by **Java** (`org.egothor.stemmer.Compile` / + `StemmerPatchTrieBinaryIO.write`) loads in **Python**, and +- a model compiled by **Python** (`radixor.compile(...)`) loads in **Java**. + +(The outer gzip wrapper bytes differ between the two gzip implementations; this +is irrelevant — both sides decompress to the same v7 stream.) diff --git a/docs/python/installation.md b/docs/python/installation.md new file mode 100644 index 0000000..f6f13d2 --- /dev/null +++ b/docs/python/installation.md @@ -0,0 +1,215 @@ +# Installing and building (Linux, Windows, macOS) + +The Python package ships as an **`abi3` wheel** — a single binary per +OS/architecture that works on CPython ≥ 3.9 (including 3.14) through the stable +ABI. Most users just `pip install`; building from source is only needed for +development or unsupported platforms. + +## Install from PyPI + +PyPI is the intended primary index once the Radixor projects are approved and +published there: + +```bash +python -m pip install --only-binary=:all: radixor +``` + +PyPI publication is not live yet. Until the `radixor` and +`radixor-models-standard` project pages exist, this command cannot install the +project. + +## Install compiled packages from GitHub + +Python releases are published as immutable GitHub Release assets. A small +PEP 503 index on GitHub Pages exposes both packages to `pip`: + +```bash +python -m pip install --only-binary=:all: \ + --index-url https://leogalambos.github.io/Radixor/python/simple/ radixor +``` + +The index links directly to checksummed wheel assets in GitHub Releases; Pages +does not duplicate the package files. It is not live until the first Python +model and native releases have been published. This was verified before the +initial release: the URL returned HTTP 404 and the repository contained no +Python Release assets. + +Do not configure the GitHub index as an `--extra-index-url`: `pip` does not +prioritize one index over another. Use it as the sole `--index-url`, as shown +above. The binary-only constraint also prevents an accidental source build +with an unprepared toolchain. + +Wheels are provided for Linux (`manylinux`), Windows, and macOS +(x86‑64 and Apple Silicon). A source distribution is also published; installing +it triggers a source build, which needs the toolchain described below. + +## Install or build from the GitHub source repository + +Building requires the **Rust toolchain**, a linker for the target platform, and +**maturin**. The crate and its dependencies contain no project C/C++ sources, +but the selected Rust target still needs its normal platform linker and SDK. + +```bash +git clone https://github.com/leogalambos/Radixor +cd Radixor +python -m venv python/.venv +# activate the venv (see per-OS note below) +pip install maturin build setuptools wheel pytest +./gradlew pythonBuildStandardModels +pip install --no-deps build/python/dist/standard/radixor_models_standard-0.0.0-py3-none-any.whl +cd python +maturin develop --release # compile + install into the venv +pytest -q # run the test suite +``` + +For a reproducible application build, check out a release tag or exact commit +instead of a moving branch. Repository descriptors deliberately use the +non-release placeholder `0.0.0`; release workflows inject the version from the +Git tag into isolated staging trees. Consequently, source-checkout development +installs use `--no-deps` for the generated development model wheel, while +published packages carry normal release versions and dependency resolution +works automatically. + +The native distribution requires +`radixor-models-standard>=1.0,<2.0`; an installation of `radixor` +resolves it automatically. The local `--no-deps` command installs the generated +data wheel for development without contacting a package index. + +The installed package source (package index, environment, and `sys.path`) is +the model-provider trust boundary. Manifest SHA-256 checks detect accidental +corruption after installation; they do not authenticate a malicious provider. + +## Integrity and provenance + +Every GitHub Release contains `SHA256SUMS` for its wheel and source archives. +The release workflows also create GitHub artifact attestations for those +archives. After downloading a release, maintainers and users can verify it with: + +```bash +sha256sum --check SHA256SUMS +gh attestation verify radixor--.whl \ + --repo leogalambos/Radixor +``` + +Python packages do **not** reuse the OpenPGP key configured for Java/Maven +Central publications. Java's `SIGNING_KEY` and `SIGNING_PASSWORD` produce Maven +signatures; Python currently uses release checksums plus GitHub's +identity-bound build-provenance attestation. A future PyPI publication should +use PyPI Trusted Publishing and its supported attestations rather than copying +the Java signing mechanism. + +## Build through Gradle + +From the repository root, the supported build entry point creates the native +wheel/sdist and pure standard-model wheel/sdist: + +```bash +./gradlew pythonBuild +``` + +Artifacts are written below `build/python/dist/`; they are not installed into +the invoking interpreter. `./gradlew pythonVerifyDistributions` also validates +archive contents, dependency metadata, checksums, v7 headers, and a fresh +offline wheel-only installation. Platform convenience tasks are also available: + +```bash +./gradlew pythonBuildLinux +./gradlew pythonBuildWindows +./gradlew pythonBuildMacos +``` + +The task matching the current host delegates to `pythonBuild`. A non-host task +uses the default Rust target for that operating system and therefore succeeds +only when its Rust target, linker, and platform SDK are installed. Override a +default with `pythonLinuxTarget`, `pythonWindowsTarget`, or `pythonMacosTarget`. +For example: + +```bash +./gradlew pythonBuildWindows -PpythonWindowsTarget=x86_64-pc-windows-gnu +``` + +Use `-PpythonExecutable=/path/to/python` or +`-PmaturinExecutable=/path/to/maturin` when those tools are not on `PATH`. +These Gradle tasks are the repository integration; direct `maturin` commands +remain useful while developing inside `python/`. + +### Prerequisites per platform + +=== "Linux" + + ```bash + # Rust (rustup); most distros already ship Python 3.9+ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + python3 -m venv .venv && source .venv/bin/activate + pip install maturin pytest + ``` + Producing distributable `manylinux` wheels is easiest with + `maturin build --release` inside the official maturin/`manylinux` container. + +=== "macOS" + + ```bash + brew install rustup-init && rustup-init -y # or: curl https://sh.rustup.rs | sh + python3 -m venv .venv && source .venv/bin/activate + pip install maturin pytest + maturin develop --release + ``` + Both Apple Silicon (`aarch64-apple-darwin`) and Intel + (`x86_64-apple-darwin`) are supported; `maturin build --release --target + universal2-apple-darwin` produces a universal wheel. + +=== "Windows" + + ```powershell + winget install -e --id Rustlang.Rustup + py -m venv .venv + .\.venv\Scripts\Activate.ps1 + pip install maturin pytest + maturin develop --release + ``` + The self-contained GNU toolchain avoids needing Visual Studio Build Tools: + ```powershell + rustup toolchain install stable-x86_64-pc-windows-gnu + rustup default stable-x86_64-pc-windows-gnu + ``` + (The MSVC toolchain also works if you already have the C++ Build Tools.) + +### Python 3.14 (and newer than your PyO3 knows about) + +Because the extension targets the stable ABI, it links against interpreters +newer than the PyO3 version was released for. If a build against a very new +CPython refuses, set the forward-compatibility flag once in the build shell: + +=== "Linux / macOS" + + ```bash + export PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 + maturin develop --release + ``` + +=== "Windows (PowerShell)" + + ```powershell + $env:PYO3_USE_ABI3_FORWARD_COMPATIBILITY = "1" + maturin develop --release + ``` + +## Verifying the build + +```bash +python -c "from radixor import Stemmer; print(Stemmer('en').stem('running'))" # -> run +pytest -q +``` + +## Notes and caveats + +- **Model packaging.** Neither runtime distribution contains textual + dictionaries. `radixor-models-standard` ships 20 compiled gzip v7 resources, + the checksum/provenance manifest, and CC BY-SA 3.0 notices; optional + `pl-pl-polimorf` is excluded. +- **Catalog compatibility.** Radixor 4.1 accepts model-distribution major 1 + (`>=1.0,<2.0`) carrying the independent 2026.1 catalog identity. Missing, + incompatible, or corrupt data produces an + actionable error before native loading. +- **Toolchain PATH.** After installing rustup, open a fresh shell (or ensure + `~/.cargo/bin` is on `PATH`) so `maturin` can find `cargo`/`rustc`. diff --git a/docs/python/model-compilation.md b/docs/python/model-compilation.md new file mode 100644 index 0000000..e21fa50 --- /dev/null +++ b/docs/python/model-compilation.md @@ -0,0 +1,105 @@ +# Compiling Dictionaries in Python + +The Python package can compile a textual Radixor dictionary into the shared +version 7 binary trie format. This moves dictionary parsing, patch-command +generation, trie construction, reduction, and serialization out of application +startup. + +Use this workflow when the application owns its model file. Standard language +aliases already load validated, precompiled `.rxc` resources from +`radixor-models-standard`; they do not parse or compile textual dictionaries +when a `Stemmer` is constructed. + +## Source format + +The input is a plain UTF-8 or GZip-compressed UTF-8 tab-separated dictionary. +The first column is the canonical stem and the remaining columns are its known +surface forms: + +```text +run running runs ran +cat cats +``` + +Remarks beginning with `#` or `//` are accepted. The complete syntax and +normalization rules are documented on the shared [Dictionary Format](../dictionary-format.md) +page. + +## Compile a model + +```python +import radixor + +radixor.compile( + "stemmer.tsv.gz", + "english.rxc", + language="en", +) +``` + +`language` is used to choose traversal direction when `backward` is omitted. +Persian (`fa`), Hebrew (`he`), and Yiddish (`yi`) use forward traversal; the +other bundled languages use backward traversal. For a custom language, select +the direction explicitly: + +```python +radixor.compile( + "custom.tsv", + "custom.rxc", + backward=True, + store_original=True, + lowercase=True, +) +``` + +The arguments are: + +| Argument | Meaning | +|---|---| +| `source` | Plain or GZip-compressed textual dictionary. | +| `out_path` | Destination for the GZip-compressed version 7 trie. | +| `language` | Optional alias or model ID used only to infer traversal direction. | +| `backward` | Explicit traversal direction; overrides inference from `language`. | +| `store_original` | Include a no-op mapping for every canonical stem. Defaults to `True`. | +| `lowercase` | Record lowercase lookup normalization in the compiled metadata. Defaults to `True`. | + +Compilation refuses an input that is already a compiled trie. The destination +is written by the native extension; the caller is responsible for choosing its +location and for replacing an existing file only when that is intended. + +## Load the compiled model + +```python +from radixor import Stemmer + +stemmer = Stemmer(compiled="english.rxc") +print(stemmer.stem("running")) +``` + +`Stemmer(path=...)` also auto-detects textual dictionaries and compiled version +7 streams, but `compiled=` communicates the deployment intent more clearly. +Traversal direction, `store_original`, and lookup normalization are already +stored in a compiled artifact; constructor build options do not rewrite them. + +## Java interoperability + +Python and Java share the inner version 7 trie stream. A binary produced by +`radixor.compile(...)` can be loaded by Java's +`StemmerPatchTrieLoader.loadBinaryCompiled(...)`, and Python can load a version +7 artifact written by `StemmerPatchTrieBinaryIO`. + +The outer GZip bytes need not be identical because compressor implementations +may differ. Interoperability applies to the decompressed version 7 stream and +its persisted metadata. + +## Differences from the Java compiler + +Python compilation intentionally exposes the production dominant-result +configuration used by the Python runtime. Java additionally offers three selectable +reduction modes, more normalization controls, incremental extension, and a CLI +with explicit overwrite handling. Use [Java CLI Compilation](../cli-compilation.md) +when those controls are required. + +For normal Python use, compile once during preparation, deploy the resulting +`.rxc` file as an application-owned asset, and reuse one loaded `Stemmer` at +runtime. diff --git a/docs/python/performance.md b/docs/python/performance.md new file mode 100644 index 0000000..2e75570 --- /dev/null +++ b/docs/python/performance.md @@ -0,0 +1,160 @@ +# Performance (Python) + +This page reports **runtime stemming throughput** of the Python implementation against +common Python stemmers, and — crucially — documents exactly how the comparison +is made fair. The scripts are in the repository (`python/benchmarks/`); anyone +can reproduce the numbers. + +!!! info "Published single-machine measurement" + These results were regenerated on 2026-08-08 on the current benchmark + workstation: Fedora Linux 44 (`7.1.6-201.fc44.x86_64`), AMD Ryzen 5 5625U + (6 cores / 12 threads), CPython 3.14.6, Rust 1.97.1, and a release wheel. + All logical CPUs used the `schedutil` governor. Absolute timings remain + machine-specific; compare ratios only within this run. + +## What is measured + +- **Runtime stemming only.** Model construction / dictionary compilation happens + once in setup and is excluded from every timing. +- **Workload = the Java JMH corpus.** The *changed-token* corpus derived from + the bundled UniMorph gold-standard dictionaries: every dictionary field paired + with its line's root, normalized `trim().lower()`, keeping only tokens that + differ from their root (the forms a stemmer must actually rewrite), padded to + ≥ 5 000 tokens. This is identical to the Java `LanguageBenchmarkCorpus`. +- **Batch sizes 10/20/50/100** are swept and a line is fit to `per_call(N) = + intercept + N · slope` as a descriptive scaling summary. This is an + unconstrained OLS fit, so noise may produce a negative intercept; it is not a + physical decomposition of runtime. The *best* of many repeats is reported. + +## Fairness: making the comparison apples-to-apples + +Three asymmetries silently distort stemmer comparisons. Each is neutralized, and +where it **cannot** be neutralized the effect is described. + +1. **Result caching — neutralized.** PyStemmer caches results by default + (`maxCacheSize=10000`). Since a benchmark stems the same corpus repeatedly, + that cache would turn measured passes into dictionary lookups rather than + stemming. The harness explicitly disables **both** PyStemmer's cache + (`maxCacheSize=0`) and radixor's default cache (`cache_size=0`). The other + engines have no cache. +2. **Lowercasing — neutralized.** Snowball (PyStemmer, snowballstemmer) and + CISTEM differ in whether they case-fold. Snowball does **no** case handling; + it assumes pre-lowercased input. The corpus is pre-lowercased for every + engine, and radixor is therefore run with **`lowercase=False`** so it does + the same work. On already-lowercased input radixor returns identical results + either way. **Exception — CISTEM:** it always performs its own lowercasing + and German umlaut normalization internally and cannot be told to skip it, so + CISTEM does *slightly more* normalization work than the others. This + unavoidable extra work biases the comparison modestly **in radixor's + favour**, not CISTEM's. +3. **Hidden delegation — neutralized.** `snowballstemmer` delegates to PyStemmer + when PyStemmer is installed (they become the same C code). The harness + bypasses that and uses snowballstemmer's genuine pure-Python backend, and + records each engine's backing module + whether it is a compiled extension so + the provenance is verifiable. + +## Environment and parameters + +| Item | Published value | +|---|---| +| CPU | AMD Ryzen 5 5625U with Radeon Graphics | +| CPU topology | 6 physical cores / 12 logical CPUs | +| OS | Fedora Linux 44, kernel `7.1.6-201.fc44.x86_64` | +| CPU governor | `schedutil` on all 12 logical CPUs; boost enabled | +| Python | CPython 3.14.6 | +| Radixor | 4.1.0, release-mode ABI3 wheel, cache disabled | +| PyStemmer | 3.1.0 (`libstemmer_c` 3.1.0), cache disabled | +| snowballstemmer | 3.1.1, forced pure-Python backend | +| NLTK | 3.10.2 | +| Workload | 5,000 changed tokens per language and measurement | +| Batch sizes | 10, 20, 50, 100 | +| Timing | best of 15 measured passes after 3 warm-up passes | + +The authoritative command was: + +```bash +./gradlew pythonBenchmarkAllLanguagesBatch --rerun-tasks +``` + +It completed successfully in 3 minutes 33 seconds and emitted +the full per-size CSV and JSON reports under +`build/reports/python-benchmarks/`. + +## Results — batch size 100, cache disabled + +The table reports nanoseconds per word at `N=100` (lower is better). A dash +means that the engine has no implementation for that language. Every available +competitor was measured in the same process, with the same corpus and batch +partitioning. + +| Language | Radixor | PyStemmer (Snowball C) | CISTEM (pure Py) | snowballstemmer (pure Py) | NLTK Porter (pure Py) | +|---|---:|---:|---:|---:|---:| +| Czech (`cs`) | **224.3** | 236.6 | — | 4,835.2 | — | +| Danish (`da`) | **178.3** | 267.6 | — | 8,568.9 | — | +| German (`de`) | **230.9** | 635.5 | 3,341.9 | 33,654.1 | — | +| English (`en`) | **180.5** | 331.9 | — | 20,195.0 | 7,740.3 | +| Spanish (`es`) | **184.2** | 316.6 | — | 19,640.1 | — | +| Persian (`fa`) | **210.1** | 497.1 | — | 32,732.3 | — | +| Finnish (`fi`) | **227.8** | 258.8 | — | 12,339.5 | — | +| French (`fr`) | **234.2** | 503.7 | — | 36,161.9 | — | +| Hebrew (`he`) | **228.6** | — | — | — | — | +| Hungarian (`hu`) | **198.2** | 264.7 | — | 13,694.3 | — | +| Italian (`it`) | **170.8** | 517.0 | — | 34,504.6 | — | +| Norwegian Bokmål (`nb`) | **187.1** | 239.7 | — | 7,457.6 | — | +| Dutch (`nl`) | **187.1** | 354.8 | — | 18,148.2 | — | +| Norwegian Nynorsk (`nn`) | **168.7** | 231.2 | — | 7,489.4 | — | +| Polish (`pl`) | **194.6** | 214.5 | — | 5,282.9 | — | +| Portuguese (`pt`) | **166.9** | 293.2 | — | 21,157.2 | — | +| Russian (`ru`) | **273.4** | 414.4 | — | 15,703.8 | — | +| Swedish (`sv`) | **189.3** | 212.5 | — | 5,351.4 | — | +| Ukrainian (`uk`) | **221.5** | — | — | — | — | +| Yiddish (`yi`) | **227.5** | 624.2 | — | 33,251.6 | — | + +Radixor won all **18 / 18** direct PyStemmer comparisons. At `N=100`, its +geometric-mean speedup was **1.67×**; the largest direct advantage was **3.03×** +for Italian. Across all 20 Radixor languages, throughput ranged from **3.66 to +5.99 million words/s**. + +### CISTEM comparison for German + +The German row also provides a direct comparison with CISTEM: + +| Engine | Implementation | N=100 | vs radixor | +|---|---|---|---| +| **radixor** | Rust trie | **230.9 ns/word** | — | +| PyStemmer (de) | Snowball C | 635.5 ns/word | 2.75× slower | +| **CISTEM** | pure Python (`nltk`) | **3,341.9 ns/word** | **14.47× slower** | + +CISTEM has no batch entry point (it is a per-word Python loop), so its per-word +cost is flat across batch sizes and batching cannot amortize it. It is a compact +~40-rule German heuristic with no dictionary — a different design point that +trades coverage for simplicity. Because CISTEM's unavoidable normalization work +modestly biases the measurement in radixor's favour (point 2 above), the 14.47× +result is not a perfectly normalization-matched ratio. + +The all-language Gradle task does not measure stage-level profiling or cached +lookup performance. This page therefore does not mix such figures from an older +workstation into the published run. + +## A note on comparability of *quality* + +These are **speed** comparisons. Radixor is a **lexicon-trained transformation +stemmer**: it learns patch commands from UniMorph-grounded word–stem evidence +and can generalize those transformations beyond exact training entries. +Snowball, Porter, and CISTEM use hand-written rule systems. They produce +different stems and are not directly comparable on output; see the shared +[linguistic quality +methodology](../benchmarks/reference/linguistic-quality.md) for how stemming +quality is assessed separately from throughput. + +## Reproduce + +```bash +pip install -r python/benchmarks/requirements-bench.txt +./gradlew pythonBenchmarkAllLanguagesBatch --rerun-tasks +``` + +The run prints the machine/Python/engine versions and each engine's backing +module (provenance), and writes per-point rows (CSV) plus the full report +including environment (JSON). Methodology and fairness notes live in +`python/benchmarks/README.md`. diff --git a/docs/python/quick-start.md b/docs/python/quick-start.md new file mode 100644 index 0000000..cf51200 --- /dev/null +++ b/docs/python/quick-start.md @@ -0,0 +1,130 @@ +# Python Quick Start + +Radixor's Python implementation is a native Rust extension with a Python API. +It uses the same learned patch-command model and version 7 compiled-trie format +as the Java implementation, without requiring a JVM. + +## 1. Install the runtime and standard models + +Create an isolated environment and install Radixor: + +```bash +python -m venv .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate +``` + +=== "PyPI" + + ```bash + python -m pip install --only-binary=:all: radixor + ``` + +=== "GitHub Releases" + + ```bash + python -m pip install --only-binary=:all: \ + --index-url https://leogalambos.github.io/Radixor/python/simple/ radixor + ``` + +PyPI publication is pending. The GitHub option becomes live when the first +Python GitHub Releases populate the Pages-backed package index. See +[Installation and Builds](installation.md) for availability and source builds. + +The `radixor` wheel contains code. Its required +`radixor-models-standard` dependency contains 20 precompiled models. The +standard package excludes textual source dictionaries and optional PoliMorf +data, which keeps startup on the direct compiled-model path. + +## 2. Select and reuse a stemmer + +Construct a stemmer once and retain it for the lifetime of the application: + +```python +from radixor import Stemmer + +english = Stemmer("en") +polish = Stemmer("pl") + +print(english.stem("running")) # 'run' +print(polish.stem("koty")) +``` + +Short aliases such as `en`, `de`, and `pl` resolve to the documented default +model IDs. A full ID such as `us-uk-default` selects the same model explicitly. +The complete mapping is listed under [Built-in Languages](../built-in-languages.md). + +## 3. Prefer batch calls for collections + +Crossing the Python/native boundary once per collection is substantially more +efficient than a Python loop of scalar calls: + +```python +words = ["running", "studies", "better", "cars"] +stems = english.stem_batch(words) +``` + +`stem_batch()` preserves input order and returns one item per word. Entries can +be `None` when the trie has no applicable patch command. + +Repeated natural-language tokens use a bounded result cache shared by the +scalar and batch APIs. Its default capacity is 10,000 entries, matching +PyStemmer; choose another bound or pass `0` to disable it: + +```python +english = Stemmer("en", cache_size=10_000) +uncached = Stemmer("en", cache_size=0) +``` + +The cache covers `stem()`, `stemWord()`, `stem_batch()`, and `stemWords()`; +the `stem_all*()` methods are not cached. + +## 4. Migrate from PyStemmer + +Radixor exposes PyStemmer's familiar scalar and batch method names: + +```python +stemmer = Stemmer("en") + +stemmer.stemWord("running") +stemmer.stemWords(["running", "unknown_word"]) +``` + +`stemWord()` and `stemWords()` return unmatched input unchanged. This removes +the `None` fallback checks required by Radixor's original `stem()` and +`stem_batch()` methods, so most migration work is limited to the package import +and dependency change. + +## 5. Load a custom compiled model + +The standard installation covers the maintained default catalog. A custom +version 7 model can be loaded directly: + +```python +custom = Stemmer(compiled="models/domain-english.rxc") +``` + +To compile a maintained textual dictionary during a preparation step: + +```python +from radixor import compile + +compile("dictionaries/domain.tsv.gz", "models/domain-english.rxc", language="en") +``` + +Deploy the resulting `.rxc` file and load it at application startup. See +[Dictionary Compilation](model-compilation.md) for format interoperability and +the production compilation profile. + +## 6. Production checklist + +- Pin compatible `radixor` and `radixor-models-standard` releases in the + application's dependency lock. +- Construct and reuse stemmers instead of rebuilding them per request. +- Use batch calls for token collections. +- Choose `stem*` or `stemWord*` semantics deliberately for unmatched words. +- Treat custom dictionaries and compiled models as trusted application input. +- Regression-test representative vocabulary before changing model versions. + +Continue with [Installation and Builds](installation.md) for wheel/platform +details, [Usage and API](usage.md) for the complete call surface, or +[Performance](performance.md) for benchmark methodology and results. diff --git a/docs/python/usage.md b/docs/python/usage.md new file mode 100644 index 0000000..d901f56 --- /dev/null +++ b/docs/python/usage.md @@ -0,0 +1,173 @@ +# Usage and examples + +## Creating a stemmer + +```python +from radixor import Stemmer + +s = Stemmer("en") # by language code (bundled model) +s = Stemmer("us-uk-default") # by full model ID +s = Stemmer(path="my_dictionary.gz") # a custom gzipped TSV source dictionary +s = Stemmer(compiled="en.rxc") # a pre-compiled binary (instant load) +``` + +The traversal direction is derived from the language (right-to-left `fa`/`he`/`yi` +use FORWARD, all others BACKWARD); override with `backward=True|False` for a +custom `path=`. + +## Stemming a single word + +```python +s.stem("running") # 'run' +s.stem("cats") # 'cat' +s.stem("zzzzz") # None -> not reducible / unknown +``` + +`stem()` returns the single **dominant** stem, or `None`. + +!!! info "Why a known word may return itself" + A surface form that is *also* a canonical headword (e.g. an English word + that is both its own lemma and an inflection of another lemma) returns + itself, because the dominant sense is “this word is its own stem”. The + inflectional reading is still available via `stem_all()`. + +## Batch stemming — the fast path + +For anything beyond a handful of words, use the batch API. It crosses the +Python↔native boundary **once** for the whole list, which is the dominant cost +when stemming from Python. + +```python +words = ["running", "cats", "stemming", "quickly"] +s.stem_batch(words) # ['run', 'cat', 'stem', 'quick'] (None for unknowns) +``` + +```python +# Multiple candidate stems per word (ambiguity preserved): +s.stem_all("running") # e.g. ['run', 'runn'] +s.stem_all_batch(["running", "cats"]) +``` + +## PyStemmer-compatible methods + +Radixor also exposes PyStemmer's scalar and batch method names. They differ +from the native Radixor methods only when the trie cannot find a patch command: + +| Method | Recognized word | Word without a patch command | Return type | +| --- | --- | --- | --- | +| `stem(word)` | dominant stem | `None` | `str | None` | +| `stem_batch(words)` | dominant stem at the same position | `None` at the same position | `list[str | None]` | +| `stemWord(word)` | dominant stem | original input word | `str` | +| `stemWords(words)` | dominant stem at the same position | original input word at the same position | `list[str]` | + +Use `stemWord()` and `stemWords()` when migrating code that expects +PyStemmer's no-`None` contract: + +```python +import radixor as Stemmer + +# The rest of this common PyStemmer call pattern remains unchanged. +s = Stemmer.Stemmer("english") + +s.stemWord("running") # 'run' +s.stemWord("unknown_word") # 'unknown_word' +s.stemWords(["running", "unknown_word"]) +# ['run', 'unknown_word'] +``` + +`stemWords()` retains input order and makes one Python-to-Rust call for the +whole list. PyStemmer's full language names, such as `"english"` and +`"czech"`, are accepted for bundled Radixor languages alongside two-letter +codes and full model IDs. + +The compatibility contract covers these method names, full language aliases, +and unmatched-word fallback behavior. Radixor configuration keywords remain +Radixor-specific: use `cache_size`, not PyStemmer's `maxCacheSize`. Both +libraries default to a cache capacity of 10,000 entries. + +## Bounded result cache + +Real text repeats tokens. The default bounded cache returns the already-built +result object on a recognized-word hit (a reference-count bump — no +re-stemming, no new result string). Unknown words are cached as misses, so +`stemWord()` and `stemWords()` still create their required original-word +result. Its default capacity is **10,000 entries**, matching PyStemmer: + +```python +s = Stemmer("en") # cache up to 10,000 distinct input words +s = Stemmer("en", cache_size=50_000) # choose a custom capacity +s = Stemmer("en", cache_size=0) # explicitly disable caching +``` + +One cache is shared by `stem()`, `stemWord()`, `stem_batch()`, and +`stemWords()`. The `stem_all()` and `stem_all_batch()` methods are not cached. +Caching never changes results; it only avoids recomputation. Entries are +inserted until the configured capacity is reached; there is no eviction. For a +high-cardinality stream without useful token repetition, use `cache_size=0`. + +## Skipping lowercasing for pre-normalized input + +By default lookups lowercase the input (`LOWERCASE_WITH_LOCALE_ROOT`). If your +pipeline already lowercases tokens, skip the redundant work: + +```python +s = Stemmer("en", lowercase=False) # assume already-lowercased input +s.stem("running") # 'run' +s.stem("Running") # None -> not lowercased, so no match +``` + +The model's keys are always lowercase; `lowercase=False` only turns off +per-lookup normalization. On already-lowercased input the results are identical. + +## Compile once, load instantly + +Compiling a trie from text costs a few seconds for large languages. Compile it +once to Radixor's binary format and load it directly afterwards: + +```python +import radixor + +radixor.compile("stemmer.gz", "en.rxc", language="en") +s = radixor.Stemmer(compiled="en.rxc") +``` + +See [Compiling Dictionaries in Python](model-compilation.md) for the source +format, traversal and normalization options, deployment guidance, Java +interoperability, and the controls that remain Java-only. + +## Using a custom dictionary + +A source dictionary is a gzipped (or plain) TSV file, one entry per line, the +first column the canonical stem and the rest its variants; `#` and `//` start +line remarks: + +``` +run running runs ran +cat cats +``` + +```python +s = Stemmer(path="custom.gz", backward=True, store_original=True) +``` + +`store_original=True` (default) maps each stem to itself (a no-op patch) so the +stem is recognised. See [Dictionary Format](../dictionary-format.md) for the +authoritative specification shared with the Java project. + +## Thread-safety + +A `Stemmer` is safe to share across threads. The bounded cache is guarded +internally; the compiled trie is immutable after construction. + +## API summary + +| Call | Returns | Notes | +|---|---|---| +| `Stemmer(lang \| path= \| compiled=, *, backward, store_original, lowercase, cache_size=10_000)` | stemmer | auto-detects compiled vs textual for `path=`; `cache_size=0` disables caching | +| `stem(word)` | `str \| None` | dominant stem | +| `stem_batch(words)` | `list[str \| None]` | **preferred** for many words | +| `stemWord(word)` | `str` | PyStemmer-compatible; returns an unmatched word unchanged | +| `stemWords(words)` | `list[str]` | PyStemmer-compatible batch call; preserves unmatched words and input order | +| `stem_all(word)` | `list[str]` | all candidate stems, best first | +| `stem_all_batch(words)` | `list[list[str]]` | | +| `radixor.compile(source, out, *, language, backward, store_original, lowercase)` | `None` | writes a v7 binary | diff --git a/docs/quick-start.md b/docs/quick-start.md index 2d88258..71d9d74 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -1,6 +1,9 @@ -# Quick Start +# Java Quick Start -This guide introduces the fastest practical path to using **Radixor**. +This guide introduces the fastest practical path to using **Radixor in Java**. +Python users have an equivalent application-oriented path in the +[Python Quick Start](python/quick-start.md), followed by the +[Python Usage and API guide](python/usage.md). If you are new to Radixor and want the shortest possible path to a first working stem, start with [Fast Track](fast-track.md). This Quick Start is a broader developer walkthrough: it introduces the diff --git a/docs/technology-lineage.md b/docs/technology-lineage.md new file mode 100644 index 0000000..37d8666 --- /dev/null +++ b/docs/technology-lineage.md @@ -0,0 +1,236 @@ +# Technology and Lineage + +Radixor has an unusual position in the stemming ecosystem because its history +predates several implementations with which it is now compared. + +The useful way to describe that history is not as a list of project names, but +as a set of **algorithmic lineages and runtime models**. + +## The Egothor lineage + +The transformation-based approach behind Radixor was described by Leo Galambos +in *Lemmatizer for Document Information Retrieval Systems in JAVA* (SOFSEM +2001). The Egothor implementation developed the patch-command/trie approach used +to compile word-form transformations into a compact stemming structure. + +Radixor is a modern implementation of that lineage. It is not a binary or source +repackaging of the old project: the current implementation has a new runtime +representation, compiled patch commands, deterministic multi-result semantics, +modern reduction modes, persistence, model packaging, integrity validation, and +current Java/Python integration. + +See [Why Radixor Is Different](why-radixor-is-different.md) for the architecture +rather than the chronology. + +## Stempel is a historical Egothor branch, not an independent algorithmic lineage + +This point is easy to miss when Stempel is encountered through Lucene or +Elasticsearch. + +Lucene's own Stempel documentation states that the core stemming algorithm and +implementation were taken **verbatim / virtually unchanged from the Egothor +project**. The Stempel distribution is principally associated with its Polish +stemming tables, even though the underlying algorithm is not inherently +Polish-specific. + +That makes Stempel historically important, but it should be interpreted +correctly in Radixor comparisons: + +- it demonstrates that the older Egothor technique survived in major search + infrastructure; +- it is not evidence of a separate later algorithm that Radixor subsequently + copied; +- benchmarking Stempel against Radixor is effectively a comparison between a + preserved legacy branch of the technique and its modern re-engineering. + +Official Lucene reference: +[StempelStemmer](https://lucene.apache.org/core/9_9_1/analysis/stempel/org/apache/lucene/analysis/stempel/StempelStemmer.html). + +In the current Polish benchmark, the direct Stempel path has balanced accuracy +**0.855699** versus **0.991105** for Radixor, and its direct runtime is measured +at **4.229×** the Radixor time. The Lucene StempelFilter path is **4.803×** the +Radixor time. See the [Polish benchmark](benchmarks/languages/polish.md). + +## Morfologik is a closed-vocabulary morphological lookup + +Morfologik solves a different problem from Radixor. Its runtime is a +dictionary-driven morphological lookup backed by a finite-state automaton (FSA). +The distinction is not merely terminology: it determines what happens when +production text contains a word form that the dictionary does not know. + +`DictionaryLookup.lookup(...)` searches the compiled automaton for the supplied +surface form and returns the stored base-form analyses only when that form is +present. If the lookup fails, it returns an empty result. Lucene's +`MorfologikFilter` tries the original token and then its lowercase form; if both +lookups fail, the filter emits the original token unchanged. + +There is therefore **no rule-based or learned transformation fallback for an +out-of-vocabulary word**. + +Primary-source implementations: + +- [Morfologik `DictionaryLookup`](https://github.com/morfologik/morfologik-stemming/blob/master/morfologik-stemming/src/main/java/morfologik/stemming/DictionaryLookup.java) +- [Lucene `MorfologikFilter`](https://github.com/apache/lucene/blob/main/lucene/analysis/morfologik/src/java/org/apache/lucene/analysis/morfologik/MorfologikFilter.java) + +This makes dictionary completeness an operational requirement, not just a +quality-tuning parameter. A new domain term, previously unseen inflection, +product name, spelling variant, or other surface form outside the compiled +dictionary receives no morphological reduction from Morfologik. In an +open-vocabulary search system, maintaining coverage therefore requires a +sufficiently comprehensive dictionary and continued dictionary updates. + +Radixor uses lexical resources differently. Its source data is **training +evidence for transformations**. Word-to-root relationships are converted into +patch commands, organized in a trie, structurally reduced, and compiled into a +runtime machine. The deployed stemmer selects transformation behaviour and +applies it to the input token; it is not restricted to retrieving a stored +analysis for an exact dictionary member. + +This architectural difference matters when interpreting quality numbers. +Morfologik can provide strong analyses for vocabulary covered by its dictionary, +but that strength does not imply generalization to unseen forms. Radixor is +designed to preserve the linguistic evidence of large lexical resources while +turning it into reusable transformation behaviour. + +The current Polish benchmark also places the two approaches at very different +points on the measured quality/performance envelope: + +- deterministic primary-output balanced accuracy is **0.991105** for Radixor + and **0.948392** for `MorfologikFilter`; +- when all emitted candidates are considered, balanced accuracy is + **1.000000** for Radixor and **0.987528** for Morfologik; +- the measured Lucene `MorfologikFilter` runtime is **15.997×** the Radixor + runtime in the same Java benchmark. + +See the [Polish benchmark](benchmarks/languages/polish.md). + +The useful conclusion is not that Morfologik is unsophisticated. It is a +morphological dictionary system with a richer analysis objective. The important +engineering distinction is sharper: **it pays the runtime and storage cost of +dictionary/FSA analysis while remaining bounded by dictionary coverage; +Radixor compiles lexical evidence into a substantially smaller hot-path +transformation problem that can also operate beyond explicitly observed word +forms.** + +## Snowball and Porter are fixed rule systems + +Porter and Snowball form another distinct lineage. Their language algorithms are +explicit rule programs, usually centered on suffix regions and ordered rewrite +rules. + +They have a genuine advantage over closed dictionary lookup: their rules +naturally apply to previously unseen words. The trade-off is that the +linguistic behaviour is encoded in the hand-designed rule program itself rather +than learned from lexical evidence. + +At `N=100`, the current Python batch benchmark shows that rule-based +generalization does not require accepting a runtime advantage over Radixor: + +- **18 / 18** direct language comparisons are won by Radixor against PyStemmer + 3.1.0; +- the geometric-mean speedup is **1.67×**; +- the largest measured direct advantage is **3.03×** (Italian); +- Radixor spans **3.66–5.99 million words/s** across all 20 measured Radixor + languages at batch size `N=100`. + +Those are performance results. The newly integrated official Snowball 3.1.0 Java +quality comparators also make the linguistic trade-off visible for the three +algorithms added in that Snowball generation: + +| Language, `ALL_WORDS` | Radixor balanced accuracy | Snowball 3.1.0 balanced accuracy | Radixor OI / UI | Snowball OI / UI | +| --- | ---: | ---: | ---: | ---: | +| Czech | **0.996617** | 0.786366 | **0% / 0.676519%** | 0.000904% / 42.725842% | +| Persian | **0.976360** | 0.535123 | **0% / 4.728041%** | 0.001278% / 92.974054% | +| Polish | **0.991105** | 0.823625 | **0% / 1.779024%** | 0.000967% / 35.273970% | + +The lowercase-only evaluation gives the same picture: + +- **Czech:** 0.997195 vs 0.784821 balanced accuracy, with UI 0.561033% vs + 43.034822%; +- **Persian:** 0.976360 vs 0.535123, with UI 4.728041% vs 92.974054%; +- **Polish:** 0.991301 vs 0.823465, with UI 1.739895% vs 35.306102%. + +The dominant difference is under-stemming rather than excessive conflation. +Snowball's over-stemming remains very low in these measurements, but it leaves +a much larger share of gold-related forms ungrouped. That distinction matters: +a conservative stemmer can look safe when judged only by false conflations +while still sacrificing substantial recall. + +Finnish remains another useful illustration. The published `ALL_WORDS` +primary-output quality result is **0.984838** balanced accuracy for Radixor +versus **0.740279** for the Snowball Finnish Lucene path, with under-stemming +**3.032474%** versus **51.944179%**. At `N=100` in the current Python batch run, +Radixor is **1.14×** faster than PyStemmer's Finnish implementation. + +See the [Finnish benchmark](benchmarks/languages/finnish.md), the +[Czech benchmark](benchmarks/languages/czech.md), the +[Persian benchmark](benchmarks/languages/persian.md), and the +[Polish benchmark](benchmarks/languages/polish.md). + +The important comparison is therefore not “dictionary versus rules”. +Radixor occupies a third position: **it learns transformations from lexical +evidence, compiles them into a reduced patch-command trie, and retains +algorithmic generalization at runtime without hardcoding a fixed suffix program.** + +## Lucene light, minimal, plural, and possessive filters + +Several Lucene language filters are intentionally narrow transformations. A +minimal or light stemmer may be extremely fast precisely because it performs +less linguistic conflation. + +That is not a defect. It is a different objective. + +The important benchmark discipline is therefore: + +> do not interpret runtime without the corresponding grouping quality. + +A stemmer that removes only a tiny set of endings and a stemmer that attempts +broad morphological conflation are not doing equivalent work merely because +both return a string called a “stem”. + +## Hunspell + +Hunspell combines dictionaries with affix rules and can produce several +candidate stems. It is another useful comparator because it occupies a middle +ground between direct dictionary analysis and pure suffix stemming. + +Its architecture is still different from Radixor's compiled patch-command trie: +Hunspell interprets lexical and affix resources, whereas Radixor has already +compiled observed transformation behaviour into a reduced runtime machine. + +## What the current benchmark results justify saying + +The project does not need to position Radixor merely as “another stemmer”. + +A more accurate statement is: + +> **Radixor is a learned transformation stemmer built around reduced +> patch-command tries. Its current public benchmarks show that this architecture +> can move the quality/performance frontier rather than merely trade one for the +> other.** + +That is an architectural and empirical claim, not a claim that every alternative +project is poorly designed. Different systems were built for different goals: + +| Family | Primary runtime idea | Typical strength | Key distinction from Radixor | +| --- | --- | --- | --- | +| Radixor | Learned patch commands in a reduced compiled trie | High-quality conflation with compact deterministic runtime | Training data compiles into reusable transformations | +| Stempel | Historical Egothor implementation + stemming tables | Proven legacy deployment, especially Polish | Same historical algorithmic lineage; older implementation branch | +| Morfologik | Dictionary/FSA morphological lookup | Rich in-vocabulary lemmatization and multiple analyses | Closed-vocabulary lookup: unknown forms have no stemming fallback; Lucene passes them through unchanged | +| Snowball / Porter | Fixed language rule programs | Portable rule-based stemming with natural OOV coverage | Rules generalize to unseen forms, but are authored rather than learned from lexical evidence | +| Lucene light/minimal | Deliberately narrow handcrafted rules | Very low runtime cost | Intentionally less linguistic work | +| Hunspell | Dictionary + affix rules | Lexical/affix analysis and candidate outputs | Runtime interprets lexicon/affix resources | + +The benchmark pages remain the authority for each language and comparator. The +purpose of this page is to make the **technology categories** explicit so readers +do not have to infer them from implementation names. + +## Historical references + +- Leo Galambos, *Lemmatizer for Document Information Retrieval Systems in JAVA* + (SOFSEM 2001) +- [Lucene StempelStemmer documentation](https://lucene.apache.org/core/9_9_1/analysis/stempel/org/apache/lucene/analysis/stempel/StempelStemmer.html) +- [Lucene Morfologik package documentation](https://lucene.apache.org/core/10_3_2/analysis/morfologik/org/apache/lucene/analysis/morfologik/package-summary.html) +- [Architecture](architecture.md) +- [Benchmark results](benchmarks/index.md) +- [Tested stemmer inventory](benchmarks/reference/tested-stemmers.md) diff --git a/docs/why-radixor-is-different.md b/docs/why-radixor-is-different.md new file mode 100644 index 0000000..c6f8b3e --- /dev/null +++ b/docs/why-radixor-is-different.md @@ -0,0 +1,161 @@ +# Why Radixor Is Different + +Radixor is **dictionary-trained, not dictionary-bound**. + +The source dictionaries are build-time evidence from which Radixor learns +word-to-stem transformations. The runtime artifact is not a flat table that can +only answer words already present in that evidence. It is a compact, +deterministic **trie of patch commands**. + +That distinction is the shortest way to understand the project. + +## A learned transformation stemmer + +A conventional dictionary lookup stores a relationship such as: + +```text +running -> run +``` + +Radixor instead derives a transformation that can be represented conceptually as: + +```text +running -> -> run +``` + +Many word forms share the same transformation behaviour. Radixor organizes those +commands in a trie, reduces structurally equivalent regions, contracts uniform +preferred-command subtrees, and freezes the result into an immutable compiled +runtime structure. + +The pipeline is therefore: + +```mermaid +flowchart TD + evidence[Lexical evidence] + transformations[Word-to-root transformations] + commands[Patch commands] + mutable[Mutable trie] + reduction[Subtree contraction and semantic reduction] + compiled[Compiled trie] + runtime[Runtime command selection and application] + + evidence --> transformations --> commands --> mutable + mutable --> reduction --> compiled --> runtime +``` + +The dictionary is important because it supplies the linguistic evidence. It does +**not** define a closed runtime vocabulary. + +## What happens to an unseen word? + +The compiled trie selects transformation behaviour rather than storing a full +lemma string for every possible input. + +Uniform subtrees can be contracted into accepting leaves. When lookup reaches an +accepting leaf whose preferred patch command is already determined, the runtime +can apply that command even with input characters remaining. This is one of the +ways the compiled model can generalize beyond explicitly observed dictionary +forms. + +Generalization is not a promise that every arbitrary unknown token has a useful +stem. No practical stemmer can make that guarantee. The important property is +that Radixor is **not limited to exact dictionary membership**. + +For the implementation details, see [Architecture](architecture.md). + +## Why patch commands matter + +Patch commands encode *how to transform* a word rather than merely *which string +to return*. + +That gives the runtime model several useful properties: + +- repeated transformation behaviour can be shared; +- trie paths share structural information between related inputs; +- equivalent subtrees can be reduced; +- the final command is applied directly to the original token; +- the runtime can expose a deterministic preferred result; +- the same compiled node may retain ranked alternative commands when ambiguity + should not be discarded. + +The result is closer to a compact learned transformation machine than to either a +flat dictionary or a handwritten suffix list. + +## Why the trie matters + +The trie is not just a storage container around a dictionary. + +It is the structure that makes the learned transformations reusable. Shared +paths represent shared input structure; reduction merges equivalent behaviour; +uniform-subtree contraction can terminate preferred-result lookup early. + +This is also why the source dictionary may be very large while the deployed +representation remains compact and fast. + +## Radixor is not three common things + +### It is not a closed dictionary lemmatizer + +A closed dictionary lemmatizer primarily asks whether the current surface form +exists in a lexicon or automaton and, if it does, returns stored analyses. + +Radixor uses lexical resources differently: it **compiles transformation +behaviour from them**. + +### It is not another fixed suffix-rule stemmer + +Porter- and Snowball-family stemmers encode explicit rules for a language. +Those systems can generalize because the rules apply to unseen text, but the +rules themselves are fixed algorithmic knowledge. + +Radixor learns its transformation behaviour from language data and then compiles +that behaviour into its runtime trie. + +### It is not a full morphological analyzer + +A full analyzer may return lemmas, parts of speech, grammatical tags, and +multiple analyses. That is valuable when applications need morphological +interpretation. + +Radixor has a narrower search-oriented objective: produce compact, high-quality +term conflation with predictable runtime cost. It can preserve multiple stemming +candidates, but it does not attempt to become a general-purpose morphological +analysis framework. + +## What is distinctive about the combination + +Any one ingredient in isolation is familiar: + +- dictionaries are familiar; +- tries are familiar; +- string edit commands are familiar; +- subtree reduction is familiar. + +The distinctive architecture is their **combination**: + +> lexical evidence → patch commands → trie organization → semantic reduction → +> compact deterministic runtime transformation + +That architecture separates expensive learning and compilation from hot-path +runtime work. + +## Modern Radixor adds more than the historical implementation + +Radixor preserves the useful Egothor idea while rebuilding the operational model +for current software: + +- immutable compiled tries; +- compiled patch-command objects rather than repeated textual interpretation; +- deterministic ranked multi-result lookup; +- configurable reduction semantics; +- uniform-subtree contraction; +- binary persistence; +- independent language-model versioning and integrity verification; +- reopening and extending compiled structures; +- Java and Python native runtimes; +- reproducible quality and performance benchmark infrastructure. + +For the historical lineage and how Stempel, Morfologik, Snowball, and other +comparators relate to this architecture, continue with +[Technology and Lineage](technology-lineage.md). diff --git a/gradle/python.gradle b/gradle/python.gradle new file mode 100644 index 0000000..9c4285c --- /dev/null +++ b/gradle/python.gradle @@ -0,0 +1,303 @@ +def pythonProjectDirectory = layout.projectDirectory.dir('python') +def pythonHostDistributionDirectory = layout.buildDirectory.dir('python/dist/host') +def pythonSdistDistributionDirectory = layout.buildDirectory.dir('python/dist/sdist') +def pythonStandardDistributionDirectory = layout.buildDirectory.dir('python/dist/standard') +def pythonGeneratedStandardProjectDirectory = layout.buildDirectory.dir('python/generated/models-standard') +def pythonBenchmarkRuntimeDirectory = layout.buildDirectory.dir('python/runtime/benchmark') +def pythonModelCompilerRuntimeDirectory = layout.buildDirectory.dir('python/runtime/model-compiler') +def pythonBenchmarkReportDirectory = layout.buildDirectory.dir('reports/python-benchmarks') +def pythonTemporaryDirectory = layout.buildDirectory.dir('python/tmp') + +def hostOsName = System.getProperty('os.name', '').toLowerCase(Locale.ROOT) +def hostPlatform = hostOsName.contains('win') ? 'windows' + : hostOsName.contains('mac') || hostOsName.contains('darwin') ? 'macos' + : 'linux' +def hostArchitectureName = System.getProperty('os.arch', '').toLowerCase(Locale.ROOT) +def hostArchitecture = hostArchitectureName in ['aarch64', 'arm64'] ? 'aarch64' : 'x86_64' + +def pythonExecutable = providers.gradleProperty('pythonExecutable') + .orElse(hostPlatform == 'windows' ? 'python' : 'python3') +def maturinExecutable = providers.gradleProperty('maturinExecutable').orElse('maturin') +def pythonToolIdentity = providers.exec { + commandLine(pythonExecutable.get(), '--version') +}.standardOutput.asText.map { String value -> value.strip() } +def maturinToolIdentity = providers.exec { + commandLine(maturinExecutable.get(), '--version') +}.standardOutput.asText.map { String value -> value.strip() } +def rustToolIdentity = providers.exec { + commandLine('rustc', '--version') +}.standardOutput.asText.map { String value -> value.strip() } +def pythonBenchmarkWords = providers.gradleProperty('pythonBenchmarkWords').orElse('5000') +def pythonBenchmarkRepeats = providers.gradleProperty('pythonBenchmarkRepeats').orElse('15') +def pythonBenchmarkWarmup = providers.gradleProperty('pythonBenchmarkWarmup').orElse('3') + +def rustTargets = [ + linux : [x86_64: 'x86_64-unknown-linux-gnu', aarch64: 'aarch64-unknown-linux-gnu'], + windows: [x86_64: 'x86_64-pc-windows-msvc', aarch64: 'aarch64-pc-windows-msvc'], + macos : [x86_64: 'x86_64-apple-darwin', aarch64: 'aarch64-apple-darwin'] +] + +def pythonBuildStandardModels = tasks.register('pythonBuildStandardModels', Exec) { + group = 'python' + description = 'Builds the pure py3-none-any standard-model wheel and offline-ready sdist.' + + dependsOn('regeneratePythonStandardModels') + inputs.dir(pythonGeneratedStandardProjectDirectory) + inputs.file(pythonProjectDirectory.file('scripts/build_standard_distribution.py')) + inputs.property('pythonExecutable', pythonExecutable) + inputs.property('pythonToolIdentity', pythonToolIdentity) + outputs.dir(pythonStandardDistributionDirectory) + + workingDir(layout.projectDirectory) + doFirst { + final File output = pythonStandardDistributionDirectory.get().asFile + if (output.exists() && !output.deleteDir()) { + throw new GradleException("Cannot clean standard-model distribution directory: ${output}") + } + output.mkdirs() + commandLine(pythonExecutable.get(), 'python/scripts/build_standard_distribution.py', + '--project', pythonGeneratedStandardProjectDirectory.get().asFile.absolutePath, + '--outdir', output.absolutePath) + } +} + +def pythonBuildSdist = tasks.register('pythonBuildSdist', Exec) { + group = 'python' + description = 'Builds the Radixor native source distribution without runtime model data.' + + inputs.files(pythonProjectDirectory.file('Cargo.toml'), pythonProjectDirectory.file('Cargo.lock'), + pythonProjectDirectory.file('pyproject.toml'), layout.projectDirectory.file('gradle/python.gradle')) + inputs.dir(pythonProjectDirectory.dir('src')) + inputs.files(fileTree(pythonProjectDirectory.dir('radixor')) { + exclude 'models/**' + }) + inputs.property('maturinExecutable', maturinExecutable) + inputs.property('maturinToolIdentity', maturinToolIdentity) + outputs.dir(pythonSdistDistributionDirectory) + + workingDir(pythonProjectDirectory) + doFirst { + final File output = pythonSdistDistributionDirectory.get().asFile + if (output.exists() && !output.deleteDir()) { + throw new GradleException("Cannot clean Python sdist output directory: ${output}") + } + output.mkdirs() + commandLine(maturinExecutable.get(), 'sdist', '--out', output.absolutePath) + } +} + +def registerPythonWheelBuild = { String taskName, String taskDescription, Provider outputDirectory, + String target -> + tasks.register(taskName, Exec) { + group = 'python' + description = taskDescription + + inputs.files(pythonProjectDirectory.file('Cargo.toml'), pythonProjectDirectory.file('Cargo.lock'), + pythonProjectDirectory.file('pyproject.toml'), layout.projectDirectory.file('gradle/python.gradle')) + inputs.dir(pythonProjectDirectory.dir('src')) + inputs.dir(pythonProjectDirectory.dir('radixor')) + inputs.property('maturinExecutable', maturinExecutable) + inputs.property('maturinToolIdentity', maturinToolIdentity) + inputs.property('rustToolIdentity', rustToolIdentity) + inputs.property('pythonBuildTarget', target == null ? 'host' : target) + if (target == null) { + inputs.property('pythonExecutable', pythonExecutable) + inputs.property('pythonToolIdentity', pythonToolIdentity) + } + outputs.dir(outputDirectory) + + workingDir(pythonProjectDirectory) + doFirst { + final File output = outputDirectory.get().asFile + if (output.exists() && !output.deleteDir()) { + throw new GradleException("Cannot clean Python wheel output directory: ${output}") + } + output.mkdirs() + final List arguments = [ + 'build', '--release', '--locked', '--out', output.absolutePath + ] + if (target != null) { + arguments.addAll(['--target', target]) + } else { + arguments.addAll(['--interpreter', pythonExecutable.get()]) + } + commandLine([maturinExecutable.get()] + arguments) + } + } +} + +def pythonBuildNativeWheel = registerPythonWheelBuild( + 'pythonBuildNativeWheel', + 'Builds the Radixor native Python wheel for the current host platform.', + pythonHostDistributionDirectory, + null +) +def pythonBuild = tasks.register('pythonBuild') { + group = 'python' + description = 'Builds the host native wheel/sdist and generated standard-model wheel/sdist.' + dependsOn(pythonBuildNativeWheel, pythonBuildStandardModels, pythonBuildSdist) +} + +rustTargets.each { String platform, Map architectureTargets -> + final String taskName = 'pythonBuild' + platform.capitalize() + if (platform == hostPlatform) { + tasks.register(taskName) { + group = 'python' + description = "Builds the Radixor Python release wheel for ${platform} on the current host." + dependsOn(pythonBuild) + } + } else { + final String propertyName = 'python' + platform.capitalize() + 'Target' + final String target = providers.gradleProperty(propertyName) + .getOrElse(architectureTargets[hostArchitecture]) + registerPythonWheelBuild( + taskName, + "Cross-builds the Radixor Python release wheel for ${platform}; requires the target toolchain.", + layout.buildDirectory.dir("python/dist/${platform}"), + target + ) + } +} + +tasks.register('preparePythonBenchmarkRuntime', Sync) { + group = 'python' + description = 'Extracts the host Python wheel into an isolated benchmark runtime.' + + dependsOn(pythonBuild) + + from { + final Set wheels = fileTree(pythonHostDistributionDirectory).matching { + include '*.whl' + }.files + if (wheels.size() != 1) { + throw new GradleException("Expected exactly one host Python wheel, found ${wheels.size()} in " + + pythonHostDistributionDirectory.get().asFile) + } + zipTree(wheels.first()) + } + from { + final Set wheels = fileTree(pythonStandardDistributionDirectory).matching { + include '*.whl' + }.files + if (wheels.size() != 1) { + throw new GradleException("Expected exactly one standard-model Python wheel, found ${wheels.size()} in " + + pythonStandardDistributionDirectory.get().asFile) + } + zipTree(wheels.first()) + } + into(pythonBenchmarkRuntimeDirectory) +} + +tasks.register('preparePythonModelCompilerRuntime', Sync) { + group = 'python' + description = 'Extracts the host native wheel for deterministic standard-model regeneration.' + dependsOn(pythonBuildNativeWheel) + from { + final Set wheels = fileTree(pythonHostDistributionDirectory).matching { + include '*.whl' + }.files + if (wheels.size() != 1) { + throw new GradleException("Expected exactly one host Python wheel, found ${wheels.size()} in " + + pythonHostDistributionDirectory.get().asFile) + } + zipTree(wheels.first()) + } + into(pythonModelCompilerRuntimeDirectory) +} + +tasks.register('regeneratePythonStandardModels', Exec) { + group = 'python' + description = 'Generates standard .rxc artifacts and metadata below build/ from canonical model sources.' + dependsOn(tasks.named('preparePythonModelCompilerRuntime')) + inputs.file(layout.projectDirectory.file('models/model-projects.properties')) + inputs.file(layout.projectDirectory.file('models/catalog-version.txt')) + inputs.files(fileTree(layout.projectDirectory.dir('models')) { + include '*/build.gradle', '*/model-version.txt', '*/src/modelInput/stemmer.gz', + '*/src/modelInput/NOTICE-model-data.txt' + }) + inputs.files(fileTree(pythonProjectDirectory.dir('models-standard')) { + exclude 'build/**', 'dist/**', '*.egg-info/**', '**/__pycache__/**', + 'radixor_models_standard/manifest.json', 'radixor_models_standard/models/*.rxc', + 'radixor_models_standard/notices/*/NOTICE-model-data.txt' + }) + inputs.file(pythonProjectDirectory.file('scripts/build_standard_models.py')) + outputs.dir(pythonGeneratedStandardProjectDirectory) + workingDir(layout.projectDirectory) + doFirst { + environment('PYTHONPATH', pythonModelCompilerRuntimeDirectory.get().asFile.absolutePath) + commandLine( + pythonExecutable.get(), 'python/scripts/build_standard_models.py', + '--project', pythonGeneratedStandardProjectDirectory.get().asFile.absolutePath, + '--distribution-version', '0.0.0' + ) + } +} + +tasks.register('pythonVerifyDistributions', Exec) { + group = 'verification' + description = 'Verifies both Python archives and a fresh offline wheel-only installation.' + dependsOn(pythonBuild) + inputs.dir(pythonHostDistributionDirectory) + inputs.dir(pythonSdistDistributionDirectory) + inputs.dir(pythonStandardDistributionDirectory) + inputs.file(pythonProjectDirectory.file('scripts/verify_distributions.py')) + outputs.upToDateWhen { false } + workingDir(layout.projectDirectory) + commandLine( + pythonExecutable.get(), 'python/scripts/verify_distributions.py', + '--main-wheel-dir', pythonHostDistributionDirectory.get().asFile.absolutePath, + '--main-sdist-dir', pythonSdistDistributionDirectory.get().asFile.absolutePath, + '--standard-dir', pythonStandardDistributionDirectory.get().asFile.absolutePath + ) +} + +tasks.register('pythonBenchmarkAllLanguagesBatch', Exec) { + group = 'verification' + description = 'Benchmarks the host Python wheel and available competitors for every supported language.' + + dependsOn(tasks.named('preparePythonBenchmarkRuntime')) + + inputs.files(pythonProjectDirectory.file('benchmarks/run_benchmark.py'), + pythonProjectDirectory.file('benchmarks/corpus.py'), + pythonProjectDirectory.file('benchmarks/engines.py'), + layout.projectDirectory.file('gradle/python.gradle')) + inputs.dir(pythonBenchmarkRuntimeDirectory) + inputs.property('pythonExecutable', pythonExecutable) + inputs.property('pythonToolIdentity', pythonToolIdentity) + inputs.property('pythonBenchmarkWords', pythonBenchmarkWords) + inputs.property('pythonBenchmarkRepeats', pythonBenchmarkRepeats) + inputs.property('pythonBenchmarkWarmup', pythonBenchmarkWarmup) + outputs.file(pythonBenchmarkReportDirectory.map { it.file('all-languages-batch.csv') }) + outputs.file(pythonBenchmarkReportDirectory.map { it.file('all-languages-batch.json') }) + outputs.upToDateWhen { false } + + workingDir(pythonProjectDirectory) + doFirst { + final File reportDirectory = pythonBenchmarkReportDirectory.get().asFile + reportDirectory.mkdirs() + environment('PYTHONPATH', pythonBenchmarkRuntimeDirectory.get().asFile.absolutePath) + commandLine( + pythonExecutable.get(), + 'benchmarks/run_benchmark.py', + '--all-languages', + '--sizes', '10', '20', '50', '100', + '--words', pythonBenchmarkWords.get(), + '--repeats', pythonBenchmarkRepeats.get(), + '--warmup', pythonBenchmarkWarmup.get(), + '--csv', new File(reportDirectory, 'all-languages-batch.csv').absolutePath, + '--json', new File(reportDirectory, 'all-languages-batch.json').absolutePath + ) + } +} + +tasks.withType(Exec).configureEach { Exec task -> + if (task.name.startsWith('python') || task.name == 'regeneratePythonStandardModels') { + task.doFirst { + final File temporaryDirectory = pythonTemporaryDirectory.get().asFile + temporaryDirectory.mkdirs() + environment('TMPDIR', temporaryDirectory.absolutePath) + environment('TEMP', temporaryDirectory.absolutePath) + environment('TMP', temporaryDirectory.absolutePath) + } + } +} diff --git a/gradle/snowball-benchmarks.gradle b/gradle/snowball-benchmarks.gradle index c99b510..b6f5133 100644 --- a/gradle/snowball-benchmarks.gradle +++ b/gradle/snowball-benchmarks.gradle @@ -1,7 +1,10 @@ import org.gradle.plugins.ide.eclipse.model.SourceFolder +import java.security.MessageDigest -def snowballVersion = '3.0.1' + +def snowballVersion = '3.1.0' +def snowballArchiveSha256 = '5dab34d491f55f47b6e971569ffe6aadf5991512c648ddfe5d331b494cf6d655' def snowballArchiveName = "libstemmer_java-${snowballVersion}.tar.gz" def snowballDistributionDirectoryName = "libstemmer_java-${snowballVersion}" def snowballRootRelativePath = 'third-party/snowball' @@ -73,11 +76,32 @@ tasks.register('downloadSnowballJava') { } } +tasks.register('verifySnowballJava') { + group = 'verification' + description = 'Verifies the official Snowball Java source distribution checksum.' + + dependsOn(tasks.named('downloadSnowballJava')) + inputs.file(snowballDownloadFile) + inputs.property('expectedSha256', snowballArchiveSha256) + + doLast { + final File archive = snowballDownloadFile.get().asFile + final String actualSha256 = MessageDigest.getInstance('SHA-256') + .digest(archive.bytes) + .encodeHex() + .toString() + if (actualSha256 != snowballArchiveSha256) { + throw new GradleException("Snowball ${snowballVersion} archive SHA-256 mismatch: " + + "expected ${snowballArchiveSha256}, found ${actualSha256}.") + } + } +} + tasks.register('extractSnowballJava', Copy) { group = 'build setup' description = 'Extracts the official Snowball Java source distribution.' - dependsOn(tasks.named('downloadSnowballJava')) + dependsOn(tasks.named('verifySnowballJava')) from(tarTree(resources.gzip(snowballDownloadFile))) into(snowballExtractDirectory) diff --git a/mkdocs.yml b/mkdocs.yml index 5013e9b..6fd49ae 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,15 +1,17 @@ site_name: Radixor -site_description: High-performance multi-language stemming toolkit for Java +site_description: Learned transformation stemming with compiled patch-command tries for Java and Python site_url: https://leogalambos.github.io/Radixor/ repo_url: https://github.com/leogalambos/Radixor repo_name: leogalambos/Radixor -copyright: "© 2026 Egothor. Licensed under BSD-3-Clause." +copyright: "© 2026 Egothor · Radixor software is BSD-3-Clause licensed." theme: name: material language: en + custom_dir: docs/overrides + logo: assets/images/radixor-logo.png + favicon: assets/images/radixor-logo.png features: - - navigation.instant - navigation.sections - navigation.top - search.suggest @@ -17,14 +19,20 @@ theme: - content.code.copy palette: - scheme: default - primary: indigo - accent: indigo + primary: white + accent: blue -extra: +extra: generator: false extra_css: - assets/stylesheets/extra.css + - assets/stylesheets/landing-v2.css + - assets/stylesheets/radixor-docs-safety.css + +extra_javascript: + - https://unpkg.com/mermaid@11/dist/mermaid.min.js + - assets/javascripts/mermaid.js markdown_extensions: - admonition @@ -32,18 +40,28 @@ markdown_extensions: - md_in_html - pymdownx.details - pymdownx.highlight - - pymdownx.superfences + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: + alternate_style: true - tables nav: - Home: index.md - Start: - - Fast Track: fast-track.md - - Quick Start: quick-start.md - - Integration Deep Dive: integration-deep-dive.md + - Choose a Runtime: getting-started.md + - Why Radixor Is Different: why-radixor-is-different.md + - Python Fast Track: python/fast-track.md + - Java Fast Track: fast-track.md + - Python Quick Start: python/quick-start.md + - Java Quick Start: quick-start.md - - Integration: + - Java: + - Integration Deep Dive: integration-deep-dive.md - Overview: programmatic-usage.md - Model Selection and Loading: model-selection-and-loading.md - Loading and Building Stemmers: programmatic-loading-and-building.md @@ -52,6 +70,15 @@ nav: - Migration and Backward Compatibility: migration-and-backward-compatibility.md - CLI Compilation: cli-compilation.md + - Python: + - Fast Track: python/fast-track.md + - Quick Start: python/quick-start.md + - Overview: python/index.md + - Installation and Builds: python/installation.md + - Usage and API: python/usage.md + - Dictionary Compilation: python/model-compilation.md + - Benchmarks: python/performance.md + - Dictionaries and Languages: - Stemmer Models: stemmer-models.md - Published Model Catalog: stemmer-model-catalog.md @@ -60,6 +87,7 @@ nav: - Contributing Dictionaries: contributing-dictionaries.md - Architecture and Semantics: + - Technology and Lineage: technology-lineage.md - Overview: architecture-and-reduction.md - Architecture: architecture.md - Reduction Semantics: reduction-semantics.md diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 0000000..16cf713 --- /dev/null +++ b/python/.gitignore @@ -0,0 +1,23 @@ +# Rust and maturin build products +/target/ +**/dist/ +/models-standard/build/ +/radixor/models/ +*.so +*.egg-info/ + +# Standard-model payload is generated deterministically below the repository +# build directory and belongs only in the published wheel/sdist. +/models-standard/radixor_models_standard/manifest.json +/models-standard/radixor_models_standard/models/*.rxc +/models-standard/radixor_models_standard/notices/*/NOTICE-model-data.txt + +# Local Python environments and caches +.venv/ +__pycache__/ +*.py[cod] +.pytest_cache/ + +# Reports produced by direct benchmark-script runs +/benchmarks/results*.csv +/benchmarks/results*.json diff --git a/python/Cargo.lock b/python/Cargo.lock new file mode 100644 index 0000000..86a59df --- /dev/null +++ b/python/Cargo.lock @@ -0,0 +1,254 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "portable-atomic" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radixor" +version = "0.0.0" +dependencies = [ + "flate2", + "pyo3", + "unicode-general-category", + "unicode-normalization", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-general-category" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" diff --git a/python/Cargo.toml b/python/Cargo.toml new file mode 100644 index 0000000..024b319 --- /dev/null +++ b/python/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "radixor" +version = "0.0.0" +edition = "2021" + +[lib] +name = "_radixor" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { version = "0.22", features = ["extension-module", "abi3-py39"] } +flate2 = "1.0" +unicode-normalization = "0.1" +unicode-general-category = "0.6" + +[profile.release] +lto = true +codegen-units = 1 +opt-level = 3 +strip = true diff --git a/python/LICENSE b/python/LICENSE new file mode 100644 index 0000000..b752302 --- /dev/null +++ b/python/LICENSE @@ -0,0 +1,28 @@ +Copyright (C) 2026, Leo Galambos +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 copyright holder 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 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/python/README.md b/python/README.md new file mode 100644 index 0000000..0ee3a7c --- /dev/null +++ b/python/README.md @@ -0,0 +1,253 @@ +# radixor — Fastest Stemming for Python + +**radixor** is a Python extension for the [Radixor](https://github.com/leogalambos/Radixor) stemmer library, built on a Rust core via [PyO3](https://pyo3.rs/). It provides sub-microsecond per-word stemming with a batch API that amortises the Python↔Rust bridge overhead across thousands of words at once. + +## Why radixor? + +| Library | Approach | Batch API | +|---|---|---| +| **radixor** | Compiled patch-command trie in Rust | ✅ `stem_batch()` | +| PyStemmer (Snowball) | C extension (`libstemmer`) | ✅ `stemWords()` | +| snowballstemmer | Pure-Python Snowball | ✅ (Python loop) | +| NLTK Porter / CISTEM | Pure Python | ❌ | + +**Performance.** On the shared UniMorph gold-standard corpus, measuring runtime +stemming only (construction excluded) and with a fair, cache-disabled, +same-input methodology, radixor won all **18 / 18** direct comparisons with +PyStemmer 3.1.0 (Snowball's C `libstemmer`) in the published 2026-08-08 run. +At batch size 100, the geometric-mean speedup was **1.67×**. The complete +machine metadata and current results are in the [Python performance +documentation](../docs/python/performance.md); benchmark implementation and +fairness notes are in [`benchmarks/`](benchmarks/README.md). + +## Installation + +From PyPI, once publication is enabled: + +```bash +python -m pip install --only-binary=:all: radixor +``` + +The GitHub Releases-backed index is the independent alternative: + +```bash +python -m pip install --only-binary=:all: \ + --index-url https://leogalambos.github.io/Radixor/python/simple/ radixor +``` + +The GitHub command becomes usable after the first model and native releases +populate that index. See the [installation guide](../docs/python/installation.md) +for current availability and source-checkout builds. + +Wheels are provided for Linux, macOS, and Windows (Python 3.9+). The install +also resolves the mandatory pure `radixor-models-standard` dependency +with 20 precompiled standard models. Building the native source distribution +requires Rust ≥ 1.75 and [maturin](https://www.maturin.rs/). + +## Quick start + +```python +from radixor import Stemmer + +s = Stemmer("en") # English (us-uk-default model) +s.stem("running") # → "run" +s.stem("cats") # → "cat" +s.stem("unknown_word") # → None +``` + +## Batch API — the fast path + +```python +words = ["running", "cats", "stemming", "quickly"] + +# Amortises the Python→Rust bridge cost across all words at once +stems = s.stem_batch(words) +# → ["run", "cat", "stem", "quick"] +``` + +For large corpora (tens of thousands of words) the batch call is the recommended interface. It avoids per-call Python frame overhead and keeps the hot loop entirely inside Rust. + +## Migrating from PyStemmer + +Radixor provides PyStemmer's `stemWord` and `stemWords` method names. These +compatibility methods also follow PyStemmer's fallback behavior: when the trie +has no patch command, they return the original word instead of `None`. + +```python +# PyStemmer: import Stemmer +import radixor as Stemmer + +stemmer = Stemmer.Stemmer("english") +stemmer.stemWord("running") # → "run" +stemmer.stemWord("unknown_word") # → "unknown_word" +stemmer.stemWords(["running", "unknown"]) # → ["run", "unknown"] +``` + +The original Radixor methods remain unchanged: `stem` and `stem_batch` return +`None` for words without a matching patch command. Radixor accepts PyStemmer's +full language names for the languages represented by its bundled models, as +well as its existing two-letter codes and model IDs. + +## Supported languages + +| Code | Language | Model ID | +|---|---|---| +| `cs` | Czech | `cs-cz-default` | +| `da` | Danish | `da-dk-default` | +| `de` | German | `de-de-default` | +| `en` | English | `us-uk-default` | +| `es` | Spanish | `es-es-default` | +| `fa` | Persian | `fa-ir-default` | +| `fi` | Finnish | `fi-fi-default` | +| `fr` | French | `fr-fr-default` | +| `he` | Hebrew | `he-il-default` | +| `hu` | Hungarian | `hu-hu-default` | +| `it` | Italian | `it-it-default` | +| `nb` | Norwegian Bokmål | `nb-no-default` | +| `nl` | Dutch | `nl-nl-default` | +| `nn` | Norwegian Nynorsk | `nn-no-default` | +| `pl` | Polish | `pl-pl-unimorph` | +| `pt` | Portuguese | `pt-pt-default` | +| `ru` | Russian | `ru-ru-default` | +| `sv` | Swedish | `sv-se-default` | +| `uk` | Ukrainian | `uk-ua-default` | +| `yi` | Yiddish | `yi-default` | + +## API reference + +### `Stemmer(language=None, *, path=None, compiled=None, backward=None, store_original=True, lowercase=True, cache_size=10_000)` + +Create a stemmer for the given language code, model ID, custom textual +dictionary, or previously compiled version 7 trie. Textual dictionaries are +compiled in Rust; `compiled=` loads a prepared binary directly. + +```python +s = Stemmer("de") # by language code +s = Stemmer("de-de-default") # by model ID +s = Stemmer(path="/data/custom.gz") # custom gzipped dictionary +s = Stemmer(compiled="/data/custom.rxc") # prepared v7 binary +``` + +`backward` selects the traversal direction; when left as `None` it is derived +from the language (BACKWARD, except right-to-left `fa`/`he`/`yi` which use +FORWARD). `store_original` (default `True`) maps each canonical stem to a no-op +patch so the stem itself is recognised. `lowercase=False` skips runtime +lowercasing for already-normalized input, and `cache_size` enables the bounded +result cache. The default holds up to 10,000 entries, matching PyStemmer; +`cache_size=0` disables it. One cache is shared by `stem()`, `stemWord()`, +`stem_batch()`, and `stemWords()`; the `stem_all*()` methods are not cached. + +### `stem(word: str) → str | None` + +Return the stem, or `None` when the compiled trie finds no applicable patch +command. This does not mean that lookup is restricted to exact training words. + +### `stem_batch(words: list[str]) → list[str | None]` + +Stem an entire list. Preferred for large inputs. + +### `stemWord(word: str) → str` + +PyStemmer-compatible scalar method. Return the original word if it cannot be +stemmed. + +### `stemWords(words: list[str]) → list[str]` + +PyStemmer-compatible batch method. Return each unrecognized word unchanged. + +### `stem_all(word: str) → list[str]` + +Return all stems ordered by descending corpus frequency. Useful when multiple valid stems exist. + +### `stem_all_batch(words: list[str]) → list[list[str]]` + +Return all stems for each word in a batch. + +## Compiling a model (compile once, load instantly) + +Compiling the trie from a textual dictionary takes time for large languages +(seconds). You can compile it **once** to Radixor's binary format and then load +it near-instantly — the same workflow Java users have: + +```python +import radixor +radixor.compile("stemmer.gz", "en.rxc", language="en") # or backward=True/False +s = radixor.Stemmer(compiled="en.rxc") # instant load, no re-compile +``` + +The compiled file uses Radixor's **v7 trie format and is byte-compatible with +the Java `StemmerPatchTrieBinaryIO`** (the inner stream is identical), so a file +compiled by Java can be loaded by Python and vice versa. `Stemmer(path=...)` +auto-detects whether it was given a compiled trie or a textual dictionary. + +## Using a custom model + +Provide your own gzipped source dictionary (tab-separated +`stemvariant1variant2…` per line, `#` / `//` line remarks allowed) and +load it directly: + +```python +s = Stemmer(path="my_dictionary.gz") # BACKWARD by default +s = Stemmer(path="my_rtl_dictionary.gz", backward=False) # right-to-left +``` + +The dictionary is compiled to a patch-command trie in Rust at construction time. + +## Building from source + +```bash +cd Radixor/ +pip install maturin build setuptools wheel pytest +./gradlew pythonBuildStandardModels +pip install --no-deps build/python/dist/standard/radixor_models_standard-0.0.0-py3-none-any.whl +cd python/ +maturin develop --release # editable install with release optimisations +``` + +From the repository root, Gradle builds the native wheel/sdist and pure +standard-model wheel/sdist without installing them globally: + +```bash +./gradlew pythonBuild +``` + +The convenience tasks `pythonBuildLinux`, `pythonBuildWindows`, and +`pythonBuildMacos` use the host build when the requested platform matches the +current system. Other platforms are cross-compiled with the corresponding Rust +target and therefore require that target and its linker/SDK to be installed. +Override a default target with, for example, +`-PpythonWindowsTarget=x86_64-pc-windows-gnu`. Build artifacts are written below +`build/python/dist/`. + +The complete batch benchmark runs Radixor for all bundled languages and every +available comparison engine for the languages it supports, using batch sizes +10, 20, 50, and 100: + +Comparison engines are auto-detected in the environment of `pythonExecutable`. +Install `python/benchmarks/requirements-bench.txt` there to enable the complete +comparison set. + +```bash +./gradlew pythonBenchmarkAllLanguagesBatch +``` + +Use `pythonBenchmarkWords`, `pythonBenchmarkRepeats`, and +`pythonBenchmarkWarmup` Gradle properties to tune the run. CSV and JSON reports +are written below `build/reports/python-benchmarks/`. + +Neither runtime distribution contains textual dictionaries. The standard data +sdist contains build-ready gzip v7 `.rxc` files, a checksummed provenance +manifest, and per-model CC BY-SA 3.0 notices. They are generated below `build/` +from canonical `models/*/src/modelInput/stemmer.gz` inputs and are never stored +in Git. `./gradlew regeneratePythonStandardModels` performs this deterministic +generation; repository topology selects the 20 defaults and excludes optional +`pl-pl-polimorf`. + +`radixor` requires `radixor-models-standard>=1.0,<2.0`. The Python distribution +version is independent of its `2026.1` Java model-catalog identity and of the +individual model versions recorded in the manifest. + +## License + +The native/API package is BSD-3-Clause — see [LICENSE](LICENSE). Model data +is separately licensed under CC BY-SA 3.0 in its packaged notices. diff --git a/python/benchmarks/README.md b/python/benchmarks/README.md new file mode 100644 index 0000000..5017ecd --- /dev/null +++ b/python/benchmarks/README.md @@ -0,0 +1,111 @@ +# Runtime stemming benchmarks + +These scripts measure **runtime stemming throughput only** — model construction +and dictionary compilation happen once during setup and are **excluded** from +every timing. Anyone can reproduce the numbers on their own machine. + +## What is measured, and why batch sizes + +Each engine is driven through its batch entry point over a fixed word budget +(default 5 000 tokens), split into batches of **10, 20, 50, 100** words. The +harness fits the descriptive line `per_call(N) ≈ intercept + N · slope` across +those sizes. The fit is unconstrained and timing noise can make its intercept +negative, so it describes observed scaling rather than physically separating +overhead from word work. We report the *best* (minimum) of many repeats — the +microbenchmark convention that suppresses OS/GC noise. + +## Data — identical to the Java JMH benchmarks + +The workload is the **changed-token corpus** built from the repository's +canonical dictionaries (`models//src/modelInput/stemmer.gz`), +mirroring `LanguageBenchmarkCorpus` in the Java project: each dictionary field +is paired with its line's root, normalized `trim().lower()`, and only tokens +that **differ** from their root are kept, in dictionary order, padded to ≥ 5 000 +tokens. See `corpus.py`. + +## Fairness — three things that quietly break stemmer comparisons + +Getting a *fair* comparison turned out to matter more than any micro-optimization. +Three asymmetries, if left in, make the numbers meaningless: + +1. **Result caching.** PyStemmer caches results by default (`maxCacheSize=10000`). + Because a benchmark stems the same corpus every repeat, that cache turns + measured passes into dict lookups rather than stemming. **The harness + explicitly disables both caches**: PyStemmer uses `maxCacheSize=0` and + radixor uses `cache_size=0`, so both engines do real stemming. + snowballstemmer-pure, nltk-porter, and cistem have no cache. + +2. **Lowercasing.** Snowball/PyStemmer do no case handling — they assume the + caller pre-lowercased the input (our corpus is pre-lowercased for everyone). + radixor normally lowercases internally; for a same-work comparison the + harness runs radixor with **`lowercase=False`** (assume-already-lowercased), + so Snowball and radixor do identical normalization work on identical input. + CISTEM always performs its own lowercasing and German umlaut normalization; + that unavoidable extra work modestly biases its comparison in radixor's + favour. + +3. **Delegation.** `snowballstemmer` delegates to PyStemmer when it is installed + (they become the same C code). The harness bypasses that and uses + snowballstemmer's genuine pure-Python backend, and records each engine's + backing module + whether it is a compiled extension (`--json`) as proof. + +## Engines compared + +| Engine | Implementation | Batch API | +|---|---|---| +| `radixor` | Rust patch-command trie (cache disabled) | `stem_batch` — one FFI call per batch | +| `PyStemmer` | Snowball C `libstemmer` (cache disabled) | `stemWords(list)` — one C call per batch | +| `snowballstemmer-pure` | Official **pure-Python** Snowball | `stemWords(list)` — Python loop | +| `nltk-porter` | Porter (English), pure Python | scalar loop | +| `cistem` | CISTEM (German), pure Python (`nltk`) | scalar loop | + +## Reproduce + +```bash +cd python/ +maturin develop --release # build the radixor extension into your env +cd .. +./gradlew pythonBuildStandardModels # generate and package standard models +pip install --no-deps build/python/dist/standard/radixor_models_standard-0.0.0-py3-none-any.whl +cd python/ +pip install -r benchmarks/requirements-bench.txt +python benchmarks/run_benchmark.py --language en de fr ru fi \ + --sizes 10 20 50 100 --repeats 21 \ + --json benchmarks/results.json --csv benchmarks/results.csv +``` + +From the repository root, the Gradle integration builds an isolated host wheel +and benchmarks Radixor plus every available comparison engine over all +supported languages with the fixed 10/20/50/100 size sweep: + +```bash +./gradlew pythonBenchmarkAllLanguagesBatch +``` + +The generated CSV and JSON reports are placed in +`build/reports/python-benchmarks/`. + +Comparison engines are auto-detected in the environment selected by the Gradle +`pythonExecutable` property. Install `requirements-bench.txt` in that environment +to enable the complete comparison set. + +The run prints machine/Python/engine versions and each engine's backing module, +and writes per-point rows (CSV) plus the full report incl. environment and +provenance (JSON), so results are self-describing and verifiable. + +## Published results + +The canonical, current single-machine results and complete environment metadata +are published on the documentation site's [Python performance +page](../../docs/python/performance.md). Keeping the measured table in one place +prevents results from different CPUs or benchmark runs from being mixed. + +## Interpretation + +- In the published 2026-08-08 run, **radixor is the fastest stemmer measured in + Python** in all 18 languages directly shared with PyStemmer. +- The benchmark intentionally disables caches. Cached-operation performance is + outside this suite and must not be inferred from its results. +- radixor and Snowball remain different *classes* of stemmer: radixor is + dictionary-based (UniMorph gold coverage), Snowball is rule-based. radixor + gives dictionary-quality stems *and* the best measured throughput. diff --git a/python/benchmarks/corpus.py b/python/benchmarks/corpus.py new file mode 100644 index 0000000..9758fc6 --- /dev/null +++ b/python/benchmarks/corpus.py @@ -0,0 +1,103 @@ +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Deterministic benchmark corpus, mirroring the Java JMH LanguageBenchmarkCorpus. + +The timing workload contains dictionary forms that differ from their canonical +root. Each field is normalized with ``strip().lower()``, retained when it needs +stemming, and repeated in stable dictionary order until the corpus reaches +``MINIMUM_TIMING_TOKEN_COUNT``. This mirrors +``LanguageBenchmarkCorpus.buildChangedTimingCorpus`` and supplies the same +token sequence to every benchmark engine. +""" + +from __future__ import annotations + +import gzip +from pathlib import Path + +MINIMUM_TIMING_TOKEN_COUNT = 5_000 + + +def _normalize(token: str) -> str: + # Java: token.trim().toLowerCase(Locale.ROOT) + return token.strip().lower() + + +def _contains_whitespace(token: str) -> bool: + return any(ch.isspace() for ch in token) + + +def read_changed_tokens(dict_gz_path: str | Path) -> list[str]: + """Return the changed-token list (token != root) in dictionary order. + + Not yet padded to the timing minimum; see :func:`build_timing_corpus`. + """ + tokens: list[str] = [] + with gzip.open(dict_gz_path, "rt", encoding="utf-8") as fh: + for line in fh: + if not line or line.isspace(): + continue + # Match the Java benchmark: only a leading marker starts a comment; + # inline markers remain part of the dictionary field. + if line.startswith("#") or line.startswith("//"): + continue + fields = line.split("\t") + if not fields: + continue + root = _normalize(fields[0]) + if not root or _contains_whitespace(root): + continue + for field in fields: + token = _normalize(field) + if not token or _contains_whitespace(token): + continue + if token != root: # changed-token workload + tokens.append(token) + return tokens + + +def build_timing_corpus( + dict_gz_path: str | Path, + minimum_token_count: int = MINIMUM_TIMING_TOKEN_COUNT, +) -> list[str]: + """Return the padded changed-token timing corpus (>= minimum_token_count).""" + changed = read_changed_tokens(dict_gz_path) + if not changed: + raise ValueError(f"No changed-token corpus tokens available in {dict_gz_path}") + if len(changed) >= minimum_token_count: + return changed + # Repeat in stable order to reach the minimum, exactly like the Java code. + out: list[str] = [] + n = len(changed) + for i in range(minimum_token_count): + out.append(changed[i % n]) + return out diff --git a/python/benchmarks/engines.py b/python/benchmarks/engines.py new file mode 100644 index 0000000..614efd9 --- /dev/null +++ b/python/benchmarks/engines.py @@ -0,0 +1,360 @@ +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Stemmer engine adapters for the benchmark. + +Every engine exposes a uniform interface: + + engine.name -> str + engine.kind -> "native-batch" | "c-batch" | "py-batch" | "py-loop" + engine.supports(code) -> bool (ISO-639-1 language code) + engine.make(code) -> callable(list[str]) -> list[str] (the batch fn) + +``kind`` records how batching is implemented: + +- ``native-batch``: one native batch call +- ``c-batch``: C extension with a list entry point +- ``py-batch``: pure-Python object with a list method +- ``py-loop``: repeated scalar calls in Python + +Engines whose package is not installed are simply reported as unavailable, so +the benchmark runs with whatever the user has. +""" + +from __future__ import annotations + +import inspect +from pathlib import Path +from typing import Callable, Optional + + +def _module_info(obj) -> dict: + """Provenance of the module that actually provides ``obj``. + + The returned metadata identifies the backing module and distinguishes + native extensions from pure-Python implementations. + + Resolves via ``type(obj).__module__`` -> ``sys.modules`` because + ``inspect.getmodule`` returns ``None`` for Cython extension instances. + """ + import sys + + name = type(obj).__module__ + mod = sys.modules.get(name) or inspect.getmodule(obj) + file = getattr(mod, "__file__", None) or "" + lower = file.lower() + compiled = ( + lower.endswith((".pyd", ".so", ".dll")) or "cpython" in lower or "abi3" in lower + ) + return { + "backing_module": name, + "backing_file": file, + "compiled_extension": compiled, + } + + +# ISO-639-1 -> Snowball algorithm name (matches the Java SnowballLanguageCase +# mapping; nb/nn both map to the single Snowball "norwegian" algorithm). +_SNOWBALL_NAMES: dict[str, str] = { + "cs": "czech", + "fa": "persian", + "pl": "polish", + "da": "danish", + "nl": "dutch", + "en": "english", + "fi": "finnish", + "fr": "french", + "de": "german", + "hu": "hungarian", + "it": "italian", + "nb": "norwegian", + "nn": "norwegian", + "pt": "portuguese", + "ru": "russian", + "es": "spanish", + "sv": "swedish", + "yi": "yiddish", +} + +BatchFn = Callable[[list[str]], list[str]] + + +class Engine: + name: str = "engine" + kind: str = "py-loop" + + def available(self) -> bool: + raise NotImplementedError + + def supports(self, code: str) -> bool: + raise NotImplementedError + + def make(self, code: str) -> BatchFn: + raise NotImplementedError + + def provenance(self, code: str) -> dict: + return { + "backing_module": self.name, + "backing_file": "", + "compiled_extension": False, + "algorithm": None, + } + + +class RadixorEngine(Engine): + name = "radixor" + kind = "native-batch" + + def __init__(self, lowercase: bool = False) -> None: + # The shared corpus is already lowercase. Disable Radixor's redundant + # normalization unless a benchmark explicitly includes that cost. + self._lowercase = lowercase + + def available(self) -> bool: + try: + import radixor # noqa: F401 + + return True + except Exception: + return False + + def supports(self, code: str) -> bool: + try: + from radixor import _LANGUAGE_ALIASES + + return code in _LANGUAGE_ALIASES + except Exception: + return False + + def make(self, code: str) -> BatchFn: + from radixor import Stemmer + + s = Stemmer(code, lowercase=self._lowercase, cache_size=0) + return s.stem_batch + + def provenance(self, code: str) -> dict: + from radixor import Stemmer + + s = Stemmer(code, lowercase=self._lowercase, cache_size=0) + info = _module_info(s._core) + info["algorithm"] = "radixor-trie" + info["lowercase"] = self._lowercase + info["cache_disabled"] = True + return info + + +class PyStemmerEngine(Engine): + name = "PyStemmer" + kind = "c-batch" + + def available(self) -> bool: + try: + import Stemmer # noqa: F401 + + return True + except Exception: + return False + + def _algorithms(self) -> set[str]: + import Stemmer + + return {a.lower() for a in Stemmer.algorithms()} + + def supports(self, code: str) -> bool: + name = _SNOWBALL_NAMES.get(code) + return bool(name) and name in self._algorithms() + + @staticmethod + def _new_stemmer(code: str): + import Stemmer + + stemmer = Stemmer.Stemmer(_SNOWBALL_NAMES[code]) + # Repeated passes would otherwise measure PyStemmer's default cache + # after the first pass. Both native engines therefore run uncached. + stemmer.maxCacheSize = 0 + return stemmer + + def make(self, code: str) -> BatchFn: + stemmer = self._new_stemmer(code) + # PyStemmer's native list entry point: one C call for the whole batch. + return stemmer.stemWords + + def provenance(self, code: str) -> dict: + stemmer = self._new_stemmer(code) + info = _module_info(stemmer) + info["algorithm"] = _SNOWBALL_NAMES[code] + info["cache_disabled"] = True + # Record whether PyStemmer resolves to an independent native extension. + try: + import snowballstemmer + + snowball_dir = str(Path(snowballstemmer.__file__).resolve().parent).lower() + except Exception: + snowball_dir = None + backing = info["backing_file"].lower() + info["independent_of_snowballstemmer"] = bool( + info["compiled_extension"] + and (snowball_dir is None or snowball_dir not in backing) + ) + return info + + +class SnowballStemmerEngine(Engine): + """Pure-Python Snowball backend. + + ``snowballstemmer.stemmer()`` delegates to PyStemmer (the C extension) when + PyStemmer is installed, which would make this engine a duplicate of + ``PyStemmer``. To retain a distinct implementation, this adapter imports + the language's pure-Python class directly from + ``snowballstemmer._stemmer``. + """ + + name = "snowballstemmer-pure" + kind = "py-batch" + + def available(self) -> bool: + try: + import snowballstemmer # noqa: F401 + + return True + except Exception: + return False + + def _load_class(self, code: str): + import importlib + + name = _SNOWBALL_NAMES.get(code) + if not name: + return None + module = importlib.import_module(f"snowballstemmer.{name}_stemmer") + class_name = name.capitalize() + "Stemmer" + return getattr(module, class_name, None) + + def supports(self, code: str) -> bool: + try: + return self._load_class(code) is not None + except Exception: + return False + + def make(self, code: str) -> BatchFn: + stemmer = self._load_class(code)() + return stemmer.stemWords # pure-Python loop over the list, internally + + def provenance(self, code: str) -> dict: + stemmer = self._load_class(code)() + info = _module_info(stemmer) + info["algorithm"] = _SNOWBALL_NAMES[code] + return info + + +class NltkPorterEngine(Engine): + name = "nltk-porter" + kind = "py-loop" + + def available(self) -> bool: + try: + from nltk.stem import PorterStemmer # noqa: F401 + + return True + except Exception: + return False + + def supports(self, code: str) -> bool: + return code == "en" # Porter is English-only + + def make(self, code: str) -> BatchFn: + from nltk.stem import PorterStemmer + + ps = PorterStemmer() + stem = ps.stem + + def batch(words: list[str]) -> list[str]: + return [stem(w) for w in words] + + return batch + + def provenance(self, code: str) -> dict: + from nltk.stem import PorterStemmer + + info = _module_info(PorterStemmer()) + info["algorithm"] = "porter" + return info + + +class CistemEngine(Engine): + """CISTEM — a fast lightweight German stemmer (German only).""" + + name = "cistem" + kind = "py-loop" + + def available(self) -> bool: + try: + from nltk.stem.cistem import Cistem # noqa: F401 + + return True + except Exception: + return False + + def supports(self, code: str) -> bool: + return code == "de" + + def make(self, code: str) -> BatchFn: + from nltk.stem.cistem import Cistem + + stem = Cistem().stem + + def batch(words: list[str]) -> list[str]: + return [stem(w) for w in words] + + return batch + + def provenance(self, code: str) -> dict: + from nltk.stem.cistem import Cistem + + info = _module_info(Cistem()) + info["algorithm"] = "cistem" + return info + + +ALL_ENGINES: list[Engine] = [ + RadixorEngine(), + PyStemmerEngine(), + SnowballStemmerEngine(), + NltkPorterEngine(), + CistemEngine(), +] + + +def available_engines(names: Optional[set[str]] = None) -> list[Engine]: + engines = [e for e in ALL_ENGINES if e.available()] + if names: + engines = [e for e in engines if e.name in names] + return engines diff --git a/python/benchmarks/requirements-bench.txt b/python/benchmarks/requirements-bench.txt new file mode 100644 index 0000000..1045292 --- /dev/null +++ b/python/benchmarks/requirements-bench.txt @@ -0,0 +1,6 @@ +# Optional comparison engines for the runtime stemming benchmark. +# radixor itself must already be built (maturin develop) in the same env. +# Any subset may be installed; the harness auto-detects what is present. +PyStemmer # Snowball as a C extension (libstemmer); real batch API +snowballstemmer # Official pure-Python Snowball (used via its pure backend) +nltk # Porter (English) pure-Python reference baseline diff --git a/python/benchmarks/run_benchmark.py b/python/benchmarks/run_benchmark.py new file mode 100644 index 0000000..be993c3 --- /dev/null +++ b/python/benchmarks/run_benchmark.py @@ -0,0 +1,436 @@ +#!/usr/bin/env python3 +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Runtime stemming benchmark for the radixor Python extension vs alternatives. + +Measures ONLY runtime stemming throughput — model construction / dictionary +compilation happens once in setup and is excluded from all timings. + +Batch sizes are swept (default 10/20/50/100) and an unconstrained descriptive +line is fitted for each engine: per_call_time(N) = intercept + slope * N. The +fit summarizes scaling across the measured sizes; timing noise can make its +intercept negative, so it must not be read as a physical overhead measurement. + +Data is the same as the Java JMH benchmarks: the changed-token corpus derived +from the bundled UniMorph gold-standard dictionaries (see corpus.py). + +Examples +-------- + python run_benchmark.py --language en + python run_benchmark.py --all-languages --engines radixor + python run_benchmark.py --language en de ru --repeats 15 --csv results.csv + python run_benchmark.py --language en --sizes 10 20 50 100 200 --json out.json +""" + +from __future__ import annotations + +import argparse +import csv as csvmod +import gc +import json +import platform +import statistics +import sys +import time +from pathlib import Path +from typing import Optional + +HERE = Path(__file__).resolve().parent +sys.path.insert(0, str(HERE)) # allow running as a plain script + +import corpus as corpus_mod # noqa: E402 +import engines as engines_mod # noqa: E402 + + +def _chunks(seq: list[str], n: int) -> list[list[str]]: + return [seq[i : i + n] for i in range(0, len(seq), n)] + + +def _time_sequence_ns(batch_fn, batches: list[list[str]]) -> int: + """Time one full pass over all batches (nanoseconds).""" + start = time.perf_counter_ns() + for b in batches: + batch_fn(b) + return time.perf_counter_ns() - start + + +def _linfit(xs: list[float], ys: list[float]) -> tuple[float, float]: + """Ordinary least squares: returns (intercept, slope).""" + n = len(xs) + mean_x = sum(xs) / n + mean_y = sum(ys) / n + sxx = sum((x - mean_x) ** 2 for x in xs) + sxy = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, ys)) + slope = sxy / sxx if sxx else 0.0 + intercept = mean_y - slope * mean_x + return intercept, slope + + +_DIST_NAMES = { + "radixor": "radixor", + "PyStemmer": "PyStemmer", + "snowballstemmer-pure": "snowballstemmer", + "nltk-porter": "nltk", +} + + +def _engine_version(name: str) -> Optional[str]: + import importlib.metadata as md + + dist = _DIST_NAMES.get(name) + if not dist: + return None + try: + return md.version(dist) + except Exception: + return "editable" if name == "radixor" else None + + +def _processor_name() -> str: + """Return a useful CPU model name without adding a platform dependency.""" + name = platform.processor().strip() + if name: + return name + + cpuinfo = Path("/proc/cpuinfo") + if cpuinfo.is_file(): + for line in cpuinfo.read_text(encoding="utf-8", errors="replace").splitlines(): + key, separator, value = line.partition(":") + if separator and key.strip() in {"model name", "Hardware"}: + name = value.strip() + if name: + return name + + return "unknown" + + +def run(args) -> dict: + from radixor import _LANGUAGE_ALIASES + + engine_filter = set(args.engines) if args.engines else None + engines = engines_mod.available_engines(engine_filter) + if not engines: + print( + "No stemmer engines available. Install PyStemmer / snowballstemmer / nltk.", + file=sys.stderr, + ) + sys.exit(2) + + results: list[dict] = [] + strict_engine_names = ( + (engine_filter or {"radixor"}) if args.all_languages else set() + ) + failures: list[str] = [] + available_engine_names = {engine.name for engine in engines} + for missing_engine in sorted(strict_engine_names - available_engine_names): + failures.append(f"engine unavailable: {missing_engine}") + + for code in args.language: + model_id = _LANGUAGE_ALIASES.get(code, code) + if args.model_path: + dict_path = Path(args.model_path) + else: + # Corpus construction deliberately uses the canonical repository + # source. Runtime distributions contain only compiled model data. + dict_path = ( + HERE.parents[1] + / "models" + / model_id + / "src" + / "modelInput" + / "stemmer.gz" + ) + if not dict_path.is_file(): + print( + f"[{code}] canonical benchmark dictionary not found: {dict_path}", + file=sys.stderr, + ) + if args.all_languages: + failures.append(f"{code}: dictionary not found: {dict_path}") + continue + + full = corpus_mod.build_timing_corpus(dict_path) + budget = min(args.words, len(full)) if args.words > 0 else len(full) + pool = full[:budget] + print( + f"\n=== language={code} model={model_id} " + f"corpus={len(pool)} changed tokens ===" + ) + + for engine in engines: + if not engine.supports(code): + if engine.name in strict_engine_names: + failures.append(f"{code}: engine does not support {engine.name}") + continue + try: + batch_fn = engine.make(code) + except Exception as exc: # pragma: no cover - engine setup failure + print(f" [{engine.name}] setup failed: {exc}", file=sys.stderr) + if engine.name in strict_engine_names: + failures.append(f"{code}: {engine.name} setup failed: {exc}") + continue + + prov = engine.provenance(code) + results.append( + { + "language": code, + "model": model_id, + "engine": engine.name, + "kind": engine.kind, + "batch_size": "PROVENANCE", + **prov, + } + ) + print( + f" {engine.name:<16} backing={prov.get('backing_module')} " + f"compiled={prov.get('compiled_extension')} " + f"algo={prov.get('algorithm')}" + ) + + # sanity: output length must equal input length + probe = batch_fn(pool[: min(8, len(pool))]) + if len(probe) != min(8, len(pool)): + print( + f" [{engine.name}] unexpected output shape; skipping", + file=sys.stderr, + ) + if engine.name in strict_engine_names: + failures.append( + f"{code}: {engine.name} returned an unexpected output shape" + ) + continue + + per_call_best: list[float] = [] + for size in args.sizes: + batches = _chunks(pool, size) + n_calls = len(batches) + n_words = len(pool) + + # warmup + for _ in range(args.warmup): + _time_sequence_ns(batch_fn, batches) + + gc_was_enabled = gc.isenabled() + gc.disable() + try: + totals = [ + _time_sequence_ns(batch_fn, batches) + for _ in range(args.repeats) + ] + finally: + if gc_was_enabled: + gc.enable() + + med_total = statistics.median(totals) + min_total = min(totals) + # Per-word/per-call reported from the best (min) pass — the + # microbenchmark convention that suppresses OS/GC scheduling + # noise. The later OLS fit is descriptive and unconstrained. + per_word_ns = min_total / n_words + per_call_ns = min_total / n_calls + per_call_best.append(per_call_ns) + throughput = n_words / (min_total / 1e9) + + row = { + "language": code, + "model": model_id, + "engine": engine.name, + "kind": engine.kind, + "batch_size": size, + "calls": n_calls, + "words": n_words, + "repeats": args.repeats, + "median_total_ms": med_total / 1e6, + "min_total_ms": min_total / 1e6, + "per_word_ns": per_word_ns, + "per_call_us": per_call_ns / 1e3, + "throughput_words_per_s": throughput, + } + results.append(row) + print( + f" {engine.name:<16} [{engine.kind:<12}] " + f"N={size:<4} {per_word_ns:8.1f} ns/word " + f"{per_call_ns / 1e3:8.2f} us/call " + f"{throughput / 1e6:6.2f} M words/s" + ) + + # Unconstrained descriptive OLS fit across batch sizes. Keep the + # historical JSON key for report compatibility. + if len(args.sizes) >= 2: + intercept_ns, slope_ns = _linfit( + [float(s) for s in args.sizes], per_call_best + ) + results.append( + { + "language": code, + "model": model_id, + "engine": engine.name, + "kind": engine.kind, + "batch_size": "FIT", + "regie_ns_per_call": intercept_ns, + "real_ns_per_word": slope_ns, + } + ) + print( + f" {engine.name:<16} -> estimated intercept/call = " + f"{intercept_ns / 1e3:7.2f} us " + f"estimated slope = {slope_ns:7.1f} ns/word" + ) + + if args.all_languages: + expected_measurements = { + (code, engine_name, size) + for code in args.language + for engine_name in strict_engine_names + for size in args.sizes + } + actual_measurements = { + (row["language"], row["engine"], row["batch_size"]) + for row in results + if isinstance(row.get("batch_size"), int) + } + missing_measurements = sorted(expected_measurements - actual_measurements) + if missing_measurements: + failures.append(f"missing measurement rows: {missing_measurements}") + if failures: + raise RuntimeError( + "Incomplete all-language benchmark: " + "; ".join(failures) + ) + + return { + "environment": { + "platform": platform.platform(), + "processor": _processor_name(), + "python": sys.version.split()[0], + "python_impl": platform.python_implementation(), + "engine_versions": {e.name: _engine_version(e.name) for e in engines}, + }, + "parameters": { + "languages": args.language, + "sizes": args.sizes, + "words_budget": args.words, + "repeats": args.repeats, + "warmup": args.warmup, + }, + "results": results, + } + + +def main() -> None: + p = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + languages = p.add_mutually_exclusive_group() + languages.add_argument( + "--language", + "-l", + nargs="+", + default=None, + help="ISO-639-1 language code(s) or model id(s). Default: en", + ) + languages.add_argument( + "--all-languages", + action="store_true", + help="Benchmark every language alias bundled by radixor", + ) + p.add_argument( + "--sizes", + "-s", + type=int, + nargs="+", + default=[10, 20, 50, 100], + help="Batch sizes to sweep. Default: 10 20 50 100", + ) + p.add_argument( + "--words", + "-w", + type=int, + default=5000, + help="Words processed per measurement (<=0 = whole corpus). Default: 5000", + ) + p.add_argument( + "--repeats", + "-r", + type=int, + default=15, + help="Timed repeats per point (best/min reported). Default: 15", + ) + p.add_argument("--warmup", type=int, default=3, help="Warmup passes. Default: 3") + p.add_argument( + "--engines", + nargs="+", + default=None, + help="Restrict to named engines (radixor PyStemmer snowballstemmer nltk-porter)", + ) + p.add_argument( + "--model-path", + default=None, + help="Explicit gzipped dictionary path (single-language runs)", + ) + p.add_argument("--csv", default=None, help="Write per-point rows to this CSV file") + p.add_argument( + "--json", default=None, help="Write full results (incl. environment) to JSON" + ) + args = p.parse_args() + + if args.all_languages: + from radixor import _LANGUAGE_ALIASES + + args.language = sorted(_LANGUAGE_ALIASES) + elif args.language is None: + args.language = ["en"] + + report = run(args) + + if args.json: + Path(args.json).write_text(json.dumps(report, indent=2), encoding="utf-8") + print(f"\nwrote {args.json}") + if args.csv: + rows = [ + r + for r in report["results"] + if r.get("batch_size") not in ("FIT", "PROVENANCE") + ] + if rows: + with open(args.csv, "w", newline="", encoding="utf-8") as fh: + w = csvmod.DictWriter(fh, fieldnames=list(rows[0].keys())) + w.writeheader() + w.writerows(rows) + print(f"wrote {args.csv}") + + print("\nEnvironment:") + for k, v in report["environment"].items(): + print(f" {k}: {v}") + + +if __name__ == "__main__": + main() diff --git a/python/models-standard/LICENSE-MODEL-DATA.txt b/python/models-standard/LICENSE-MODEL-DATA.txt new file mode 100644 index 0000000..e936a23 --- /dev/null +++ b/python/models-standard/LICENSE-MODEL-DATA.txt @@ -0,0 +1,12 @@ +Radixor Standard Model Data License + +The model data in this distribution, including Radixor's protectable +selection, transformation, metadata, and packaging contributions, is licensed +under Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0): + +https://creativecommons.org/licenses/by-sa/3.0/ + +Each model's attribution, upstream provenance, and license details are recorded +in radixor_models_standard/notices//NOTICE-model-data.txt. Those +notices form part of this distribution and must be retained with redistributed +model data. diff --git a/python/models-standard/MANIFEST.in b/python/models-standard/MANIFEST.in new file mode 100644 index 0000000..3da8d75 --- /dev/null +++ b/python/models-standard/MANIFEST.in @@ -0,0 +1,4 @@ +include LICENSE-MODEL-DATA.txt +include README.md +recursive-include radixor_models_standard *.json *.rxc *.txt +global-exclude stemmer.gz *.gz __pycache__ *.py[cod] diff --git a/python/models-standard/README.md b/python/models-standard/README.md new file mode 100644 index 0000000..462c026 --- /dev/null +++ b/python/models-standard/README.md @@ -0,0 +1,26 @@ +# Radixor standard models + +This pure-Python distribution supplies Radixor's 20 precompiled standard +language models. It is installed automatically by `pip install radixor`; users +normally do not import it directly. + +The catalog version is `2026.1`. Individual model versions recorded in the +generated `radixor_models_standard/manifest.json` are currently `1.0.0`. The +optional Polish PoliMorf model is intentionally not part of the standard +catalog. +The first Python model distribution is released as `1.0.0`; its version is +independent of both the catalog identity and the individual model versions. +The checked-in descriptors use `0.0.0` as a deliberate non-release placeholder. +The release workflow creates an isolated project below `build/`, injects the Git +tag version, and deterministically compiles all model resources there. + +Only gzip-compressed Radixor v7 (`.rxc`) tries are shipped. They are release +artifacts, not checked-in repository files. Canonical textual dictionaries +remain in the Radixor source repository and are not included in this wheel or +source distribution. Model data is licensed under CC BY-SA 3.0; see +`LICENSE-MODEL-DATA.txt` and the generated per-model notices. + +The checked-out directory is intentionally only a packaging skeleton and is +not directly buildable as the complete data distribution. From the repository +root, use `./gradlew pythonBuildStandardModels`; the generated project and its +wheel/sdist are written below `build/python/`. diff --git a/python/models-standard/pyproject.toml b/python/models-standard/pyproject.toml new file mode 100644 index 0000000..2b93dbe --- /dev/null +++ b/python/models-standard/pyproject.toml @@ -0,0 +1,29 @@ +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[project] +name = "radixor-models-standard" +version = "0.0.0" +requires-python = ">=3.9" +description = "Precompiled standard language models for Radixor" +readme = "README.md" +license = "CC-BY-SA-3.0" +license-files = ["LICENSE-MODEL-DATA.txt"] +keywords = ["stemming", "nlp", "linguistics", "model-data"] +classifiers = [ + "Programming Language :: Python :: 3", + "Topic :: Text Processing :: Linguistic", +] + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.packages.find] +where = ["."] +include = ["radixor_models_standard*"] + +[tool.setuptools.package-data] +radixor_models_standard = ["manifest.json"] +"radixor_models_standard.models" = ["*.rxc"] +"radixor_models_standard.notices" = ["*/*.txt"] diff --git a/python/models-standard/radixor_models_standard/__init__.py b/python/models-standard/radixor_models_standard/__init__.py new file mode 100644 index 0000000..cdfcd47 --- /dev/null +++ b/python/models-standard/radixor_models_standard/__init__.py @@ -0,0 +1,37 @@ +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Installed resources for the Radixor 2026.1 standard model catalog.""" + +CATALOG_VERSION = "2026.1" +__version__ = "0.0.0" + +__all__ = ["CATALOG_VERSION"] diff --git a/python/models-standard/radixor_models_standard/models/__init__.py b/python/models-standard/radixor_models_standard/models/__init__.py new file mode 100644 index 0000000..5c26878 --- /dev/null +++ b/python/models-standard/radixor_models_standard/models/__init__.py @@ -0,0 +1,32 @@ +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Compiled model resources; not a public Python API.""" diff --git a/python/models-standard/radixor_models_standard/notices/__init__.py b/python/models-standard/radixor_models_standard/notices/__init__.py new file mode 100644 index 0000000..d04b2fb --- /dev/null +++ b/python/models-standard/radixor_models_standard/notices/__init__.py @@ -0,0 +1,32 @@ +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Per-model attribution and license notice resources.""" diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 0000000..abd6ddd --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,82 @@ +[build-system] +requires = ["maturin>=1.7,<2.0"] +build-backend = "maturin" + +[project] +name = "radixor" +version = "0.0.0" +requires-python = ">=3.9" +description = "Radixor stemmer – fastest stemming for Python, backed by Rust" +readme = "README.md" +license = "BSD-3-Clause" +license-files = ["LICENSE"] +dependencies = ["radixor-models-standard>=1.0,<2.0"] +keywords = [ + "python", + "information retrieval", + "language processing", + "morphology", + "stemming algorithms", + "stemmers", + "nlp", + "rust", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Natural Language :: Czech", + "Natural Language :: Danish", + "Natural Language :: Dutch", + "Natural Language :: English", + "Natural Language :: Finnish", + "Natural Language :: French", + "Natural Language :: German", + "Natural Language :: Hebrew", + "Natural Language :: Hungarian", + "Natural Language :: Italian", + "Natural Language :: Norwegian", + "Natural Language :: Persian", + "Natural Language :: Polish", + "Natural Language :: Portuguese", + "Natural Language :: Russian", + "Natural Language :: Spanish", + "Natural Language :: Swedish", + "Natural Language :: Ukrainian", + "Natural Language :: Yiddish", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Rust", + "Topic :: Internet :: WWW/HTTP :: Indexing/Search", + "Topic :: Text Processing :: Indexing", + "Topic :: Text Processing :: Linguistic", +] + +[tool.maturin] +python-source = "." +module-name = "radixor._radixor" +features = ["pyo3/extension-module"] +exclude = [ + "radixor/models/**", + "models-standard/**", + "dist/**", + "benchmarks/results*.csv", + "benchmarks/results*.json", + "**/__pycache__/**", + "**/*.pyc", + "**/*.pyo", + "**/*.pyd", +] + +[tool.ruff] +extend-exclude = ["models-standard/build", "target"] +line-length = 88 +target-version = "py39" + +[tool.ruff.lint] +select = ["E4", "E7", "E9", "F", "I"] diff --git a/python/radixor/__init__.py b/python/radixor/__init__.py new file mode 100644 index 0000000..f5f4ec8 --- /dev/null +++ b/python/radixor/__init__.py @@ -0,0 +1,398 @@ +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Python API for the Rust-backed Radixor stemmer. + +Usage:: + + from radixor import Stemmer + + s = Stemmer("en") + print(s.stem("running")) # single word + print(s.stem_batch(words)) # batch API for collections +""" + +from __future__ import annotations + +import gzip +import hashlib +import importlib.resources +import json +import re +from contextlib import contextmanager +from pathlib import Path +from typing import Any, Iterator, Optional + +from radixor._radixor import StemmerCore +from radixor._radixor import compile as _compile + +_LANGUAGE_ALIASES: dict[str, str] = { + # Friendly aliases -> model ID + "cs": "cs-cz-default", + "czech": "cs-cz-default", + "da": "da-dk-default", + "danish": "da-dk-default", + "de": "de-de-default", + "german": "de-de-default", + "en": "us-uk-default", + "english": "us-uk-default", + "es": "es-es-default", + "spanish": "es-es-default", + "fa": "fa-ir-default", + "persian": "fa-ir-default", + "fi": "fi-fi-default", + "finnish": "fi-fi-default", + "fr": "fr-fr-default", + "french": "fr-fr-default", + "he": "he-il-default", + "hebrew": "he-il-default", + "hu": "hu-hu-default", + "hungarian": "hu-hu-default", + "it": "it-it-default", + "italian": "it-it-default", + "nb": "nb-no-default", + "norwegian": "nb-no-default", + "nl": "nl-nl-default", + "dutch": "nl-nl-default", + "nn": "nn-no-default", + "pl": "pl-pl-unimorph", + "polish": "pl-pl-unimorph", + "pt": "pt-pt-default", + "portuguese": "pt-pt-default", + "ru": "ru-ru-default", + "russian": "ru-ru-default", + "sv": "sv-se-default", + "swedish": "sv-se-default", + "uk": "uk-ua-default", + "ukrainian": "uk-ua-default", + "yi": "yi-default", + "yiddish": "yi-default", +} + +# Right-to-left languages use FORWARD traversal; everything else BACKWARD. +# Keyed by model ID prefix (language part). +_RIGHT_TO_LEFT_MODELS: frozenset[str] = frozenset( + {"fa-ir-default", "he-il-default", "yi-default"} +) + +_STANDARD_PACKAGE = "radixor_models_standard" +_STANDARD_CATALOG_VERSION = "2026.1" +_STANDARD_DISTRIBUTION_VERSION = re.compile( + r"(?:0\.0\.0|1\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*))\Z" +) +_MODEL_ID = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*\Z") +_SHA256 = re.compile(r"[0-9a-f]{64}\Z") +_V7_MAGIC = b"EGTR" +_V7_VERSION = 7 + + +def _load_standard_manifest() -> dict[str, Any]: + """Load and validate the installed standard model catalog manifest.""" + try: + ref = importlib.resources.files(_STANDARD_PACKAGE).joinpath("manifest.json") + except (ModuleNotFoundError, TypeError) as exc: + raise ModuleNotFoundError( + "The standard Radixor model package is not installed. Install a compatible " + "provider with 'pip install radixor-models-standard>=1.0,<2.0', " + "or reinstall Radixor with 'pip install radixor'." + ) from exc + try: + manifest = json.loads(ref.read_text(encoding="utf-8")) + except (FileNotFoundError, OSError, UnicodeError, json.JSONDecodeError) as exc: + raise RuntimeError( + "The installed radixor-models-standard manifest is missing or corrupt; " + "reinstall radixor-models-standard." + ) from exc + + try: + models = manifest["models"] + format_info = manifest["format"] + if manifest["schema_version"] != 1: + raise ValueError("unsupported schema_version") + if manifest["catalog_version"] != _STANDARD_CATALOG_VERSION: + raise ValueError( + f"catalog {manifest['catalog_version']!r} is incompatible with " + f"Radixor catalog {_STANDARD_CATALOG_VERSION!r}" + ) + distribution_version = manifest["distribution_version"] + if ( + not isinstance(distribution_version, str) + or _STANDARD_DISTRIBUTION_VERSION.fullmatch(distribution_version) is None + ): + raise ValueError("incompatible distribution_version") + if format_info != {"compression": "gzip", "magic": "EGTR", "version": 7}: + raise ValueError("unsupported compiled model format") + if not isinstance(models, list) or not models: + raise ValueError("models must be a non-empty list") + seen: set[str] = set() + for model in models: + model_id = model["id"] + if ( + not isinstance(model_id, str) + or _MODEL_ID.fullmatch(model_id) is None + or model_id in seen + or model["file"] != f"models/{model_id}.rxc" + or not isinstance(model["version"], str) + or _SHA256.fullmatch(model["sha256"]) is None + ): + raise ValueError("invalid model entry") + seen.add(model_id) + except (KeyError, TypeError, ValueError) as exc: + raise RuntimeError( + f"The installed radixor-models-standard manifest is incompatible or corrupt: {exc}. " + "Install radixor-models-standard>=1.0,<2.0." + ) from exc + return manifest + + +def _manifest_model(model_id: str) -> dict[str, Any]: + if not isinstance(model_id, str) or _MODEL_ID.fullmatch(model_id) is None: + raise ValueError( + f"Invalid Radixor model ID {model_id!r}; expected lowercase letters, digits, and hyphens." + ) + manifest = _load_standard_manifest() + for model in manifest["models"]: + if model["id"] == model_id: + return model + raise FileNotFoundError( + f"Model '{model_id}' is not in the standard Radixor catalog. " + "Pass a custom source path via Stemmer(path=...) or a compiled v7 path " + "via Stemmer(compiled=...)." + ) + + +def _validate_standard_model(path: Path, model: dict[str, Any]) -> None: + try: + digest = hashlib.sha256(path.read_bytes()).hexdigest() + except OSError as exc: + raise RuntimeError( + f"Standard model '{model['id']}' cannot be read; reinstall radixor-models-standard." + ) from exc + if digest != model["sha256"]: + raise RuntimeError( + f"Standard model '{model['id']}' failed SHA-256 validation; " + "reinstall radixor-models-standard." + ) + try: + with gzip.open(path, "rb") as stream: + header = stream.read(8) + except (OSError, EOFError) as exc: + raise RuntimeError( + f"Standard model '{model['id']}' is not a valid gzip-compressed v7 resource; " + "reinstall radixor-models-standard." + ) from exc + if header[:4] != _V7_MAGIC or len(header) != 8: + raise RuntimeError( + f"Standard model '{model['id']}' does not contain the Radixor EGTR format marker; " + "reinstall radixor-models-standard." + ) + version = int.from_bytes(header[4:8], "big", signed=True) + if version != _V7_VERSION: + raise RuntimeError( + f"Standard model '{model['id']}' uses unsupported compiled format v{version}; " + f"Radixor requires v{_V7_VERSION}." + ) + + +@contextmanager +def _standard_model_path(model_id: str) -> Iterator[Path]: + """Yield a validated standard model path for synchronous native loading.""" + model = _manifest_model(model_id) + ref = ( + importlib.resources.files(_STANDARD_PACKAGE) + .joinpath("models") + .joinpath(f"{model_id}.rxc") + ) + try: + with importlib.resources.as_file(ref) as path: + if not path.is_file(): + raise FileNotFoundError + _validate_standard_model(path, model) + yield path + except FileNotFoundError as exc: + raise FileNotFoundError( + f"Standard model '{model_id}' is missing from radixor-models-standard; " + "reinstall radixor-models-standard." + ) from exc + + +def _is_backward(model_id: str) -> bool: + """Traversal direction implied by the model's language (RTL => FORWARD).""" + return model_id not in _RIGHT_TO_LEFT_MODELS + + +class Stemmer: + """Thread-safe stemmer backed by a Radixor patch-command trie. + + Standard language models are loaded from validated, precompiled v7 resources + supplied by the mandatory ``radixor-models-standard`` distribution. + + Parameters + ---------- + language: + Two-letter ISO 639-1 code (e.g. ``"en"``) or a full model ID + (e.g. ``"us-uk-default"``). Ignored when ``path`` is given. + path: + Explicit path to either a gzipped source dictionary or a compiled + ``.rxc`` trie (Java-interoperable v7 format); the format is + auto-detected. Takes precedence over ``language``. + compiled: + Alias for ``path`` intended for compiled ``.rxc`` files (see + :func:`compile`). For compiled input, ``backward`` / ``store_original`` + are baked into the file and ignored. + backward: + Traversal direction override. When ``None`` (default) it is derived + from the language (BACKWARD, except right-to-left fa/he/yi which use + FORWARD). Only consulted for ``path``-based construction if given. + store_original: + When ``True`` (default) each canonical stem maps to the no-op patch, + so the stem itself is recognised. + lowercase: + When ``True`` (default) lookups lowercase the input word. Set to + ``False`` when you guarantee the input is already lowercased (skips the + per-lookup normalization; the model's keys are always lowercase). + cache_size: + Maximum entries in the bounded result cache (default ``10_000``, + matching PyStemmer). Set to ``0`` to disable caching. Cached results are + shared by :meth:`stem`, :meth:`stemWord`, :meth:`stem_batch`, and + :meth:`stemWords`; ``stem_all`` methods are not cached. + """ + + def __init__( + self, + language: Optional[str] = None, + *, + path: Optional[str] = None, + compiled: Optional[str] = None, + backward: Optional[bool] = None, + store_original: bool = True, + lowercase: bool = True, + cache_size: int = 10_000, + ) -> None: + source = path if path is not None else compiled + if source is not None: + model_path = source + is_backward = True if backward is None else backward + elif language is not None: + model_id = _LANGUAGE_ALIASES.get(language, language) + is_backward = _is_backward(model_id) if backward is None else backward + with _standard_model_path(model_id) as model_path: + self._core = StemmerCore( + str(model_path), is_backward, store_original, lowercase, cache_size + ) + return + else: + raise ValueError("Provide 'language', 'path', or 'compiled'.") + self._core = StemmerCore( + model_path, is_backward, store_original, lowercase, cache_size + ) + + def stem(self, word: str) -> Optional[str]: + """Return a stem, or ``None`` when no patch command applies.""" + return self._core.stem(word) + + def stem_batch(self, words: list[str]) -> list[Optional[str]]: + """Stem many words in one call. + + Preferred over calling :meth:`stem` in a loop: the Python→Rust bridge + overhead is amortised across the whole batch, making this significantly + faster for large word lists. + + Returns a list of the same length; entries are ``None`` when the + compiled trie finds no applicable patch command. + """ + return self._core.stem_batch(words) + + def stemWord(self, word: str) -> str: + """Return a stem using PyStemmer-compatible fallback semantics. + + If no patch command can be found, return *word* unchanged. Use + :meth:`stem` when a missing result must remain distinguishable as + ``None``. + """ + return self._core.stemWord(word) + + def stemWords(self, words: list[str]) -> list[str]: + """Stem words using PyStemmer-compatible fallback semantics. + + The returned list has the same length and order as *words*; each word + without a matching patch command is returned unchanged. + """ + return self._core.stemWords(words) + + def stem_all(self, word: str) -> list[str]: + """Return all stems for *word* ordered by descending frequency.""" + return self._core.stem_all(word) + + def stem_all_batch(self, words: list[str]) -> list[list[str]]: + """Return all stems for each word in *words* as a list of lists.""" + return self._core.stem_all_batch(words) + + +def compile( + source: str, + out_path: str, + *, + language: Optional[str] = None, + backward: Optional[bool] = None, + store_original: bool = True, + lowercase: bool = True, +) -> None: + """Compile a textual source dictionary into a Java-interoperable compiled + trie file (v7 format) that :class:`Stemmer` can load instantly. + + Parameters + ---------- + source: + Path to a gzipped (or plain) TSV source dictionary. + out_path: + Destination compiled file (conventionally ``*.rxc``). + language: + Optional language code/model ID used only to derive ``backward`` when + it is not given (right-to-left fa/he/yi compile FORWARD). + backward: + Traversal direction. When ``None`` it is derived from ``language`` if + provided, otherwise defaults to BACKWARD. + store_original, lowercase: + Same meaning as :class:`Stemmer`; baked into the compiled file. + + The resulting file is byte-compatible (inner stream) with the Radixor Java + ``StemmerPatchTrieBinaryIO`` v7 format, so Java and Python can share it. + """ + if backward is None: + if language is not None: + backward = _is_backward(_LANGUAGE_ALIASES.get(language, language)) + else: + backward = True + _compile(source, out_path, backward, store_original, lowercase) + + +__all__ = ["Stemmer", "compile"] diff --git a/python/radixor/py.typed b/python/radixor/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/python/scripts/assemble_release.py b/python/scripts/assemble_release.py new file mode 100755 index 0000000..7298508 --- /dev/null +++ b/python/scripts/assemble_release.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Validate and assemble the exact files allowed in a Python GitHub Release.""" + +from __future__ import annotations + +import argparse +import hashlib +import re +import shutil +from pathlib import Path + +from verify_distributions import ( + _verify_main_sdist, + _verify_main_wheel, + _verify_standard_sdist, + _verify_standard_wheel, +) + +REPOSITORY = Path(__file__).resolve().parents[2] +BUILD_ROOT = (REPOSITORY / "build").resolve() +VERSION = re.compile(r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\Z") + + +def _release_files(root: Path) -> list[Path]: + files = sorted( + path + for path in root.rglob("*") + if path.is_file() + and (path.name.endswith(".whl") or path.name.endswith(".tar.gz")) + ) + names = [path.name for path in files] + if len(names) != len(set(names)): + raise ValueError(f"Duplicate release filenames: {names}") + return files + + +def _require_one(files: list[Path], predicate, description: str) -> Path: + matches = [path for path in files if predicate(path.name)] + if len(matches) != 1: + raise ValueError( + f"Expected one {description}, found {[path.name for path in matches]}" + ) + return matches[0] + + +def _validate_native(files: list[Path], version: str) -> None: + prefix = f"radixor-{version}-cp39-abi3-" + wheels = [path for path in files if path.suffix == ".whl"] + sdist = _require_one( + files, lambda name: name == f"radixor-{version}.tar.gz", "native sdist" + ) + if len(wheels) != 4: + raise ValueError( + f"Expected four native wheels, found {[path.name for path in wheels]}" + ) + expected = { + "linux-x86_64": lambda name: name.startswith(prefix) + and "manylinux" in name + and name.endswith("x86_64.whl"), + "linux-aarch64": lambda name: name.startswith(prefix) + and "manylinux" in name + and name.endswith("aarch64.whl"), + "macos-universal2": lambda name: name.startswith(prefix) + and "macosx" in name + and name.endswith("universal2.whl"), + "windows-x86_64": lambda name: name == f"{prefix}win_amd64.whl", + } + for description, predicate in expected.items(): + _require_one(wheels, predicate, description) + for wheel in wheels: + _verify_main_wheel(wheel, version) + _verify_main_sdist(sdist, version) + + +def _validate_models(files: list[Path], version: str) -> None: + wheel = _require_one( + files, + lambda name: name == f"radixor_models_standard-{version}-py3-none-any.whl", + "standard-model wheel", + ) + sdist = _require_one( + files, + lambda name: name == f"radixor_models_standard-{version}.tar.gz", + "standard-model sdist", + ) + if len(files) != 2: + raise ValueError( + f"Unexpected standard-model release files: {[path.name for path in files]}" + ) + _verify_standard_wheel(wheel, version) + _verify_standard_sdist(sdist, version) + + +def _output_directory(path: Path) -> Path: + output = path.resolve() + try: + output.relative_to(BUILD_ROOT) + except ValueError as exc: + raise ValueError(f"Release output must be below {BUILD_ROOT}") from exc + if output == BUILD_ROOT: + raise ValueError("Release output cannot be the build root") + if output.exists(): + shutil.rmtree(output) + output.mkdir(parents=True) + return output + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("distribution", choices=("native", "models-standard")) + parser.add_argument("version") + parser.add_argument("artifacts", type=Path) + parser.add_argument("output", type=Path) + args = parser.parse_args() + + if VERSION.fullmatch(args.version) is None: + raise SystemExit(f"Invalid stable release version: {args.version!r}") + files = _release_files(args.artifacts.resolve()) + if args.distribution == "native": + _validate_native(files, args.version) + else: + _validate_models(files, args.version) + + output = _output_directory(args.output) + for source in files: + shutil.copy2(source, output / source.name) + checksums = [ + f"{hashlib.sha256(path.read_bytes()).hexdigest()} {path.name}" + for path in sorted(output.iterdir()) + if path.is_file() + ] + (output / "SHA256SUMS").write_text( + "\n".join(checksums) + "\n", encoding="utf-8", newline="\n" + ) + print(f"assembled {len(files)} release artifacts in {output}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/python/scripts/build_standard_distribution.py b/python/scripts/build_standard_distribution.py new file mode 100644 index 0000000..01ae68f --- /dev/null +++ b/python/scripts/build_standard_distribution.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Build the standard-model wheel and sdist through its declared backend.""" + +from __future__ import annotations + +import argparse +import json +import os +import shutil +import tempfile +from pathlib import Path + +EXPECTED_MODEL_COUNT = 20 + + +def _ignore_build_artifacts(_directory: str, names: list[str]) -> set[str]: + """Exclude local build state from the isolated distribution source tree.""" + + ignored = { + name + for name in names + if name in {"build", "dist", "__pycache__"} + or name.endswith((".egg-info", ".pyc", ".pyo")) + } + return ignored + + +def _validate_generated_project(project: Path) -> None: + """Reject an ungenerated or incomplete standard-model project.""" + + package = project / "radixor_models_standard" + manifest_path = package / "manifest.json" + if not manifest_path.is_file(): + raise ValueError( + "standard-model manifest is missing; run build_standard_models.py first" + ) + + try: + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + model_ids = { + model["id"] for model in manifest["models"] if isinstance(model, dict) + } + except (json.JSONDecodeError, KeyError, TypeError) as exc: + raise ValueError("standard-model manifest is invalid") from exc + + compiled = {path.stem for path in (package / "models").glob("*.rxc")} + notices = { + path.parent.name + for path in (package / "notices").glob("*/NOTICE-model-data.txt") + } + if len(model_ids) != EXPECTED_MODEL_COUNT: + raise ValueError( + f"expected {EXPECTED_MODEL_COUNT} standard models, found {len(model_ids)}" + ) + if compiled != model_ids: + raise ValueError("compiled standard models do not match the manifest") + if notices != model_ids: + raise ValueError("standard-model notices do not match the manifest") + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--project", required=True, type=Path) + parser.add_argument("--outdir", required=True, type=Path) + args = parser.parse_args() + + try: + from setuptools.build_meta import build_sdist, build_wheel + except ImportError as exc: + raise SystemExit( + "setuptools>=77 is required to build radixor-models-standard" + ) from exc + + source_project = args.project.resolve() + try: + _validate_generated_project(source_project) + except ValueError as exc: + parser.error(str(exc)) + output = args.outdir.resolve() + output.mkdir(parents=True, exist_ok=True) + + with tempfile.TemporaryDirectory(prefix="radixor-models-build-") as temporary: + project = Path(temporary, "project") + shutil.copytree( + source_project, + project, + ignore=_ignore_build_artifacts, + ) + os.chdir(project) + wheel = build_wheel(str(output)) + sdist = build_sdist(str(output)) + + print(f"built {wheel} and {sdist}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/python/scripts/build_standard_models.py b/python/scripts/build_standard_models.py new file mode 100644 index 0000000..616e767 --- /dev/null +++ b/python/scripts/build_standard_models.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python3 +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Regenerate the pure-Python standard model package deterministically. + +The canonical build topology selects ``default`` models. Source dictionaries +are compiler inputs only and are never copied into either Python distribution. +Run this script with a built Radixor extension importable by the selected +Python interpreter. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import shutil +import sys +import tempfile +from pathlib import Path +from typing import Any + +REPOSITORY = Path(__file__).resolve().parents[2] +PYTHON_ROOT = REPOSITORY / "python" +BUILD_ROOT = REPOSITORY / "build" +SOURCE_PROJECT = PYTHON_ROOT / "models-standard" +TOPOLOGY = REPOSITORY / "models" / "model-projects.properties" +CATALOG_VERSION = REPOSITORY / "models" / "catalog-version.txt" +EXPECTED_FORMAT = {"compression": "gzip", "magic": "EGTR", "version": 7} +VERSION = re.compile(r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\Z") +METADATA_FIELDS = ( + "sourceName", + "sourceVersion", + "sourceRevision", + "sourceProject", + "sourceRepository", + "sourceDataset", + "sourceRevisionStatus", + "sourceLicense", + "sourceLicenseUri", + "sourceAttribution", + "sourceVerificationDate", + "transformationsSummary", +) + + +def _sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def _default_models() -> list[str]: + entries: dict[str, str] = {} + for raw_line in TOPOLOGY.read_text(encoding="utf-8").splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + model_id, separator, membership = line.partition("=") + if not separator or not model_id or membership not in {"default", "optional"}: + raise ValueError(f"Invalid model topology line: {raw_line!r}") + entries[model_id] = membership + model_ids = sorted( + model_id for model_id, membership in entries.items() if membership == "default" + ) + if len(model_ids) != 20 or "pl-pl-polimorf" in model_ids: + raise ValueError( + "Standard Python catalog must contain 20 defaults and exclude pl-pl-polimorf" + ) + return model_ids + + +def _gradle_metadata(path: Path) -> dict[str, str]: + text = path.read_text(encoding="utf-8") + result: dict[str, str] = {} + for field in METADATA_FIELDS: + match = re.search(rf"^\s*{field}\s*=\s*'([^']*)'\s*$", text, re.MULTILINE) + if match is None: + raise ValueError(f"Missing {field} in {path}") + result[field] = match.group(1) + return result + + +def _replace_once(path: Path, pattern: str, replacement: str) -> None: + text = path.read_text(encoding="utf-8") + updated, count = re.subn(pattern, replacement, text, count=1, flags=re.MULTILINE) + if count != 1: + raise ValueError(f"Expected exactly one version field in {path}") + path.write_text(updated, encoding="utf-8", newline="\n") + + +def _stage_project(output: Path, distribution_version: str) -> Path: + project = output.resolve() + try: + project.relative_to(BUILD_ROOT.resolve()) + except ValueError as exc: + raise ValueError(f"Generated model project must be below {BUILD_ROOT}") from exc + if project == BUILD_ROOT.resolve(): + raise ValueError("Generated model project cannot be the build root") + if project.exists(): + shutil.rmtree(project) + project.parent.mkdir(parents=True, exist_ok=True) + shutil.copytree( + SOURCE_PROJECT, + project, + ignore=shutil.ignore_patterns( + "build", + "dist", + "*.egg-info", + "__pycache__", + "*.pyc", + "manifest.json", + "*.rxc", + "NOTICE-model-data.txt", + ), + ) + _replace_once( + project / "pyproject.toml", + r'^version = "0\.0\.0"$', + f'version = "{distribution_version}"', + ) + _replace_once( + project / "radixor_models_standard" / "__init__.py", + r'^__version__ = "0\.0\.0"$', + f'__version__ = "{distribution_version}"', + ) + return project + + +def _model_manifest( + model_id: str, + compile_model: Any, + package_root: Path, + reproducibility_directory: Path, +) -> dict[str, Any]: + project = REPOSITORY / "models" / model_id + source = project / "src" / "modelInput" / "stemmer.gz" + notice = project / "src" / "modelInput" / "NOTICE-model-data.txt" + version = (project / "model-version.txt").read_text(encoding="utf-8").strip() + if not source.is_file() or not notice.is_file() or not version: + raise FileNotFoundError(f"Incomplete canonical model project: {project}") + + destination = package_root / "models" / f"{model_id}.rxc" + destination.parent.mkdir(parents=True, exist_ok=True) + compile_model(str(source), str(destination), language=model_id) + reproduction = reproducibility_directory / destination.name + compile_model(str(source), str(reproduction), language=model_id) + if destination.read_bytes() != reproduction.read_bytes(): + raise ValueError(f"Non-deterministic compiled model output for {model_id}") + + notice_destination = package_root / "notices" / model_id / notice.name + notice_destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(notice, notice_destination) + + metadata = _gradle_metadata(project / "build.gradle") + if metadata["sourceLicense"] != "CC-BY-SA-3.0": + raise ValueError(f"Standard model {model_id} must be CC-BY-SA-3.0") + return { + "file": f"models/{model_id}.rxc", + "id": model_id, + "notice": f"notices/{model_id}/{notice.name}", + "provenance": { + "attribution": metadata["sourceAttribution"], + "dataset": metadata["sourceDataset"], + "license": metadata["sourceLicense"], + "license_uri": metadata["sourceLicenseUri"], + "repository": metadata["sourceRepository"], + "revision": metadata["sourceRevision"], + "revision_status": metadata["sourceRevisionStatus"], + "source_name": metadata["sourceName"], + "source_project": metadata["sourceProject"], + "source_version": metadata["sourceVersion"], + "transformations": metadata["transformationsSummary"], + "verification_date": metadata["sourceVerificationDate"], + }, + "sha256": _sha256(destination), + "source": { + "path": f"models/{model_id}/src/modelInput/stemmer.gz", + "sha256": _sha256(source), + }, + "version": version, + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--project", required=True, type=Path) + parser.add_argument("--distribution-version", required=True) + args = parser.parse_args() + if VERSION.fullmatch(args.distribution_version) is None: + raise SystemExit( + f"Invalid standard-model distribution version: {args.distribution_version!r}" + ) + + try: + from radixor import compile as compile_model + except ImportError as exc: + print( + "error: build the Radixor extension first (for example, " + "maturin develop --release) and rerun this script", + file=sys.stderr, + ) + print(f"detail: {exc}", file=sys.stderr) + return 2 + + project = _stage_project(args.project, args.distribution_version) + package_root = project / "radixor_models_standard" + model_ids = _default_models() + expected_files = {f"{model_id}.rxc" for model_id in model_ids} + models_directory = package_root / "models" + models_directory.mkdir(parents=True, exist_ok=True) + for stale in models_directory.glob("*.rxc"): + if stale.name not in expected_files: + stale.unlink() + notices_directory = package_root / "notices" + notices_directory.mkdir(parents=True, exist_ok=True) + for stale in notices_directory.iterdir(): + if stale.is_dir() and stale.name not in model_ids: + shutil.rmtree(stale) + + reproducibility_root = BUILD_ROOT / "python" / "tmp" + reproducibility_root.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory( + prefix="radixor-model-reproducibility-", dir=reproducibility_root + ) as temporary: + models = [ + _model_manifest( + model_id, + compile_model, + package_root, + Path(temporary), + ) + for model_id in model_ids + ] + manifest = { + "catalog_version": CATALOG_VERSION.read_text(encoding="utf-8").strip(), + "distribution_version": args.distribution_version, + "format": EXPECTED_FORMAT, + "models": models, + "schema_version": 1, + "topology": "models/model-projects.properties", + } + manifest_path = package_root / "manifest.json" + with manifest_path.open("w", encoding="utf-8", newline="\n") as stream: + stream.write( + json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + "\n" + ) + print(f"generated {len(models)} standard compiled models in {package_root}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/python/scripts/prepare_release_tree.py b/python/scripts/prepare_release_tree.py new file mode 100755 index 0000000..35d4c59 --- /dev/null +++ b/python/scripts/prepare_release_tree.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Create an isolated, tag-versioned Python release source tree.""" + +from __future__ import annotations + +import argparse +import re +import shutil +from pathlib import Path + +REPOSITORY = Path(__file__).resolve().parents[2] +PYTHON_ROOT = REPOSITORY / "python" +BUILD_ROOT = REPOSITORY / "build" +VERSION = re.compile(r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\Z") + + +def _replace_once(path: Path, pattern: str, replacement: str) -> None: + text = path.read_text(encoding="utf-8") + updated, count = re.subn(pattern, replacement, text, count=1, flags=re.MULTILINE) + if count != 1: + raise ValueError(f"Expected exactly one version field in {path}") + path.write_text(updated, encoding="utf-8", newline="\n") + + +def _prepare_output(path: Path) -> Path: + output = path.resolve() + try: + output.relative_to(BUILD_ROOT.resolve()) + except ValueError as exc: + raise ValueError(f"Release staging output must be below {BUILD_ROOT}") from exc + if output == BUILD_ROOT.resolve(): + raise ValueError("Release staging output cannot be the build root") + if output.exists(): + shutil.rmtree(output) + output.parent.mkdir(parents=True, exist_ok=True) + return output + + +def _copy_native(output: Path, version: str) -> None: + shutil.copytree( + PYTHON_ROOT, + output, + ignore=shutil.ignore_patterns( + ".gitignore", + ".pytest_cache", + ".ruff_cache", + ".venv", + "__pycache__", + "*.pyc", + "_radixor*.dll", + "_radixor*.dylib", + "_radixor*.pyd", + "_radixor*.so", + "_native*.dll", + "_native*.dylib", + "_native*.pyd", + "_native*.so", + "benchmarks", + "dist", + "models", + "models-standard", + "target", + "tests", + ), + ) + _replace_once( + output / "pyproject.toml", + r'^version = "0\.0\.0"$', + f'version = "{version}"', + ) + _replace_once( + output / "Cargo.toml", + r'^version = "0\.0\.0"$', + f'version = "{version}"', + ) + lock = output / "Cargo.lock" + lock_text = lock.read_text(encoding="utf-8") + pattern = r'(\[\[package\]\]\nname = "radixor"\n)version = "0\.0\.0"' + lock_text, count = re.subn(pattern, rf'\g<1>version = "{version}"', lock_text) + if count != 1: + raise ValueError("Expected exactly one radixor package entry in Cargo.lock") + lock.write_text(lock_text, encoding="utf-8", newline="\n") + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("distribution", choices=("native",)) + parser.add_argument("version") + parser.add_argument("output", type=Path) + args = parser.parse_args() + + if VERSION.fullmatch(args.version) is None: + raise SystemExit(f"Invalid stable release version: {args.version!r}") + output = _prepare_output(args.output) + _copy_native(output, args.version) + print(f"prepared {args.distribution} {args.version} in {output}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/python/scripts/update_simple_index.py b/python/scripts/update_simple_index.py new file mode 100755 index 0000000..8b113a9 --- /dev/null +++ b/python/scripts/update_simple_index.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Update Radixor's static PEP 503 index with verified GitHub Release assets.""" + +from __future__ import annotations + +import argparse +import hashlib +import html +import re +from html.parser import HTMLParser +from pathlib import Path +from urllib.parse import quote + +PACKAGES = ("radixor", "radixor-models-standard") +REPOSITORY = re.compile(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\Z") +SHA256 = re.compile(r"[0-9a-f]{64}\Z") +VERSION = re.compile(r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\Z") + + +class _AnchorParser(HTMLParser): + def __init__(self) -> None: + super().__init__() + self.anchors: dict[str, str] = {} + self._href: str | None = None + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + if tag == "a": + self._href = dict(attrs).get("href") + + def handle_data(self, data: str) -> None: + if self._href is not None and data.strip(): + self.anchors[data.strip()] = self._href + + def handle_endtag(self, tag: str) -> None: + if tag == "a": + self._href = None + + +def _existing_links(path: Path, expected_prefix: str) -> dict[str, str]: + if not path.exists(): + return {} + parser = _AnchorParser() + parser.feed(path.read_text(encoding="utf-8")) + for filename, href in parser.anchors.items(): + base, separator, digest = href.rpartition("#sha256=") + if ( + not separator + or not base.startswith(expected_prefix) + or SHA256.fullmatch(digest) is None + or base.rsplit("/", 1)[-1] != quote(filename, safe="._-") + ): + raise ValueError(f"Existing package-index link is unsafe: {href!r}") + return parser.anchors + + +def _render_root(root: Path) -> None: + links = "\n".join( + f' {html.escape(package)}
' + for package in PACKAGES + ) + (root / "index.html").write_text( + '\nRadixor Python packages\n' + f"\n{links}\n\n", + encoding="utf-8", + newline="\n", + ) + + +def _render_project(path: Path, package: str, links: dict[str, str]) -> None: + anchors = "\n".join( + f' {html.escape(filename)}
' + for filename, href in sorted(links.items()) + ) + path.write_text( + '\nLinks for {html.escape(package)}\n' + f"\n{anchors}\n\n", + encoding="utf-8", + newline="\n", + ) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--root", required=True, type=Path) + parser.add_argument("--repository", required=True) + parser.add_argument("--package", required=True, choices=PACKAGES) + parser.add_argument("--version", required=True) + parser.add_argument("--tag", required=True) + parser.add_argument("--artifacts", required=True, type=Path) + args = parser.parse_args() + + if REPOSITORY.fullmatch(args.repository) is None: + raise SystemExit("Invalid GitHub repository identity") + if VERSION.fullmatch(args.version) is None: + raise SystemExit("Invalid stable release version") + expected_tag = ( + f"python@{args.version}" + if args.package == "radixor" + else f"python-models-standard@{args.version}" + ) + if args.tag != expected_tag: + raise SystemExit(f"Tag {args.tag!r} does not match {expected_tag!r}") + + root = args.root.resolve() + if root.name != "simple" or root.parent.name != "python": + raise SystemExit("The package index must end in python/simple") + root.mkdir(parents=True, exist_ok=True) + project = root / args.package + project.mkdir(exist_ok=True) + page = project / "index.html" + prefix = f"https://github.com/{args.repository}/releases/download/" + links = _existing_links(page, prefix) + release_prefix = f"{prefix}{quote(args.tag, safe='@._-')}/" + artifacts = sorted( + path + for path in args.artifacts.iterdir() + if path.is_file() + and (path.name.endswith(".whl") or path.name.endswith(".tar.gz")) + ) + if not artifacts: + raise SystemExit("No package artifacts were provided") + for artifact in artifacts: + digest = hashlib.sha256(artifact.read_bytes()).hexdigest() + href = f"{release_prefix}{quote(artifact.name, safe='._-')}#sha256={digest}" + previous = links.get(artifact.name) + if previous is not None and previous != href: + raise ValueError( + f"Refusing to replace existing index entry {artifact.name}" + ) + links[artifact.name] = href + + _render_root(root) + _render_project(page, args.package, links) + print(f"indexed {len(artifacts)} artifacts for {args.package} {args.version}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/python/scripts/verify_distributions.py b/python/scripts/verify_distributions.py new file mode 100644 index 0000000..a1cbef0 --- /dev/null +++ b/python/scripts/verify_distributions.py @@ -0,0 +1,598 @@ +#!/usr/bin/env python3 +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Verify Radixor's native and standard-model release archives.""" + +from __future__ import annotations + +import argparse +import gzip +import hashlib +import json +import os +import stat +import subprocess +import sys +import tarfile +import tempfile +import zipfile +from email.parser import BytesParser +from pathlib import Path, PurePosixPath + +EXPECTED_DEFAULT_COUNT = 20 +EXPECTED_DEPENDENCY = "radixor-models-standard>=1.0,<2.0" +REPOSITORY = Path(__file__).resolve().parents[2] +EXPECTED_NATIVE_LICENSE = REPOSITORY.joinpath("LICENSE").read_bytes() +EXPECTED_MODEL_IDS = { + "cs-cz-default", + "da-dk-default", + "de-de-default", + "es-es-default", + "fa-ir-default", + "fi-fi-default", + "fr-fr-default", + "he-il-default", + "hu-hu-default", + "it-it-default", + "nb-no-default", + "nl-nl-default", + "nn-no-default", + "pl-pl-unimorph", + "pt-pt-default", + "ru-ru-default", + "sv-se-default", + "uk-ua-default", + "us-uk-default", + "yi-default", +} +STANDARD_NAME = "radixor-models-standard" +STANDARD_VERSION = "0.0.0" +MAIN_NAME = "radixor" +MAIN_VERSION = "0.0.0" + + +def _one(directory: Path, pattern: str) -> Path: + matches = sorted(directory.glob(pattern)) + if len(matches) != 1: + raise ValueError( + f"Expected exactly one {pattern} in {directory}, found {len(matches)}" + ) + return matches[0] + + +def _assert_no_repository_temp_gzip() -> None: + offenders = sorted( + path.name for path in REPOSITORY.glob("tmp*.gz") if path.is_file() + ) + if offenders: + raise ValueError( + "Repository root contains unmanaged Python temporary gzip files; " + f"remove them and fix the producing workflow: {offenders}" + ) + + +def _wheel_metadata(archive: zipfile.ZipFile) -> object: + names = [ + name for name in archive.namelist() if name.endswith(".dist-info/METADATA") + ] + if len(names) != 1: + raise ValueError("Wheel must contain exactly one METADATA file") + return BytesParser().parsebytes(archive.read(names[0])) + + +def _normalized_member_name(name: str, archive_name: str) -> str: + if not name or "\\" in name or name.startswith("/"): + raise ValueError(f"{archive_name} contains unsafe archive path: {name!r}") + stripped = name[:-1] if name.endswith("/") else name + parts = stripped.split("/") + if not stripped or any(part in {"", ".", ".."} for part in parts): + raise ValueError(f"{archive_name} contains unsafe archive path: {name!r}") + return "/".join(parts) + + +def _validate_zip_members(archive: zipfile.ZipFile, archive_name: str) -> list[str]: + names: list[str] = [] + seen: set[str] = set() + for info in archive.infolist(): + name = _normalized_member_name(info.filename, archive_name) + if name in seen: + raise ValueError(f"{archive_name} contains duplicate member: {name}") + seen.add(name) + mode = (info.external_attr >> 16) & 0xFFFF + if stat.S_ISLNK(mode): + raise ValueError(f"{archive_name} contains symlink-like ZIP member: {name}") + names.append(name) + return names + + +def _validate_tar_members(archive: tarfile.TarFile, archive_name: str) -> list[str]: + names: list[str] = [] + seen: set[str] = set() + for member in archive.getmembers(): + name = _normalized_member_name(member.name, archive_name) + if name in seen: + raise ValueError(f"{archive_name} contains duplicate member: {name}") + seen.add(name) + if not (member.isfile() or member.isdir()): + raise ValueError(f"{archive_name} contains unsafe special member: {name}") + names.append(name) + return names + + +def _assert_distribution_metadata( + metadata: object, name: str, version: str, archive_name: str +) -> None: + if metadata.get("Name") != name or metadata.get("Version") != version: + raise ValueError( + f"{archive_name} has unexpected distribution identity: " + f"{metadata.get('Name')} {metadata.get('Version')}" + ) + + +def _tar_file_bytes(archive: tarfile.TarFile, name: str) -> bytes: + member = archive.getmember(name) + stream = archive.extractfile(member) + if stream is None: + raise ValueError(f"Cannot read archive member: {name}") + return stream.read() + + +def _validate_standard_manifest( + manifest: object, expected_version: str = STANDARD_VERSION +) -> list[dict]: + if not isinstance(manifest, dict) or set(manifest) != { + "catalog_version", + "distribution_version", + "format", + "models", + "schema_version", + "topology", + }: + raise ValueError("Standard model manifest has an invalid top-level schema") + if ( + manifest["schema_version"] != 1 + or manifest["catalog_version"] != "2026.1" + or manifest["distribution_version"] != expected_version + or manifest["topology"] != "models/model-projects.properties" + or manifest["format"] != {"compression": "gzip", "magic": "EGTR", "version": 7} + or not isinstance(manifest["models"], list) + ): + raise ValueError("Standard model manifest has incompatible catalog metadata") + models = manifest["models"] + if { + model.get("id") for model in models if isinstance(model, dict) + } != EXPECTED_MODEL_IDS: + raise ValueError( + "Standard model manifest does not match the default model topology" + ) + if len(models) != len(EXPECTED_MODEL_IDS): + raise ValueError("Standard model manifest contains duplicate model entries") + provenance_keys = { + "attribution", + "dataset", + "license", + "license_uri", + "repository", + "revision", + "revision_status", + "source_name", + "source_project", + "source_version", + "transformations", + "verification_date", + } + for model in models: + if set(model) != { + "file", + "id", + "notice", + "provenance", + "sha256", + "source", + "version", + }: + raise ValueError("Standard model manifest contains an invalid model entry") + model_id = model["id"] + if ( + model["file"] != f"models/{model_id}.rxc" + or model["notice"] != f"notices/{model_id}/NOTICE-model-data.txt" + or model["version"] != "1.0.0" + or not isinstance(model["sha256"], str) + or len(model["sha256"]) != 64 + or set(model["provenance"]) != provenance_keys + or model["provenance"]["license"] != "CC-BY-SA-3.0" + or set(model["source"]) != {"path", "sha256"} + or model["source"]["path"] != f"models/{model_id}/src/modelInput/stemmer.gz" + ): + raise ValueError(f"Standard model manifest entry is invalid: {model_id}") + if not all(isinstance(value, str) for value in model["provenance"].values()): + raise ValueError(f"Standard model provenance is invalid: {model_id}") + return models + + +def _assert_no_source_dictionaries(names: list[str], archive_name: str) -> None: + offenders = [ + name + for name in names + if name.endswith("stemmer.gz") or "/radixor/models/" in name + ] + if offenders: + raise ValueError( + f"{archive_name} contains forbidden source dictionaries: {offenders}" + ) + + +def _assert_no_standard_model_payload(names: list[str], archive_name: str) -> None: + offenders = [] + for name in names: + parts = PurePosixPath(name).parts + if ( + name.endswith(".rxc") + or "models-standard" in parts + or any(part.startswith("radixor_models_standard") for part in parts) + ): + offenders.append(name) + if offenders: + raise ValueError( + f"{archive_name} contains standard-model payload owned by " + f"radixor-models-standard: {offenders}" + ) + + +def _assert_no_local_build_outputs(names: list[str], archive_name: str) -> None: + offenders = [] + for name in names: + path = PurePosixPath(name) + parts = path.parts + is_benchmark_result = ( + "benchmarks" in parts + and path.name.startswith("results") + and path.suffix in {".csv", ".json"} + ) + if "dist" in parts or is_benchmark_result: + offenders.append(name) + if offenders: + raise ValueError( + f"{archive_name} contains local distribution or benchmark outputs: {offenders}" + ) + + +def _assert_no_python_cache( + names: list[str], archive_name: str, *, allow_native_pyd: bool = False +) -> None: + offenders = [] + for name in names: + path = PurePosixPath(name) + is_native_module = ( + allow_native_pyd + and path.suffix == ".pyd" + and path.name.startswith("_radixor") + ) + if ( + "__pycache__" in path.parts + or path.suffix in {".pyc", ".pyo"} + or (path.suffix == ".pyd" and not is_native_module) + ): + offenders.append(name) + if offenders: + raise ValueError(f"{archive_name} contains Python cache artifacts: {offenders}") + + +def _assert_main_dependency(metadata, archive_name: str) -> None: + requirements = metadata.get_all("Requires-Dist", []) + normalized = {requirement.replace(" ", "") for requirement in requirements} + if EXPECTED_DEPENDENCY not in normalized: + raise ValueError( + f"Missing compatible standard-model dependency in {archive_name}: " + f"{requirements}" + ) + + +def _verify_main_wheel(path: Path, expected_version: str = MAIN_VERSION) -> None: + with zipfile.ZipFile(path) as archive: + names = _validate_zip_members(archive, path.name) + _assert_no_source_dictionaries(names, path.name) + _assert_no_standard_model_payload(names, path.name) + _assert_no_python_cache(names, path.name, allow_native_pyd=True) + metadata = _wheel_metadata(archive) + _assert_distribution_metadata(metadata, MAIN_NAME, expected_version, path.name) + if metadata.get("License-Expression") != "BSD-3-Clause": + raise ValueError( + f"{path.name} must declare License-Expression: BSD-3-Clause" + ) + license_names = [ + name for name in names if name.endswith(".dist-info/licenses/LICENSE") + ] + if ( + len(license_names) != 1 + or archive.read(license_names[0]) != EXPECTED_NATIVE_LICENSE + ): + raise ValueError( + f"{path.name} does not contain the full repository BSD-3-Clause LICENSE" + ) + _assert_main_dependency(metadata, path.name) + + +def _verify_main_sdist(path: Path, expected_version: str = MAIN_VERSION) -> None: + with tarfile.open(path, "r:gz") as archive: + names = _validate_tar_members(archive, path.name) + _assert_no_source_dictionaries(names, path.name) + _assert_no_standard_model_payload(names, path.name) + _assert_no_local_build_outputs(names, path.name) + _assert_no_python_cache(names, path.name) + license_members = [ + member + for member in archive.getmembers() + if PurePosixPath(member.name).name == "LICENSE" and member.isfile() + ] + if len(license_members) != 1: + raise ValueError( + f"{path.name} must contain exactly one BSD-3-Clause LICENSE" + ) + license_stream = archive.extractfile(license_members[0]) + if license_stream is None or license_stream.read() != EXPECTED_NATIVE_LICENSE: + raise ValueError( + f"{path.name} does not contain the full repository BSD-3-Clause LICENSE" + ) + root = f"radixor-{expected_version}" + metadata = BytesParser().parsebytes( + _tar_file_bytes(archive, f"{root}/PKG-INFO") + ) + _assert_distribution_metadata(metadata, MAIN_NAME, expected_version, path.name) + _assert_main_dependency(metadata, path.name) + + +def _verify_standard_wheel( + path: Path, expected_version: str = STANDARD_VERSION +) -> None: + if not path.name.endswith("-py3-none-any.whl"): + raise ValueError(f"Standard model wheel is not pure py3-none-any: {path.name}") + with zipfile.ZipFile(path) as archive: + names = _validate_zip_members(archive, path.name) + _assert_no_source_dictionaries(names, path.name) + _assert_no_python_cache(names, path.name) + metadata = _wheel_metadata(archive) + _assert_distribution_metadata( + metadata, STANDARD_NAME, expected_version, path.name + ) + if metadata.get("License-Expression") != "CC-BY-SA-3.0": + raise ValueError("Standard model data must declare CC-BY-SA-3.0") + manifest = json.loads(archive.read("radixor_models_standard/manifest.json")) + models = _validate_standard_manifest(manifest, expected_version) + dist_info = next( + name.rsplit("/", 1)[0] + for name in names + if name.endswith(".dist-info/METADATA") + ) + allowed = { + "radixor_models_standard/__init__.py", + "radixor_models_standard/manifest.json", + "radixor_models_standard/models/__init__.py", + "radixor_models_standard/notices/__init__.py", + f"{dist_info}/METADATA", + f"{dist_info}/WHEEL", + f"{dist_info}/RECORD", + f"{dist_info}/top_level.txt", + f"{dist_info}/licenses/LICENSE-MODEL-DATA.txt", + } + for model in models: + model_name = f"radixor_models_standard/{model['file']}" + notice_name = f"radixor_models_standard/{model['notice']}" + allowed.update({model_name, notice_name}) + data = archive.read(model_name) + if hashlib.sha256(data).hexdigest() != model["sha256"]: + raise ValueError(f"Checksum mismatch for {model['id']}") + if gzip.decompress(data)[:8] != b"EGTR\x00\x00\x00\x07": + raise ValueError(f"Invalid v7 marker/version for {model['id']}") + if notice_name not in names: + raise ValueError(f"Missing model notice for {model['id']}") + unexpected = set(names) - allowed + missing = allowed - set(names) + if unexpected or missing: + raise ValueError( + f"Standard wheel allowlist mismatch; unexpected={sorted(unexpected)}, " + f"missing={sorted(missing)}" + ) + for info in archive.infolist(): + if info.filename in names and ((info.external_attr >> 16) & 0o111): + raise ValueError( + f"Standard wheel contains executable member: {info.filename}" + ) + + +def _verify_standard_sdist( + path: Path, expected_version: str = STANDARD_VERSION +) -> None: + with tarfile.open(path, "r:gz") as archive: + names = _validate_tar_members(archive, path.name) + _assert_no_source_dictionaries(names, path.name) + _assert_no_python_cache(names, path.name) + root = f"radixor_models_standard-{expected_version}" + metadata = BytesParser().parsebytes( + _tar_file_bytes(archive, f"{root}/PKG-INFO") + ) + _assert_distribution_metadata( + metadata, STANDARD_NAME, expected_version, path.name + ) + manifest_name = f"{root}/radixor_models_standard/manifest.json" + models = _validate_standard_manifest( + json.loads(_tar_file_bytes(archive, manifest_name)), expected_version + ) + egg_info = f"{root}/radixor_models_standard.egg-info" + allowed_files = { + f"{root}/LICENSE-MODEL-DATA.txt", + f"{root}/MANIFEST.in", + f"{root}/PKG-INFO", + f"{root}/README.md", + f"{root}/pyproject.toml", + f"{root}/setup.cfg", + f"{root}/radixor_models_standard/__init__.py", + manifest_name, + f"{root}/radixor_models_standard/models/__init__.py", + f"{root}/radixor_models_standard/notices/__init__.py", + f"{egg_info}/PKG-INFO", + f"{egg_info}/SOURCES.txt", + f"{egg_info}/dependency_links.txt", + f"{egg_info}/top_level.txt", + } + allowed_dirs = { + root, + f"{root}/radixor_models_standard", + f"{root}/radixor_models_standard/models", + f"{root}/radixor_models_standard/notices", + egg_info, + } + for model in models: + model_name = f"{root}/radixor_models_standard/{model['file']}" + notice_name = f"{root}/radixor_models_standard/{model['notice']}" + notice_dir = notice_name.rsplit("/", 1)[0] + allowed_files.update({model_name, notice_name}) + allowed_dirs.add(notice_dir) + data = _tar_file_bytes(archive, model_name) + if hashlib.sha256(data).hexdigest() != model["sha256"]: + raise ValueError( + f"Checksum mismatch for {model['id']} in standard sdist" + ) + if gzip.decompress(data)[:8] != b"EGTR\x00\x00\x00\x07": + raise ValueError( + f"Invalid v7 marker/version for {model['id']} in standard sdist" + ) + files = {member.name for member in archive.getmembers() if member.isfile()} + directories = {member.name for member in archive.getmembers() if member.isdir()} + if files != allowed_files or directories != allowed_dirs: + raise ValueError( + f"Standard sdist allowlist mismatch; unexpected files={sorted(files - allowed_files)}, " + f"missing files={sorted(allowed_files - files)}, " + f"unexpected dirs={sorted(directories - allowed_dirs)}, " + f"missing dirs={sorted(allowed_dirs - directories)}" + ) + executable = [ + member.name + for member in archive.getmembers() + if member.isfile() and member.mode & 0o111 + ] + if executable: + raise ValueError( + f"Standard sdist contains executable members: {executable}" + ) + + +def _verify_fresh_install(main_wheel: Path, standard_wheel: Path) -> None: + managed_temp = REPOSITORY / "build" / "python" / "tmp" + managed_temp.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory( + prefix="radixor-wheel-install-", dir=managed_temp + ) as temporary: + environment = Path(temporary) / "venv" + subprocess.run([sys.executable, "-m", "venv", str(environment)], check=True) + scripts = "Scripts" if os.name == "nt" else "bin" + python = environment / scripts / ("python.exe" if os.name == "nt" else "python") + if "radixor_models_standard-0.0.0-" in standard_wheel.name: + subprocess.run( + [ + str(python), + "-m", + "pip", + "install", + "--no-index", + "--no-deps", + str(standard_wheel), + str(main_wheel), + ], + check=True, + ) + else: + subprocess.run( + [ + str(python), + "-m", + "pip", + "install", + "--no-index", + "--find-links", + str(main_wheel.parent), + "--find-links", + str(standard_wheel.parent), + "radixor", + ], + check=True, + ) + result = subprocess.run( + [ + str(python), + "-c", + "from radixor import Stemmer; print(Stemmer('en').stem('running'))", + ], + check=True, + capture_output=True, + text=True, + ) + if result.stdout.strip() != "run": + raise ValueError(f"Unexpected installed model result: {result.stdout!r}") + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--main-wheel-dir", type=Path) + parser.add_argument("--main-sdist-dir", type=Path) + parser.add_argument("--standard-dir", type=Path) + parser.add_argument("--main-version", default=MAIN_VERSION) + parser.add_argument("--standard-version", default=STANDARD_VERSION) + parser.add_argument("--skip-install", action="store_true") + args = parser.parse_args() + + _assert_no_repository_temp_gzip() + if (args.main_wheel_dir is None) != (args.main_sdist_dir is None): + parser.error("--main-wheel-dir and --main-sdist-dir must be used together") + if args.main_wheel_dir is None and args.standard_dir is None: + parser.error("at least one distribution directory must be provided") + + main_wheel = None + standard_wheel = None + if args.main_wheel_dir is not None: + main_wheel = _one(args.main_wheel_dir, "radixor-*.whl") + main_sdist = _one(args.main_sdist_dir, "radixor-*.tar.gz") + _verify_main_wheel(main_wheel, args.main_version) + _verify_main_sdist(main_sdist, args.main_version) + if args.standard_dir is not None: + standard_wheel = _one(args.standard_dir, "radixor_models_standard-*.whl") + standard_sdist = _one(args.standard_dir, "radixor_models_standard-*.tar.gz") + _verify_standard_wheel(standard_wheel, args.standard_version) + _verify_standard_sdist(standard_sdist, args.standard_version) + if not args.skip_install and main_wheel is not None and standard_wheel is not None: + _verify_fresh_install(main_wheel, standard_wheel) + print("verified requested Radixor distributions") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/python/src/builder.rs b/python/src/builder.rs new file mode 100644 index 0000000..ae0ce09 --- /dev/null +++ b/python/src/builder.rs @@ -0,0 +1,682 @@ +// Copyright (C) 2026, Leo Galambos +// 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 copyright holder 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 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. + +// Port of the Radixor Java trie compilation pipeline +// (org.egothor.stemmer.FrequencyTrie.Builder + org.egothor.stemmer.trie.*): +// mutable trie build -> bottom-up reduction -> freeze to an immutable compiled trie. +// +// Faithful port notes: +// * Build semantics mirror StemmerPatchTrieLoader.load: for each dictionary +// entry we optionally insert the stem mapped to the NOOP patch "Na" (when +// store_original) and every variant != stem mapped to +// encode_patch(variant, stem, backward). +// * Keys are indexed per WordTraversalDirection: BACKWARD consumes characters +// right-to-left (logicalIndex = len-1-offset), FORWARD left-to-right. +// * Reduction hardcodes the production configuration verified from the Java +// source: ReductionMode = MERGE_SUBTREES_WITH_EQUIVALENT_DOMINANT_GET_RESULTS, +// dominantWinnerMinPercent = 75, dominantWinnerOverSecondRatio = 3, +// contractUniformSubtrees = true (metadataForCompilation always applies +// ReductionSettings.withUniformSubtreeContraction). +// * All character/patch data is handled as UTF-16 code units (Java `char`), +// exactly as the runtime trie.rs expects. +#![allow(dead_code)] + +use std::cell::RefCell; +use std::collections::{BTreeMap, HashMap}; +use std::rc::Rc; +use std::sync::Arc; + +use crate::dict::DictEntry; +use crate::encoder::encode_patch; +use crate::patch::PatchCommand; +use crate::trie::{CaseMode, DiacriticMode, FrequencyTrie, TraversalDirection, TrieMetadata}; + +/// Canonical no-op patch command (PatchCommandEncoder.NOOP_PATCH = "Na"). +const NOOP_PATCH: &str = "Na"; + +/// dominantWinnerMinPercent (ReductionSettings.DEFAULT_DOMINANT_WINNER_MIN_PERCENT). +const DOMINANT_WINNER_MIN_PERCENT: i64 = 75; + +/// dominantWinnerOverSecondRatio (ReductionSettings.DEFAULT_DOMINANT_WINNER_OVER_SECOND_RATIO). +const DOMINANT_WINNER_OVER_SECOND_RATIO: i64 = 3; + +// Ordered value-count map (Java LinkedHashMap semantics) + +/// Insertion-ordered map from a patch-command string to its accumulated local +/// frequency. Mirrors the `LinkedHashMap` used for `valueCounts` on +/// mutable nodes and `localCounts` on reduced nodes. +#[derive(Clone, Default)] +struct OrderedCounts { + entries: Vec<(String, i32)>, + index: HashMap, +} + +impl OrderedCounts { + fn new() -> Self { + OrderedCounts { + entries: Vec::new(), + index: HashMap::new(), + } + } + + fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + fn len(&self) -> usize { + self.entries.len() + } + + /// Adds `count` to `value`, preserving first-seen insertion order. This is + /// both the build-time `put` accumulation and the reduction-time + /// `mergeLocalCounts` aggregation. + fn add(&mut self, value: &str, count: i32) { + if let Some(&position) = self.index.get(value) { + self.entries[position].1 += count; + } else { + let position = self.entries.len(); + self.index.insert(value.to_string(), position); + self.entries.push((value.to_string(), count)); + } + } +} + +// MutableNode (org.egothor.stemmer.trie.MutableNode) + +/// Mutable build-time node: children indexed by transition character plus the +/// local terminal value counts stored exactly at this node. +struct MutableNode { + children: BTreeMap, + value_counts: OrderedCounts, +} + +impl MutableNode { + fn new() -> Self { + MutableNode { + children: BTreeMap::new(), + value_counts: OrderedCounts::new(), + } + } +} + +/// Stores a value at the node addressed by `key`, incrementing its local +/// frequency by one. Mirrors `FrequencyTrie.Builder.put`. +fn put(root: &mut MutableNode, key: &[u16], value: &str, backward: bool) { + let length = key.len(); + let mut current = root; + for offset in 0..length { + // WordTraversalDirection.logicalIndex(length, offset). + let logical_index = if backward { + length - 1 - offset + } else { + offset + }; + let edge = key[logical_index]; + current = current + .children + .entry(edge) + .or_insert_with(MutableNode::new); + } + current.value_counts.add(value, 1); +} + +// ReducedNode (org.egothor.stemmer.trie.ReducedNode) + +/// Canonical reduced node used during subtree merging. Reduced nodes are shared: +/// there is exactly one instance per reduction signature, referenced through +/// `Rc` so that identical subtrees share a single instance (and therefore a +/// single frozen `Arc`). +struct ReducedNode { + /// Canonical reduction signature (see `compute_signature`). + signature: String, + /// Aggregated local value counts. + local_counts: OrderedCounts, + /// Canonical children by edge, naturally sorted ascending by the BTreeMap. + children: BTreeMap>>, + /// Whether this node is a contracted accepting leaf. + accepts: bool, +} + +impl ReducedNode { + /// Merges additional local counts into this canonical node. + fn merge_local_counts(&mut self, additional: &OrderedCounts) { + for (value, count) in &additional.entries { + self.local_counts.add(value, *count); + } + } + + /// Merges child references into this canonical node. For nodes with the same + /// reduction signature the child edge sets and child signatures are + /// compatible, so this only verifies canonical identity and stores it. + fn merge_children(&mut self, additional: &BTreeMap>>) { + for (edge, child) in additional { + match self.children.get(edge) { + Some(existing) => { + if !Rc::ptr_eq(existing, child) { + panic!("Incompatible canonical child encountered during reduction."); + } + } + None => { + self.children.insert(*edge, Rc::clone(child)); + } + } + } + } +} + +// LocalValueSummary (org.egothor.stemmer.trie.LocalValueSummary) + +/// Deterministic local terminal value summary of a node. +struct LocalValueSummary { + /// Locally stored values ordered by descending frequency, then shorter text, + /// then lexicographic (UTF-16) text, then first-seen insertion order. + ordered_values: Vec, + /// Frequencies aligned with `ordered_values` (needed for v7 serialization). + ordered_counts: Vec, + total_count: i64, + dominant_value: Option, + dominant_count: i64, + second_count: i64, +} + +impl LocalValueSummary { + /// Builds a summary from local counts, applying the exact Java ordering. + fn of(counts: &OrderedCounts) -> Self { + struct Sortable { + value: String, + count: i32, + // Java String.length() and String.compareTo operate on UTF-16 code + // units, so text ordering must compare the u16 sequence, never UTF-8. + text16: Vec, + insertion_order: usize, + } + + let mut entries: Vec = counts + .entries + .iter() + .enumerate() + .map(|(insertion_order, (value, count))| Sortable { + value: value.clone(), + count: *count, + text16: value.encode_utf16().collect(), + insertion_order, + }) + .collect(); + + entries.sort_by(|left, right| { + // 1. descending frequency + right + .count + .cmp(&left.count) + // 2. shorter text wins + .then_with(|| left.text16.len().cmp(&right.text16.len())) + // 3. lexicographically lower text (UTF-16 code units) wins + .then_with(|| left.text16.cmp(&right.text16)) + // 4. stable first-seen insertion order + .then_with(|| left.insertion_order.cmp(&right.insertion_order)) + }); + + let ordered_values: Vec = entries.iter().map(|entry| entry.value.clone()).collect(); + let ordered_counts: Vec = entries.iter().map(|entry| entry.count).collect(); + let total_count: i64 = entries.iter().map(|entry| entry.count as i64).sum(); + let dominant_value = entries.first().map(|entry| entry.value.clone()); + let dominant_count = entries.first().map(|entry| entry.count as i64).unwrap_or(0); + let second_count = entries.get(1).map(|entry| entry.count as i64).unwrap_or(0); + + LocalValueSummary { + ordered_values, + ordered_counts, + total_count, + dominant_value, + dominant_count, + second_count, + } + } + + /// Whether the dominant value satisfies both configured dominance + /// constraints (percent AND ratio), matching + /// `LocalValueSummary.hasQualifiedDominantWinner`. + fn has_qualified_dominant_winner(&self) -> bool { + if self.dominant_value.is_none() { + return false; + } + + let percent_satisfied = + self.dominant_count * 100 >= self.total_count * DOMINANT_WINNER_MIN_PERCENT; + + let ratio_satisfied = if self.second_count == 0 { + true + } else { + self.dominant_count >= self.second_count * DOMINANT_WINNER_OVER_SECOND_RATIO + }; + + percent_satisfied && ratio_satisfied + } +} + +// ReductionSignature (org.egothor.stemmer.trie.ReductionSignature and friends) + +/// Appends `text` to `buffer` using a length-prefixed, collision-free encoding +/// so arbitrary UTF-16 patch strings can be embedded without ambiguity. +fn push_len_prefixed(buffer: &mut String, text: &str) { + buffer.push_str(&text.len().to_string()); + buffer.push('#'); + buffer.push_str(text); +} + +/// Produces the canonical reduction signature of a subtree as an unambiguous +/// hashable string. Two subtrees receive equal signatures exactly when the Java +/// `ReductionSignature.equals` would consider them equal: +/// +/// * local descriptor — for DOMINANT mode this is the dominant descriptor +/// (only the dominant value) when the summary has a qualified dominant +/// winner, otherwise the ranked descriptor (the full ordered value list), +/// * whether the node accepts remaining input, +/// * the sorted list of (edge label, child signature) pairs. +fn compute_signature( + summary: &LocalValueSummary, + children: &BTreeMap>>, + accepts: bool, +) -> String { + let mut signature = String::new(); + + // Local descriptor. 'D' and 'R' markers keep a DominantLocalDescriptor + // distinct from a RankedLocalDescriptor holding the same single value, + // exactly as the Java class-based equality does. + if summary.has_qualified_dominant_winner() { + signature.push('D'); + push_len_prefixed(&mut signature, summary.dominant_value.as_ref().unwrap()); + } else { + signature.push('R'); + signature.push_str(&summary.ordered_values.len().to_string()); + signature.push(';'); + for value in &summary.ordered_values { + push_len_prefixed(&mut signature, value); + } + } + + // acceptsRemainingInput. + signature.push(if accepts { 'A' } else { 'a' }); + + // Child descriptors in sorted edge order (BTreeMap iterates ascending). + signature.push_str(&children.len().to_string()); + signature.push(';'); + for (label, child) in children { + signature.push_str(&label.to_string()); + signature.push(':'); + push_len_prefixed(&mut signature, &child.borrow().signature); + } + + signature +} + +/// Returns aggregated single-value local counts when the supplied internal +/// subtree can be contracted into an accepting leaf, otherwise `None`. +/// +/// Contraction applies (matching `FrequencyTrie.Builder.contractUniformSubtree`) +/// when the node has at least one child, every child is a single-value leaf with +/// no further children, and all those child values plus the local value (if any) +/// are the same single value. The contracted count is always 1. +fn contract_uniform_subtree( + local_counts: &OrderedCounts, + children: &BTreeMap>>, +) -> Option { + if children.is_empty() { + return None; + } + + let mut uniform_value: Option = None; + let mut value_seen = false; + + if !local_counts.is_empty() { + if local_counts.len() != 1 { + return None; + } + uniform_value = Some(local_counts.entries[0].0.clone()); + value_seen = true; + } + + for child in children.values() { + let child_ref = child.borrow(); + let is_single_value_leaf = + child_ref.children.is_empty() && child_ref.local_counts.len() == 1; + if !is_single_value_leaf { + return None; + } + let child_value = child_ref.local_counts.entries[0].0.clone(); + if value_seen && uniform_value.as_deref() != Some(child_value.as_str()) { + return None; + } + uniform_value = Some(child_value); + value_seen = true; + } + + if !value_seen { + return None; + } + + let mut contracted = OrderedCounts::new(); + contracted.add(uniform_value.as_ref().unwrap(), 1); + Some(contracted) +} + +/// Reduces a mutable node to a canonical reduced node (bottom-up). +/// +/// The order of operations mirrors the Java `reduce`: +/// 1. reduce every child first, +/// 2. try `contractUniformSubtree` (always enabled here), +/// 3. compute the local summary and reduction signature, +/// 4. deduplicate through the context map, merging counts and children into +/// an existing canonical node when the signature already exists. +fn reduce( + node: &MutableNode, + context: &mut HashMap>>, +) -> Rc> { + let mut reduced_children: BTreeMap>> = BTreeMap::new(); + for (edge, child) in node.children.iter() { + let reduced_child = reduce(child, context); + reduced_children.insert(*edge, reduced_child); + } + + let mut local_counts = node.value_counts.clone(); + let mut accepts_remaining_input = false; + + // contractUniformSubtrees is always true for the production configuration. + if let Some(contracted) = contract_uniform_subtree(&local_counts, &reduced_children) { + local_counts = contracted; + reduced_children = BTreeMap::new(); + accepts_remaining_input = true; + } + + let summary = LocalValueSummary::of(&local_counts); + let signature = compute_signature(&summary, &reduced_children, accepts_remaining_input); + + if let Some(canonical) = context.get(&signature).cloned() { + { + let mut canonical_mut = canonical.borrow_mut(); + canonical_mut.merge_local_counts(&local_counts); + canonical_mut.merge_children(&reduced_children); + } + return canonical; + } + + let canonical = Rc::new(RefCell::new(ReducedNode { + signature: signature.clone(), + local_counts, + children: reduced_children, + accepts: accepts_remaining_input, + })); + context.insert(signature, Rc::clone(&canonical)); + canonical +} + +// Freeze (FrequencyTrie.Builder.freeze -> flat CSR arrays) + +/// Maximum contiguous child-label span for which a node uses a dense +/// direct-index table instead of binary search (mirrors the Java +/// CompiledNode `maxExpandedIndex` fanout strategy). +pub(crate) const MAX_DENSE_SPAN: usize = 512; + +/// Frozen arrays of the compiled trie in CSR layout (see trie.rs). +pub(crate) struct FrozenTrie { + pub(crate) edge_start: Vec, + pub(crate) edge_labels: Vec, + pub(crate) edge_targets: Vec, + pub(crate) accepts: Vec, + pub(crate) value_start: Vec, + pub(crate) values: Vec>, + /// Patch strings parallel to `values` (needed only for serialization). + pub(crate) value_strings: Vec, + /// Frequencies parallel to `values` (needed only for v7 serialization). + pub(crate) value_counts: Vec, + pub(crate) dense_start: Vec, + pub(crate) dense_base: Vec, + pub(crate) dense_targets: Vec, +} + +/// Per-node build record collected during interning, in node-id order. +#[derive(Default)] +struct NodeBuild { + edges: Vec, + targets: Vec, + accepts: bool, + values: Vec>, + value_strings: Vec, + value_counts: Vec, +} + +/// Assigns a stable node id to each distinct canonical reduced node and records +/// its edges (ascending), child ids, and best-first values. +/// +/// Shared canonical reduced nodes (identical `Rc` allocations) are interned once +/// — the analogue of the Java `IdentityHashMap` cache +/// — so structural sharing from reduction is preserved as shared node ids. Equal +/// patch strings are compiled once and shared through `patch_cache`. +fn intern( + node: &Rc>, + index_of: &mut HashMap, + nodes: &mut Vec, + patch_cache: &mut HashMap>, + backward: bool, +) -> u32 { + let identity = Rc::as_ptr(node) as usize; + if let Some(&existing) = index_of.get(&identity) { + return existing; + } + let id = nodes.len() as u32; + index_of.insert(identity, id); + nodes.push(NodeBuild::default()); // reserve this id's slot before recursing + + let node_ref = node.borrow(); + let summary = LocalValueSummary::of(&node_ref.local_counts); + + // BTreeMap iterates ascending by edge label, so edges stay sorted. + let mut edges: Vec = Vec::with_capacity(node_ref.children.len()); + let mut targets: Vec = Vec::with_capacity(node_ref.children.len()); + for (edge, child) in node_ref.children.iter() { + edges.push(*edge); + targets.push(intern(child, index_of, nodes, patch_cache, backward)); + } + + let values: Vec> = summary + .ordered_values + .iter() + .map(|patch| { + Arc::clone( + patch_cache + .entry(patch.clone()) + .or_insert_with(|| Arc::new(PatchCommand::parse(patch, backward))), + ) + }) + .collect(); + + nodes[id as usize] = NodeBuild { + edges, + targets, + accepts: node_ref.accepts, + values, + value_strings: summary.ordered_values.clone(), + value_counts: summary.ordered_counts.clone(), + }; + id +} + +/// Freezes the reduced graph rooted at `root` (node id 0) into flat CSR arrays. +fn freeze(root: &Rc>, backward: bool) -> FrozenTrie { + let mut index_of: HashMap = HashMap::new(); + let mut nodes: Vec = Vec::new(); + let mut patch_cache: HashMap> = HashMap::new(); + intern(root, &mut index_of, &mut nodes, &mut patch_cache, backward); + + let node_count = nodes.len(); + let mut edge_start: Vec = Vec::with_capacity(node_count + 1); + let mut edge_labels: Vec = Vec::new(); + let mut edge_targets: Vec = Vec::new(); + let mut accepts: Vec = Vec::with_capacity(node_count); + let mut value_start: Vec = Vec::with_capacity(node_count + 1); + let mut values: Vec> = Vec::new(); + let mut value_strings: Vec = Vec::new(); + let mut value_counts: Vec = Vec::new(); + let mut dense_start: Vec = Vec::with_capacity(node_count + 1); + let mut dense_base: Vec = Vec::with_capacity(node_count); + let mut dense_targets: Vec = Vec::new(); + + edge_start.push(0); + value_start.push(0); + dense_start.push(0); + for nb in &nodes { + edge_labels.extend_from_slice(&nb.edges); + edge_targets.extend_from_slice(&nb.targets); + edge_start.push(edge_labels.len() as u32); + accepts.push(nb.accepts); + for v in &nb.values { + values.push(Arc::clone(v)); + } + for v in &nb.value_strings { + value_strings.push(v.clone()); + } + value_counts.extend_from_slice(&nb.value_counts); + value_start.push(values.len() as u32); + + // Decide dense vs sparse child lookup by fanout/span. + let count = nb.edges.len(); + let mut dense = false; + if count >= 2 { + let first = nb.edges[0] as usize; + let last = nb.edges[count - 1] as usize; // edges are ascending + let span = last - first + 1; + if span <= MAX_DENSE_SPAN { + let base = nb.edges[0]; + let seg = dense_targets.len(); + dense_targets.resize(seg + span, 0); + for (k, &label) in nb.edges.iter().enumerate() { + dense_targets[seg + (label - base) as usize] = nb.targets[k] + 1; + } + dense_base.push(base); + dense_start.push(dense_targets.len() as u32); + dense = true; + } + } + if !dense { + dense_base.push(0); + dense_start.push(dense_targets.len() as u32); // span 0 => sparse + } + } + + FrozenTrie { + edge_start, + edge_labels, + edge_targets, + accepts, + value_start, + values, + value_strings, + value_counts, + dense_start, + dense_base, + dense_targets, + } +} + +pub(crate) fn metadata_for(backward: bool, lowercase: bool) -> TrieMetadata { + TrieMetadata { + traversal: if backward { + TraversalDirection::Backward + } else { + TraversalDirection::Forward + }, + case_mode: if lowercase { + CaseMode::LowercaseWithLocaleRoot + } else { + CaseMode::AsIs + }, + diacritic_mode: DiacriticMode::AsIs, + } +} + +/// Build the reduced+frozen trie arrays from dictionary entries (shared by the +/// in-memory builder and the compiler). +pub(crate) fn build_frozen( + entries: &[DictEntry], + backward: bool, + store_original: bool, +) -> FrozenTrie { + let mut root = MutableNode::new(); + for entry in entries { + let stem16: Vec = entry.stem.encode_utf16().collect(); + if store_original { + put(&mut root, &stem16, NOOP_PATCH, backward); + } + for variant in &entry.variants { + if variant != &entry.stem { + let variant16: Vec = variant.encode_utf16().collect(); + let patch = encode_patch(&variant16, &stem16, backward); + put(&mut root, &variant16, &patch, backward); + } + } + } + let mut context: HashMap>> = HashMap::new(); + let reduced_root = reduce(&root, &mut context); + freeze(&reduced_root, backward) +} + +fn frozen_into_trie(frozen: FrozenTrie, metadata: TrieMetadata) -> FrequencyTrie { + FrequencyTrie::new( + frozen.edge_start, + frozen.edge_labels, + frozen.edge_targets, + frozen.accepts, + frozen.value_start, + frozen.values, + frozen.dense_start, + frozen.dense_base, + frozen.dense_targets, + metadata, + ) +} + +// Public entry point + +/// Compiles dictionary entries into a read-only patch-command trie, faithfully +/// reproducing the Java `StemmerPatchTrieLoader.load` build followed by +/// `FrequencyTrie.Builder.build` (reduce + freeze). +/// +/// * `backward` — `true` selects BACKWARD traversal (all languages except the +/// right-to-left fa/he/yi), `false` selects FORWARD. +/// * `store_original` — when `true`, each stem is inserted mapped to the NOOP +/// patch `"Na"` so the stem itself is recognised. +pub fn build_trie_from_dict( + entries: &[DictEntry], + backward: bool, + store_original: bool, + lowercase: bool, +) -> FrequencyTrie { + let frozen = build_frozen(entries, backward, store_original); + frozen_into_trie(frozen, metadata_for(backward, lowercase)) +} diff --git a/python/src/dict.rs b/python/src/dict.rs new file mode 100644 index 0000000..3c1d64a --- /dev/null +++ b/python/src/dict.rs @@ -0,0 +1,122 @@ +// Copyright (C) 2026, Leo Galambos +// 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 copyright holder 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 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. + +// Port of StemmerDictionaryParser (Java) — line-oriented, tab-separated dictionary. +// +// Layout: first column = canonical stem, following tab-separated columns = variants. +// Remarks: the earliest occurrence of `#` or `//` terminates the logical line. +// Case: LOWERCASE_WITH_LOCALE_ROOT lowercases the line (locale-independent here). +// Items containing any whitespace character are ignored (Java: Character.isWhitespace). + +use flate2::read::GzDecoder; +use std::io::{self, Read}; + +/// One parsed dictionary entry: a canonical stem and its accepted variants, +/// in encounter order. +pub struct DictEntry { + pub stem: String, + pub variants: Vec, +} + +/// Decompress gzipped UTF-8 dictionary bytes and parse them into entries. +/// `lowercase` mirrors CaseProcessingMode.LOWERCASE_WITH_LOCALE_ROOT. +#[allow(dead_code)] // public helper; the runtime path decompresses then parse_text +pub fn parse_gz_dict(compressed: &[u8], lowercase: bool) -> io::Result> { + let mut decoder = GzDecoder::new(compressed); + let mut text = String::new(); + decoder.read_to_string(&mut text)?; + Ok(parse_text(&text, lowercase)) +} + +/// Parse an already-decompressed dictionary text. +pub fn parse_text(text: &str, lowercase: bool) -> Vec { + let mut entries = Vec::new(); + + for raw_line in text.lines() { + // stripRemark(line).trim(), then lowercase. + let stripped = strip_remark(raw_line).trim(); + if stripped.is_empty() { + continue; + } + let normalized: String = if lowercase { + stripped.to_lowercase() + } else { + stripped.to_string() + }; + if normalized.is_empty() { + continue; + } + + // split on '\t' keeping trailing empties (Java split("\t", -1)). + let mut columns = normalized.split('\t'); + + let stem = match columns.next() { + Some(c) => c.trim(), + None => continue, + }; + if stem.is_empty() || contains_whitespace(stem) { + continue; + } + + let mut variants = Vec::new(); + for col in columns { + let variant = col.trim(); + if variant.is_empty() || contains_whitespace(variant) { + continue; + } + variants.push(variant.to_string()); + } + + entries.push(DictEntry { + stem: stem.to_string(), + variants, + }); + } + + entries +} + +/// Removes a trailing remark: the earliest of `#` or `//` terminates the line. +fn strip_remark(line: &str) -> &str { + let hash = line.find('#'); + let slash = line.find("//"); + let remark = match (hash, slash) { + (None, None) => return line, + (Some(h), None) => h, + (None, Some(s)) => s, + (Some(h), Some(s)) => h.min(s), + }; + &line[..remark] +} + +/// Matches Java Character.isWhitespace closely enough for dictionary items. +#[inline] +fn contains_whitespace(item: &str) -> bool { + item.chars().any(|c| c.is_whitespace()) +} diff --git a/python/src/encoder.rs b/python/src/encoder.rs new file mode 100644 index 0000000..ee0b5d9 --- /dev/null +++ b/python/src/encoder.rs @@ -0,0 +1,349 @@ +// Copyright (C) 2026, Leo Galambos +// 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 copyright holder 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 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. + +// Port of PatchCommandEncoder (Java) — DP-based minimum-cost edit script. +// Costs: insert=1, delete=1, replace=1, match=0, mismatch_penalty=100. +// Produces compact opcode strings: D(elete), I(nsert), R(eplace), -(skip), N(oop). +// Count argument: 'a' + count - 1 (i.e., COUNT_SENTINEL = 'a' - 1 = 96). + +const MISMATCH_PENALTY: i32 = 100; +const COUNT_SENTINEL: u16 = b'a' as u16 - 1; // 96 = 0x60 + +#[derive(Clone, Copy, PartialEq)] +enum Trace { + Delete, + Insert, + Replace, + Match, +} + +/// Encode the patch command that transforms `source` (UTF-16 slice) into `target`. +/// Returns "Na" when source == target. +pub fn encode_patch(source: &[u16], target: &[u16], backward: bool) -> String { + if source == target { + return "Na".to_string(); + } + if backward { + encode_backward(source, target) + } else { + encode_forward(source, target) + } +} + +// Backward traversal encoding. + +fn encode_backward(source: &[u16], target: &[u16]) -> String { + let src_len = source.len(); + let tgt_len = target.len(); + let cols = tgt_len + 1; + + let mut cost = vec![0i32; (src_len + 1) * cols]; + let mut trace = vec![Trace::Match; (src_len + 1) * cols]; + + let idx = |r: usize, c: usize| r * cols + c; + + // Boundary conditions (Egothor backward: rows=source, cols=target) + for i in 1..=src_len { + cost[idx(i, 0)] = i as i32; + trace[idx(i, 0)] = Trace::Delete; + } + for j in 1..=tgt_len { + cost[idx(0, j)] = j as i32; + trace[idx(0, j)] = Trace::Insert; + } + + // Fill left-to-right, top-to-bottom (sourceIndex 1..=srcLen, targetIndex 1..=tgtLen) + for si in 1..=src_len { + let src_ch = source[si - 1]; // sourceCharacters[sourceIndex + sourceCharacterOffset=-1] + for ti in 1..=tgt_len { + let tgt_ch = target[ti - 1]; + + // sourceNeighbor = sourceIndex - 1, targetNeighbor = targetIndex - 1 + let del = cost[idx(si - 1, ti)] + 1; // DELETE from [si-1][ti] + let ins = cost[idx(si, ti - 1)] + 1; // INSERT from [si][ti-1] + let diag = cost[idx(si - 1, ti - 1)]; + let rep = diag + 1; + let mat = diag + + if src_ch == tgt_ch { + 0 + } else { + MISMATCH_PENALTY + }; + + // Priority: MATCH (baseline), then DELETE (<=), INSERT (<), REPLACE (<) + let mut best = mat; + let mut bt = Trace::Match; + if del <= best { + best = del; + bt = Trace::Delete; + } + if ins < best { + best = ins; + bt = Trace::Insert; + } + if rep < best { + bt = Trace::Replace; + } + let _ = best; + + cost[idx(si, ti)] = if bt == Trace::Replace { + rep + } else if bt == Trace::Insert { + ins + } else if bt == Trace::Delete { + del + } else { + mat + }; + trace[idx(si, ti)] = bt; + } + } + + build_patch_backward(&trace, target, cols, src_len, tgt_len) +} + +fn build_patch_backward( + trace: &[Trace], + target: &[u16], + cols: usize, + src_len: usize, + tgt_len: usize, +) -> String { + let idx = |r: usize, c: usize| r * cols + c; + + let mut patch = String::new(); + let mut pending_deletes: u16 = COUNT_SENTINEL; + let mut pending_skips: u16 = COUNT_SENTINEL; + + let mut si = src_len; + let mut ti = tgt_len; + + while si != 0 || ti != 0 { + match trace[idx(si, ti)] { + Trace::Delete => { + if pending_skips != COUNT_SENTINEL { + append_instruction(&mut patch, '-', pending_skips); + pending_skips = COUNT_SENTINEL; + } + pending_deletes = pending_deletes.wrapping_add(1); + si -= 1; + } + Trace::Insert => { + if pending_deletes != COUNT_SENTINEL { + append_instruction(&mut patch, 'D', pending_deletes); + pending_deletes = COUNT_SENTINEL; + } + if pending_skips != COUNT_SENTINEL { + append_instruction(&mut patch, '-', pending_skips); + pending_skips = COUNT_SENTINEL; + } + ti -= 1; + append_instruction(&mut patch, 'I', target[ti]); + } + Trace::Replace => { + if pending_deletes != COUNT_SENTINEL { + append_instruction(&mut patch, 'D', pending_deletes); + pending_deletes = COUNT_SENTINEL; + } + if pending_skips != COUNT_SENTINEL { + append_instruction(&mut patch, '-', pending_skips); + pending_skips = COUNT_SENTINEL; + } + ti -= 1; + si -= 1; + append_instruction(&mut patch, 'R', target[ti]); + } + Trace::Match => { + if pending_deletes != COUNT_SENTINEL { + append_instruction(&mut patch, 'D', pending_deletes); + pending_deletes = COUNT_SENTINEL; + } + pending_skips = pending_skips.wrapping_add(1); + si -= 1; + ti -= 1; + } + } + } + + if pending_deletes != COUNT_SENTINEL { + append_instruction(&mut patch, 'D', pending_deletes); + } + + patch +} + +// Forward traversal encoding. + +fn encode_forward(source: &[u16], target: &[u16]) -> String { + let src_len = source.len(); + let tgt_len = target.len(); + let cols = tgt_len + 1; + + let mut cost = vec![0i32; (src_len + 1) * cols]; + let mut trace = vec![Trace::Match; (src_len + 1) * cols]; + + let idx = |r: usize, c: usize| r * cols + c; + + // Boundary conditions (fill from bottom-right corner) + // cost[srcLen][tgtLen] = 0, trace = MATCH + for si in (0..src_len).rev() { + cost[idx(si, tgt_len)] = cost[idx(si + 1, tgt_len)] + 1; + trace[idx(si, tgt_len)] = Trace::Delete; + } + for ti in (0..tgt_len).rev() { + cost[idx(src_len, ti)] = cost[idx(src_len, ti + 1)] + 1; + trace[idx(src_len, ti)] = Trace::Insert; + } + + // Fill right-to-left, bottom-to-top + for si in (0..src_len).rev() { + let src_ch = source[si]; // sourceCharacters[sourceIndex + sourceCharacterOffset=0] + for ti in (0..tgt_len).rev() { + let tgt_ch = target[ti]; + + // sourceNeighbor = sourceIndex + 1, targetNeighbor = targetIndex + 1 + let del = cost[idx(si + 1, ti)] + 1; + let ins = cost[idx(si, ti + 1)] + 1; + let diag = cost[idx(si + 1, ti + 1)]; + let rep = diag + 1; + let mat = diag + + if src_ch == tgt_ch { + 0 + } else { + MISMATCH_PENALTY + }; + + let mut best = mat; + let mut bt = Trace::Match; + if del <= best { + best = del; + bt = Trace::Delete; + } + if ins < best { + best = ins; + bt = Trace::Insert; + } + if rep < best { + bt = Trace::Replace; + } + let _ = best; + + cost[idx(si, ti)] = if bt == Trace::Replace { + rep + } else if bt == Trace::Insert { + ins + } else if bt == Trace::Delete { + del + } else { + mat + }; + trace[idx(si, ti)] = bt; + } + } + + build_patch_forward(&trace, target, cols, src_len, tgt_len) +} + +fn build_patch_forward( + trace: &[Trace], + target: &[u16], + cols: usize, + src_len: usize, + tgt_len: usize, +) -> String { + let idx = |r: usize, c: usize| r * cols + c; + + let mut patch = String::new(); + let mut pending_deletes: u16 = COUNT_SENTINEL; + let mut pending_skips: u16 = COUNT_SENTINEL; + + let mut si = 0usize; + let mut ti = 0usize; + + while si != src_len || ti != tgt_len { + match trace[idx(si, ti)] { + Trace::Delete => { + if pending_skips != COUNT_SENTINEL { + append_instruction(&mut patch, '-', pending_skips); + pending_skips = COUNT_SENTINEL; + } + pending_deletes = pending_deletes.wrapping_add(1); + si += 1; + } + Trace::Insert => { + if pending_deletes != COUNT_SENTINEL { + append_instruction(&mut patch, 'D', pending_deletes); + pending_deletes = COUNT_SENTINEL; + } + if pending_skips != COUNT_SENTINEL { + append_instruction(&mut patch, '-', pending_skips); + pending_skips = COUNT_SENTINEL; + } + append_instruction(&mut patch, 'I', target[ti]); + ti += 1; + } + Trace::Replace => { + if pending_deletes != COUNT_SENTINEL { + append_instruction(&mut patch, 'D', pending_deletes); + pending_deletes = COUNT_SENTINEL; + } + if pending_skips != COUNT_SENTINEL { + append_instruction(&mut patch, '-', pending_skips); + pending_skips = COUNT_SENTINEL; + } + append_instruction(&mut patch, 'R', target[ti]); + si += 1; + ti += 1; + } + Trace::Match => { + if pending_deletes != COUNT_SENTINEL { + append_instruction(&mut patch, 'D', pending_deletes); + pending_deletes = COUNT_SENTINEL; + } + pending_skips = pending_skips.wrapping_add(1); + si += 1; + ti += 1; + } + } + } + + if pending_deletes != COUNT_SENTINEL { + append_instruction(&mut patch, 'D', pending_deletes); + } + + patch +} + +// Instruction encoding helpers. + +#[inline] +fn append_instruction(patch: &mut String, opcode: char, argument: u16) { + patch.push(opcode); + patch.push(char::from_u32(argument as u32).unwrap_or('\u{FFFD}')); +} diff --git a/python/src/lib.rs b/python/src/lib.rs new file mode 100644 index 0000000..5c53d80 --- /dev/null +++ b/python/src/lib.rs @@ -0,0 +1,346 @@ +// Copyright (C) 2026, Leo Galambos +// 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 copyright holder 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 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. + +mod builder; +mod dict; +mod encoder; +mod patch; +mod serial; +mod trie; + +use flate2::read::GzDecoder; +use pyo3::prelude::*; +use pyo3::pybacked::PyBackedStr; +use pyo3::types::{PyList, PyString}; +use std::collections::HashMap; +use std::fs; +use std::io::Read; +use std::sync::{Arc, Mutex}; +use trie::FrequencyTrie; + +/// Decompress a gzip byte image, or return the bytes unchanged when they are +/// not gzip-framed (so plain-text dictionaries also work). +fn decompress_or_raw(bytes: &[u8]) -> Vec { + if bytes.len() >= 2 && bytes[0] == 0x1F && bytes[1] == 0x8B { + let mut out = Vec::new(); + if GzDecoder::new(bytes).read_to_end(&mut out).is_ok() { + return out; + } + } + bytes.to_vec() +} + +/// Decode UTF-16 code units into a reused UTF-8 buffer (lossy on unpaired +/// surrogates, which never occur in valid patch output). +#[inline] +fn decode_utf16_into(units: &[u16], out: &mut String) { + out.clear(); + for r in char::decode_utf16(units.iter().copied()) { + out.push(r.unwrap_or('\u{FFFD}')); + } +} + +/// Runtime stemmer core: compiles a gzipped textual dictionary into a +/// patch-command trie (in Rust) and stems words against it. +#[pyclass(module = "radixor._radixor")] +struct StemmerCore { + trie: Arc, + // Optional result cache (like PyStemmer's): maps an input word to the + // already-built Python result object (a str, or None). A hit is a refcount + // bump — no re-stemming and no new string. Disabled when `cache_cap == 0`. + cache: Option>>>, + cache_cap: usize, +} + +impl StemmerCore { + fn stem_cached( + &self, + py: Python<'_>, + word: &str, + key_buf: &mut Vec, + u16_buf: &mut Vec, + u8_buf: &mut String, + ) -> Py { + let may_insert = if let Some(cache) = &self.cache { + let map = cache.lock().unwrap(); + if let Some(obj) = map.get(word) { + return obj.clone_ref(py); + } + map.len() < self.cache_cap + } else { + false + }; + + let computed: Py = match self.trie.stem_len_into(word, key_buf, u16_buf) { + Some(_) => { + decode_utf16_into(u16_buf, u8_buf); + PyString::new_bound(py, u8_buf).into_any().unbind() + } + None => py.None(), + }; + + // A full insertion-only cache cannot become writable again, so avoid + // a second lock and hash probe for later distinct words. + if may_insert { + let cache = self.cache.as_ref().expect("enabled cache"); + let mut map = cache.lock().unwrap(); + // Another thread may have populated this word while this thread + // was stemming it. Return the shared cached object when it did. + if let Some(obj) = map.get(word) { + return obj.clone_ref(py); + } + if map.len() < self.cache_cap { + map.insert(word.to_owned(), computed.clone_ref(py)); + } + } + computed + } + + fn stem_batch_impl<'py>( + &self, + py: Python<'py>, + words: &[PyBackedStr], + fallback_to_original: bool, + ) -> PyResult> { + let mut key_buf: Vec = Vec::new(); + let mut u16_buf: Vec = Vec::new(); + let mut u8_buf = String::new(); + let list = PyList::empty_bound(py); + + // Misses remain cached as None so calls through the compatibility API + // cannot change the existing stem/stem_batch missing-value contract. + for w in words { + let key: &str = w; + let obj = self.stem_cached(py, key, &mut key_buf, &mut u16_buf, &mut u8_buf); + if fallback_to_original && obj.bind(py).is_none() { + list.append(PyString::new_bound(py, key))?; + } else { + list.append(obj.bind(py))?; + } + } + Ok(list) + } +} + +#[pymethods] +impl StemmerCore { + /// Compile a model from a gzipped TSV source dictionary. + /// + /// * `path` — path to either a gzipped TSV source dictionary + /// (`stem\tvariant1\tvariant2...` per line) OR a compiled `.rxc` trie + /// (Java-interoperable v7 format). The format is auto-detected. + /// * `backward` — BACKWARD traversal (all languages except the + /// right-to-left fa/he/yi, which use FORWARD). Ignored for compiled input + /// (baked into the file). + /// * `store_original` — map each canonical stem to the no-op patch so the + /// stem itself is recognised. Ignored for compiled input. + #[new] + #[pyo3(signature = (path, backward=true, store_original=true, lowercase=true, cache_size=10_000))] + fn new( + path: &str, + backward: bool, + store_original: bool, + lowercase: bool, + cache_size: usize, + ) -> PyResult { + let raw = + fs::read(path).map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))?; + let decompressed = decompress_or_raw(&raw); + // Auto-detect: a compiled v7 trie starts with the stream magic; anything + // else is a textual TSV dictionary compiled here in Rust. + let trie = if serial::is_v7_stream(&decompressed) { + serial::read_stream(&decompressed) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))? + } else { + // Dictionary keys are always lowercased at build time (canonical + // form). `lowercase` controls whether lookups lowercase the input at + // runtime; set it False for already-lowercased input. + let text = String::from_utf8_lossy(&decompressed); + let entries = dict::parse_text(&text, true); + builder::build_trie_from_dict(&entries, backward, store_original, lowercase) + }; + let cache = if cache_size > 0 { + // Keep PyStemmer's default entry limit without charging every + // Stemmer instance for 10,000 buckets before its first lookup. + Some(Mutex::new(HashMap::new())) + } else { + None + }; + Ok(StemmerCore { + trie: Arc::new(trie), + cache, + cache_cap: cache_size, + }) + } + + fn stem(&self, py: Python<'_>, word: &str) -> Py { + self.stem_cached( + py, + word, + &mut Vec::new(), + &mut Vec::new(), + &mut String::new(), + ) + } + + /// PyStemmer-compatible scalar API. An unrecognized word is returned + /// unchanged instead of producing None. + #[pyo3(name = "stemWord")] + fn stem_word(&self, py: Python<'_>, word: &str) -> Py { + let obj = self.stem_cached( + py, + word, + &mut Vec::new(), + &mut Vec::new(), + &mut String::new(), + ); + if obj.bind(py).is_none() { + PyString::new_bound(py, word).into_any().unbind() + } else { + obj + } + } + + fn stem_batch<'py>( + &self, + py: Python<'py>, + words: Vec, + ) -> PyResult> { + self.stem_batch_impl(py, &words, false) + } + + /// PyStemmer-compatible batch API. Unrecognized words keep their position + /// in the result and are returned unchanged. + #[pyo3(name = "stemWords")] + fn stem_words<'py>( + &self, + py: Python<'py>, + words: Vec, + ) -> PyResult> { + self.stem_batch_impl(py, &words, true) + } + + fn stem_all(&self, word: &str) -> Vec { + self.trie.stem_all(word) + } + + /// Diagnostic: full batch round-trip (marshal input, allocate one String + /// per word, build the result list) with NO stemming. Measures the + /// irreducible Python<->Rust boundary + string-allocation floor. + fn _echo_batch(&self, words: Vec) -> Vec> { + words.iter().map(|w| Some(w.to_string())).collect() + } + + /// Diagnostic: pure input marshalling (sum of byte lengths), no stemming, + /// no output strings, no result list. + fn _len_batch(&self, words: Vec) -> u64 { + words.iter().map(|w| w.len() as u64).sum() + } + + /// Diagnostic: normalize + UTF-16 encode only. + fn _encode_batch(&self, words: Vec) -> u64 { + let mut key_buf = Vec::new(); + words + .iter() + .map(|w| self.trie.bench_encode(w, &mut key_buf) as u64) + .sum() + } + + /// Diagnostic: normalize + encode + trie walk (no patch apply). + fn _encodefind_batch(&self, words: Vec) -> u64 { + let mut key_buf = Vec::new(); + let mut acc = 0u64; + for w in &words { + if self.trie.bench_find(w, &mut key_buf) { + acc += 1; + } + } + acc + } + + /// Diagnostic: full stemming algorithm (normalize + UTF-16 encode + trie + /// walk + patch apply) but returning only the summed stem length — no + /// per-word output String and no Python result list. + fn _stem_lengths_batch(&self, words: Vec) -> u64 { + let mut key_buf = Vec::new(); + let mut out_buf = Vec::new(); + let mut acc = 0u64; + for w in &words { + if let Some(n) = self.trie.stem_len_into(w, &mut key_buf, &mut out_buf) { + acc += n as u64; + } + } + acc + } + + fn stem_all_batch(&self, words: Vec) -> Vec> { + words.iter().map(|w| self.trie.stem_all(w)).collect() + } +} + +/// Compile a gzipped/plain TSV source dictionary into a Java-interoperable +/// compiled trie file (v7 format), so it can be loaded instantly later. +/// +/// * `source_path` — path to a `stemmer.gz` (or plain TSV) source dictionary. +/// * `out_path` — destination compiled file (conventionally `*.rxc`). +/// * `backward` / `store_original` / `lowercase` — same meaning as the +/// `Stemmer` constructor; baked into the compiled file. +#[pyfunction] +#[pyo3(signature = (source_path, out_path, backward=true, store_original=true, lowercase=true))] +fn compile( + source_path: &str, + out_path: &str, + backward: bool, + store_original: bool, + lowercase: bool, +) -> PyResult<()> { + let raw = + fs::read(source_path).map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))?; + let decompressed = decompress_or_raw(&raw); + if serial::is_v7_stream(&decompressed) { + return Err(pyo3::exceptions::PyValueError::new_err( + "source is already a compiled trie", + )); + } + let text = String::from_utf8_lossy(&decompressed); + let entries = dict::parse_text(&text, true); + let frozen = builder::build_frozen(&entries, backward, store_original); + let metadata = builder::metadata_for(backward, lowercase); + let bytes = serial::write_v7(&frozen, &metadata) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; + fs::write(out_path, bytes).map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))?; + Ok(()) +} + +#[pymodule] +fn _radixor(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_function(wrap_pyfunction!(compile, m)?)?; + Ok(()) +} diff --git a/python/src/patch.rs b/python/src/patch.rs new file mode 100644 index 0000000..5f6176e --- /dev/null +++ b/python/src/patch.rs @@ -0,0 +1,494 @@ +// Copyright (C) 2026, Leo Galambos +// 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 copyright holder 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 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. + +#[derive(Debug, Clone)] +pub enum PatchCommand { + Preserve, + DeleteSuffix(usize), + DeletePrefix(usize), + AppendChar(u16), + PrependChar(u16), + ReplaceLastChar(u16), + ReplaceFirstChar(u16), + BackwardCompound { + opcodes: Vec, + operands: Vec, + length_delta: i32, + min_len: usize, + }, + ForwardCompound { + opcodes: Vec, + operands: Vec, + length_delta: i32, + min_len: usize, + }, +} + +const SKIP: u8 = b'-'; +const DELETE: u8 = b'D'; +const INSERT: u8 = b'I'; +const REPLACE: u8 = b'R'; +const NOOP: u8 = b'N'; + +fn decode_count(arg: u16) -> Option { + if arg < b'a' as u16 { + return None; + } + Some((arg - b'a' as u16) as usize + 1) +} + +fn compile_operand(opcode: u8, arg: u16) -> Option { + match opcode { + SKIP | DELETE => { + let count = decode_count(arg)?; + if count < 1 { + None + } else { + Some(count as u32) + } + } + INSERT | REPLACE => Some(arg as u32), + NOOP => { + if arg == b'a' as u16 { + None + } else { + panic!("Invalid NOOP arg") + } + } + _ => panic!("Unknown opcode: {}", opcode as char), + } +} + +fn length_delta(opcodes: &[u8], operands: &[u32]) -> i32 { + let mut delta: i32 = 0; + for (i, &op) in opcodes.iter().enumerate() { + match op { + DELETE => delta -= operands[i] as i32, + INSERT => delta += 1, + _ => {} + } + } + delta +} + +fn backward_min_len(opcodes: &[u8], operands: &[u32]) -> usize { + let mut min_len: usize = 0; + let mut consumed_from_end: usize = 0; + for (i, &op) in opcodes.iter().enumerate() { + let operand = operands[i] as usize; + match op { + SKIP => consumed_from_end += operand, + DELETE => { + min_len = min_len.max(consumed_from_end + operand); + consumed_from_end += operand; + } + INSERT => { + min_len = min_len.max(consumed_from_end); + } + REPLACE => { + min_len = min_len.max(consumed_from_end + 1); + consumed_from_end += 1; + } + _ => {} + } + } + min_len +} + +fn forward_min_len(opcodes: &[u8], operands: &[u32]) -> usize { + let mut min_len: usize = 0; + let mut position: i32 = 0; + let mut len_delta: i32 = 0; + for (i, &op) in opcodes.iter().enumerate() { + let operand = operands[i] as i32; + match op { + SKIP => position += operand, + DELETE => { + let needed = (position + operand - len_delta).max(0) as usize; + min_len = min_len.max(needed); + len_delta -= operand; + } + INSERT => { + let needed = (position - len_delta).max(0) as usize; + min_len = min_len.max(needed); + len_delta += 1; + position += 1; + } + REPLACE => { + let needed = (position + 1 - len_delta).max(0) as usize; + min_len = min_len.max(needed); + position += 1; + } + _ => {} + } + } + min_len +} + +impl PatchCommand { + pub fn parse(patch: &str, backward: bool) -> Self { + let chars: Vec = patch.encode_utf16().collect(); + let len = chars.len(); + if len == 0 || len & 1 != 0 { + return PatchCommand::Preserve; + } + + if len == 2 { + let opcode = chars[0] as u8; + let arg = chars[1]; + return Self::compile_single(opcode, arg, backward); + } + + let op_count = len / 2; + let mut opcodes = Vec::with_capacity(op_count); + let mut operands = Vec::with_capacity(op_count); + + for i in 0..op_count { + let opcode = chars[i * 2] as u8; + let arg = chars[i * 2 + 1]; + match compile_operand(opcode, arg) { + None => return PatchCommand::Preserve, + Some(operand) => { + opcodes.push(opcode); + operands.push(operand); + } + } + } + + let ld = length_delta(&opcodes, &operands); + if backward { + let min_len = backward_min_len(&opcodes, &operands); + PatchCommand::BackwardCompound { + opcodes, + operands, + length_delta: ld, + min_len, + } + } else { + let min_len = forward_min_len(&opcodes, &operands); + PatchCommand::ForwardCompound { + opcodes, + operands, + length_delta: ld, + min_len, + } + } + } + + fn compile_single(opcode: u8, arg: u16, backward: bool) -> Self { + match opcode { + DELETE => { + let count = match decode_count(arg) { + Some(c) if c >= 1 => c, + _ => return PatchCommand::Preserve, + }; + if backward { + PatchCommand::DeleteSuffix(count) + } else { + PatchCommand::DeletePrefix(count) + } + } + INSERT => { + if backward { + PatchCommand::AppendChar(arg) + } else { + PatchCommand::PrependChar(arg) + } + } + REPLACE => { + if backward { + PatchCommand::ReplaceLastChar(arg) + } else { + PatchCommand::ReplaceFirstChar(arg) + } + } + SKIP | NOOP => PatchCommand::Preserve, + _ => panic!("Unknown opcode: {}", opcode as char), + } + } + + fn computed_length(&self, src_len: usize) -> usize { + let (ld, min_len) = match self { + PatchCommand::Preserve => (0i32, 0usize), + PatchCommand::DeleteSuffix(n) | PatchCommand::DeletePrefix(n) => (-(*n as i32), 0), + PatchCommand::AppendChar(_) | PatchCommand::PrependChar(_) => (1, 0), + PatchCommand::ReplaceLastChar(_) | PatchCommand::ReplaceFirstChar(_) => (0, 1), + PatchCommand::BackwardCompound { + length_delta, + min_len, + .. + } => (*length_delta, *min_len), + PatchCommand::ForwardCompound { + length_delta, + min_len, + .. + } => (*length_delta, *min_len), + }; + if src_len < min_len { + return src_len; + } + let applied = src_len as i32 + ld; + if applied < 1 { + src_len + } else { + applied as usize + } + } + + pub fn apply(&self, source: &[u16]) -> Vec { + let mut out = Vec::new(); + self.apply_into(source, &mut out); + out + } + + /// Apply the patch into a caller-owned buffer, avoiding a per-call + /// allocation on the hot path. `out` is cleared and overwritten. + pub fn apply_into(&self, source: &[u16], out: &mut Vec) { + let src_len = source.len(); + let out_len = self.computed_length(src_len); + out.clear(); + match self { + PatchCommand::Preserve => out.extend_from_slice(source), + PatchCommand::DeleteSuffix(_) => { + if out_len < src_len { + out.extend_from_slice(&source[..out_len]); + } else { + out.extend_from_slice(source); + } + } + PatchCommand::DeletePrefix(n) => { + if out_len < src_len { + out.extend_from_slice(&source[*n..]); + } else { + out.extend_from_slice(source); + } + } + PatchCommand::AppendChar(ch) => { + out.extend_from_slice(source); + out.push(*ch); + } + PatchCommand::PrependChar(ch) => { + out.push(*ch); + out.extend_from_slice(source); + } + PatchCommand::ReplaceLastChar(ch) => { + out.extend_from_slice(source); + if src_len != 0 { + let l = out.len(); + out[l - 1] = *ch; + } + } + PatchCommand::ReplaceFirstChar(ch) => { + out.extend_from_slice(source); + if src_len != 0 { + out[0] = *ch; + } + } + PatchCommand::BackwardCompound { + opcodes, operands, .. + } => { + if src_len < self.min_len_for_compound() || out_len < 1 { + out.extend_from_slice(source); + } else { + apply_backward_into(opcodes, operands, source, out_len, out); + } + } + PatchCommand::ForwardCompound { + opcodes, operands, .. + } => { + if src_len < self.min_len_for_compound() || out_len < 1 { + out.extend_from_slice(source); + } else { + apply_forward_into(opcodes, operands, source, out_len, out); + } + } + } + } + + fn min_len_for_compound(&self) -> usize { + match self { + PatchCommand::BackwardCompound { min_len, .. } => *min_len, + PatchCommand::ForwardCompound { min_len, .. } => *min_len, + _ => 0, + } + } +} + +fn fill_with_source(out: &mut Vec, source: &[u16]) { + out.clear(); + out.extend_from_slice(source); +} + +fn apply_backward_into( + opcodes: &[u8], + operands: &[u32], + source: &[u16], + produced_len: usize, + out: &mut Vec, +) { + let src_len = source.len(); + out.clear(); + out.resize(produced_len, 0); + let mut current_len = src_len as i32; + let mut position = src_len as i32 - 1; + let mut src_end = src_len as i32; + let mut out_end = produced_len as i32; + + for (i, &op) in opcodes.iter().enumerate() { + let operand = operands[i] as i32; + match op { + SKIP => { + let skip = operand.min(src_end); + src_end -= skip; + out_end -= skip; + if out_end < 0 { + return fill_with_source(out, source); + } + let s = src_end as usize; + let o = out_end as usize; + out[o..o + skip as usize].copy_from_slice(&source[s..s + skip as usize]); + position = position - operand + 1; + } + DELETE => { + let del_end_excl = position + 1; + position -= operand - 1; + if position < 0 || position > current_len || position > del_end_excl { + return fill_with_source(out, source); + } + let deleted = (del_end_excl.min(current_len) - position) as i32; + if src_end < deleted { + return fill_with_source(out, source); + } + src_end -= deleted; + current_len -= deleted; + } + INSERT => { + if position < -1 || position >= current_len || out_end <= 0 { + return fill_with_source(out, source); + } + out_end -= 1; + out[out_end as usize] = operand as u16; + current_len += 1; + position += 1; + } + REPLACE => { + if position < 0 || position >= current_len || src_end <= 0 || out_end <= 0 { + return fill_with_source(out, source); + } + src_end -= 1; + out_end -= 1; + out[out_end as usize] = operand as u16; + } + _ => return fill_with_source(out, source), + } + position -= 1; + } + + if src_end != out_end { + return fill_with_source(out, source); + } + let prefix_len = src_end as usize; + out[..prefix_len].copy_from_slice(&source[..prefix_len]); +} + +fn apply_forward_into( + opcodes: &[u8], + operands: &[u32], + source: &[u16], + produced_len: usize, + out: &mut Vec, +) { + let src_len = source.len(); + out.clear(); + out.resize(produced_len, 0); + let mut current_len = src_len as i32; + let mut position: i32 = 0; + let mut src_idx: i32 = 0; + let mut out_idx: i32 = 0; + + for (i, &op) in opcodes.iter().enumerate() { + let operand = operands[i] as i32; + match op { + SKIP => { + let skip = operand.min(src_len as i32 - src_idx); + let s = src_idx as usize; + let o = out_idx as usize; + out[o..o + skip as usize].copy_from_slice(&source[s..s + skip as usize]); + src_idx += skip; + out_idx += skip; + position = position + operand - 1; + } + DELETE => { + if position < 0 || position > current_len { + return fill_with_source(out, source); + } + let del_len = operand.min(current_len - position); + if src_idx + del_len > src_len as i32 { + return fill_with_source(out, source); + } + src_idx += del_len; + current_len -= del_len; + position -= 1; + } + INSERT => { + if position < 0 || position > current_len || out_idx >= produced_len as i32 { + return fill_with_source(out, source); + } + out[out_idx as usize] = operand as u16; + out_idx += 1; + current_len += 1; + } + REPLACE => { + if position < 0 + || position >= current_len + || src_idx >= src_len as i32 + || out_idx >= produced_len as i32 + { + return fill_with_source(out, source); + } + src_idx += 1; + out[out_idx as usize] = operand as u16; + out_idx += 1; + } + _ => return fill_with_source(out, source), + } + position += 1; + } + + let remaining = (src_len as i32 - src_idx) as usize; + if remaining > produced_len - out_idx as usize { + return fill_with_source(out, source); + } + let o = out_idx as usize; + let s = src_idx as usize; + out[o..o + remaining].copy_from_slice(&source[s..s + remaining]); + if out_idx as usize + remaining != produced_len { + fill_with_source(out, source); + } +} diff --git a/python/src/serial.rs b/python/src/serial.rs new file mode 100644 index 0000000..61ebba9 --- /dev/null +++ b/python/src/serial.rs @@ -0,0 +1,450 @@ +// Copyright (C) 2026, Leo Galambos +// 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 copyright holder 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 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. + +// Java-interoperable compiled-trie binary I/O ("v7" stream), matching +// org.egothor.stemmer.StemmerPatchTrieBinaryIO / FrequencyTrie.writeTo/readFrom. +// +// File layout = gzip( big-endian Java DataOutputStream stream ): +// i32 STREAM_MAGIC=0x45475452 ; i32 STREAM_VERSION=7 +// i32 nodeCount ; i32 rootId(=0) +// writeUTF(metadata.toTextBlock()) // Java modified UTF-8 +// i32 valueCount ; valueCount x writeUTF(patch) // value dictionary +// per node id 0..nodeCount-1: +// u8 acceptsRemainingInput +// i32 edgeCount ; edgeCount x { u16 edgeLabel ; i32 childId } +// i32 valueCount ; valueCount x { i32 valueId ; i32 count } +// +// The outer gzip framing (headers/mtime) is not byte-identical across Java and +// Rust, but the INNER stream is, and both directions gunzip+parse each other. + +use std::collections::HashMap; +use std::io::{self, Read, Write}; +use std::sync::Arc; + +use flate2::read::GzDecoder; +use flate2::write::GzEncoder; +use flate2::Compression; + +use crate::builder::{FrozenTrie, MAX_DENSE_SPAN}; +use crate::patch::PatchCommand; +use crate::trie::{CaseMode, DiacriticMode, FrequencyTrie, TraversalDirection, TrieMetadata}; + +const STREAM_MAGIC: i32 = 0x4547_5452; +const STREAM_VERSION: i32 = 7; + +// Big-endian writer helpers matching Java DataOutputStream. + +fn put_i32(out: &mut Vec, v: i32) { + out.extend_from_slice(&v.to_be_bytes()); +} + +fn put_u16(out: &mut Vec, v: u16) { + out.extend_from_slice(&v.to_be_bytes()); +} + +/// Java DataOutputStream.writeUTF: u16 big-endian byte length + modified UTF-8. +fn put_java_utf(out: &mut Vec, s: &str) -> io::Result<()> { + let mut bytes: Vec = Vec::with_capacity(s.len()); + for u in s.encode_utf16() { + if (0x0001..=0x007F).contains(&u) { + bytes.push(u as u8); + } else if u == 0 || (0x0080..=0x07FF).contains(&u) { + bytes.push(0xC0 | ((u >> 6) as u8 & 0x1F)); + bytes.push(0x80 | (u as u8 & 0x3F)); + } else { + bytes.push(0xE0 | ((u >> 12) as u8 & 0x0F)); + bytes.push(0x80 | ((u >> 6) as u8 & 0x3F)); + bytes.push(0x80 | (u as u8 & 0x3F)); + } + } + if bytes.len() > 0xFFFF { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "string too long for Java modified UTF-8", + )); + } + put_u16(out, bytes.len() as u16); + out.extend_from_slice(&bytes); + Ok(()) +} + +// Metadata text block, byte-identical to TrieMetadata.toTextBlock. + +fn text_block(meta: &TrieMetadata) -> String { + let forward = matches!(meta.traversal, TraversalDirection::Forward); + let case = match meta.case_mode { + CaseMode::LowercaseWithLocaleRoot => "LOWERCASE_WITH_LOCALE_ROOT", + CaseMode::AsIs => "AS_IS", + }; + let diac = match meta.diacritic_mode { + DiacriticMode::AsIs => "AS_IS", + DiacriticMode::Remove => "REMOVE", + }; + let mut s = String::with_capacity(256); + s.push_str("radixor.metadata.v1\n"); + s.push_str("formatVersion=7\n"); + s.push_str(if forward { + "traversalDirection=FORWARD\n" + } else { + "traversalDirection=BACKWARD\n" + }); + s.push_str(if forward { + "rightToLeft=true\n" + } else { + "rightToLeft=false\n" + }); + s.push_str("reductionMode=MERGE_SUBTREES_WITH_EQUIVALENT_DOMINANT_GET_RESULTS\n"); + s.push_str("dominantWinnerMinPercent=75\n"); + s.push_str("dominantWinnerOverSecondRatio=3\n"); + s.push_str("contractUniformSubtrees=true\n"); + s.push_str(&format!("diacriticProcessingMode={}\n", diac)); + s.push_str(&format!("caseProcessingMode={}\n", case)); + s +} + +/// Serialize the frozen trie to the inner (uncompressed) v7 stream. +fn write_stream(frozen: &FrozenTrie, meta: &TrieMetadata) -> io::Result> { + let node_count = frozen.accepts.len(); + let mut out = Vec::with_capacity(1024 + frozen.edge_labels.len() * 6); + + put_i32(&mut out, STREAM_MAGIC); + put_i32(&mut out, STREAM_VERSION); + put_i32(&mut out, node_count as i32); + put_i32(&mut out, 0); // rootId + put_java_utf(&mut out, &text_block(meta))?; + + // Value dictionary: distinct patch strings in first-occurrence order across + // nodes(id) x values(local) — frozen.value_strings is already in that order. + let mut value_id: HashMap<&str, i32> = HashMap::new(); + let mut distinct: Vec<&str> = Vec::new(); + for s in &frozen.value_strings { + if !value_id.contains_key(s.as_str()) { + value_id.insert(s.as_str(), distinct.len() as i32); + distinct.push(s.as_str()); + } + } + put_i32(&mut out, distinct.len() as i32); + for s in &distinct { + put_java_utf(&mut out, s)?; + } + + for node in 0..node_count { + out.push(if frozen.accepts[node] { 1 } else { 0 }); + + let elo = frozen.edge_start[node] as usize; + let ehi = frozen.edge_start[node + 1] as usize; + put_i32(&mut out, (ehi - elo) as i32); + for k in elo..ehi { + put_u16(&mut out, frozen.edge_labels[k]); + put_i32(&mut out, frozen.edge_targets[k] as i32); + } + + let vlo = frozen.value_start[node] as usize; + let vhi = frozen.value_start[node + 1] as usize; + put_i32(&mut out, (vhi - vlo) as i32); + for k in vlo..vhi { + let id = value_id[frozen.value_strings[k].as_str()]; + put_i32(&mut out, id); + put_i32(&mut out, frozen.value_counts[k]); + } + } + + Ok(out) +} + +/// Serialize the frozen trie to a gzip-compressed v7 file image. +pub(crate) fn write_v7(frozen: &FrozenTrie, meta: &TrieMetadata) -> io::Result> { + let stream = write_stream(frozen, meta)?; + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(&stream)?; + encoder.finish() +} + +// Compiled-stream reader. + +struct Reader<'a> { + data: &'a [u8], + pos: usize, +} + +impl<'a> Reader<'a> { + fn new(data: &'a [u8]) -> Self { + Reader { data, pos: 0 } + } + + fn take(&mut self, n: usize) -> io::Result<&'a [u8]> { + if self.pos + n > self.data.len() { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "unexpected end of trie stream", + )); + } + let slice = &self.data[self.pos..self.pos + n]; + self.pos += n; + Ok(slice) + } + + fn i32(&mut self) -> io::Result { + let b = self.take(4)?; + Ok(i32::from_be_bytes([b[0], b[1], b[2], b[3]])) + } + + fn u16(&mut self) -> io::Result { + let b = self.take(2)?; + Ok(u16::from_be_bytes([b[0], b[1]])) + } + + fn u8(&mut self) -> io::Result { + Ok(self.take(1)?[0]) + } + + fn java_utf(&mut self) -> io::Result { + let len = self.u16()? as usize; + let bytes = self.take(len)?; + decode_java_utf(bytes) + } +} + +fn decode_java_utf(bytes: &[u8]) -> io::Result { + let mut units: Vec = Vec::with_capacity(bytes.len()); + let mut i = 0; + while i < bytes.len() { + let b = bytes[i]; + if b & 0x80 == 0 { + units.push(b as u16); + i += 1; + } else if b & 0xE0 == 0xC0 { + if i + 1 >= bytes.len() { + return Err(malformed()); + } + let b1 = bytes[i + 1]; + units.push((((b as u16 & 0x1F) << 6) | (b1 as u16 & 0x3F)) as u16); + i += 2; + } else if b & 0xF0 == 0xE0 { + if i + 2 >= bytes.len() { + return Err(malformed()); + } + let b1 = bytes[i + 1]; + let b2 = bytes[i + 2]; + units.push(((b as u16 & 0x0F) << 12) | ((b1 as u16 & 0x3F) << 6) | (b2 as u16 & 0x3F)); + i += 3; + } else { + return Err(malformed()); + } + } + Ok(String::from_utf16_lossy(&units)) +} + +fn malformed() -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, "malformed modified UTF-8") +} + +fn parse_metadata(text: &str) -> TrieMetadata { + let mut traversal = TraversalDirection::Backward; + let mut case_mode = CaseMode::LowercaseWithLocaleRoot; + let mut diacritic_mode = DiacriticMode::AsIs; + for line in text.lines() { + if let Some((key, value)) = line.split_once('=') { + match key { + "traversalDirection" => { + traversal = if value == "FORWARD" { + TraversalDirection::Forward + } else { + TraversalDirection::Backward + }; + } + "caseProcessingMode" => { + case_mode = if value == "AS_IS" { + CaseMode::AsIs + } else { + CaseMode::LowercaseWithLocaleRoot + }; + } + "diacriticProcessingMode" => { + diacritic_mode = if value == "REMOVE" { + DiacriticMode::Remove + } else { + DiacriticMode::AsIs + }; + } + _ => {} + } + } + } + TrieMetadata { + traversal, + case_mode, + diacritic_mode, + } +} + +/// Rebuild dense direct-index tables from the CSR edges (same policy as freeze). +fn build_dense( + edge_start: &[u32], + edge_labels: &[u16], + edge_targets: &[u32], +) -> (Vec, Vec, Vec) { + let node_count = edge_start.len() - 1; + let mut dense_start: Vec = Vec::with_capacity(node_count + 1); + let mut dense_base: Vec = Vec::with_capacity(node_count); + let mut dense_targets: Vec = Vec::new(); + dense_start.push(0); + for node in 0..node_count { + let lo = edge_start[node] as usize; + let hi = edge_start[node + 1] as usize; + let count = hi - lo; + let mut dense = false; + if count >= 2 { + let first = edge_labels[lo] as usize; + let last = edge_labels[hi - 1] as usize; + let span = last - first + 1; + if span <= MAX_DENSE_SPAN { + let base = edge_labels[lo]; + let seg = dense_targets.len(); + dense_targets.resize(seg + span, 0); + for k in lo..hi { + dense_targets[seg + (edge_labels[k] - base) as usize] = edge_targets[k] + 1; + } + dense_base.push(base); + dense_start.push(dense_targets.len() as u32); + dense = true; + } + } + if !dense { + dense_base.push(0); + dense_start.push(dense_targets.len() as u32); + } + } + (dense_start, dense_base, dense_targets) +} + +/// Read a gzip-compressed Java v7 compiled-trie image into a runtime trie. +#[allow(dead_code)] // convenience wrapper; lib.rs decompresses then calls read_stream +pub(crate) fn read_v7(gz_bytes: &[u8]) -> io::Result { + let mut data = Vec::new(); + GzDecoder::new(gz_bytes).read_to_end(&mut data)?; + read_stream(&data) +} + +/// Whether `decompressed` (an already-gunzipped byte stream) is a v7 trie image. +pub(crate) fn is_v7_stream(decompressed: &[u8]) -> bool { + decompressed.len() >= 4 + && i32::from_be_bytes([ + decompressed[0], + decompressed[1], + decompressed[2], + decompressed[3], + ]) == STREAM_MAGIC +} + +/// Parse the inner (uncompressed) v7 stream into a runtime trie. +pub(crate) fn read_stream(data: &[u8]) -> io::Result { + let mut r = Reader::new(data); + if r.i32()? != STREAM_MAGIC { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "bad trie stream magic", + )); + } + let version = r.i32()?; + if version != STREAM_VERSION { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("unsupported trie stream version {version} (expected {STREAM_VERSION})"), + )); + } + let node_count = r.i32()? as usize; + let root_id = r.i32()?; + if root_id != 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "unsupported non-zero root node id", + )); + } + let metadata = parse_metadata(&r.java_utf()?); + let backward = matches!(metadata.traversal, TraversalDirection::Backward); + + let value_table_len = r.i32()? as usize; + let mut value_table: Vec> = Vec::with_capacity(value_table_len); + for _ in 0..value_table_len { + let patch = r.java_utf()?; + value_table.push(Arc::new(PatchCommand::parse(&patch, backward))); + } + + let mut edge_start: Vec = Vec::with_capacity(node_count + 1); + let mut edge_labels: Vec = Vec::new(); + let mut edge_targets: Vec = Vec::new(); + let mut accepts: Vec = Vec::with_capacity(node_count); + let mut value_start: Vec = Vec::with_capacity(node_count + 1); + let mut values: Vec> = Vec::new(); + edge_start.push(0); + value_start.push(0); + + for _ in 0..node_count { + accepts.push(r.u8()? != 0); + let edge_count = r.i32()? as usize; + for _ in 0..edge_count { + let label = r.u16()?; + let child = r.i32()? as u32; + edge_labels.push(label); + edge_targets.push(child); + } + edge_start.push(edge_labels.len() as u32); + + let value_count = r.i32()? as usize; + for _ in 0..value_count { + let value_id = r.i32()? as usize; + let _count = r.i32()?; // frequency: not used at runtime + if value_id >= value_table.len() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "value id out of range", + )); + } + values.push(Arc::clone(&value_table[value_id])); + } + value_start.push(values.len() as u32); + } + + let (dense_start, dense_base, dense_targets) = + build_dense(&edge_start, &edge_labels, &edge_targets); + + Ok(FrequencyTrie::new( + edge_start, + edge_labels, + edge_targets, + accepts, + value_start, + values, + dense_start, + dense_base, + dense_targets, + metadata, + )) +} diff --git a/python/src/trie.rs b/python/src/trie.rs new file mode 100644 index 0000000..19cd686 --- /dev/null +++ b/python/src/trie.rs @@ -0,0 +1,294 @@ +// Copyright (C) 2026, Leo Galambos +// 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 copyright holder 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 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. + +use crate::patch::PatchCommand; +use std::borrow::Cow; +use std::sync::Arc; +use unicode_general_category::{get_general_category, GeneralCategory}; +use unicode_normalization::UnicodeNormalization; + +#[derive(Debug, Clone)] +pub enum TraversalDirection { + Backward, + Forward, +} + +#[derive(Debug, Clone)] +pub enum CaseMode { + LowercaseWithLocaleRoot, + AsIs, +} + +#[derive(Debug, Clone)] +pub enum DiacriticMode { + AsIs, + Remove, +} + +#[derive(Debug, Clone)] +pub struct TrieMetadata { + pub traversal: TraversalDirection, + pub case_mode: CaseMode, + pub diacritic_mode: DiacriticMode, +} + +/// Compiled patch-command trie in a flat, cache-friendly CSR layout. +/// +/// Instead of a graph of heap-allocated, reference-counted nodes (which forces +/// a pointer chase and a likely cache miss at every character step), the whole +/// trie is stored as a handful of contiguous arrays indexed by node id: +/// +/// * `edge_start[i] .. edge_start[i+1]` slices `edge_labels` / `edge_targets` +/// for node `i` (labels sorted ascending, so child lookup is a binary search +/// over a contiguous, cache-hot slice — no pointer chasing, no atomics), +/// * `accepts[i]` marks a contracted accepting leaf, +/// * `value_start[i] .. value_start[i+1]` slices `values` (best value first). +/// +/// Node 0 is the root. Shared (deduplicated) subtrees simply reference the same +/// node id, so structural sharing from reduction is preserved without `Arc`. +pub struct FrequencyTrie { + edge_start: Vec, + edge_labels: Vec, + edge_targets: Vec, + accepts: Vec, + value_start: Vec, + values: Vec>, + // Adaptive child lookup (mirrors the Java CompiledNode fanout strategy): + // high-fanout nodes whose child labels span a small contiguous range get a + // dense direct-index table (O(1) child access); sparse nodes fall back to + // binary search over `edge_labels`. A node `i` is dense iff + // `dense_start[i+1] > dense_start[i]`; then `dense_targets[dense_start[i] + + // (label - dense_base[i])]` holds `child_id + 1` (0 = no such edge). + dense_start: Vec, + dense_base: Vec, + dense_targets: Vec, + pub metadata: TrieMetadata, +} + +impl FrequencyTrie { + #[allow(clippy::too_many_arguments)] + pub fn new( + edge_start: Vec, + edge_labels: Vec, + edge_targets: Vec, + accepts: Vec, + value_start: Vec, + values: Vec>, + dense_start: Vec, + dense_base: Vec, + dense_targets: Vec, + metadata: TrieMetadata, + ) -> Self { + FrequencyTrie { + edge_start, + edge_labels, + edge_targets, + accepts, + value_start, + values, + dense_start, + dense_base, + dense_targets, + metadata, + } + } + + /// Normalize a lookup key (used by the rare diacritic-removal path and by + /// `stem_all`). Borrows the input when no transformation is needed. + fn normalize_key<'a>(&self, word: &'a str) -> Cow<'a, str> { + let lowered: Cow<'a, str> = + if matches!(self.metadata.case_mode, CaseMode::LowercaseWithLocaleRoot) + && word.chars().any(|c| c.is_uppercase()) + { + Cow::Owned(word.to_lowercase()) + } else { + Cow::Borrowed(word) + }; + if matches!(self.metadata.diacritic_mode, DiacriticMode::Remove) { + Cow::Owned(strip_diacritics(&lowered)) + } else { + lowered + } + } + + /// Encode the normalized lookup key into `key_buf` in a single pass over the + /// input: lowercasing (when configured) is folded into the UTF-16 encoding + /// so the UTF-8 input is decoded only once and no intermediate `String` is + /// allocated. The diacritic-removal path (unused by the bundled models) + /// falls back to the general `normalize_key`. + #[inline] + fn encode_key(&self, word: &str, key_buf: &mut Vec) { + key_buf.clear(); + if matches!(self.metadata.diacritic_mode, DiacriticMode::Remove) { + let normalized = self.normalize_key(word); + key_buf.extend(normalized.encode_utf16()); + return; + } + if matches!(self.metadata.case_mode, CaseMode::LowercaseWithLocaleRoot) { + let mut unit = [0u16; 2]; + for c in word.chars() { + if c.is_ascii() { + // ASCII fast path: lowercasing requires a single branch. + key_buf.push(c.to_ascii_lowercase() as u16); + } else if c.is_lowercase() { + // Already lowercase (e.g. lowercase Cyrillic/Greek): encode + // directly and skip the costly Unicode special-casing. + key_buf.extend_from_slice(c.encode_utf16(&mut unit)); + } else { + for lc in c.to_lowercase() { + key_buf.extend_from_slice(lc.encode_utf16(&mut unit)); + } + } + } + } else { + key_buf.extend(word.encode_utf16()); + } + } + + /// Find the child of `node` on `label` via binary search over the node's + /// contiguous, ascending edge-label slice. Uses unchecked indexing on + /// provably in-range offsets to drop bounds checks from the hot loop. + #[inline] + fn child(&self, node: usize, label: u16) -> Option { + // Dense high-fanout node: O(1) direct index. + // SAFETY: node and node+1 index dense_start (len = num_nodes+1). + let ds = unsafe { *self.dense_start.get_unchecked(node) } as usize; + let de = unsafe { *self.dense_start.get_unchecked(node + 1) } as usize; + if de > ds { + let base = unsafe { *self.dense_base.get_unchecked(node) }; + let idx = label.wrapping_sub(base) as usize; + if idx < de - ds { + // SAFETY: ds + idx < de <= dense_targets.len(). + let t = unsafe { *self.dense_targets.get_unchecked(ds + idx) }; + if t != 0 { + return Some((t - 1) as usize); + } + } + return None; + } + // Sparse node: binary search over the contiguous ascending edge slice. + // SAFETY: node and node+1 index edge_start (len = num_nodes+1). + let lo = unsafe { *self.edge_start.get_unchecked(node) } as usize; + let hi = unsafe { *self.edge_start.get_unchecked(node + 1) } as usize; + // SAFETY: lo <= hi <= edge_labels.len() by construction. + let labels = unsafe { self.edge_labels.get_unchecked(lo..hi) }; + match labels.binary_search(&label) { + // SAFETY: lo+pos < hi <= edge_targets.len(). + Ok(pos) => Some(unsafe { *self.edge_targets.get_unchecked(lo + pos) } as usize), + Err(_) => None, + } + } + + /// Walk the trie for `key`, returning the accepting/terminal node id. + #[inline] + fn find_node(&self, key: &[u16]) -> Option { + let mut node = 0usize; + match self.metadata.traversal { + TraversalDirection::Backward => { + for &label in key.iter().rev() { + if unsafe { *self.accepts.get_unchecked(node) } { + return Some(node); + } + node = self.child(node, label)?; + } + } + TraversalDirection::Forward => { + for &label in key.iter() { + if unsafe { *self.accepts.get_unchecked(node) } { + return Some(node); + } + node = self.child(node, label)?; + } + } + } + Some(node) + } + + #[inline] + fn preferred_value(&self, node: usize) -> Option<&Arc> { + let start = self.value_start[node] as usize; + let end = self.value_start[node + 1] as usize; + if start == end { + None + } else { + Some(&self.values[start]) + } + } + + /// Stem into caller-owned scratch buffers and return the produced length + /// without allocating an output String. This also supports diagnostics + /// that isolate the algorithm from output-String allocation. + pub fn stem_len_into( + &self, + word: &str, + key_buf: &mut Vec, + out_buf: &mut Vec, + ) -> Option { + self.encode_key(word, key_buf); + let node = self.find_node(key_buf)?; + let patch = self.preferred_value(node)?; + patch.apply_into(key_buf, out_buf); + Some(out_buf.len()) + } + + /// Diagnostic: only normalize + UTF-16 encode the key. + pub fn bench_encode(&self, word: &str, key_buf: &mut Vec) -> usize { + self.encode_key(word, key_buf); + key_buf.len() + } + + /// Diagnostic: normalize + encode + trie walk (no patch apply). + pub fn bench_find(&self, word: &str, key_buf: &mut Vec) -> bool { + self.encode_key(word, key_buf); + self.find_node(key_buf).is_some() + } + + /// Return all stems in frequency order. + pub fn stem_all(&self, word: &str) -> Vec { + let mut key_u16: Vec = Vec::new(); + self.encode_key(word, &mut key_u16); + match self.find_node(&key_u16) { + None => Vec::new(), + Some(node) => { + let start = self.value_start[node] as usize; + let end = self.value_start[node + 1] as usize; + self.values[start..end] + .iter() + .map(|p| String::from_utf16_lossy(&p.apply(&key_u16))) + .collect() + } + } + } +} + +pub fn strip_diacritics(s: &str) -> String { + s.nfd() + .filter(|ch| !matches!(get_general_category(*ch), GeneralCategory::NonspacingMark)) + .collect() +} diff --git a/python/tests/conftest.py b/python/tests/conftest.py new file mode 100644 index 0000000..c59cae4 --- /dev/null +++ b/python/tests/conftest.py @@ -0,0 +1,44 @@ +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Make the generated standard resource package available to source tests.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +REPOSITORY = Path(__file__).resolve().parents[2] +GENERATED_MODELS_DISTRIBUTION = ( + REPOSITORY / "build" / "python" / "generated" / "models-standard" +) +if GENERATED_MODELS_DISTRIBUTION.is_dir(): + sys.path.insert(0, str(GENERATED_MODELS_DISTRIBUTION)) diff --git a/python/tests/test_distribution_verifier.py b/python/tests/test_distribution_verifier.py new file mode 100644 index 0000000..486a383 --- /dev/null +++ b/python/tests/test_distribution_verifier.py @@ -0,0 +1,93 @@ +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Regression tests for Python release-archive metadata validation.""" + +from __future__ import annotations + +import sys +from email.message import Message +from pathlib import Path + +import pytest + +SCRIPTS = Path(__file__).resolve().parents[1] / "scripts" +sys.path.insert(0, str(SCRIPTS)) + +from build_standard_distribution import ( # noqa: E402 + _ignore_build_artifacts, + _validate_generated_project, +) +from verify_distributions import _assert_main_dependency # noqa: E402 + + +def test_standard_model_source_tree_contains_no_generated_payload() -> None: + source = Path(__file__).resolve().parents[1] / "models-standard" + package = source / "radixor_models_standard" + + assert not (package / "manifest.json").exists() + assert not list((package / "models").glob("*.rxc")) + assert not list((package / "notices").glob("*/NOTICE-model-data.txt")) + + +def test_standard_model_build_rejects_source_skeleton() -> None: + source = Path(__file__).resolve().parents[1] / "models-standard" + + with pytest.raises(ValueError, match="manifest is missing"): + _validate_generated_project(source) + + +def test_standard_model_build_ignores_local_build_state() -> None: + names = [ + "build", + "dist", + "radixor_models_standard.egg-info", + "__pycache__", + "module.pyc", + "module.pyo", + "manifest.json", + ] + + assert _ignore_build_artifacts("unused", names) == set(names[:-1]) + + +def test_native_distribution_requires_compatible_standard_models() -> None: + metadata = Message() + metadata["Requires-Dist"] = "radixor-models-standard >=1.0, <2.0" + + _assert_main_dependency(metadata, "radixor-4.1.0.tar.gz") + + +def test_native_distribution_rejects_missing_standard_models() -> None: + metadata = Message() + + with pytest.raises(ValueError, match="standard-model dependency"): + _assert_main_dependency(metadata, "radixor-4.1.0.tar.gz") diff --git a/python/tests/test_radixor.py b/python/tests/test_radixor.py new file mode 100644 index 0000000..1657c86 --- /dev/null +++ b/python/tests/test_radixor.py @@ -0,0 +1,303 @@ +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Acceptance tests for the radixor Python extension. + +Run after building the extension: + + cd python/ + pip install maturin pytest + maturin develop --release + pytest -q + +The synthetic tests are self-contained and deterministic (no network, no +bundled data). +""" + +from __future__ import annotations + +import gzip +import inspect +from pathlib import Path + +from radixor import Stemmer + + +def _write_gz_dict(lines: list[str], tmp_path: Path) -> str: + """Write a gzipped TSV dictionary into pytest's temporary directory.""" + path = tmp_path / "dictionary.gz" + with gzip.open(path, "wt", encoding="utf-8", newline="\n") as gz: + gz.write("\n".join(lines)) + return str(path) + + +# Synthetic, deterministic pipeline tests. + + +def test_backward_suffix_stemming_roundtrip(tmp_path: Path): + # stemvariant... ; backward (suffix) stemming. + dict_lines = [ + "run\trunning\truns\tran", + "cat\tcats", + "walk\twalking\twalks\twalked", + ] + path = _write_gz_dict(dict_lines, tmp_path) + s = Stemmer(path=path, backward=True, store_original=True) + + # Every listed variant must stem back to its canonical stem. + assert s.stem("running") == "run" + assert s.stem("runs") == "run" + assert s.stem("ran") == "run" + assert s.stem("cats") == "cat" + assert s.stem("walking") == "walk" + assert s.stem("walked") == "walk" + + # store_original: the stem itself is recognised (no-op patch). + assert s.stem("run") == "run" + assert s.stem("cat") == "cat" + + +def test_store_original_controls_bare_stem_identity(tmp_path: Path): + # With a single rule and store_original=True, the stem maps to itself via + # the no-op patch, and the "cat" vs "cats" terminals carry different values + # so the trie does NOT collapse to a universal rule. + path = _write_gz_dict(["cat\tcats"], tmp_path) + s_keep = Stemmer(path=path, backward=True, store_original=True) + assert s_keep.stem("cats") == "cat" + assert s_keep.stem("cat") == "cat" + + # With store_original=False, only the single rule cats->cat is present. + # Radixor's always-on uniform-subtree contraction generalizes that lone + # rule to ALL input (this is the intended generalization behavior), so the + # bare stem is rewritten by the same delete-one-suffix rule. + s_drop = Stemmer(path=path, backward=True, store_original=False) + assert s_drop.stem("cats") == "cat" + assert s_drop.stem("cat") == "ca" # generalized: delete final char + assert s_drop.stem("dogs") == "dog" # rule applies to unseen input too + + +def test_unknown_word_returns_none(tmp_path: Path): + path = _write_gz_dict(["cat\tcats"], tmp_path) + s = Stemmer(path=path, backward=True) + assert s.stem("zzzunknown") is None + + +def test_pystemmer_scalar_api_returns_original_word_for_unknown(tmp_path: Path): + path = _write_gz_dict(["cat\tcats"], tmp_path) + s = Stemmer(path=path, backward=True) + + assert s.stemWord("cats") == "cat" + assert s.stemWord("ZzZUnknown") == "ZzZUnknown" + # The original Radixor API keeps its existing missing-value contract. + assert s.stem("ZzZUnknown") is None + + +def test_pystemmer_batch_api_returns_original_words_for_unknowns(tmp_path: Path): + path = _write_gz_dict(["run\trunning\truns", "cat\tcats"], tmp_path) + s = Stemmer(path=path, backward=True) + words = ["running", "Nope", "cats", "QzXqZx"] + + assert s.stemWords(words) == ["run", "Nope", "cat", "QzXqZx"] + assert s.stem_batch(words) == ["run", None, "cat", None] + + +def test_pystemmer_batch_cache_does_not_change_original_api(tmp_path: Path): + path = _write_gz_dict(["cat\tcats"], tmp_path) + s = Stemmer(path=path, backward=True, cache_size=100) + + assert s.stemWords(["Unknown", "cats", "Unknown"]) == ["Unknown", "cat", "Unknown"] + assert s.stem_batch(["Unknown", "cats", "Unknown"]) == [None, "cat", None] + + +def test_wrapper_forwards_default_cache_size_and_zero_opt_out(monkeypatch): + import radixor + + constructor_calls = [] + + class RecordingStemmerCore: + def __init__(self, *args): + constructor_calls.append(args) + + monkeypatch.setattr(radixor, "StemmerCore", RecordingStemmerCore) + + radixor.Stemmer(path="model.rxc") + radixor.Stemmer(path="model.rxc", cache_size=0) + + assert constructor_calls[0][-1] == 10_000 + assert constructor_calls[1][-1] == 0 + + +def test_native_constructor_default_cache_size(): + from radixor._radixor import StemmerCore + + assert inspect.signature(StemmerCore).parameters["cache_size"].default == 10_000 + + +def test_pystemmer_language_name_alias(): + import radixor as StemmerModule + + # Only the dependency/import line changes from PyStemmer's conventional + # ``import Stemmer; Stemmer.Stemmer("english")`` usage. + s = StemmerModule.Stemmer("english") + assert s.stemWord("running") == "run" + assert s.stemWords(["running", "unknown_word"]) == ["run", "unknown_word"] + + +def test_case_is_lowercased(tmp_path: Path): + path = _write_gz_dict(["cat\tcats"], tmp_path) + s = Stemmer(path=path, backward=True) + assert s.stem("CATS") == "cat" + assert s.stem("Cats") == "cat" + + +def test_batch_matches_scalar(tmp_path: Path): + path = _write_gz_dict(["run\trunning\truns", "cat\tcats"], tmp_path) + s = Stemmer(path=path, backward=True) + words = ["running", "runs", "cats", "nope", "run"] + assert s.stem_batch(words) == [s.stem(w) for w in words] + + +def test_compile_roundtrip_matches_from_text(tmp_path: Path): + import os + + import radixor + + dict_lines = [ + "run\trunning\truns\tran", + "cat\tcats", + "walk\twalking\twalks\twalked", + ] + src = _write_gz_dict(dict_lines, tmp_path) + out = src + ".rxc" + radixor.compile(src, out, backward=True) + + from_text = Stemmer(path=src, backward=True) + from_compiled = Stemmer(compiled=out) + + words = [ + "running", + "runs", + "ran", + "cats", + "walking", + "walked", + "run", + "cat", + "walk", + "unknownzzz", + ] + assert from_compiled.stem_batch(words) == from_text.stem_batch(words) + # The compiled artifact uses the gzip-wrapped EGTR v7 stream format. + import gzip + + with gzip.open(out, "rb") as fh: + assert fh.read(4) == b"EGTR" + os.unlink(out) + + +def test_cache_does_not_change_results(tmp_path: Path): + path = _write_gz_dict(["run\trunning\truns", "cat\tcats"], tmp_path) + plain = Stemmer(path=path, backward=True, cache_size=0) + cached = Stemmer(path=path, backward=True, cache_size=1000) + words = ["running", "runs", "cats", "nope", "run", "running", "cats"] + assert cached.stem_batch(words) == plain.stem_batch(words) + # Repeated lookups exercise the cache-hit path. + assert cached.stem_batch(["running"] * 5) == ["run"] * 5 + + +def test_default_cache_is_shared_across_scalar_and_batch_apis(tmp_path: Path): + root = "cacheable-root-value" + variant = "cacheable-root-values" + path = _write_gz_dict([f"{root}\t{variant}"], tmp_path) + cached = Stemmer(path=path, backward=True) + + first = cached.stem(variant) + assert first == root + assert cached.stem(variant) is first + assert cached.stemWord(variant) is first + assert cached.stem_batch([variant])[0] is first + assert cached.stemWords([variant])[0] is first + + disabled = Stemmer(path=path, backward=True, cache_size=0) + uncached_first = disabled.stem(variant) + uncached_second = disabled.stem(variant) + assert uncached_first == uncached_second == root + assert uncached_first is not uncached_second + + +def test_full_cache_keeps_existing_entries_without_admitting_new_ones(tmp_path: Path): + roots = ("first-cacheable-root", "second-cacheable-root") + variants = tuple(f"{root}-value" for root in roots) + path = _write_gz_dict( + [f"{root}\t{variant}" for root, variant in zip(roots, variants)], tmp_path + ) + stemmer = Stemmer(path=path, backward=True, cache_size=1) + + first = stemmer.stem(variants[0]) + assert stemmer.stem(variants[0]) is first + + uncached = stemmer.stem(variants[1]) + assert uncached == roots[1] + assert stemmer.stem(variants[1]) == uncached + assert stemmer.stem(variants[1]) is not uncached + assert stemmer.stem(variants[0]) is first + + +def test_lowercase_false_assumes_prelowered(tmp_path: Path): + path = _write_gz_dict(["cat\tcats"], tmp_path) + s = Stemmer(path=path, backward=True, lowercase=False) + assert s.stem("cats") == "cat" # already-lowercase input works + assert s.stem("CATS") is None # not lowercased -> no match + + +def test_forward_prefix_stemming(tmp_path: Path): + # Forward traversal handles prefix-oriented morphology (RTL languages). + path = _write_gz_dict(["kitab\talkitab\talkitabu"], tmp_path) + s = Stemmer(path=path, backward=False, store_original=True) + assert s.stem("alkitab") == "kitab" + assert s.stem("alkitabu") == "kitab" + assert s.stem("kitab") == "kitab" + + +def test_stem_all_returns_candidates(tmp_path: Path): + path = _write_gz_dict(["run\trunning", "runn\trunning"], tmp_path) + s = Stemmer(path=path, backward=True, store_original=True) + alls = s.stem_all("running") + # "running" maps to both "run" and "runn"; both must be reachable. + assert set(alls) >= {"run", "runn"} + + +# Installed standard-model smoke test. + + +def test_installed_english_compiled_model(): + s = Stemmer("en") + assert s.stem_batch(["running", "walked", "cats"]) == ["run", "walk", "cat"] diff --git a/python/tests/test_standard_models.py b/python/tests/test_standard_models.py new file mode 100644 index 0000000..370380f --- /dev/null +++ b/python/tests/test_standard_models.py @@ -0,0 +1,179 @@ +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +from __future__ import annotations + +import gzip +import hashlib +import json +from contextlib import nullcontext +from importlib import resources +from pathlib import Path + +import pytest + +import radixor + +EXPECTED_MODEL_IDS = { + "cs-cz-default", + "da-dk-default", + "de-de-default", + "es-es-default", + "fa-ir-default", + "fi-fi-default", + "fr-fr-default", + "he-il-default", + "hu-hu-default", + "it-it-default", + "nb-no-default", + "nl-nl-default", + "nn-no-default", + "pl-pl-unimorph", + "pt-pt-default", + "ru-ru-default", + "sv-se-default", + "uk-ua-default", + "us-uk-default", + "yi-default", +} + + +def _manifest() -> dict: + ref = resources.files("radixor_models_standard").joinpath("manifest.json") + return json.loads(ref.read_text(encoding="utf-8")) + + +def test_standard_manifest_model_set_versions_and_license(): + manifest = _manifest() + assert manifest["catalog_version"] == "2026.1" + assert manifest["distribution_version"] == "0.0.0" + assert manifest["format"] == {"compression": "gzip", "magic": "EGTR", "version": 7} + assert {model["id"] for model in manifest["models"]} == EXPECTED_MODEL_IDS + assert "pl-pl-polimorf" not in {model["id"] for model in manifest["models"]} + assert {model["version"] for model in manifest["models"]} == {"1.0.0"} + assert {model["provenance"]["license"] for model in manifest["models"]} == { + "CC-BY-SA-3.0" + } + + +def test_standard_artifact_checksums_and_v7_headers(): + root = resources.files("radixor_models_standard") + for model in _manifest()["models"]: + data = root.joinpath("models").joinpath(f"{model['id']}.rxc").read_bytes() + assert hashlib.sha256(data).hexdigest() == model["sha256"] + assert gzip.decompress(data)[:8] == b"EGTR\x00\x00\x00\x07" + + +def test_missing_standard_data_package_is_actionable(monkeypatch): + original_files = radixor.importlib.resources.files + + def missing(package: str): + if package == radixor._STANDARD_PACKAGE: + raise ModuleNotFoundError(package) + return original_files(package) + + monkeypatch.setattr(radixor.importlib.resources, "files", missing) + with pytest.raises( + ModuleNotFoundError, match="pip install radixor-models-standard" + ): + radixor.Stemmer("en") + + +def test_missing_standard_model_is_actionable(): + with pytest.raises(FileNotFoundError, match="not in the standard Radixor catalog"): + radixor.Stemmer("zz-zz-default") + + +@pytest.mark.parametrize( + ("mutation", "message"), + [ + ( + lambda manifest: manifest.update(catalog_version="2027.1"), + "incompatible or corrupt", + ), + ( + lambda manifest: manifest.update(format={"magic": "bad"}), + "incompatible or corrupt", + ), + ( + lambda manifest: manifest.update(distribution_version="2.0.0"), + "incompatible or corrupt", + ), + ], +) +def test_incompatible_manifest_is_actionable( + tmp_path: Path, monkeypatch, mutation, message +): + manifest = _manifest() + mutation(manifest) + (tmp_path / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8") + monkeypatch.setattr(radixor.importlib.resources, "files", lambda package: tmp_path) + with pytest.raises(RuntimeError, match=message): + radixor.Stemmer("en") + + +def test_checksum_failure_is_detected(tmp_path: Path, monkeypatch): + manifest = _manifest() + (tmp_path / "models").mkdir() + (tmp_path / "models" / "us-uk-default.rxc").write_bytes(b"not the model") + (tmp_path / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8") + monkeypatch.setattr(radixor.importlib.resources, "files", lambda package: tmp_path) + monkeypatch.setattr(radixor.importlib.resources, "as_file", nullcontext) + with pytest.raises(RuntimeError, match="SHA-256 validation"): + radixor.Stemmer("en") + + +@pytest.mark.parametrize( + ("stream_header", "message"), + [ + (b"NOPE\x00\x00\x00\x07", "EGTR format marker"), + (b"EGTR\x00\x00\x00\x08", "unsupported compiled format v8"), + ], +) +def test_format_marker_and_version_are_validated( + tmp_path: Path, monkeypatch, stream_header: bytes, message: str +): + manifest = _manifest() + model = next(item for item in manifest["models"] if item["id"] == "us-uk-default") + data = gzip.compress(stream_header, mtime=0) + model["sha256"] = hashlib.sha256(data).hexdigest() + (tmp_path / "models").mkdir() + (tmp_path / "models" / "us-uk-default.rxc").write_bytes(data) + (tmp_path / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8") + monkeypatch.setattr(radixor.importlib.resources, "files", lambda package: tmp_path) + monkeypatch.setattr(radixor.importlib.resources, "as_file", nullcontext) + with pytest.raises(RuntimeError, match=message): + radixor.Stemmer("en") + + +def test_invalid_model_id_is_rejected_before_resource_lookup(): + with pytest.raises(ValueError, match="Invalid Radixor model ID"): + radixor.Stemmer("../us-uk-default") diff --git a/src/jmh/java/org/egothor/stemmer/benchmark/SnowballLanguageCase.java b/src/jmh/java/org/egothor/stemmer/benchmark/SnowballLanguageCase.java index a015eda..eb382e2 100644 --- a/src/jmh/java/org/egothor/stemmer/benchmark/SnowballLanguageCase.java +++ b/src/jmh/java/org/egothor/stemmer/benchmark/SnowballLanguageCase.java @@ -31,6 +31,7 @@ package org.egothor.stemmer.benchmark; import org.egothor.stemmer.StemmerPatchTrieLoader; +import org.egothor.stemmer.benchmark.snowball.ext.czechStemmer; import org.egothor.stemmer.benchmark.snowball.ext.danishStemmer; import org.egothor.stemmer.benchmark.snowball.ext.dutchStemmer; import org.egothor.stemmer.benchmark.snowball.ext.finnishStemmer; @@ -39,6 +40,8 @@ import org.egothor.stemmer.benchmark.snowball.ext.germanStemmer; import org.egothor.stemmer.benchmark.snowball.ext.hungarianStemmer; import org.egothor.stemmer.benchmark.snowball.ext.italianStemmer; import org.egothor.stemmer.benchmark.snowball.ext.norwegianStemmer; +import org.egothor.stemmer.benchmark.snowball.ext.persianStemmer; +import org.egothor.stemmer.benchmark.snowball.ext.polishStemmer; import org.egothor.stemmer.benchmark.snowball.ext.portugueseStemmer; import org.egothor.stemmer.benchmark.snowball.ext.russianStemmer; import org.egothor.stemmer.benchmark.snowball.ext.spanishStemmer; @@ -50,6 +53,11 @@ import org.egothor.stemmer.benchmark.snowball.ext.yiddishStemmer; */ enum SnowballLanguageCase { + /** + * Czech Snowball stemming over the Radixor Czech dictionary. + */ + CZECH("Czech", StemmerPatchTrieLoader.Language.CS_CZ, czechStemmer::new), + /** * Danish Snowball stemming over the Radixor Danish dictionary. */ @@ -97,6 +105,16 @@ enum SnowballLanguageCase { NORWEGIAN_NYNORSK("Norwegian Nynorsk", StemmerPatchTrieLoader.Language.NN_NO, norwegianStemmer::new, "Norwegian"), + /** + * Persian Snowball stemming over the Radixor Persian dictionary. + */ + PERSIAN("Persian", StemmerPatchTrieLoader.Language.FA_IR, persianStemmer::new), + + /** + * Polish Snowball stemming over the Radixor Polish dictionary. + */ + POLISH("Polish", StemmerPatchTrieLoader.Language.PL_PL, polishStemmer::new), + /** * Portuguese Snowball stemming over the Radixor Portuguese dictionary. */ @@ -142,6 +160,19 @@ enum SnowballLanguageCase { */ private final String luceneSnowballName; + /** + * Creates a direct-only language case not provided by the current Lucene + * Snowball implementation. + * + * @param displayLanguage human-readable language name + * @param radixorLanguage matching Radixor language resource + * @param directFactory direct Snowball stemmer factory + */ + SnowballLanguageCase(final String displayLanguage, final StemmerPatchTrieLoader.Language radixorLanguage, + final SnowballStemmerAdapter.Factory directFactory) { + this(displayLanguage, radixorLanguage, directFactory, null); + } + /** * Creates a language case. * @@ -191,6 +222,9 @@ enum SnowballLanguageCase { * @return Lucene SnowballFilter algorithm name */ String luceneSnowballName() { + if (this.luceneSnowballName == null) { + throw new IllegalStateException("Lucene Snowball does not provide " + this.displayLanguage); + } return this.luceneSnowballName; } } diff --git a/src/jmh/java/org/egothor/stemmer/benchmark/SnowballLanguageStemmerComparisonBenchmark.java b/src/jmh/java/org/egothor/stemmer/benchmark/SnowballLanguageStemmerComparisonBenchmark.java index 4d4bf01..1cb5b90 100644 --- a/src/jmh/java/org/egothor/stemmer/benchmark/SnowballLanguageStemmerComparisonBenchmark.java +++ b/src/jmh/java/org/egothor/stemmer/benchmark/SnowballLanguageStemmerComparisonBenchmark.java @@ -116,6 +116,49 @@ public class SnowballLanguageStemmerComparisonBenchmark { } } + /** + * Shared corpus state for every official direct Snowball implementation. + * + *

+ * Czech, Persian, and Polish are available in the official Snowball 3.1.0 + * distribution but not through the Lucene SnowballFilter version used by + * this project. Keeping the direct parameter domain separate prevents JMH + * from constructing unsupported Lucene workloads. + *

+ */ + @State(Scope.Benchmark) + public static class DirectSharedState { + + /** + * Language/algorithm case under comparison. + */ + @Param({ "CZECH", "DANISH", "DUTCH", "FINNISH", "FRENCH", "GERMAN", "HUNGARIAN", "ITALIAN", + "NORWEGIAN_BOKMAL", "NORWEGIAN_NYNORSK", "PERSIAN", "POLISH", "PORTUGUESE", "RUSSIAN", + "SPANISH", "SWEDISH", "YIDDISH" }) + public String languageCaseName; + + /** + * Resolved language/algorithm case. + */ + private SnowballLanguageCase languageCase; + + /** + * Shared deterministic changed-token dictionary corpus. + */ + private String[] tokens; + + /** + * Initializes the selected direct Snowball corpus before measurement. + * + * @throws IOException if the corpus cannot be loaded + */ + @Setup(Level.Trial) + public void setUp() throws IOException { + this.languageCase = SnowballLanguageCase.valueOf(this.languageCaseName); + this.tokens = LanguageBenchmarkCorpus.createTokens(this.languageCase.radixorLanguage()); + } + } + /** * Per-thread direct Snowball state. */ @@ -130,10 +173,10 @@ public class SnowballLanguageStemmerComparisonBenchmark { /** * Initializes direct Snowball state for the selected language. * - * @param sharedState selected language state + * @param sharedState selected direct Snowball language state */ @Setup(Level.Trial) - public void setUp(final SharedState sharedState) { + public void setUp(final DirectSharedState sharedState) { this.snowballStemmer = sharedState.languageCase.createDirectStemmer(); } } @@ -198,7 +241,7 @@ public class SnowballLanguageStemmerComparisonBenchmark { /** * Runs Radixor over the selected Snowball-language corpus. * - * @param sharedState shared benchmark state + * @param sharedState shared direct Snowball benchmark state * @param blackhole result sink */ @Benchmark @@ -220,7 +263,7 @@ public class SnowballLanguageStemmerComparisonBenchmark { * @param blackhole result sink */ @Benchmark - public void snowballDirect(final SharedState sharedState, final DirectState directState, + public void snowballDirect(final DirectSharedState sharedState, final DirectState directState, final Blackhole blackhole) { final String[] tokens = sharedState.tokens; final SnowballStemmerAdapter stemmer = directState.snowballStemmer; diff --git a/src/jmh/java/org/egothor/stemmer/benchmark/StemmerComparisonBenchmarkQuality.java b/src/jmh/java/org/egothor/stemmer/benchmark/StemmerComparisonBenchmarkQuality.java index 2bb652f..89a296e 100644 --- a/src/jmh/java/org/egothor/stemmer/benchmark/StemmerComparisonBenchmarkQuality.java +++ b/src/jmh/java/org/egothor/stemmer/benchmark/StemmerComparisonBenchmarkQuality.java @@ -179,6 +179,7 @@ public class StemmerComparisonBenchmarkQuality { "UKRAINIAN_RADIXOR", "UKRAINIAN_MORFOLOGIK_DIRECT", "UKRAINIAN_LUCENE_MORFOLOGIK_FILTER", + "SNOWBALL_CZECH_DIRECT", "SNOWBALL_DANISH_DIRECT", "SNOWBALL_DANISH_LUCENE_FILTER", "SNOWBALL_DUTCH_DIRECT", @@ -197,6 +198,8 @@ public class StemmerComparisonBenchmarkQuality { "SNOWBALL_NORWEGIAN_BOKMAL_LUCENE_FILTER", "SNOWBALL_NORWEGIAN_NYNORSK_DIRECT", "SNOWBALL_NORWEGIAN_NYNORSK_LUCENE_FILTER", + "SNOWBALL_PERSIAN_DIRECT", + "SNOWBALL_POLISH_DIRECT", "SNOWBALL_PORTUGUESE_DIRECT", "SNOWBALL_PORTUGUESE_LUCENE_FILTER", "SNOWBALL_RUSSIAN_DIRECT", @@ -365,6 +368,7 @@ public class StemmerComparisonBenchmarkQuality { UKRAINIAN_RADIXOR(StemmerPatchTrieLoader.Language.UK_UA), UKRAINIAN_MORFOLOGIK_DIRECT(StemmerPatchTrieLoader.Language.UK_UA), UKRAINIAN_LUCENE_MORFOLOGIK_FILTER(StemmerPatchTrieLoader.Language.UK_UA), + SNOWBALL_CZECH_DIRECT(StemmerPatchTrieLoader.Language.CS_CZ, SnowballLanguageCase.CZECH), SNOWBALL_DANISH_DIRECT(StemmerPatchTrieLoader.Language.DA_DK, SnowballLanguageCase.DANISH), SNOWBALL_DANISH_LUCENE_FILTER(StemmerPatchTrieLoader.Language.DA_DK, SnowballLanguageCase.DANISH), SNOWBALL_DUTCH_DIRECT(StemmerPatchTrieLoader.Language.NL_NL, SnowballLanguageCase.DUTCH), @@ -387,6 +391,8 @@ public class StemmerComparisonBenchmarkQuality { SnowballLanguageCase.NORWEGIAN_NYNORSK), SNOWBALL_NORWEGIAN_NYNORSK_LUCENE_FILTER(StemmerPatchTrieLoader.Language.NN_NO, SnowballLanguageCase.NORWEGIAN_NYNORSK), + SNOWBALL_PERSIAN_DIRECT(StemmerPatchTrieLoader.Language.FA_IR, SnowballLanguageCase.PERSIAN), + SNOWBALL_POLISH_DIRECT(StemmerPatchTrieLoader.Language.PL_PL, SnowballLanguageCase.POLISH), SNOWBALL_PORTUGUESE_DIRECT(StemmerPatchTrieLoader.Language.PT_PT, SnowballLanguageCase.PORTUGUESE), SNOWBALL_PORTUGUESE_LUCENE_FILTER(StemmerPatchTrieLoader.Language.PT_PT, SnowballLanguageCase.PORTUGUESE), SNOWBALL_RUSSIAN_DIRECT(StemmerPatchTrieLoader.Language.RU_RU, SnowballLanguageCase.RUSSIAN), diff --git a/src/test/java/org/egothor/stemmer/benchmark/SnowballLanguageStemmerComparisonBenchmarkTest.java b/src/test/java/org/egothor/stemmer/benchmark/SnowballLanguageStemmerComparisonBenchmarkTest.java new file mode 100644 index 0000000..d06b3fc --- /dev/null +++ b/src/test/java/org/egothor/stemmer/benchmark/SnowballLanguageStemmerComparisonBenchmarkTest.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (C) 2026, Leo Galambos + * 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 copyright holder 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 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. + ******************************************************************************/ +package org.egothor.stemmer.benchmark; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.openjdk.jmh.annotations.Param; + +/** + * Verifies that direct-only Snowball 3.1.0 algorithms cannot enter the Lucene + * SnowballFilter benchmark domain. + */ +final class SnowballLanguageStemmerComparisonBenchmarkTest { + + /** + * Verifies the direct and Lucene parameter domains independently. + * + * @throws ReflectiveOperationException if the benchmark state contract changes + */ + @Test + void directAndLuceneParameterDomainsRemainExplicit() throws ReflectiveOperationException { + final Set directCases = parameterValues( + SnowballLanguageStemmerComparisonBenchmark.DirectSharedState.class); + final Set luceneCases = parameterValues( + SnowballLanguageStemmerComparisonBenchmark.SharedState.class); + final Set registeredCases = Arrays.stream(SnowballLanguageCase.values()) + .map(Enum::name) + .collect(Collectors.toUnmodifiableSet()); + + assertEquals(registeredCases, directCases); + assertEquals(17, directCases.size()); + assertEquals(14, luceneCases.size()); + assertEquals(Set.of("CZECH", "PERSIAN", "POLISH"), difference(directCases, luceneCases)); + + for (String luceneCase : luceneCases) { + SnowballLanguageCase.valueOf(luceneCase).luceneSnowballName(); + } + for (String directOnlyCase : difference(directCases, luceneCases)) { + assertThrows(IllegalStateException.class, + () -> SnowballLanguageCase.valueOf(directOnlyCase).luceneSnowballName()); + } + } + + /** + * Reads the declared JMH parameter values from a benchmark state. + * + * @param stateClass benchmark state class + * @return immutable parameter-value set + * @throws NoSuchFieldException if the state no longer declares the parameter + */ + private static Set parameterValues(final Class stateClass) throws NoSuchFieldException { + final Field field = stateClass.getField("languageCaseName"); + return Set.of(field.getAnnotation(Param.class).value()); + } + + /** + * Returns the values present in {@code left} but absent from {@code right}. + * + * @param left source set + * @param right excluded set + * @return immutable set difference + */ + private static Set difference(final Set left, final Set right) { + return left.stream().filter(value -> !right.contains(value)).collect(Collectors.toUnmodifiableSet()); + } +} diff --git a/src/test/java/org/egothor/stemmer/benchmark/quality/QualityStemmerMatrixTest.java b/src/test/java/org/egothor/stemmer/benchmark/quality/QualityStemmerMatrixTest.java index d486ec2..c6cdf3e 100644 --- a/src/test/java/org/egothor/stemmer/benchmark/quality/QualityStemmerMatrixTest.java +++ b/src/test/java/org/egothor/stemmer/benchmark/quality/QualityStemmerMatrixTest.java @@ -59,10 +59,13 @@ final class QualityStemmerMatrixTest { @Test @DisplayName("Candidate discovery is derived from every JMH quality candidate") void discoversEveryCandidate() { final List candidates = QualityStemmerMatrix.candidates(); - assertEquals(98, candidates.size(), "The current adapter-language matrix size changed; report coverage must be reviewed."); + assertEquals(102, candidates.size(), "The current adapter-language matrix size changed; report coverage must be reviewed."); assertTrue(candidates.stream().anyMatch(candidate -> !candidate.name().endsWith("_RADIXOR"))); assertTrue(candidates.stream().anyMatch(candidate -> candidate.name().equals("DA_DK_RADIXOR"))); assertTrue(candidates.stream().anyMatch(candidate -> candidate.name().equals("YI_RADIXOR"))); + assertTrue(candidates.stream().anyMatch(candidate -> candidate.name().equals("SNOWBALL_CZECH_DIRECT"))); + assertTrue(candidates.stream().anyMatch(candidate -> candidate.name().equals("SNOWBALL_PERSIAN_DIRECT"))); + assertTrue(candidates.stream().anyMatch(candidate -> candidate.name().equals("SNOWBALL_POLISH_DIRECT"))); assertTrue(candidates.stream().anyMatch(candidate -> candidate.name().equals("POLISH_POLIMORF_RADIXOR") && candidate.resultLanguage().equals("pl-pl-polimorf"))); assertTrue(candidates.stream().anyMatch(candidate -> candidate.name().equals("POLISH_LUCENE_STEMPEL_DIRECT") @@ -74,7 +77,7 @@ final class QualityStemmerMatrixTest { void completePublicationSelectionUsesOnlyDefaultModels() { final List candidates = StemmingQualityApplication.selectCandidates( EnumSet.allOf(Language.class), ""); - assertEquals(92, candidates.size()); + assertEquals(95, candidates.size()); assertTrue(candidates.stream().allMatch(candidate -> candidate.dictionaryModelId().equals(candidate.language().defaultModelId()))); assertTrue(candidates.stream().noneMatch(candidate -> @@ -95,7 +98,7 @@ final class QualityStemmerMatrixTest { final Path report = this.temporaryDirectory.resolve("matrix.csv"); QualityReportWriter.writeCsv(report, rows); final String text = Files.readString(report, StandardCharsets.UTF_8); - assertEquals(197, text.lines().count()); + assertEquals(205, text.lines().count()); for (Candidate candidate : QualityStemmerMatrix.candidates()) { final String prefix = "\"" + candidate.name() + "\",\"" + candidate.resultLanguage() + "\",\"\",\"\",\"\","; diff --git a/tools/parse-python-release-tag.sh b/tools/parse-python-release-tag.sh new file mode 100755 index 0000000..11e55e9 --- /dev/null +++ b/tools/parse-python-release-tag.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +tag="${1:-}" + +version_pattern='(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)' + +if [[ "${tag}" =~ ^python@(${version_pattern})$ ]]; then + printf 'PYTHON_DISTRIBUTION=radixor\nPYTHON_VERSION=%s\n' "${BASH_REMATCH[1]}" +elif [[ "${tag}" =~ ^python-models-standard@(${version_pattern})$ ]]; then + printf 'PYTHON_DISTRIBUTION=radixor-models-standard\nPYTHON_VERSION=%s\n' "${BASH_REMATCH[1]}" +else + echo "Invalid Python release tag: ${tag}" >&2 + exit 2 +fi diff --git a/tools/run-published-accuracy-benchmarks.sh b/tools/run-published-accuracy-benchmarks.sh new file mode 100755 index 0000000..d1ba928 --- /dev/null +++ b/tools/run-published-accuracy-benchmarks.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -euo pipefail + +report_date="${1:-$(date +%F)}" +project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${project_root}" + +classpath_file="build/reports/jmh/jmh-runtime-classpath.txt" +if [[ ! -s "${classpath_file}" ]]; then + printf 'Missing %s; run ./gradlew writeJmhRuntimeClasspath --no-daemon first.\n' "${classpath_file}" >&2 + exit 1 +fi +IFS= read -r jmh_classpath < "${classpath_file}" + +tmp_dir="${project_root}/build/tmp/jmh" +report_dir="${project_root}/build/reports/jmh" +mkdir -p "${tmp_dir}" "${report_dir}" + +accuracy_include='^org\.egothor\.stemmer\.benchmark\.(EnglishHunspellStemmerComparisonBenchmarkQuality|EnglishStemmerComparisonBenchmarkQuality|HunspellStemmerComparisonBenchmarkQuality|StemmerComparisonBenchmarkQuality)\..*$' +coverage_include='^org\.egothor\.stemmer\.benchmark\.EnglishRadixorDictionaryCoverageBenchmark\.exactRootAgreement$' +accuracy_csv="${report_dir}/stemmer-accuracy-${report_date}.csv" + +common_arguments=( + -f 0 + -wi 0 + -i 1 + -r 1ms + -t 1 + -bm avgt + -tu ns + -rf csv +) + +java -Djava.io.tmpdir="${tmp_dir}" -Xms6g -Xmx6g \ + -cp "${jmh_classpath}" org.openjdk.jmh.Main \ + "${accuracy_include}" "${common_arguments[@]}" \ + -rff "${accuracy_csv}" \ + -o "${report_dir}/stemmer-accuracy-${report_date}.txt" + +for candidate in SNOWBALL_CZECH_DIRECT SNOWBALL_PERSIAN_DIRECT SNOWBALL_POLISH_DIRECT; do + if ! awk -F, -v candidate="${candidate}" ' + NR > 1 { + value = $8 + gsub(/^"|"$/, "", value) + if (value == candidate) { + found = 1 + } + } + END { exit found ? 0 : 1 } + ' "${accuracy_csv}"; then + printf 'Exact-root report omits %s.\n' "${candidate}" >&2 + exit 1 + fi + for counter in correctMatches evaluatedTokens changedCorrectMatches changedEvaluatedTokens \ + rootPreservedMatches rootEvaluatedTokens; do + if ! awk -F, -v candidate="${candidate}" -v suffix="exactRootAgreement:${counter}" ' + NR > 1 { + benchmark = $1 + value = $8 + gsub(/^"|"$/, "", benchmark) + gsub(/^"|"$/, "", value) + if (value == candidate && benchmark ~ suffix "$") { + found = 1 + } + } + END { exit found ? 0 : 1 } + ' "${accuracy_csv}"; then + printf 'Exact-root report omits %s for %s.\n' "${counter}" "${candidate}" >&2 + exit 1 + fi + done +done + +java -Djava.io.tmpdir="${tmp_dir}" -Xms6g -Xmx6g \ + -cp "${jmh_classpath}" org.openjdk.jmh.Main \ + "${coverage_include}" "${common_arguments[@]}" \ + -rff "${report_dir}/english-coverage-accuracy-${report_date}.csv" \ + -o "${report_dir}/english-coverage-accuracy-${report_date}.txt" diff --git a/tools/tests/test_update_benchmark_documentation.py b/tools/tests/test_update_benchmark_documentation.py new file mode 100644 index 0000000..8ce5d6e --- /dev/null +++ b/tools/tests/test_update_benchmark_documentation.py @@ -0,0 +1,174 @@ +############################################################################### +# Copyright (C) 2026, Leo Galambos +# 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 copyright holder 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 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. +############################################################################### + +"""Tests for deterministic publication of current Java benchmark reports.""" + +from __future__ import annotations + +import importlib.util +import sys +import unittest +from pathlib import Path + + +SCRIPT = Path(__file__).resolve().parents[1] / "update-benchmark-documentation.py" +SPEC = importlib.util.spec_from_file_location("update_benchmark_documentation", SCRIPT) +assert SPEC is not None and SPEC.loader is not None +MODULE = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = MODULE +SPEC.loader.exec_module(MODULE) + + +def speed_row(score: float, error: float = 1_000.0) -> dict[str, str]: + """Create the JMH fields consumed by the documentation updater.""" + return { + "Score": str(score), + "Score Error (99.9%)": str(error), + "Unit": "ns/op", + } + + +class BenchmarkDocumentationTest(unittest.TestCase): + """Covers current-report selection and measured-corpus arithmetic.""" + + def test_speed_uses_timing_token_denominator(self) -> None: + radixor = MODULE.Key("example.persianRadixor", ()) + snowball = MODULE.Key( + "example.snowballDirect", (("languageCaseName", "PERSIAN"),) + ) + data = MODULE.JmhData( + primary={ + radixor: speed_row(250_000.0), + snowball: speed_row(500_000.0), + }, + auxiliary={}, + ) + text = """## Speed + +| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | +| --- | --- | ---: | ---: | ---: | ---: | --- | +| Radixor | `persianRadixor` | pending | pending | pending | pending | baseline | +| Official Snowball direct | `snowballDirect[PERSIAN]` | pending | pending | pending | pending | direct | + +## Interpretation Notes +""" + + updated = MODULE.update_speed_table(text, data, 5_000, "FA_IR") + + self.assertIn("| 0.250 | 0.001 | 50.0 | 1.000 |", updated) + self.assertIn("| 0.500 | 0.001 | 100.0 | 2.000 |", updated) + self.assertEqual( + updated, + MODULE.update_speed_table(updated, data, 5_000, "FA_IR"), + ) + + def test_language_parameter_resolves_ambiguous_method(self) -> None: + czech = MODULE.Key( + "example.luceneHunspellStemFilter", + (("languageCaseName", "CZECH"),), + ) + polish = MODULE.Key( + "example.luceneHunspellStemFilter", + (("languageCaseName", "POLISH"),), + ) + data = MODULE.JmhData( + primary={czech: speed_row(10.0), polish: speed_row(20.0)}, + auxiliary={}, + ) + + selected = MODULE.select_speed_key("luceneHunspellStemFilter", data, "CS_CZ") + + self.assertEqual(czech, selected) + + def test_partially_pending_speed_row_is_rejected(self) -> None: + text = """## Speed + +| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note | +| --- | --- | ---: | ---: | ---: | ---: | --- | +| Radixor | `persianRadixor` | pending | 0.001 | pending | pending | baseline | + +## Interpretation Notes +""" + + with self.assertRaisesRegex(ValueError, "Partially pending speed row"): + MODULE.update_speed_table( + text, MODULE.JmhData(primary={}, auxiliary={}), 5_000, "FA_IR" + ) + + def test_malformed_accuracy_row_is_rejected(self) -> None: + text = """## Accuracy + +| Stemmer | All exact | Changed exact | Root preserved | Note | +| --- | ---: | ---: | ---: | --- | +| Official Snowball direct | 75.00% | 62.500% | 100.000% | direct | + +## Speed +""" + + with self.assertRaisesRegex(ValueError, "Malformed accuracy row"): + MODULE.update_accuracy_table( + text, MODULE.JmhData(primary={}, auxiliary={}), "FA_IR", {} + ) + + def test_pending_accuracy_row_uses_current_auxiliary_counters(self) -> None: + key = MODULE.Key( + "example.exactRootAgreement", + (("candidateName", "SNOWBALL_PERSIAN_DIRECT"),), + ) + counters = { + "correctMatches": 75.0, + "evaluatedTokens": 100.0, + "changedCorrectMatches": 50.0, + "changedEvaluatedTokens": 80.0, + "rootPreservedMatches": 20.0, + "rootEvaluatedTokens": 20.0, + } + data = MODULE.JmhData(primary={}, auxiliary={key: counters}) + text = """## Accuracy + +| Stemmer | All exact | Changed exact | Root preserved | Note | +| --- | ---: | ---: | ---: | --- | +| Official Snowball direct | pending | pending | pending | direct | + +## Speed +""" + + updated = MODULE.update_accuracy_table(text, data, "FA_IR", {}) + + self.assertIn("| 75.000% | 62.500% | 100.000% |", updated) + self.assertEqual( + updated, + MODULE.update_accuracy_table(updated, data, "FA_IR", {}), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/update-benchmark-documentation.py b/tools/update-benchmark-documentation.py index 9c7b200..2b0ea91 100644 --- a/tools/update-benchmark-documentation.py +++ b/tools/update-benchmark-documentation.py @@ -104,7 +104,6 @@ def parse_arguments() -> argparse.Namespace: parser.add_argument("--docs-root", type=Path, default=Path("docs")) parser.add_argument("--readme", type=Path, default=Path("README.md")) parser.add_argument("--corpus", type=Path, required=True) - parser.add_argument("--old-comparison", type=Path, required=True) parser.add_argument("--accuracy", type=Path, required=True) parser.add_argument("--speed", type=Path, required=True) parser.add_argument("--coverage-accuracy", type=Path, required=True) @@ -165,9 +164,13 @@ def read_corpora(path: Path) -> dict[str, dict[str, object]]: ) entry["commands"].append((row["Command class"], int(row["Command count"]))) if set(corpora) != set(LANGUAGES.values()): - raise ValueError(f"Corpus report languages differ from documentation languages: {sorted(corpora)}") + raise ValueError( + f"Corpus report languages differ from documentation languages: {sorted(corpora)}" + ) if any(entry["model"] == "pl-pl-polimorf" for entry in corpora.values()): - raise ValueError("The default-model corpus report must not contain pl-pl-polimorf.") + raise ValueError( + "The default-model corpus report must not contain pl-pl-polimorf." + ) return corpora @@ -180,11 +183,11 @@ def render_corpus_sections(language: str, entry: dict[str, object]) -> str: lines = [ "## Dictionary Corpus", "", - "| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed speed tokens |", - "| --- | --- | --- | ---: | ---: | ---: | ---: |", + "| Model ID | Model version | Language | Dictionary rows | Complete quality tokens | Already-root tokens | Changed tokens | JMH timing tokens |", + "| --- | --- | --- | ---: | ---: | ---: | ---: | ---: |", f"| `{entry['model']}` | `{entry['version']}` | `{language}` | {format_integer(int(entry['rows']))} | " f"{format_integer(total)} | {format_integer(int(entry['roots']))} | " - f"{format_integer(int(entry['changed']))} |", + f"{format_integer(int(entry['changed']))} | {format_integer(int(entry['timing']))} |", "", "## Radixor Patch Command Distribution", "", @@ -205,7 +208,9 @@ def render_corpus_sections(language: str, entry: dict[str, object]) -> str: f"{100.0 * count / total:.3f}% |" ) if command_total != total: - raise ValueError(f"Patch command count {command_total} differs from corpus total {total} for {language}.") + raise ValueError( + f"Patch command count {command_total} differs from corpus total {total} for {language}." + ) return "\n".join(lines) + "\n\n" @@ -252,17 +257,27 @@ def select_accuracy_key( for key, counters in data.auxiliary.items() if AUXILIARY_NAMES.issubset(counters) and language_words.issubset( - words(key.benchmark + " " + " ".join(f"{name} {value}" for name, value in key.parameters)) + words( + key.benchmark + + " " + + " ".join(f"{name} {value}" for name, value in key.parameters) + ) ) ] if not matches: - raise ValueError(f"No current JMH accuracy row matches language {language} and label {label}.") + raise ValueError( + f"No current JMH accuracy row matches language {language} and label {label}." + ) label_words = words(label) - language_words def score(key: Key) -> tuple[int, int, int, int, int]: identity_words = ( - words(key.benchmark + " " + " ".join(f"{name} {value}" for name, value in key.parameters)) + words( + key.benchmark + + " " + + " ".join(f"{name} {value}" for name, value in key.parameters) + ) - language_words ) return ( @@ -273,9 +288,13 @@ def select_accuracy_key( int(language_words.issubset(words(key.benchmark))), ) - ranked = sorted(((score(key), key) for key in matches), reverse=True, key=lambda item: item[0]) + ranked = sorted( + ((score(key), key) for key in matches), reverse=True, key=lambda item: item[0] + ) if ranked[0][0][0] == 0: - raise ValueError(f"No implementation identity words match accuracy label {label} for {language}.") + raise ValueError( + f"No implementation identity words match accuracy label {label} for {language}." + ) if len(ranked) > 1 and ranked[0][0] == ranked[1][0]: raise ValueError( f"Ambiguous current JMH accuracy identity for {label} in {language}: " @@ -300,11 +319,31 @@ def update_accuracy_table( ) -> str: start = text.index("## Accuracy") end = text.index("## Speed", start) - section = text[start:end] + section = text[start:end].rstrip() output: list[str] = [] for line in section.splitlines(): cells = [cell.strip() for cell in line.split("|")[1:-1]] - if len(cells) == 5 and all(re.fullmatch(r"\d+\.\d{3}%", cell) for cell in cells[1:4]): + measured = len(cells) == 5 and all( + re.fullmatch(r"\d+\.\d{3}%", cell) for cell in cells[1:4] + ) + pending = len(cells) == 5 and all(cell == "pending" for cell in cells[1:4]) + partial_pending = ( + len(cells) == 5 + and any(cell == "pending" for cell in cells[1:4]) + and not pending + ) + if partial_pending: + raise ValueError( + f"Partially pending accuracy row for {cells[0]} in {language}." + ) + if ( + len(cells) == 5 + and cells[0] not in {"Stemmer", "---"} + and not measured + and not pending + ): + raise ValueError(f"Malformed accuracy row for {cells[0]} in {language}.") + if measured or pending: if cells[0] == "Radixor": values = rounded_accuracy(corpus_accuracy(corpus)) else: @@ -312,12 +351,8 @@ def update_accuracy_table( values = rounded_accuracy(accuracy(new_data, key)) cells[1:4] = [f"{value}%" for value in values] line = "| " + " | ".join(cells) + " |" - elif language == "HE_IL" and len(cells) == 5 and cells[0] == "Radixor" and cells[1] == "pending": - values = rounded_accuracy(corpus_accuracy(corpus)) - cells[1:4] = [f"{value}%" for value in values] - line = "| " + " | ".join(cells) + " |" output.append(line) - replacement = "\n".join(output) + "\n\n" + replacement = "\n".join(output).rstrip() + "\n\n" return text[:start] + replacement + text[end:] @@ -328,9 +363,9 @@ def method_and_parameter(display: str) -> tuple[str, str]: return match.group(1), match.group(2) or "" -def speed_matches(display: str, data: JmhData) -> list[Key]: +def speed_matches(display: str, data: JmhData, language: str) -> list[Key]: method, language_case = method_and_parameter(display) - return [ + matches = [ key for key, row in data.primary.items() if key.method == method @@ -338,57 +373,54 @@ def speed_matches(display: str, data: JmhData) -> list[Key]: and key not in data.auxiliary and row["Unit"] == "ns/op" ] + if language_case or len(matches) <= 1: + return matches + language_words = LANGUAGE_IDENTITY_WORDS[language] + return [ + key + for key in matches + if language_words.issubset( + words(" ".join(value for _, value in key.parameters)) + ) + ] -def closest_speed_key(display: str, score_ms: float, data: JmhData) -> tuple[Key, float]: - matches = speed_matches(display, data) - if not matches: - raise ValueError(f"No JMH speed row matches {display}") - selected = min(matches, key=lambda key: abs(float(data.primary[key]["Score"]) / 1_000_000.0 - score_ms)) - difference = abs(float(data.primary[selected]["Score"]) / 1_000_000.0 - score_ms) - return selected, difference - - -def select_speed_key(display: str, published_score_ms: float, old_data: JmhData, new_data: JmhData) -> Key: - current, current_difference = closest_speed_key(display, published_score_ms, new_data) - if current_difference < 0.001: - return current - selected, difference = closest_speed_key(display, published_score_ms, old_data) - if difference >= 0.001: - raise ValueError(f"Old speed row for {display} differs by {difference:.6f} ms from documentation.") - return selected +def select_speed_key(display: str, data: JmhData, language: str) -> Key: + matches = speed_matches(display, data, language) + if len(matches) != 1: + raise ValueError( + f"Expected one current JMH speed row for {display} in {language}, found {len(matches)}." + ) + return matches[0] def update_speed_table( text: str, - old_data: JmhData, new_data: JmhData, - changed_tokens: int, + timing_tokens: int, language: str, ) -> str: start = text.index("## Speed") end = text.index("## Interpretation Notes", start) - section = text[start:end] + section = text[start:end].rstrip() parsed: list[tuple[str, list[str] | None, Key | None]] = [] radixor_score = math.nan for line in section.splitlines(): cells = [cell.strip() for cell in line.split("|")[1:-1]] if len(cells) == 7 and cells[1].startswith("`") and cells[1].endswith("`"): display = cells[1].strip("`") - if cells[2] == "pending" and language == "HE_IL": - matches = [ - key - for key, row in new_data.primary.items() - if key.method == "hebrewRadixor" and key not in new_data.auxiliary and row["Unit"] == "ns/op" - ] - if len(matches) != 1: - raise ValueError(f"Expected one Hebrew speed row, found {len(matches)}") - key = matches[0] - elif re.fullmatch(r"\d+\.\d{3}", cells[2]): - key = select_speed_key(display, float(cells[2]), old_data, new_data) - else: - parsed.append((line, None, None)) - continue + pending = all(cell == "pending" for cell in cells[2:6]) + measured = all(re.fullmatch(r"\d+\.\d+", cell) for cell in cells[2:6]) + partial_pending = ( + any(cell == "pending" for cell in cells[2:6]) and not pending + ) + if partial_pending: + raise ValueError( + f"Partially pending speed row for {cells[0]} in {language}." + ) + if not pending and not measured: + raise ValueError(f"Malformed speed row for {cells[0]} in {language}.") + key = select_speed_key(display, new_data, language) if key not in new_data.primary: raise ValueError(f"New JMH report omits speed key {key}") score = float(new_data.primary[key]["Score"]) @@ -408,18 +440,17 @@ def update_speed_table( error = float(row["Score Error (99.9%)"]) cells[2] = f"{score / 1_000_000.0:.3f}" cells[3] = f"{error / 1_000_000.0:.3f}" - cells[4] = f"{score / changed_tokens:.1f}" + cells[4] = f"{score / timing_tokens:.1f}" cells[5] = f"{score / radixor_score:.3f}" line = "| " + " | ".join(cells) + " |" output.append(line) - replacement = "\n".join(output) + "\n\n" + replacement = "\n".join(output).rstrip() + "\n\n" return text[:start] + replacement + text[end:] def update_language_pages( docs_root: Path, corpora: dict[str, dict[str, object]], - old_data: JmhData, accuracy_data: JmhData, speed_data: JmhData, ) -> None: @@ -429,7 +460,11 @@ def update_language_pages( text = path.read_text(encoding="utf-8") corpus_start = text.index("## Dictionary Corpus") accuracy_start = text.index("## Accuracy", corpus_start) - text = text[:corpus_start] + render_corpus_sections(language, corpora[language]) + text[accuracy_start:] + text = ( + text[:corpus_start] + + render_corpus_sections(language, corpora[language]) + + text[accuracy_start:] + ) text = re.sub( r"Speed uses JMH average time, \d+ warmup iterations, \d+ measurement iterations, " r"\d+ forks?, and 1 thread\.", @@ -439,11 +474,15 @@ def update_language_pages( count=1, ) text = update_accuracy_table(text, accuracy_data, language, corpora[language]) - text = update_speed_table(text, old_data, speed_data, int(corpora[language]["changed"]), language) + text = update_speed_table( + text, speed_data, int(corpora[language]["timing"]), language + ) path.write_text(text, encoding="utf-8") -def update_corpora_reference(docs_root: Path, corpora: dict[str, dict[str, object]]) -> None: +def update_corpora_reference( + docs_root: Path, corpora: dict[str, dict[str, object]] +) -> None: path = docs_root / "benchmarks" / "reference" / "corpora.md" text = path.read_text(encoding="utf-8") original_header = "| Language resource |" @@ -453,7 +492,9 @@ def update_corpora_reference(docs_root: Path, corpora: dict[str, dict[str, objec elif current_header in text: table_start = text.index(current_header) else: - raise ValueError("The corpora reference contains no recognized corpus-table header.") + raise ValueError( + "The corpora reference contains no recognized corpus-table header." + ) table_end = text.index("\n\n", table_start) lines = [ "| Default model ID | Version | SHA-256 | Language | Dictionary rows | Total tokens | Already-root tokens | Changed tokens | Speed timing tokens |", @@ -468,7 +509,9 @@ def update_corpora_reference(docs_root: Path, corpora: dict[str, dict[str, objec f"{format_integer(int(entry['changed']))} | {format_integer(int(entry['timing']))} |" ) replacement = "\n".join(lines) - path.write_text(text[:table_start] + replacement + text[table_end:], encoding="utf-8") + path.write_text( + text[:table_start] + replacement + text[table_end:], encoding="utf-8" + ) def coverage_rows(accuracy_data: JmhData, speed_data: JmhData) -> list[str]: @@ -558,23 +601,31 @@ def update_coverage( "quality/speed envelope: the amount and quality of dictionary knowledge affect stemming precision,\n" "while contracted tries reduce lookup cost in uniform regions of the compiled graph.\n\n" ) - index.write_text(index_text[:key_start] + key_section + index_text[key_end:], encoding="utf-8") + index.write_text( + index_text[:key_start] + key_section + index_text[key_end:], encoding="utf-8" + ) def main() -> None: arguments = parse_arguments() corpora = read_corpora(arguments.corpus) - old_data = read_jmh(arguments.old_comparison) accuracy_data = read_jmh(arguments.accuracy) speed_data = read_jmh(arguments.speed) coverage_accuracy_data = read_jmh(arguments.coverage_accuracy) coverage_speed_data = read_jmh(arguments.coverage_speed) measured_keys = set(accuracy_data.primary) | set(speed_data.primary) if any("PolishPolimorf" in key.benchmark for key in measured_keys): - raise ValueError("A published report contains the excluded PolishPolimorf benchmark.") - update_language_pages(arguments.docs_root, corpora, old_data, accuracy_data, speed_data) + raise ValueError( + "A published report contains the excluded PolishPolimorf benchmark." + ) + update_language_pages(arguments.docs_root, corpora, accuracy_data, speed_data) update_corpora_reference(arguments.docs_root, corpora) - update_coverage(arguments.docs_root, arguments.readme, coverage_accuracy_data, coverage_speed_data) + update_coverage( + arguments.docs_root, + arguments.readme, + coverage_accuracy_data, + coverage_speed_data, + ) if __name__ == "__main__":

d(`pnb#{?ePl!o|v+I`{XQ zyOo6tu1-9Y!^W7~9D0;OOXJPZ_&A?~y=uQ2^S#Fe(ut{MY8~n|xLZEzU>yAoY4Z#* zIt6^yEbS170IQS9ecyPoFc)c)W7}4H)@r2%?of;{{Sjnf;5xAPYuS7_a7451P6U~T6~dquHi32IR{DClQ@+~DAvGjTOAm8 zBlmH?RWS=+ydS*hnx4Mef6e!Z!jy|t+Z}jb7)cFRM|+|DqFG5`*UTGt4YB>+o2{?l zkW0pc3jXEWTn)-rxpmFtWyTS7gaAKGuQ~vWtXX0opwz2LwAt&&0P1D7yR7J>azu?c zso9;m$Z2xFrO@@jd=*%?ia$rd>K8QnA>R3tpeG<=T8>Le1^cSxBxzmIbW)Yc5pcKksKu*Y38_IO2jw5Afw`A%++5q>Fky$d<>_%73wjynwy^FilJ3^ z0makoj-?Kt$Em2O02QV$%0C#|{z+nF)#ozp+UHtjna|K!Ov0}>G2&q6Dx$u(Q2i&v z>ATzN7&wQ@F6_fLi#mNVb68yW1ia1WdYYFHrvCszgIV5v^6&n|X1DZ>={wq8H0Qa` z+vppHv9$VfLl2;KoU~>Nh~!v1n%$eh{r>*|T0)qXqrpds$_;%fcce&z3s_%j9Q+-| zJ3)S+hN`Xjj^#QB-E&XG(q0#te&EGq(_Ug}<5leFod>!!o`h3JF~S#|4=3W)flIS)6%W zr{V`dLo!^Pe8tpRzBqFIwy0*UV+4R~fT&Oi0LD#}MXT-(ixvKaz61`9zs423G=l8T z(Tn(0b!5yOY7Z!|(^fhS{${7b^ooS}^Okn@y?%9$igg(d-%VkgdgYX$W?WC<>0O~k zCJ(%^6Mkwx2c>t8GXxOeQF(NKeqgn7ZEu}_cG_kj2g#xLgH5U~AcY(a1B0U+%9V8w zPOIWm67KW&cgMn@KGiF6vC{p8{5}y-TBz=|eE0D+vgruZ4A3#%cY`aV%wqP8@=Dw` z7vOcXK?_}it^mCXBO1@(O8q*(DtS_l0sPrz5AA5;$ADiX7&sycFd)lV7q1+`#VJM3 zf%~9Sg1mW~LqqwLK;w+~1?$F+Z0dZY`-e#CQO}G@dVaW(Zo-8PJy5+nyu zvK~&5A||D2=aobWQ$-j<^nwet{oYlVxH!9@zJT<_g7`qaencMtdp2S0^Ahib{Kfl> z3QnT)K9dfkTO4ZmIq*OP-F;8aHp_|O#j*5A>5Z(U`BkU5!$(<@zK@I@AZ@9@zE*&L zD2c0qYTqHOerGDBxarHG;RS~DwR{R6;F=2^!hsI-=b)_@<$VMLD z0+O%na8FdRC*5CZLf*I6#*j3Dep-ALG6h~qdDl^yHMq2-so_ zFj15Rsjy#6y<5uw>C~pXR-M9feH?|l#2}_VI=Zf_gAU{ox6zX~h)jx9Acncx7bdqa zS%u*jV#}4Ra(`=I9VdSguKYEuip}jsuJTR^YoYF=Q!8G!sz$Idl-X0JDggrySd1Bh>m(6NI} z(VqJ2uiYHTdfribaQ?@(vp+tW`WqZ=D21a<&Zo6OyHR|Pzp=Emf=U4Bk0132*DB<4 zH@}alTi~j`;=IcZ{{ZK^@JAgbb_jMlm(LG~I7XoV0M_=iFw2vO&D_rUNq+~#0Maj} zqBmaQ3?#&%G_=}YyVJzW4{Ke2B1+4L{{Z1`;edxlWv37Lj5?K8V7i_Hjk;?W?i_Ok zsaB6Oe&v*{JcfQ{8nZZmjl@xBXZjC%!mT|S4{=G^c|k^5Dd;~5%d)r{JpGjdD?WgL z@K|1W(p`K9s(i~MZ)gxNoI)<1uzx@YG*HeaE3wqzG(nmU3&COZIk{G9rH1173`DK= zfFYo6J8arq#&kRQS^ofvSRWt0KwH~A%mLerBZTxWqQ;uDGv)l!z!I?nCqxQ-u0BR- zfB0A4eay*6@nj8NE`6w?QRKJvfyM9WM21~;ILC=}iR&n;^9%}n;hoL*`EG?!j+ z#@`x8E$__R{{Rxs0Fgj$zwGP!W}CxfF79fS(k!a_UeH*jdpVk&J{U*JQDbJ~I-vb5 z-d$-1{{WeFGQa#>KGX3GPDy*mc055h#IgwqO1mcCMaxJB=wccY-0=>Q$#g<;%r3L$ zXtE7>grS>#7m3`Vx`S4er#};n_#^Mm0Z^o+6x&BoFrrZHe3P_9Bx{)YaR(Iw{A=z~ znhNia`3*?AhON7tO4)=h0J?HRqhn5*^pu=_#lg~y{Te<>B_Z=o)eo(VK7Hh!n?KCzL;Rvp4%~eDG(Tv3`udrit+l5nrl=p3Xy!x25qZM2? zd*#*LVUD-(T66Gcxv29TUMpbRqtY$n#MAPBGtzdcBfCKBTd6O28&nfY&-qR2XP7Y=+y-a#&-CV-4>b2U6Pj)*=4>SM$(3e~2}xqXxjYJP{q z0_tNGtf%!x2+IoEy%qCMDlV)+o!%KFXc_1ecVy_M7}fI89EbJ7t6Tz)EiL?-C!sZ3 zwC*7)+oJ`JQJs#^4iw)10MkX;Ox3-)5cuiid2rkKVSNJi0axMkr31hA6KljkE0aMJ znDS8h0;O?vW}@x6Z_|r>?hq3_dY(1CmBSD}W zA2>}>x@3;67I#-HSGfIv9-(p%hez$7aOoR-G4ZF`IQec__=q)EOwxzQVYiv)U;I}U z?xVZ!GTtts$n(_CA9!;;P4g@}iZYkXq--^YTb*I<4j~*%UXc?nbd-xMM8I$S%e$D% z=Mw$K4?04fzkR@tF$Pk2sh%cf!P;1rr_f$~kr~h08DXSTa>?m~n}IHwb9;ip z;vAydy&#k`rO&of^p~`>z{~Tejy*WVc$wiFIDt%1?fAz;q=ZGFnqYKee5a5y-DakjTX{Co`^#u3vVbG+ZmU!~H# z^)G&?!kbFw>e?LGf7?-vS|T~S zxz*NM*Asdgy(%4`R0|M1#m7@9U1d)$$>4}RTVvtZd|>Hiiy74(^X^$sUk-5QTx;Ti zEOl##O#v8rw`6oGa!_m?<97<-t-CIyS5lj*@8A;4u&4@p41u``xV>5MBd=8op}a85 zb^1Y-qi3wrXlRnnzI;Vl%>ejRtAPihL?HKkZ?+l@k+wI?dX75_?k6h}e;5n+R1sR@ z+z!E9Kx=@$fyZuQAItz}C~O1(y*UAMp21`SnHm&0z>BaeWDTgvNbyh=LtKw zvJCr$znFN9}Ojz+lW2a+W!DDtwr}=ii5ki=lhG6&|IO#kmmN3rqi2)P<*TK z#hQ=Pm{X(dVC?r1mi~E>U#D?3G>*wtczMk8iAvG060zJy2~x8(Oj68LD8-Fs_bh`A zCaC5NxJt9>d%`l(U26@L=bsgS+xVL2NnOJlW@9AZucn>KR&|>7DGrSy`Z-Esy%;}B zr-)l#{_`IGi~t8MUl6hyN9Gj|qM?uGQ*GdC`X;cp)d`x}mR}iytrx8TE9m~B(y`1X zwL8qUpOXE-*x^)t$~759Xr88qaoDm@mZV)b9cAugPMZQPM*6+Q0hD0 zG!PyF{{Xg2r#7=g(71(7G0heQ@hrK949qdiRP_kBeu{lAw}w#9X<8N>%aL$_q4O@* z(8NbaZ3my-G?^533LXak0P(d-y5)YYx9=>_K@ifN0mi%B+aawow5zyBa^|4u_UW8o z8F8b=la(&EGK2*M7nGN7@b7T_B`dZPmFp^&u0e2kpKq}jQgIlCx+d@8F1jG4f_BQ- zegkeFVGVMEt(SWF`)yFjt+jb;UKsR@C0K9UIa#Reyp7%Q#?>lxcyk)`G%Wm^cXoV`J5NHRZ_BQc zQsF=wZo`i>vw)2+2h=Y`HI||R9!wh3urkJ~(Y4yb>_<`4#|EcnmDSXN+Ntba67_pb zl$Zy^FF^}ioC-sGTYX$~$;8|IH0%VZr@!qL^x{{UzxU5tpsHU;1eDewT7QD>Swq&d>P zatnJed6iVGu4kRSNZ&5J)%tBxmlWd_nM+FzCTRQ328YOAgYM-CtsCs;?u=UPVv{j- zekEx2Q+BzHo*EK^Jf<)Gj}Oc!U-K!;4zObC{$=Xlg+WW+`-89X4b|#hVp+Vl(({SA zekB|2a|3@%>A7TBx$0aSAY;r*G>?ex+lgh+spRL8*ut(ISwmQr+3mvUvC_;No2sof zk0w1wAMPuns}wuLy}%fX50tO|3#aCHhYD5mk+Y?twDI&R573k#Q0!h9S)Iy#eOk?9 z7nyRU=crO@{@ML~4L_)^Y7e|QlUCtKT5G9N;<&K0=o#7ggFjkD(}PH?{v4b7-F)X+ zeL+ul#CQIE%zL*VInU-D-D`C4ic7UjcrBC(zDz{bL7ncy+;UDNBej}*1fC|iqy^7p0ZjrT-6d{wFMb#l! zf~C#7{jw!(IIB;fUV^5g*bd?*i%OA9{%Or^KJxB-Hh#AA4S7J+;GU65bp>;F7F_mR ztL^5}-toS_r`;5%&>P)@z$5?{wUp$s-2)UzwidUPvNc>&;Io0WrC3K2h&72y!v=h( z;|Gu){9?w_*XC$*Dz(wjU!?x-&IlaNFX!Ii(K?ImopeOrG6?7y&@N1} z7PLle(9k_%uF|b;kccRHkmf}-d1=B@SNFg8*ThW}aevU%^Mf}LM4@(BQg@qegE?XiP1GUmoyF|LvlYF{7 z%DkawvOF)a&-g?Oa~)qBLCq;7IO16@rRGvPVpID5IQ0mmqX}Zl)?0M?4N8MrTsf#D zJbel`mu)64AuG$W+wVyg@qb4{{W$(isxL-VlAdUU(D5-6FJsg5TqKjPn0L5 z`d+@KNlSw&{6sd(o1RUnK}RVzrFF%+OIY1<2rx)5t+nDWyvz#}^#`cU9hcBmJL>6uCG;^#&y)Qi$Hh}b@KTi=#-HCN`6#oD) zyvDaa4K&0N6w?9o4vDrH^>Z(gOM86K_pBi#jooXH+u&P(GggF%ollmO;Y%n?-iJ#tuLFiT74f6-rr3tBQ_bU{fl8S=SsepT(NC8jkFjkMzDSMj7nVts(nTtZTj5)1Z2_1)0bZ7#lDdz8w%!634F;_= z016J({{V=GY7&`K84Q&eB`Km{ZTt%!`AVZ@g#8k{!E6cTz8Av{f?OBqCF0)*1kQ7? zA+u#U*v z;K31k*NlGRTrbs0)?vrfh8O&P@fB|**|Olmhr%w?(jm9)fS=PEO$W|#jCjTk4Yg&t z2Tq0WL-#=rz?dl^t5MMcDs9j$&R2xoaSVqZJ^>T5A|i~paka6&)&1r`o9d%F?^naA zw6RR%CEm4GUaEnH?W%6P2z{m26sfOwzi{XRZzTGF05cWBd0Z7<5{2rj#<)y=*A#Uw1yvM-J z<7(0K-m^q>?xCetc9&7YXkPM)EZQrSzU^S#I1#R2Q)o+JH0lnEU?!ip0x7 zrSK2=Hrz4titX*>?e-xZ0e8Q^9+I`upwdT&Utp)qIa0f@S5r8?UI&51utsZO;KF4S3V8IP5jp#IQA|v&pkf*A@!TxNdYE zHVAx;(gZ=RvWe_p;IH5)>MDrYV%mCYd^(gLbv@gBK{gmVu2pL4yQ<|~+VV_1M;Of( z^zp;iFcwnJJu%Qx$~)Hx^$$vyM%G>I?k)x^4Mq4%ahfg%)yAJJxOZ!(OwcD~-T9yT{umrS)YjnIb4*FAS6w6EEo82VpZiX6USBlW5`RF z-{c_q%>MxJGYqyGjCE&SW%PnKZF*)Ex*-f2zGk?t)6ti~{C!__LEk9^a;p}CJBpkw z8?^>Q8Ogbo1`5c!ws826KeYO&QF9q5e+UnAJiju{XNFyOE{c>);#Fd7CH420naLLZ zf|gGY(yfCA*!059OT;4cCUC&4K@!v;(V5`9d`-#5w>A?rW!{aLiCju*)XMuCM>g%m ztKTNn%ME4T^HdvXDr$&brVu4GG=-e47W~6rM0=Vp4CmArp?J79!CcM^#Pv0(-a%f* z`-~#V%+dK$(lHc?0B4K3G#v0Mj+gY~9z8S1L{Z1UK0Oz`pOyyZJf(7+%Yt!Uk9ws~ziU zy|0+jkt{x-rW6OaXak4|h!itwQ@9UdV7D>Ehg)IPykUG>Um8oGl-YP{KbPErtG%{s zqd6_ih~^AUYld&?zy{+C8W(2XoU_Y3%b#&o!WlV6w~I6fe%H1@p1{3kXNXklNejp^ z0*xvKezk)acVsC40IVlN9}OU>O{3E(o`1^EH@)i0@`JkQ)Kz~6a2%6g`zc1YIz&@3 zuC+A1>*U}4Ay7<6ie1+%&9DGn;r8c>0&R#Q+8{$&fJW}{LDLb@8n?NSZF9mQps z`EwB_HWm5?>9fEh(2NVB)HwHuTpOi>QBSq*g=U%BpZgOQk^cZqzA_w|u2pybxZ0y! zfUvG-ZxaNp%D(Fj&XQ1RL=;^aPizy|sj1AbKuN_cc)U6BjkSxL!n8YQE!?SJo*S2l)u4HS(8toR_v0C2?GZ zI5@g_#)ZouHEuPeXd#^Xhr~1xcaFrS$3MF(ZSM?fkhr$#QCh;-fM7-D{$L)lIUw-o zweCrA5TU&xc-IqxA|loJ4tW90L83iazrvZ7XG2m`PsE2(R{<=Z7K78&y$6%o1JdY9 zVw*I+N&L;H=ApypH)4X92WPP@Y6w-e@uc)hvKMH6;-73EfU-qu9aZ-n{pGILExsc{ zd_HCkY6~~{h6!z5V+*n;s?+LMvauk*yBC>j^V0s)Hw4vGHDz%)G07G)LaDb>o$rp= z;HU;bpzov3_51Pt5}2V;bKJKm9RUW7{6k-(^p9raTVw!*IpZ?Us&@|ktp(7AY`*KAg7&C?q+yg)7B z&&I!B@O^wq{?qe-c4FtAH+D7GAaDhOqipKIMwg(an=##L%4s!uyO~qjGnbvrY1#MZ z7ojeO;Rm0@%ZRy!y5cQqnNQr!=if5-{u$!@GbaB4h7ZJ9m#n_#v6VHlJR9(xMn{wz zTnwPEgzb}i@M^1PO)$UiuzABSUCN77%k%v}{)-kFs3OR%27ISuZS?HA7lS$CTuL_N zTh_BgTTrU3a}<5YDst;s{8Su6=qQNNHdiv%qLo!uwitzDg9pKhmwZ?6=3Q|1jN@ir z)GcW9S`4nl&j-aptiUB}vJcWiwy6A+xP_R@)s<^qk}0E^=6*>cYjHW%+nzyGd>DAT z$wjlrmGuI^0I8%P(gLx8)?E=#h~&-J#lIA~gs@VFa!p=6^V=|BdXLJ#c&AT;?#vEj zL8K~|IHmF&81BcWHYvljeSzy@(ZccQM^Htpqses#`WUCJC%Iv{Ov=@B{{UY{0EH^M z2nE;fEAbs&g$$a~t1M;6R4N#C%ORxfD!Tzn3c-1DYJm%(bBYTprt#VMc>Z^}ZVg@R zqMcdqI_4kjj=$!J>*;p~FlCKK*lu1bwE8J?+LBlH{{Xm}Zrj9jvYk5p z?p&$6{Y$ye`=xi%YGrc3-B(ZK!$UgZYdCK%ue;+&J7BTrZvOx)yWUf0X2gYw;zDBU zIy-~lU>h8F6JPdYD$XF+gJpLI3^fD&EsvGJ!&XA*4acM9a+QNh$-6Listtk*iqr=| zRC4dRT@55b{{TUBY*+>AuG`;S9T<-TLZ<%!7;(E%Sgws;HvZ3RlzPQJygQ~riM6}) zS{|W%j;0)^Xg6U{BNf3`Y{9VyA5aean?6@{%Ui0tg?3G>T4_XbwABl%sDCYC!f3pf z8yy^nB#7^6hJerv4WQY=8!NN|Zdphh8Q_ZBplr6N9-+9^M$xQyA!+K?9;V<83BTF# zkpf<=yYYTxw4C56=pSn_96_%Rm>Kmq^`(HfXw=ozAn4tXlmn`m#kV$;>(Jt3p#ipg zR-J+Ys_C)_p`^PpZgh`XVN@d~U0^RUU81N&$uCEam! zbNivLDAje}5%LZ>R_1KxJ|Y>so_J_Bsn#)uAmVi55lq`nvpCmf387^n%B!;+q6MRk zCBAX5KaW#pwVD}>Zi$MXWf2k=oTffopXD?Zgy~i|5)9a8`02Z$+9O{i+cf>=aH#Qs z@{?y!7`F1#({?85K;CDcuUYm9KVX1%L$|Vw-j&1TOLjKFi2x>5prYG*ep>`)?HFK15Ht(UcCZkjA15NpnY2G7KI9$V77KQ)l=-;##O4 zQJ?v81nZP}h~os-THr09su$ocqPB61TkzG>wX!Z{OM?pfvl_m%UE043!>LSXFOIzY zEUyVBaqM$!I0J%fV%gQHdrW6MwU|_GXg-*XF1&LMW!BN<3LE$rPyB#jrmLk2zUU^8 zTGsynoGC@#Si%KWHDv;YiVB+Ft~GA$#&*&4fZXI|LD6rr9)i!SIf=(kaZ7t$*UVpA;YLXE^> z))AX)WeiViL81+Fq^tf>6eio%x^vtHrIuak+!}NV)X<3zqOJDcSGHLN#}z5S28GwY zxs?KY5B6!1azpqVbb5`m9R`&i&&w|vviEf|F*roF@8!tMkqqdJ` zW^_iN@8IK}<6LAgY?|oHgY&lPcPvB;Te*})*JFnhLApdsZDb>Ubf71^22-8C2FnA?hoJn0e2 zgz2iT$A952Tg6t~uReI=YJ)Um)7oe>+0NlG zufn*42T1NNluRbKZ|JcSv^mly@YRll#7Tpkw{44dFl`6Bd&#QnXlX5W6Izc92Y`Cl zsd=tTY;_%NW31EL78)8j+b6j-^t z%31WKh({Dp*j7$->Kd=n9+)O)4yERYkddlO64k|}(bE?37p|@`Bs%pdnviN88HaCz z>{+PGIsQo1+N{DR9Q8zZhlc1N%v5)jX2R1j@UX!WAiMC#^Ei-OR|* zrsMmhX+c+_&w{?mVSwr%(@FA$X-~$w@mt8qW^Y2^o@AA3(-_O&=JY(-ae|zpD0Zts5DJj`CA>^f0t3?_m z(KAAgo()R737b)(8Cc*}CdbrM1TaX9?V#>SVdzB={{YWi933BCw$hy9BTbfYk@&fl ziwYjG-YE4J&FvTi#>vcr~TJ5t(4@&r_<{a@AX%>Di4 z?_a0S+J64BzkWZWU;K4Ulq*qo0ML*bSY>9a?*tgt`etrQo{PZGhikM$2&kr@VC?!> zbs8N~px&QIGCaE(h^R!EJ>TD${{S$!2YLPv?aXgeNuD8wf8@Za_m}E4)1WW2Q3Z@1 zA$!Ufv|Jz0^)#Q)U&H%{Mfq$Ms@ad2w(SHFWW)+4m>{Gh-GP}-)S~T;O%h&otJtcp zk4cHcp;#~40IX{3?`vN48?ym7;&{rGJWGpegYiQvd?ehtqr1Bb`F29RRoHa zQFe6S75Q9CCEitn^Q`ZP0ZnUrz~t+oXEgxVKp(gHE}kA2HiPUmBgh!bh*}tgDwI_ehlOOOuDLU>i9! z&h7nnO1?CsR~%PB2xD&LP?%U{=_^5b3`bBwF^wyr+cPDzDDlBS*9M*oyvQe8W0q;3&R{@tyX)8Mq^`wO@@(y>R@{UVl;aVgQzvo&|yj}O)MaS zXay~lYHvFd(XFA*Pbs7kSTNoi>c6&vuyxu#{?a9+SGcZqb9DEtTXTHtiKNwo6S7#D z5~8y%(d$x&E+CvdyiRkh-ptN(sZWWI);Ar>j35om)eoz$(9ijla64R}&0PK7FNt*} z7*%)!w^Jc2&(U{tR+2u;iLr-BHUZFA;&xyyhyWmHG_#MFASHhvZ9X;gon<72EKmlQ zoz$8d;ZWrRv7c!KD2>DxBd#)3#D6m6ldl9LwVQ4)!Y9xg+M-or>5J-cl}(=z-&JcR zXmsT<%)o;#z!zK2LYV-~qjX4EA#Vsiq_%9&AzCgVf6iWv83JaDQ!CPtln{)UC~vN zjw}IVL}{c8a*yAC+xUew+7{DB`Dc7aOAMA-e~>6-%tz3r{JVRRrxmovRrwY$(Z203cMpqv$j$I?jo22j)}RD9G&%nOGj9^JcRQ{Hn!&sU$N*h~JKN!d zHp`&~UT1M2n%ap|Iejhxk$jgfg2{7nla`p)C=H4c)Gbktz48cLYoX^5ehT$2$lKS`@Xtajy8fYo?=Is2g^i||Z=GWF7ONNLbTV8&fWbdObYcptmEUH+XsIhv@(M#u zOkOJ#*iAxCBP`mjSDCLEtUJ3uf^sa5+uZJ-cz$(;IEm6Q0i^J}R|~c+wvC>cfoH@% zx5?7l{k7Jh&#aL48gzEMo>`#33iE2+;_Pd7Tq_MVXdW016I`>bwj<07_+m6UFV170 zhk?A%;5$Q}fJ^S7I^x%`08!f^n0*xHI_kQZVM{2?4wA;75}cY1}NgT`&PyFw1nRz8Ep7oXvV3i)TK3rNGE1KHP0kOHwkauG8*IKa}oOZ$M$cqTMp_g0h-NXa#}Tw4<9 zt5XB29(FZl=YGc$`sL>Jb$sq?y5KtRlc2s{^=Y)&fC?Nm{q7R61+PKlHwHGMCSxs) zOL2I%wghVp+*-2p;kf5BqI+YOUgDr5XgTupx+bRZHM%pk(_|QhIYb)dTpe=>Ekzd= z*D7U|b9m;cG&a6`3>uA16I46KUsd`xlxJBXVl4_)?)SC(eAngF-GNtO!yTQn~~ z@8BxJG!Syekg-W?(TzYViZ4L79gSiJHB&EWWV1yJRbgA&Hj%>sU8QuRQv=9J(zz_V zsGH{*lZFR(^9!PYax zbIWJd;R6rw^BO&MDqj?il`$mCl3Zz7AWyYHL2Ijivqqb>bpWr9wkdGhlP*y(sI^-x za{DGcOGXbF9Zx84Y5xEY&@cm z$r3`cD@2sMo7DY5Zl-~2=Rk6r2Q&`Sjhuz8rob?#K!+gbOR97MpkNw(1^UkHWW=x* z^|5o6-b~kzXEgOgtB>S)d3)9w`~BNX;`Y;tMYWQ-N?95y6oVo3-IQ-l^<Jyb2-U<*hKe@D5?ecIcC)EH>es2 z1rDPot6Y=xtg)T-y?)2SY!lN2F|A(xR({Kl6?jPJH$+U|HNkqWq0fSk&=il0jT zKQ5oXZ|vlDa%LUkd{?413M#QsQk!qg{6G3|hKJEvmyPbo6`7c`J2u%%Vy@gG)t8w) zyR^u1$*G!eTp6x;du@gNg0`b3G{c`V^KBqL+T9h&d0wWR>2=|koqXaN)7ii> z-qvj93^jPc?>^=_A(VJ^pNOV{&1iyd*OWv3tZ{{T7SdVI=7g);t~p;wP}H*Qw#I{B2chqjT`pR*QQ3Bxsy z&cAt{%)iBI=>}rzTbHcqc*h>9b#T!J;z8eKZBQ#ZZz=7XbFC$qP=*=92%$<`1?WI~ zg+K!&zH+&L(G~;~lR4hjx&`9}VIIq*6imGlp~P0pG)1;(Zk^n*ZICVGZpU)F)4fjr z0KH*Bsxnu)xpsV#tTW9e-1p!t8w59r+^qwx_BeQ$o>9qq@0m3BQB?(NR#l_$+H)vn zGnh9y;D>&e>eQ81*RLe#{zRzCi9^f{2J$vo5BExobza^SK+2YC6MK<+lmxvS7R?NO zgKCSlAe%sd=BsvENusb%%d#R}3kbEy>b=cMM=kHf5QkAd?)?tZ+yd&0sFjaq<150^ z#@=g4NouWR^-%=C{1QfqH@SiDUAVSA za3K>^tp5O2FtgQl&V-#bj}HQ{0i|(MRjcU;dpi4=wa z?1A)U%BhOD*4X)hHkKO37wEL38&5Dtuk3z)wfpn^w(q>6Q;A1VP8~75X?@Ovoj1F` zH@>pJe|i4^M7UM`iK{Bpff+p2`(;%w4I2m-DYluI{rSfiZPvG&meTQeTYmkwozQon zH?JfrmRe6SY+M_xeU$k}LXSEAX*R3M---#Z2O}9ygsmM}5rFzKisAGfJcH^h?J`#B zHBKe)HA8a!4WJmI26GmO%ju{ zzH#bgf|^V;fN82=h#NkJP*bbl)w4#IUCvIpqKpAba=Jb!RtHKJ;|RhOe@ z=+U3Fb-CE`)A$d*zee*hsat;S2JDF)Yk+s$M=)kdh^FG)@&T?`T!&~?tcHR&uCrCo z+UC8twxDfjXQ%r0K0dW{yQiy-TaRR36Yz=+Vp5@YpyXc1A0*1LY}G>mXv8#Zx^X?82tDkP(N|Gh4hitnK)KCqj!{;J zLe}p2s%>y$&;|u5Mg!8GR+hZih8>hJ00xCc7{z|u8p(0Iq)n^?F87Lk7;E{O{{Yy7 zygyg868?37EO#8Xr!5=<0Lp!liMgaLqfLK0#dN)L_vf11jiRDjs{#tH@~iIde~OmE zU>9b!{ScL=y;Iq1?e!I8O74%RHzL`wi$2@4TXnw8IVN7F-B9?Qm3tCv3^$hx;GL5O zxo{pf$)fLa;m%cm!t5yEibkjneeoF-t^;~Cf0E~7&MOtISgnI|lSgrj3}!l{yVtBB zm@1sob=Q-dcHeQG#%NIX2ZI5eUe1`|DZN*Um1emOsWq^rXOXXa!@456s68)*Vd11D zw&U7?szm@+rprOAaHF&dNoTvE?ccp%(uOH2IR<`WHGmjfZdLlYg(g;)>{bqd=_nn) zm-FLELx33|G;%ENv-MO!!yzZQHXE&Q11oeQM`}Zbsmh|d8;6Mq+gHbi`HETs_T%=4mOFo1W*4 zd6skZG-dr7-Vr{D9BVJ!{epgfTzK|Oex&w%pZI9CxJ_JhJyA*!3&JTZbFdW$e)V_x z=a*M`b0k#5bC!DyoQhyAC#tTMvt(BYILXa_!n@SN{JpDf(j7yl+I%LTX|;?CWbNlw zlqtY7WvNg6LvsCQ0iZ73x_5VdZ-N!AE`sP#O{}Pa1-uYxcYww^b;SPH=4|_m2QBEM zKHsau+4E=OF;(%6JU)lh`@V>2p8oLwP!2)J{QO3;sx~ig%wN#(pUaMU0uu*4%z;7+ z1U`^|>{^N0Q34UTRJg5H4czK*t95K%7IKZTLKrePS5C8B_oX&t>S1y+^zWfCxxqBcnWy9uJAIes^#zOFf>D3i-X8pQwu=Hon)-M znch&bGZfm}I(nzLc;F8wn4kuUQBG->%CpSW%-4&3v+z@bvnE+oO{5=x@9k7wyUR@L zFuYfK)0I(u5E}1>LMCqrOdtcJg#@%&k3vP|3S|>e2&B6TXrwyU>+?KzxM7%?mouV2LWIFt z%e21dFHv7T%h=&pcJrQd%qdt-tygPW;j!G6(t-jFc_ddic=*Dz;QB zdv6N)Knf6;Me`rp2#d@tu6I|P*`~t-J)d%#s^hX=>w{PaGU?>qd3fBct+&&?wx(gL zK*VYaX*4&sd*OTBQ84w9XO`VKnPMVtZsxca?4EH-1HO;gFia5vOd+$Kz#Yz6cr1pk z?gNq3GMEl>NHkWGyV;Iw-zODkK)CY-o6-wL400v&99swRu+8rPVrAi;OY5)yZ6l@mUT| zos3P~E31P`98s~h)fxaxQ)X`k7gZ4}-A~kwLF*%+?k>8rYe@e9KQGQhM38%^{r&mpdH$7IQ(c(zC`zk4sPk}^Hsm($i@;{-c5;yR?SOcg zSHZe@`XAk2`IBTky*+w*X<6;P{YAOCzTZjlM700}pdb#weO4lG_4Dt=p922?KVP@~ z20@AT{{YGU7v_D3)9+u?%PN1&<{0&l+l%D+!Am^#8&>Q`XsSuzcAYvcA6HBm!%z*- z4PfX7ft5peargV05}T6*+W6TI9RdL zSnpZp@G%_ejoxEA0as?vZ(mE~-YgYx>{)3N-l4JXhvp{UKAc2{_OWmC3EMHto2{lY zm#Su{ru1bsTltq?Hu(AoMugmP^Qq|>de7ER?r7C*y{;*oxUr`&)l&>fl6p{i(6-S; z(gC2nbu=}<<7U;W)vobo#HSMz3r@~E(xA~YL+Y3Vb0Jp^tSrR%18b3D^f;{`mZz0O z(oYJeVXwS?@#-QPjF=TfMuD9oYR&OR8yeAT=O6Nw?K}SfLXM!}ZL1~BTk4It&duXd zs2VVeloh6C;y_$J8yw51g$tmlwCTHWF3KE7qRy$qJRucDHO2;z5aLLt!T_NffrZhJ zgd_|70zu(I*x^={y2b#YfG0;fE+E4MpojvV61)@Gsj5m?Z#*}q3DTck|GjzS!Cc z5R{Bp(BVV6DpnP)xf?6=Xn{9GyUzi72<#=-dj{XgcQxdN%|ac;!}rl5&=&HnHf*nH z7{RI^ZX=sk>E9&>ny%l8LoY&G;*Vongcz(=#L&Hbs0FCWYSrSmd)8exmt~|v)2*;W zyuwy)BF-`=Q7tdGKfFR$+Q z!Q+!hd5*r*E>wuF8I5JBMzY|gtR~$I)wE@X zv6!|R_{Gu*+{^@{_sahOFXK<3YdR>MSH-ygiQ7#su7m#CDY2EbEb#tIo!=J&Lh4b9 ztCz~=Aj9dT>iLOAOx3}+3&$sDFb}P1JF;uCCpAW47N(__-`XzOkF!qT@@{cR*#P?f zvv+>;B~v%q{wgQfeJ_6>hx|cmXENRI+_xEhleXR#uCCEQpa7r(fHVLA3DuMdz*%1g>OgAbYW}Cmt~qPy04bG1 zg7+6j`4!aZ{K13B;HsS^4MH=+_q)^2?R^nSD;27OBaI9?R#@<06kh>fh{J!Ve~4+} z^e&h_N8p!M-$de2;yQl>1cO?N@*q};->AGhR4o=2=fILulPIuoj|efdfZNlx;&dy( z*vh_oO!qN!yfVXHIhwbo$7TF#>+LwmTCZgPK!Xf$U*|jV*t2 zaCVLsNUOQ)n_A(fAtO1Ayjn_8rYnHf@0R{ z;TdI|G!{RA!#-Ab^UbkeS%-h9xWU7QVXK_o?9N68N^vdAxVAt)ESM*dYXwwTFdW_{ ze}j_EB2$QgE3%yyEa(<9S!Lh)Gn%A3>ZL)?=n+i~i6~-+7(FyFkqGEZ#Kl ziHaM?5M4(4lws>?rav|sUb6t6I!>BE?E`nI;?e3*o5hrR82dyD)U(fm^AN^RCjjlY z!ikUy>n726(QH=cD4NbR>|6*qD`1T@Af;8O$_mziDHyC^T&G?dSGmq)U{=qwZC5M= zz#+Y{v(cv23`DdPU9GN1JaWpV{ulL%aP}F6>?LZ_9dlhlOgTjJ_2|AOt)XPLy41+! z<#4NqU1OaV5c`3QqMKt!*d{D5PyW6TQ;BNiU_DL!l)g~gkC`0ADW=A|ZUqgX;4y=l zQWHd>b-qo@GUDb#-4rRr=Nk`0ujre>Xe-mx@V{{#TJtD=L)ki~s~uYNljdBZ;u5adH8#N-xV9fHz5N0@h14;g zxUYDwxl30SdH2jsv7-%Xr~7_aG>X$p%^Vk}0fxaGpU5ZXcG4_d%XY`Pk%3#k68OYb z7NYb0#h00OLC%wC*U|h+Ig}wX)xqOQp~juZF8+<{_MMGaf{%-Q@AdR=-QSh-;&Ta( zsk_|HrD;y)jTfOcKuZNtrR3zOxhVYWoo@B>{Am#ilu@kp>Q%5}p2vngGT*?<3j@6w zw*%`@ErL@ja3I%&bx-CsEX%nPfUco*{Mc3gq4%%f{h?JEBYj4t7m5E2;h15g$}31pTI;0I=VAR*0aO~D7|dnV%c-sVDef8P@bLT)OkhBwn+IU;tGvg|>RuHa zx2qRm3~FljWly;eSms9CVGCcxx?U6lu{QAVPNxMsB;^} zL8F#%)I>IGd7Q9?ZirP#Wh**avmNqe+=5p!)pdc&^@el38!K(nI`&FFb<5Y)DdY() z>nHwo9};ws?rqXRT*NW05w>Zm_Bn(xH4v>P@e(jM)6d@z+H!}{et!|^J4(uL{YrBI z%n~}>%vEWaW1Rq(Kxn^X#(Lk(tWp<7E3KvKYg+F!x-RwZQTq@Vl~x*|;`*A4KWB`6jv7v;i)y48=8E zmm6x@+MAYB>&(_t)Ei489*zWQoNvoS)oJexIuWw6%WBrmw(@Q)$?Ggg6z#68H_(ySZh3#8~#& z_79k&Z^PtTt9$n@rpk*#r5xeEGOtdnS#q)LYAmLuqo7~m!7=n~GXmElXDC-cfS7cb za40eQW?Qan@WVj&9H%bts=qs4vbKQEy4Q6Avp1df<4Er)m{Y@QIhT5S%u!4S&6hJ+?HSJY{3fC0dYG78XkEd~NOl#e*7c$blg z$upGwzo0kj3XDk>xa$2T1ys7xHn_d0l<5AS!c=~kjg2*l0_o@;l1&D2%3j>=`X3MF zQ|=)9o6oQ{Q5#gcBVtl$@}WbLIw?O&m#^jV`}3^M(+}HBUNH=W8!uvp+HvemO6-O~Vm>p2YhPrargh{7Z{kss%{0TL(ZS$f$pdAs}&U zPQr|%_viDMmn^^CceH~=`?=<3d-;%7KOqy#lW ziR{voiaj!dLtRa=4T-Zv)93?~dJFzST~ukaT<>sPL9B`k6j7w4z6Pd_^G26%#GwTs zii^>WiJy8X+L|GoKW)+IBBkkr?=MFuyGw&!?q-v14*vi?uAt-b^b37KRMG@$1FY61 z^7K#}2V?-iZ%-AU%*Vrg@E(BW7Pj$ROupZ3KUs%5Z~U{XIL$WPkSG5DMnH&0Jo{ZP zlg*a_j4)D+9|_17a)c_>0@Lyg23vNBcsC`W=T^qAFO@o?jogeahQSXcG~K5l4yE|x zv=PIz3JU86b4cOPJf4_M=}9OoW`|5P=AAtWP)(1O?X2kRR(~Kd!xgJx?-d)&lLs0};$tk(QLqwZXZTIGozX6FkUvim_L` zvLQ4D`Y>FL(H0xBU_XA)Ro?nuz9FJsM)!a=eT+klpx&iQa7yT}RFO+RL62Qu=a>Ru zlo)j3_sNjKh#T8{Wv3T5%3u0Hp3$5tcEtq52zPW6-c7`!4)* z0|#D@*!Xga)gcDQTW+*hWR}lXWi9Qc-lyCLFwkZjyE$*Lql!73M}%I^NPq|(cU4ZH zh*Foz>et|5!3aXWR}23DWvQz5i~BtHtjaXCal+llg$oD@(sBL>2MMI@4uPp~!>rSGO z)Kb6sth##t0P*DD*{2XP#_G+rZ06T*m>l27;!-#)*ZZ&D1*yzob6#S)1{@b#jaz#w*@i&zJ0JJmr`Mg)r`UUfM<`)S>w%)(HPbVtLtI(U>_#!tMmbgs?llS-f zmrgFBuj7nF2yeVz>Pl6KqkM$e35YCk$0%EB{bV>HQS3VaUpC&t-$Ll%eCH!$=JR^q zb(C@Yclm~5j^f$wjdi4J4w=|g+S6NFrw(|QCq@OZHIeAQK9rfLbqPfY78?x$=4&l2 z@APgV`U4o%=IT}%TLXiO^~3k~=6^B%cE5vW!>s7su*}7OfF1J%Kk4~|Z<>Fmkd)X5 zk{?gkm=(j##zg8#V-v z>HfY2g>vvufnooo@D093qmwwRb#ak299O9Mi?|u`q?s zChCUaX@?{jh!<(uCr(S0of>$6h*TSUa3;dO$uUDWS9_G_cZu;Uux_F$!Odx_Rskb2T#tn!zMRk+iozn^e-=UJ<1Pf~~u z2vw`UhO~#P_qgxbBwUehW+AW`@Y9mrk%D-#pEX|-jG`*fu8b8nuXQK|LP%mH2 zYh4lm!4*>ZL8GS-Qmsa;oD6lP3Q+a`a}AX3Fq#tsJ(A?765L`1$PikDjZH5puo-TW>f1|f%d0!g++$|iJk2l6 zqBO_uWwoaoDZ}uHfkzAbiM!NYjNb9pQQ%5lBLs`|Y-#kz?>|!Gn7qZ0{ugepXK7K^ z5@oP*90{XIr_yf(I=jjsYc`Z`=PH$V>sxBM^3 zCEHY5;2u>#EtyY<6Hr;K7;g(rXnIi~xOMBXCCEA+&OqvD{?G3-R`kdg*EVU8OD#sC z7h3a=J%1WcGS_@nbww8GiYTl^Qe8C0PUVdiH3~NLE!3T0?MF>oJ8_2*rq*-@yy!{^ zWZ1ngzrUFdIRgaYf!@U6&;a99-!=|g$sXUxi2nc_L_#+RD44z3)ln>!T>a;}9C#G~ zc?nb;c*u|VDaSDxs3Xy3V9paU5cz#mmJnMm^)q#rff!;4C;a3Gc0l8DnTM1r&uVCd z7N+{;avdErd&1n9@TU*0HvZrdgQF43PezCegH0Tnf1vVBcNfsrj!3Crf5r~<0NSicOqeH^4eI>Lh;X_pLwbQHqabJ;o-GnvrCqo_#H$7M z5H|a%OaB0qTBCemw>TyyIbd}JRv@DnzHi>|%du{EDN-&B)BgaS&hq65rT(ZgKHLp8 z8W)GZ@`*K{gZt59`4NLM5Sd}{g4$49fu^GndBmexw)xWueU~&pe-j*i z=PvBz-ppa3{6V~`@au1P_|i1mvLcGCW_C8E!cv!LuR7_kBY(hfVX|Ij%yCem$}Y6m zg*7^H)VGOB;}WX7tdqZ({52!kE}K6A?yeOw1C%m5DY_S^cCn@R9&Na=xQ-x@)%EaF z(#SY7}kY*J{KU;3poLpYQQ0e zglV9ZRue%TTXiM~bc`<`lx0q^K?nnj52{4%WpM7vE7n{0^!gnOYVNI8^)Ne!JbfV7 zGbm?&dgf`b%3;bA)VuwD{Lg9cFL;VOdX_}AH@o+&Jb9N|@jbNQF7_P#4oO&)Xj`!R z%G{JYyGdRD0NFEQ=s&>WFi_-+w|v-6bt%Z%9fIngm{YprqqS9$FAH=PT9kGZ;2rtg z`??mPPA@ZIFnWMBdMdv(G6`zi;dhHSJ7hEDxR6}!2h`=#2P06Ia*dirTD=lA!G8$=MjM@gQ3(z}hpU|g#DSJQBO`TN?pudz84f7*X>%WhtG?Y(W? zuIq^_SxnEl{olH6pJesp@XMJI&k zT-DmQT3zv{jA@B^pd|$$lxQMTT)hy+EQRB!96i9-R1Nx7T_734d`?l5X;@ZqJ7&8&uW5w6rRorGK%%-`!1} zO_^obD^Udac-xJq`~(A1;E0$I&D!+DLLN=}U>d5+?XN$cV7@4|Hamu#v#$eCOSNAu z{LI_ie`#w~ayf4$=A2$!_~4pNM%#67C{R4x!m*|%XW5tghx|o zhDbG**KrM{;07=sA{7Cyx=6_?H+o5k% z<;>OG=|wzJzL~VNC$d7POKw4Blneg=xCw#dSXKRd8cp2ZuYbwm!4hBbL<&1uS&n6y zQEPL_y;aMWm7OZOtkCVA02G8;Ssf044^ifGxcsuuxpueCx08Q2p94UHEY`n%-*}5m zQNEMkCL1#g))-Vk1*GtHnewyTvy5|p#9EZ75ul1Jeixl)1k?tJp#8Xc(RH46dVAJ? zFr!Zq8K$$myHj&mGUdL4rlXe_EAm_pOT+~5dJJN*x$0#5o|2!EGB2U65Y~NGlg!I} zOhSEbrC$*ccFAnGFOz{~tw0=NFxHUbu5h}iC}^F}uFIH?HFc0O)^4!L=*$xlFFuQw zUk>EVZpA}nOF1Ir*&E%a6UdwmwqEg$Tw*lVv+=5-Dy6WaQkGYl|o__u20fzFGbcc+@Ln^-_i=%)KkVHz#2=W8xmkR{3VCMeuExjSou$(TQA^!j{{Vl<6 zG|V68zcIrDS9mFXE!q3L+wld=@>Iyuykh*-U%QE!i=65QF;+(OTH%-CX|7{$TmJym zF_B`)%*k4<4g`Ccho6f+d5Q=QWrm(d&WzFg%hkc1KiJ@vuAN0s68pSl7ws${G@0gE z4JHbsajz|BwOwG<^FONdZRekN7y0|^DB5Y$U1qp+V4`<*o^D&Ek#@OZ774F~ReEG8 ztG4cn%2(ZG?g82y=j&hYS}=<4ALHN+gaKS5oQA!QGY}3a3W9A;!{L9qJ&R*-vf|BM zYnind)`Ng$swn49XTe|`6_{Z_5L1!Ku|NZDa&x(fLI|;j!sNJ1wz2jFiA6f}sU$C3 zn2v_KAY)9h!@(BMHQO(wVt#_wrb&5b^`3KoP{N6#;2mb9zlNK@b5r(T_ci;uTVnZ` z7|7&l@wHqzU$|H>z{v96VOVstuDa$r2T^~8RzpgTL{VE0uLb-b3ejwFsnvLL6|{n6 zrChG@csO}+m3#YK>As&3194%cpcelC0EDw$jj`u>7g&tLP;bp%sukySaTUpA@Y3p{ zoC(a^q`k*1s*X&+l6fM1;PIzYfz)q!9Q0H)hDm<0(MXM+I^S-4@c?|276)Sa-UxdY z82wJAk-@d6C%40C8sho^ZTp=ZOE@0Y-KIO37^(spRidNXTca{*Mg}~^Pda3!c8}#9 zUf#*lKuk0TP`GV4Y9ny8=+TRqspzH9;8)?9ONLpjr)F_7>s@}0ySPTeY!t)2(uVCZ z5_Atn=WPYGrAbFbqFC4%Cb8#JXm33P;^n^AE&~Fwm_ne}OTcoJ&AsTc{!yub$!s3L zkrh_Zw;)7MY}>uV*_l$x)Q(O2U*r;|kzAohr7T%zM{%uw?NMu6>-+sj=l6+H>WA?? z{9JV!)%cpg2;(eU(=#pn!(AnRvDfLme~bDqI{j^p{{Z5M7N-UHBdcFkp;~CmCL1Y@ zw|bvxjlVMtU1y7*nfvqfuH|gi*a2z8C-HVNx#t@EJ$Lj~ZB^}y7=w6R{{X*3Af7wJ z_!74x7_b_{9v$WIL4X3HsAFuuf8RaEV`lNhrdCmzG_CL!<*d%8Z$z$QODNDWN+mV& z*GCwf%QV&rPN3U~?l)54H7$^$@TRjmT==-6A9nGqpDtm*xUo7fyO)S2JX5}7nMY53 zO;%xp?-w&?FuqgNyi5fCVt!eeBT~c~o^d*USR2?GTTL<--!AK4FmA&iVpEEFf0&mu zW*s9B*1lIdR&?SHFBN*kKZVWa_tvuGPqekKlWE`N^)zZ+@{HtWa$uL-wo$B8pXyfs z0Nk|#z|gj|tu)9N%@?HH+`k*&?LaY7@dlutkX|lq8$)cT7g~M8Te##)D_Ks`oE4=T zeXtfk7f6zEbfz>fSphXhW*aPqA@0}r7>ESjiinf76Jr&dW?g$|5f&IC^$Bci{3&H< zHe^UZ1SzTw)$s4AVHkG)oBfn{-eS|JmT1H-rEVA0xVP0TWuqbVXy}S6+NC2Xa-3hsu-$viW8|j=M zaN9C1;wWktnRzFG;RXw}i8dh1WG2GX*?3r=F!kGo%i%W&dlU!dyXmzQ~K*l9!}CNA>j)2uUp9y8*JJbp%1@?qf*g#8i+%P$2@`x+^xf zIVeLBygKB%q~V5@3Ujo}9&Pmzi=f8Y{e8zJ7+AX428e%xJ+zZGqT=;^%@cIyE#I6) z=3VSpoiA2m@dlPI=vE)XUb&Xg-(LLe`?;Tj_nuRBC+k^niDt0N{=vU;`ie#3=JQSE z$1hN2kYTU>Y5IA^eHGh_f4=^WXD+l$KC`2wIOOJg?r#mdKJh849R#-x{m-XhxK?A& zs6YbHA@~}4S81y;S-iA`xPVr6`^|o6M;9wtRo~0kop1h=2Th_n8rHVCtu!I3x}Gtu zRw~=a8RO|;*BrwqU+xUH+?Fp>bDZZfH>6k2b+aCRBHX;)e*XYdHjX*Meb|5+_v&b) zhV)VVOLm7MMgs8so~ebSGMUh6mA=;JIDwnjnT1>&WXG53i_V+JXEh}goR3&zBEYew z^(#e!ZfgGkA3^6$>z=3Oy>8yd6Yq#hUp<+YTJV1S`}!*ensG++QIkfAr=f*zRLI5% z#73HYW;E&zQtq{V%rw-Ni$>^&0^WTNp9X?-ti?~8 zmd(~yYG6B;X;SU;^wT@Rrk~%;22+;(L3gj;A48t_t=v|XoK>}2Tgx=BP8gl1 zLpFZP5~Pz{S@8Rer$R0dY3OwfvmnO5whMvGTGm7MpJn$GGpkhF`q~)8fh}GfzR89F zKquG_7&?~>%FWW5JqKxK%L_-U8;E$Z(VY!>v7c?3XYIC0l4c|sMj{I+GhJo?Sfhe1 zVASIfs;dgd0=Y@Rw4$BC{Z9;O$D< znh~)Ia6jI9sxWxr45N?TIm?`qw zqd=wZ@3=xGoXY-3TsU_pohN_0{{VlqOQumnoCv_|WCDpzp~c>vZx>a_{$^A=F+i(# zPHfV@Kos0CTKhSrCQAkYl{dJ^+LSObM*k});&RCMldARYKcft@EbQ z>zl6s01?hj21gZJT5H%u+HtC@*cxsKXkK9c+~$jY`3@`bEO-3NsJWH>>UlnRO9Zi+ zpQdG;M=#Jj`gin*o{%VcsPJP`MH}n2?~{DzJLg$_QDU=Y_IXmnFe|_vB^{kZS&S%o ziW94=lnI3h)vMXgYOJVSv6dUa+9Ua6!XQ}|Sy_gv_JPa?PODu7yXqUcq~T8-1HGpB zRJ_4imF*rml~UE*<-ao6)48e! zbk2!=;^StZn`*GHRXQU2sx-+e+KMGP!>-*5;GI^JU>T}&gaxSN^&Vp*0QMlgPs^M! zLJH{MX>~+$)9I()d4<{&B?Bb9ME)F==%n-x&Vl6dCa&CO!=$UKZe?S1??aPRo%QY? zy$6N?41oUtmdgXNsPe~%Q);h(?1-j=i*Ap)jVcE((-iYTC!87|L{aua4x0g!%R%J4 z%Lxktb`L4AEEaAq>fP4XyGrjafl5(bb3@%D7+6%k!ujK-WwloeFZcxmNYuQ@0IzRk zUiTo)1Yv6DReT(sT7|R?EIllx>i~g;I%e14JxwUq78B7zb~s3WNmGJFLf^VbP?q{J7ypJWxp863cw{nGY>LzN?cSpSn zl18gN8v%TRBD6>fx+yh`z6M@ihg{&wtK2A91Pu{RfYjSUc7`4caH`yQ2ByO~VJ;+_ z3LuVp+l*`T43qsbF8yAhnmxDyWwD(fr$=!%^eIEh!BLn*=}p#E?ZnxN*X2* z3M-i0BJe7@9rV%VZRASj0IvqCvk|2lUfpF(ASDpcl*vNFwY2togT*7LX`-){6TF;j zHKH%US#xAkwWuRUl9K>v0lAF~?hGlQGs*yEjg&w&6L9WO5|)6aiWRBwS(UF4Z5b5W zPO~bJv^Sy6FgUIU+h1jfl8W@epd5+9kc{a@LStOjKxa=J zftzV!nE`=tZXFnyky9}D1sR#%jf<5_yr?Sej^l-qY?{KIeyp)gEJ0Hl+RX=Zm^3NN zF}JYUj+!|eZsX(5vxs4@3YsIqrnnA5kq#Kux`|*V&_Grhr&;5|UKTfZ|LA{qCS+TEM2J7yNV3%@Wt3};jOD#EFvj%QF&M6zoBOp;Rzk2VwMQWfidFa$b z2@q&rG{j;9D9pN}EP+6KrD9r}Nwkh7S*tK(iy&2*45iT(q+|!n(!`IUhF3Kr?XHNR&GgN3=4VUKAIJ0eI0C5T}E2GsGGH{tb;gz3mEDJEsyN;m& zts1noL3(^Z5%s3!wP;lAQ8*}I0dG_cuEyfadxK@p2vtSsd_Wni3l^&pt$n%z18KZ; zUSmrX3%2VU7N4cSf=a0+U6bzzmiDK17Wx*H7}D57atTZuZ>|RTD#uGzV)Rru$anon z&bBWdfmjN_3&cZ!L>dh4XQ~B@he#z9k?I`44)IN@OCn^^-s86CUMs7x>HrNFsAI$+ z&Y@ihQwD{MkQ6Bt9RZ-{6js1?1~>(P&wXH;sKSP<*&<^bP({*1oGi(qXDlsx7l|R% zprYGZIv2rFuU#>E9h9h2%V~G8j-w5QoHej(=L29>pfVQXBGDHVsxGzv0A(%q#({w* z#LTA?OM#U2mVGi#<5+>WB~~Dm-K?GhFX>db1J5<;ItWzr3&cSt&Web$-4)fSdh3l?lkD%NHkNujD$E`PtN=Pr1>hctHY^QVx!B|~1ZYa-$jD8c1v z&x%-3JH~d9qlkcDcZJ1XBZl%=HEmpn2=SN(qYbRJkQjy#)uL<>ejH@s6BiRTYR(up z2viMER;Onh?WJ)xnd9jV^D|rV9~kxYm{VQ?)*|hebt#zn%txbiu##IrAa`C_#s@Fm zH4C-EI@rqRfHm21Hx@YBqqZ{&!-byuI|e}A9#X{Wq101*>7+Ieupo3g3m*t(3@-D3 z%8?G;+y<)Z)odqmfR(0n!FF_dr?5{^pfbBe4$~20>1g?AW}1LjHjj^@p(|%;J1)5p zil_XfRB3$E$#3fZ7oGyDLVK07Ze+bxccsL*w5o;|*s;#N!ke{1Ue&*x%@sJf-dVmhfSOBJEL2FDgjhKMdZ3nDX#%q9T8B5xs zFKs1{cEd9P1`ZFNfyyLj^iNvjI=%Tx}fKcslMbgw@nr7hEV1X1n3lmIk(x`0P z-OHv^p#=5HkHw~uLBLX5-C;u^*?w0cl5(*?N;3p1nsmlUbuE-LBsTN*wv#Yxt7%o! z9D!WWp=Jrk?#NQHIv-%YHh8AQ&{`w%RT8Kgn3mPj^9Zklae;j_bMx0{+a*Bw@Fl73 z(xr1yXXm7B>LIrxfd%&kM0j|szRm$1(!rjDX=Fgvc~-aWN<=MaOc=7hj`7ApESi*x zWJti9ykd$zRQj8g6YuxvAE4DBOXv#zYHVi{M^hG@f|Ccp$`gW&(?i#EGT1qb%_Q`H zm?3UJcUCIc^c!#hk7e*T_CUyL9a}WB@`7AOObB50V_kTZfniS?=H1?))G7hpuScv~ z#%;p}2Kn>x0DG+CThw|F8-d(s;1eTBL|q^wT?GWn_*}$SUIU0k>gO1x zn>U7rjx0Xv9>sPaNU(oVHUiky6o6zrYL*b*+XZ!F>Q|+WV!GP(nL!C35e;wPPl1Vo zg_hJR&3FoY+Pbr94tx|!KAed~?hDpOF|zi(CAPNm#T{pNEKyE&l;p4Nu}>JlJTXl5 z1noo5w4QaDwlvH68bs*9H8TZ*fp)1t3jyC(BL6{{;S466;f%_jD$q>wv zO4w{u3K17FErC+&RVzZ^f@dRbsk&s}LZtC-_D=+|g4`yRfT$Kv041G8XTGZDSMelp zXgQJ+Za{)I-W^u;p~GicV$6_QA57X>1q^CrZH}-Pw$0K7D)bB6D;376uj~_Gax$*M z$R*~)7D!U^Jq87-?kkQVDdZ3D`9GuvLDj(6UWr$41PW{*H94)%(=Kd2V1q@kyQ<-3 zt~BEqi9_HHLZNqQC@e`oF?5||5#Aqq`(srWAdMF2WS0j@<)SE%c`-8kZtW&)-X444 z7Dn$BVC-kb7X-v`gJiN<*YCK8H6Psl0I6NUsIDb*?XLA6UH3fE6>RVopwr0?BLu}c z^KS=+B$-*p>lK4Rir@!QBec+D@akQMf|CK>^}L#`Jg1WN%)w}cS}E)s@bGzmzqWh7 z2pq?^Y7qPNaD{A!Ojt*Sq}7TQ%WP_^4CGvW;91Qd0yZ|uBTkbQWeuP zCNl$LGRDbRZR*YDJ(jL{hcR>$1(_wWu#h%@BIn7?u5p+}U1SfXl?bOZ+w3P#gz6oI zanUh)3@RPJFmi~sQ8JZx=5^VLWhdV*FZ?vRmnBX8V@F6K@B=C1s%&FQ*_YNRQ0ds= zhfthcIElwxRQ=)e7@ZZX4jBj)wR2wdLG5QS`9Bq_rxHD&*w6_VMbb%OZK zMG&)209pf%umW1z-IBXC1{A>TN=C(_%W;p94mmM~$ZQixX)eqy_qD?qNhzVJf+1S> z6N`yW1|X3r*oTUIlBPw$#PQYw(c&!;lr7i`_J_dn6`!Le6)0u}RuIG(1xOOTO*;Mv z7P_@vZe68SYtB^ft63Q+4<{T(23atYu1Hi>nfflmCO)ppgqP=^b9?*78Y5xT=;{Yi z+OMk*L$3apSEfo%r?0C7 z!Z5Q45#4H`5yhlsPyt+{IJ@G;!S!iJQbI{Okp#=iu>uYGw~<0myNeLuFk{q&2MVmz zvaptUH$WuTAXco})fHaOH9vxYznmtyMb%3*+&14s8&dH1sM(_QhSt5`EcOrZ>zR@p zL3sr{hs9$^F%-1u+PV`%U3G-lJ^&6U6=+xCN=#QG3+#2gHD33aV$mwM?gB9>RI;j4 zGF^_op6J4PYa)@uNPN>|^z3{aHdGi1R;DtB%37Z=^NIbszG#4)rDug~_b8burIGwjZL1fAcDR1Qv8M#C zxJOd)ss$b8tAxZJf!D2?ZV9B~C6LxTgB*&Bv{tpN-^z38yHLw@3*fItwR$x>Jc6N^ z%xoee0Dva%NVQRR!`WU9c9!@sOG$s!9l74St^nWt(%&FszYw}%$sN% z!hy7vE-W%Hpr?!nj&+#?Fluc4K`N>NXV5s)<=twOjTzG`P~7|g2#Z45wpQ!9q>7|Y zyu6oFxtmKucxwi%2QysBG}r>k*^&v7E%6A^sUq@?Zv~OA^l?dDA zx~5f)h`f`mN#Q(Q28$&z+`7A&shxB+1Wu)))w;WrmA9*Dr*h~o_6i&w1j-ASwUh?! z!o{1;u{X0?nr#4abn&efvmJNjmv+XDxLF_D^SSuqUzluUy(;0V)z1sD5&k*T#guNy z$z*BH0RcKcRoyaTC?=i><9`W`9M40__ah>9F%_wgkl*LHAnHxQ(U296%)}`of14}> z=L()?)mdW^ z)efcGW<*#On{ri)?Btl|w~g`a*XYI+96f2q^++M1h3^SQv}j;f0uCXzL7ntZ)WP@x zg2`gywxCr&b^{sAq$=jx#i{%KxqZuA8EN$2q|N-+biGrH-m2wSW_x+XGbPG8W7He! zt|J*CGPK;gFu1&|$52!&Tv^Q+%ETTWUQ4|<&V2VOvU6-&=!>i5Q8Vp95cGY2#Eq{} zef6wir42_mW=y1;`rC2}8gkFBvOw&^7PcgqX2m5k zxiOFeh$BPnw5`SpKz|nW854T0^5b3Q-4Rf+jooba3WGkExd9UFbj{GPUX(p>sf(_E zm2cpJAO()tw=^9dJYoQC{Ee5QbU<`%c#|Xgk~hnjPI#`!PLy&j2TFTC@`C_+$3XnW z-sK85M!X1~sSC?Cme*YiTtdy{5wG%dA|&s`L??1u`cMD@&?(XaYNK=yyJxC;gB!HZ z*{blTa9ZH~dk~jaUgOn7hx9CRvw+iTmbi*j{{WDfV==u@m+VJO6sek1AP`vz274FN zbdaE}?=KkC()d>=T5bVky5$mwoTj^jTjqUCV~#0!<7*V?ind2{A~ua`!I1)iQq9#& zSRI16m&?WC?!|9!;zlOuLE0WbO`43krqJn>RSO9*frlBZ_BQ6W>*yZfD6~au)ry-n zb1SkKH1$~ml#5LKLM@KSehX07XbN3u0f2CzxK#kuF!`2g0P`CY2y)eh(w~3KrxPt1 zT#QF_+CCz@G16bQh@W-8ZF`g|3;9NGNvGR@4u6Iv*349eZ&>3uFHzq9@i&^Kt6x@F zrX+L;ss|6gMRl2cl%`Ec*yS9US_)&BwpO(2I}kJpSD-K&pi&FTHw3U}fFU6Dg3J>% z2;hb6%=8uZ0h1A5BJw9b@v865!+!*2{eotUyTTiFtD{0I6G&d~i>0iJd@Wxr}xeS>?dmAdC)|zWYKW|T-);edaed4oF^`C zhy9IU7gZQrGibS~mr9C@`UHH{PHDR%uAz|VmeJ-crW4MNC};(T*Q75ty%){l!u2Uy zZQLxNu$8+22Vn}h!Em3PF|1y1(w@Z%Btr@~!A%=(M#-_yHgT5fF+PQR0$r{Zf_$2A zjox82?Y~`Bfhogics`Dzm+BB4_2~fq!*s|Tz2f##kQjhI5tXG*l#^xZ?lUj`kCi^o z+mVdx8${nEa>}sP_LM#>Au6nZV7VlUf6jRP%T~%U3rH-}A}a=BH(rDzomk5D1$@+; z?Iw&U#xyEk*fqe!E5WwaU1E;~0B8kOVpyL)&ef}`Nk?&y7RFIt6Er%dpvWzi$icl7 zngpd;%aslgMzhMng*Ht)rew4ymmb%M)YmoFDJfZqv)T@=HXcE;FEutm7`dsMvypVO~Qcl zp){xl;oxo?J(aIfVuPHPRvwHi{R5Gz;E^plC2S<#GwF)(0`=+SvYk~YuBKDXZvx^$ z{{3k>f6?y%@peWP+{okI7d6Itj{IB} zsaG|UT?}InjMc^|Qp7C*h!%mEdF9UqdIGpCx#5(C77SIWoO&-lz2Dzz5K*rlEYdbQ zDphV7P*_z3$H7uTYjuNu84CbMS)J-06##>(n@-DZWrskEA!f0|7>;IPZr^!ZA!ex0 zDTe<5zY_6L)??4DGjwoX#yXiQYzJ~is?f=t$Lbi;w8riy#sm!ESOg??a5)z^t*o!O zkKjv`CR=mLa9oKG$=po9Xl%;Wby!e@?jrKJ<`gTGk$NwthdZ+gX=qjD3Bn0kUt?J7 z7;3+mH08g(v7NWmF{5{xIGw`=qIxLwNm|CLHkuB%2i+D%O%3w*YAS?HC(XM zjFr@060Chd_crYhT_AyQ z-5eMzC^eqK4wT=F1JftY196WXb6E@E;d(YYhuRrt>0@80+NMnV1Iw$U=|tjjZKtDS zpmi#`o~?SB>9}A7(baQz$a|Wi-q59LHMAi$v&cK8&)W)2Zp>;`ofw!(%gj2D- zL9^j>V3`rnDDdz26h+KUtz%+0!BZ0B3a}})oQ@$Xc?RQ1F1ZXiA!5A_3Z(NAMv+rw zE#17y?n4FPUpx?zH?lFRSfPETQz)fV24Usm_rJ5Kl4T2HK_t7X`7;S975)8z4HmU_ zh#RZk;rHV!wzqB8uD;tC!7(#M@uQg#6rctK&j5R**Oc^cj5Zc@!X=W*?qf?17xR(c zOCo(JqO1eLY~y{Ln4zxB=Zr|Sh*VZMR=Xt^w-zu1gTXptV?o;uo;>;M6>rfNCVlYk zkytqI?>bbjX)EaL$-hVeg z5ROfnX8!=N=(Jpyg!3)WEnfFcmv?mOdT3%7_`>4_8qq@+txI!wuURX@ZJIXTW1U}j zPm9<66?5^eZG6_3-m;&KGTB-KuxuDMj8K`9EbS$2?BXq&65eO?{LA=tHcl&#$fk>K z{{Xe&+P1AWo%L6}aeDY&bpbWAHkVfN_s1JSbhf;@>wlwIx|BgljfY8LZUw3P`~35~ zyas4-XZkbnS!-uMeT`jqOQFkH8VWacZtr_>WA-|TB_S`oe>l%h4gUZlIPDue)nA_V zyy~nS{{VHhac*4X<`#IFq4|w%m}rHc?bF8(h(4U}Tz{Hi;a*&Ou6ge6?Yp*LZhOa+ z(R*_=ZtHw=&-T~2_WRy>ICxB(RMEa$9sYH`?&(e0dlc7IV^NC3xuxc=U%x8oqr#Tj z?gVmDS&MADt$;LvfeNab%jJ}fKtVx8BW8)6fA6PT-u1h6Rmpo^%wFrQ?|pm0D$WJb z%@q3$ScQhAU{9vI@A9}d>vw0TQR}A@Kl2&RaanHGt2RO#6?%QY#-4Is64P2MN_75v z^POe+9p@(W?|xm*_vd$9c+Q$$T!iLmyVbfm)yID=@f$U&8|Zlr2fcN5o9Bx3 zUkIG8^zE7qsd}XqbQfoZ-|+XW#+Cd2Y;B=7wK#q`=-c5|&)*e#aOKR-o__V8GzIHG zX%+=>?@;sr$_W@Qo&Jb0Sph*R;pA|vzFjiH6gRcwCc8OL92;#=Y|@Pto*!@UPt4Hi z$B-PgKO|`X0D4iAb9THFU(1(1f3yL#FvWJs6lKLaX;DY#UH<@-2frWLT0QIBe#L3O zM<(=_D8!51uf2YE=2@Jr^xdYK=T2JYZlhSgRv6PoG{~esviHi_*Eu`Rvhj9k6;6c} z#>{Ow?$@=vyt%7$Skhav_~$pK?d8jtGbWbN(N#IEs@}OagtgD@=RVE!EkPXZdVRBw zGnTb!Z3vpOj?qh9r-#WqPUESN( z-D$SR3!P`XP32$aU&Y;RZuO=2theVcZkF=rf1&&G_VV)bxm>p`=jTpydi$O+;~D20 zySlUY^P1A~=U9IK0CeO05ni>o-u>n z_F1Nw)5eZlb$3`_5Z(5c!)J&D(?aK_E#@V{(rPgbrW;Fh{68kX<+*qDp`&jns2k(P zHSyLwVpV)p!Ab00WlFs%Zkn=FnUjqF02=%mas2%or&RY^vmA=kvW&uMLA~irGz<|C zU|rj~d_R=R8fbSfyKl)Tir5GiH~4y}RRM$kdNEc9;zkarkys+diP|*pg}wKH0;CW| zy*NL-8$I@=2Ipl@RBXy4;0zEbO#{8W#>Z|)yLRh2a?a&GQ43Aq2LMHt*eZO!A(jZ$ zchhbX3x6@58X&8V1V5hqG6fd4(gvm^ZVKIv1vcI1mB9Aa6b@J!4z&3qh&!N9X+FT; zraw=EH0o210;aC<_RNSdiIRcrd0elg*2o70dbJ1SxEV6DLDhgJ3lbWPyKEVOYyr}1 zJ4OvcQ*GGr?Vw@8TcFklGXjS_NMd6pi*Lxq;grTsi+z$fsGF8ci!FI?CjS6MsormU zi>SemYOENGTKLcRNrGJg7N+T980n)|QI7@uk=>j02pa^N+q*O#Nh~+RVERf17L3h+ z+rA^TW)oyot1|VblLU!V*CeqtC_Kg~=j`FH2C0~sBOTuKL8XmZxq1!1i4fOjq1!7l z3S!y(z9UV2#bqSX=wA<{6fgDr%x!oxiZ`16Vq6O#4}+}0Ch}x;$CEJ}MF7a6 z6uV@(cJ)0}fyXfxIt##}C$h z{V-i96P=oQgT%dKdZy?7Ds`r-%OCXjH2g=V6ok!aKkxH%TU0y_p1zjUC7M_7?{jeD zxXUk?)snCp?hJ4-0@o>wc4GbI{j=N>?)3pPp|_8q)w2ETdhgBaEc5c?$M{RFa|v!+ zxqkb5O|p@S9QWG&fHIbi9=r}U!8nL>*~gzxR<{MI`OgV zF#0b3moE^A*Vgs=7YVOFc!ZQ`0204y15&4iQfF8}W`CKBPY@rx%CGp!f4Ofmr88YV z(z`@m{kxq@h;OV}qvA8xu?jkwE3PBJbj8Q3O~=slwGD};enfBHo~1KN zJ}tNTL=%BP`waWRZa5r?@Ldp#nq~5p@fIs;WZ6_%@Q1_FBK8Y09uoVHninrSkEd6I z3(YUr!3-PuH`Mz~Y4S+u{=%K1(+{h8@BA=+#1JFYYE6dno!*% zO_4UCVQ?%e+1%9Dx|GZx+YuEY?=Wx$wv)33WmdNq-F1*wjCC5Z-v^?-dPD%Me>LQk zLpcSTdRcaNj3t)~pyL3MKyJUfm}C&6N1{#99&HHX5}{iN>tXwpEE^DJS9hp`q1h~z zZ!2=^J1`WdT`_FY-nmT_QP3?d?TVSI7h9~;7}XYA+XqHwJ!;>ceZVCzMTI2tGc2Mh=cjn5P5+t(AhLulJ<5%#`8!Wo&A?hBkB|{Y?4!rkl^0i{vku;`{#q`piq6$g+p$_?ObI#nl-6 z%>B<9_bvg6j(z7{-1?W$Z1x__7>^$jFLNE4tO&0ZW?X+*QTF-XmoO@i0p-NM`jf8T`zOOz0QD z55PgkpI7EzF>BsGAJO}97y^+Q*&L03f+g<~OS78H=ZnKUWtvNOmQ~;Od6+{i%lM1T zzuT5eqFMWL<@tvO0$Fm+nQXT$akO7PCA>qpFzEP$-1L`t@aI^@H#5wvz5-+9ja1U^2e!$>Z3BROP(R4f$3b_wt?$)zAk8;b zJ|MN%#)y9zvDXsiu>nPx2-6zKxFW=F2~Stnx0biBy8&4lyf@?nv^^pyb-H8Eea*3S zNVAWyE|V#|DnB6?yMa0#sOUaoP9&gYZvOynTvnq~N62mQ?a4psf^0Q(%DStRd>IJb z&L9K!Q88yYwV|S)TTaVxWD^%vPJG6U7zRSNC9<$e++%>yLc%PDo1Gjhpyoy1Z?@gg zyFUC85gSObhq}FSlE_0E0(8(Ox2)I!>DWdGl!w3p`fkm#+dBcxPAaycn zi7I5avy?y!t&zCnUw_H+ZfySmtBZ8!aOW;(_=JGT_CG^LaTR`A+6MEj?%rX#dKrD-Zzn`tts2o8(>g)g93Jp z2-AoU9Q8&e4%L2`(49e8#`vez#`lQu`M`btw+!AR+wnB_F^NtyGK(s~Y<5Yl;=ZQt zzPa%VE&A#!g)+XIy~|^qujuLiT%;eVev2)ZXGGk}_(h}sngv48hvBd@)RyUxA88fY%%6@|}th?*!cIfkJ^KbHn zM!tgo0N^ONgj~2g%dO@5C8NllPlUN{WxT>PV1(tYy`JOLt)`0SSX9xZw-d@6YOya1 z277pvCa$LGTJI6!c|Oqg`fK8Fir9v=Oko9%X1Swpk|z_Sr)k<*n&w+f35&~r)xrGO zf28#^9Kay}Ff+H{tPe!o8f}W3Hh*W3RdqFHJ1(d?f(}4v%N`eLe){qD6V{C$kJ(dp zE|B|a?2OYnu#aNB4hUHsr~a@@?uF$B2d82%*+t~{i5J>;WFB9ANA8yy@Z|2WeotIi z!1hf@QQLa@^pVBQofW=ATb^KSq-=gS$$>FVH7Mrl_(xM&AatK=4B!>)m`&1${{VWu zh>t+m^G>M_jicCS+7tJOL>jdtbOD^J_ zl_FVOPLfbtM&Y`zs+_f=t+5EL1c;Jkoi0HFgB4l@*!WZ|QF9YTjw0^C3AeOMs#)I< z{{XlMuqz<}e5h~>qb`;kRprBTKVWTyw4qp@<`~~fmk%+5orRw6T7R)o>|FCEK0 zI1Sb}c`n`9@nGeM*;PwvbCGMxtMGS+r8#{nu6@X|%Dh}YZ@Fef!im3JPR7DM9c46U zb)%Z==5sQ!8#Cm{_9HHm?pN&~Y-+7mGS;+4SYW*V&r?rs1()H++B0)rROsdWX4*{S zT6d?}{xi-cW9YiQ(=H9rdixe1cQUFsX&nurr0!#9D4;!oUdC!yS9mi0y+ePaTE(ev zQswpT6%8HbKFXIZ=HV7O8xykL+eTfmzs0UIDL^?<65hIXwda867kfzs^O^s zrCoK_IG1Df-dP`}@V+6WOO8I3$J6)s`u_mOxqnknr5|0UVtq0}>*R&XU7##KcRZmW zKW=F$fcNweL~9P00fEfIRqYN^)uc0Yo-j?~4W^?_W(M;%JTdBNsoFH-<&<*#LZ&l> z2xX+cwJ*Vb=Mju_sjp25K9?bC^|jX3=IUD+>Mgz^-tGr%X|Dt1kP?G@Nz0h_B39j` zPXxG1mQAU#){1uccM*@fR<3M5*Po_A5=8VF%s*hSEW2V~+3THw+TVj=1h)w9u7|o| zY?o|r)|Lig=jKPCc_rt*MX?zLZ;txTi@i*f(h~uo()*)U4cIH+{Ioz{Dq@^mj#?7O zL@&^LSQ@oZy8iYi!4%^5IBU)X>Rf=~35@;Nw_yoml!oJ_x(`z0k`Fe^?J?Y9x|1>P zg@G+CEoDT&)Mo3%d%zTcH2mTqb4d!mDuKk@SZhdtc2dh~R6~tSz#nF|wuFKWDnC`1+tC@SwR%8Tf8r5y`5<_?Un-O z*=Ch-T#unyE0xRYaQ^_QG^tNuFSpDpo7{C2C@S=rlxv%)gHcCjfCWzoV9!DfzcM;$ zs_j-K(r=eU4zgH&D03C+RJ(4rGutH$0-|ogSZz$DL4#jco|00jKqELW+R+esXXRx>npi6SXw0flBQ@Tb!8NYZuhh1lZ(62}<5ZjUV>*3T!=Y5Ed9l^P{ zRotQ!AIUu6*NYOIo$2(Ab!+ zVrlKae@atYwwyEV``^=Ft)%$JF{{kKqx9+(g9EDJ;^_2GDZ=~ST-5b7mwx5Kgf-;9 z)lCj1y*kFtVw75zdH%5wM0WOXkLlFBX$#N$Gnq?&?>CM!1?8A;a}!y5mb#bXUSIa$ z>JjHW+3hbnrK0&_(&H9x%<#&3_=}eY<)tgEOH`^epKL3MSG4bl34{Cd459;(lRs@@ zFsuzkH4RzB<_G4}%_| zW9rKbPAEq&8a(~KLlSMb>R?uF72`h{UJi8~u{=gxAiOr$WG;Av)H9~y@Iam?)(Z+o z%ao3b#jmDS%1K**rjlp-@ELW@*y2d& zwX2$=ohXH_?--CQDT#44Pz$Y_juDDoVmcAWz9_7E%4tUFTBfT&c@L~rSMTp|q`iYL z5?YehD?3=0XhtOOriDSzxsJ}3?OB8ay16+pC%cCP4%_+Q(vsEyRos%&#)Q}m<|7x!}>)1}T>C!{@16_Nn97qd(&!d`8VZfz=H zgfh`f7|KN*Q6W04G{a=>Aa-qri%>hn*(nD6$69P>ZpMLSjp?v1tKGu=Q3}H{;nVU< zYO>Azxi;qJD88!1ckzLV4#BM`8`T8Q&Gb;StmMa+1J08+op2Q!oS0RRGmg5D;_ffVwfbT3DJ4@A2?Hc!^lmgnI;v660Aj z^I%jo!)5*HsBs~p<@0>dcu_{j+&#IR9rT~UVtoW0CsQrYFHgc*wi5^^>&Gu3i*s5**arJ6&br>Rb6H!>%M{N|a=<{8+x5#eQ#YwQ02S=ZTP zV#BadVnwhvmR?oz}?nYFiM5(nc5uj^ew;I9H3U;K<3xbvHD6|EyYB+oztkpoWs1qJ5;w5yv@=N zIZg1MqP@P;cha3#=5nWt9r`d!WkS zT4WZM?!B_cEwuPJ^>Ll8p+uvoDcZLN!JOliA6HpJbxst-d}Fm3Lmyd`UY2;&UfN z$ZGC}yxI9Em)O=)dpR3-L!lIFvH-kMY}(g}EM;c3KN0)3uP*?ME04m1S7xxrCmn%# z9rD`S%Zt7q=rEI8ldJ)Pt>9Igr>{KJB!bg<3$S(s>+K;qwWHifPL+wEzx21neZ zVc9F{ih4Xy;d$SaQY;#7hOWnQ(m_FT+IZwlV?qMbK(>z7?yirdK&6BgMELC z2f;D<+y`~7$tJX$pMC~qn}xnX`J!i|DpkpT6!Yj|?#Q*W6eXge=oaObb!~^}$A2?PNPTcGMn#*I zNQ({s0Ng6`^jPWi*H7L8Qd4i{aH@q{HS)LTpK*2(ac|L= zSV%}carEcXJEmPem)diKT|Y4vAD9ti@hts^>kIEiXV{&zJ>Mq!->At{Synb?+G#dy z{Hylv$&7k}8(}Z|_%(uP7-*hOHViJ3qp(#^iaJ40IYb|!EY7u2`hhMBp==+Nu%5{U zP{#uT8*Q-qt*<8cxs6;39;;n0rny5vzEx*>#~17Q951B{1yBL$GOb6KBTqDsZM%ii z{!2Cn&)!Wzz?!n2s>r>I!nb)=zV&MJVkLQvKR}wMF_i3S%018m{Ya=G7ZD4pFrV}4 zzCo}*Wg1IVvYgxa#fu|j21T-*-H9ksT;9vJki2D`)SO033%)HSm}lu!lj|YJ;6~as z7F*!$T>_G9*b-iQ88Tki9h03l-2MqUP*@ZGeLs%hD5etDeo-8cXDKEjV86C|K*H z&NOhF6In|Q{y^cVmIc;8{taQ!i2|b4 zRkiW+pN7(yFTwr6=LPKVYnSk2fJIc6q%A@A0;f^)xGj~Mh42JNsx<;FpjSc#OK!Wu zx+!mP#@?e2{^4a;A;R7i$pPDO^QCpCQy@suO7O4D`zj&W3$d6{lSyHA(ioEQ5AZFMiQ^5`xr3hT zZeKG`)An%!%ss`@qvGBEZ}~wsg5?Kv#Ba8d+=BXz!S=?uk_@!k^7E{-{UOQg8b;es zx(`+NnV5sV5nGY3_?wk1UMB2*V|$3_?90nSGdSvEX#yDQoue2`rdkk6`bmBg{6U|r zJfLmJ)z7)n8Wwbz8fV^9xQr*5AuSJHGbC#VG@;oSHq+RpLU;?LDBMvGC5!D{Voo=AzMqUT}#-F zd-5P}_SQ@ntw=n$CG(;+H`MfeKnlz33;CWtb~ z^?cZ@YfRee>z1s<5zRwxm53N*;;3Uv=rrJgO&i3Pz#ci!%aBPLN4s9NYy6+1I zQH`D;yE`8tWrhNPTUXC6+m;Pi06Q40T9HuFXcs$WfGc11>jrv5S|txH2VHbb3xXVF zsa!aABAg@V7@QebxnZA6BWN&V2hu(7Qt_yu+XMzxLxV^a(N{%dd3l$@(EJFJ0;hot zYV%}&dN*E+9=`AlTq2A-!Ah75M8#n39`h*O5Dt!<*lEPVxNEt*@u$BCvX)wHU_FEz zASTLkGQi1~qFojZy_?q8n8429W80|>^PCUF%e(C-p1{cGaRAN6Q@@JMQ>^o_*c)3i z^};x8i&BqO8esH^s!uUhXJr;{;!|*B!!5&#+d`V(ExWN|Wc&#;F=~wJ0-pCx;Zf0= zr+OF>urnNw+2xL90OLC+@g3}%qet_jo!!)8l&cFvlLCv&Qvz<8d>Kxy)>l6fWnx+3 zngCjPU7MlZN}yp4$u-!oxcm+n_UlLvx*?9V!GRpbSkm_sv7~$l-dMxDNt)u_$roQf z+uq6PU_sJ6s=U9;5!jVNf(L&-+Rn6IF#p zVjFyz`JGg5%t}}{#uvL>PZ&C$P<0;>wz!&_%(nC=HM;%c-IFG>#1Ffay*{4ut$b0Q z8a3q1HD~FmQ~MddHTQ53Eua=QGzBbrj>1s6;{E=KrhZX&35sc&;`Oespq#)`IG7mr zBHE*jKg6%q`C@9@b^I6@`JepE*;>G#$Kx=d^54X|$?GA~@f6>Q)W?(fgkD$&dXooG zsL-n7T~Q6BSK-&!@TvEdy)EeSdjs!?Co)nE=3STSEmp z)+(XuH5lE^jbG*>OO|@4CA+0l>2_&2FiBuAu-SjGB<3_Kwg=$Rk>bA9Gs=UqqPnSZ z`Zp43A5dZ_p>ZuXT;}f%&M!zbcCQPlbga;7rALJhYqTf!wFYWa2g?3mNPF=Yj09aQw-WKvtYn#G#$p~RxL;Lb2C^cGcJo+ zoYvgALs0>Z4jj@#fs0vN>og+jsdRLpBaMjxIg)aKhtxZof$dnfiZ)fjEl!T_k)n{! z9&8=>2sUiK6S$q{;Rq+kq8C>g+9A~E4L&J70uoxloh036kwZ-xX?WdPxXZQL04Np# zcR5QCopR&s&exD~w}NwBqdKmUpQ6p^&RH&8?H}vWH7?7w$-s3jms9O`X)%`sjyk>U zIbUyZ5}Dh|cY4q<%j@ioaI1b-5-PQ$!8+TWgM%XWs{k&DBeug=DS;g*D{o3%3~UtA zpT{6o7)~)#Pp8bb8z4!^E6h%%RWsT+v{08Z${PLCs8`O)$lOxfu(=L)D=7o39H}D6^wNY)a{{pleFwnp{L(6++zz(Ao$ zSTv$FIsqGnhvy)~R$ij8gIu5#&IQ@QAPPV=ucqUu(L0Fc0|RTB1ELm+SNg><1*i1OEUob|wdAQ=gfh zKYn04>&8^>?4t_b0NRVJjT;`pv~$l)fmdH!&)(cI~T>3>MGZ5 zek3Q_5xrUk`7g2=#)P?|gL5&uIFmiz`^PzAvE-jg*9k1@-hhW%m9X0J4T*PJ==5qE z?Wm=~{7h~XKhUt-bm3)Y1{GK{46WM*0k3G{-@=cW&L7jBRLQ^>Wc61XG==aUCFd`^ zeRtjZ5cM9g7i*W`M^S)pTerjTLc&|rf%P5JP%V z-}(j-<7Adyn>HF7w}`I$8sy2tRQ8AtpH6vFoWHUR;y82Qs)->@^`z#_XU+>a2g2kDK_8VelH(nAJF z>@)>vaas4osDv{EaQBdh1BS%C#hgGrMUQ{MVvqn{;WOU`16d)iU45CH_Vrkq)zsr7 z%8{BcDcva&Dz@uKaD086nl_b3f-h5M<^2T$^)f2!3%mQAyM@@h=8|hSrMG2?XDi<5 zuq9&Y@&+O-0bnr{%u2_Kblwgs3HDCWtw6Wvd3& z3Qkqz?Z!6bbW9z3KWB$j*3#@PSXrEde7R)B3c&9KtVx+9YjZ#}9*1CYmyvy~Dm@J# zMhfjP9z@;9&}CaOjma%Pl@x1!FXdKv7bn`3BeQk^L#q^_wnQ=)`WfR8J4RdFu3T>R zPj04o73kZFfsLruYCWf-FM(!3{CeRfQ!wse`?lc_vC#u z>*J93k(zcE_po@SV!KKelWMiifM}+ol^2U^h&I5b2pL5G04O2DDPI&@zr#;D=4dh? zEiKH@cgWr}P&2X6^-o9*V^Y5`;3GOiDFdGl>?F^uV&wCNhk%eazymtb7q#~*WsFSC6Sm`kN0U(($4mG^^8~vcRk(lGYIjB(M&s{}<+`hv=h34U zjyK}u7&WR0w-b=33OI0UcEm@9pSl2EUwK9NgA!WgN>R6Zr8rqbfqv)~Z<5;qiR@a( zYIx@=tge!f<+-5;qk8~3`=m|oRP@9Ls(QtlwJzR%9>Lg;PxcG}9qPR#lxrrKRe8#L zYcXQlvY}#~0oV$~GOnwy3aZBUHlkly7ldL8IpV#601m5um#a($x!eT@;fB^Be6mg0|>e{=f;PvnS`MJtp2R!(?*30pTM}aZ{-tRqbc&08M+jiKKtY9b>zq zT7C_=5ItiA*2vPHcD8oL2cfpZcr|q1^62Wjm1H(KU(EbYkS$N-J`je2xpD1#NqK{$ zwWx3UM085}(O!c0amXxee^|wm-koT;{e9MQcGn-U&BpUk`=RzJm6{IMz`+~II6t`e zAME1$$FDV?#8xG1S*;)%{2}xo!pY9FJn5v{Ov3{r-Qa(I?zWY|HDA%uE7YVm#%-z% zhz*v4EoTMqU+|WDh$A6Xy=uP`?nPdvmxO;4EUkT?yc+>FfzR3!Bt`moWLZ|Z2Cwre zh>9G()*i%IFnD~n2XR?BaWP9T72cU(k+v1e+Xn6R~--N8E7z-JOx`p zvu6=l)BXPdoPr^4R%>3+H>A8Dpobgzbfe?Z&LZO=z9Oj>QVoRGh!zP1B)PFMkMi)(_pA*h5E4gMi9R`v1-b|U| z=`BSHI3vL{{SG5Qp1~aPQKAAn$0#7x1+16 zcU)cUbN9iE-d=#K`m&jT)-fOw883xO?LKl2+B^BI;NEPh2I% z4MyO#bdM4ZI=ywlfPlKS1<*<4G0F1C(dK2~m+2t0;L9QxxrXz^ng%XRXsbZ@T$%wz z8dG;yO$Q}r#=IuAVU?r$yqw6w8CZ=A9Ic5Bjhl)>5YQ|0MJjYSLGzI7$bbb_ZDX`G zi@SG-OA?$hZ*^qJfLa;M>Mhc5IsEMayX1y9-`Wkl>}BqopewY3quO;dk$$Sr_m|2@ z6rmDe6<$PP3t9t%RM%PQ5+Jy zcqVX7U>4e&F9DRg9VM7t0xdOH+IJi^UAQb33TTk3ckMnemjRZgqjgdIIkPGk2L{eE z&fX~L9ipBe7apbbdJJ7u>YoP0schMUizl#M%5gn~$3j81KHYf!0X3)YfJ=|yiC=a6 zux5LWrpcD~be(U$KqWN^vc{yDNI-lc25!llJ;i6$0rp13u8N^VeTCm#05a}E7fT^O zj4IZ3nSU|C>BgRVygz@YS24^XfL;QbZ~_pO>ZD6yjhvu&Fg0_kn!hD_O7Wu+Q16pb zd@YD#P*k&_hfM6oZaR#Q2OnyM;#XHk-+boC9Q&%dYSXowVXiD2%kmkm-WJ2$F_yud z8GGArw-uVl19e^E7gOyWGZItqu|Y07yHQ1*_^d>!3b7O+%o$ z#bDzw{(s3XQri3D87FHCUPLo3r+nNQ`Y^$i=qSCo-+zX=lWI$+u|uYw9&6^j1= zc9WTwDFGYFi*z(`k5Q)e0)Ijn^()QlEm#)6 z=h3IR7p{+TZbGvs25#f%)Q^+|*b<*e;?;GTwkJedwXWvW^5?r1mWsnx3hjPzdzbDd zgQ`*-_g#35-U(GPC-5liDoS>wB6EP z?cqS>1MlQz$(eCKA~kMCtx{IiDMPYl;?`NGEgEd8Wn{J`yFvA|^qMi9#ahVat;6(t zN@aMBNy7^woOWkT@tus$<_O}~SLX|q-<=A(d4QKn?F!MG;wl8zU8cfb%nA&n5|t+C z0@t>n=|!`QQagKv*@_uBG1RK1HFR}jUaD4cGzEMCDR9Gd{N7jPea{3dztZZ}(p}bc zE1cZr`HZPku3GVwGU3K_n^qoqYL6Oq0*z`Y8&EH!WF|E&ko^XLYc-uz`379hD$#2G+M;KcTQ(=?aJ za>R1Cy~~2K$gS(YS(5U=^6GE*z*pe}?em`rltvruyYDQZ32YZ?E`|zy=1Q%psONXk zh;E$0y89-aOL;1@Ue%amfrbs)jjNSKVhp z__HiEv1tawqw0>wK1DA63Hd9(qR8a?hk z(B==6V+mR`*e3i?jz$~`wYQ^OMspcJn~#X23puA-7u0l2Qx7uLNYiN3kqK|b-E){H zD$cMe+{Qb}$!S+YX84db+-Bn%pX~Hhl&MNUcDw=1>m-AwwaKP}cVsT1$Erm3OJT6I zur^_zZzOeHw3Te=Bd&sCiJ}Xuz*$E(tTwF3p?i0!X7;p!S&FqF&h|NPc!)Eu(lEnr zT)iW?rOzbBu^dtB5DdMjwtsrTmzyq6VFFqLi|A7oM2f=v0CiME0hOJi-mDuNZsz*j zypi11j`f;9#aic@nx{BdZfRuJTCT$bE*aX&Ikg;5`CbL9a$feX2^>*0I%S6RU^T&L zH9|)}gb(I2!8%;KVk$<0yy4t-&r5#c-d)Q`w`8r|>M+0v zm9|f+4~;-J^SQg*f1zfVa30ErMX+sl%!g()L|>^%yi?Go@VRd2tGt#8UqNa{DEv zn=vWZOdvk^7x3yoi}sf81dDloLRlkscx8AlV4RzmEGX!f{{R=MyAjo|zkF?Tn=EPK)R*7g3_Fi+`?iM2LhH-aML+?7B8rkllC;ji>X}B| zRe{m)f+$4NJKZhD4)9ASjGwaEsuU8kB>e!(vX2EPP`=Zu164+O739+eQ}dKC=Hmp1 zIYv+o-uoi`RoElX-ov!)MoWur_lvWgy7*Ytd`bAq>7djJqgZFk=jTvWt5v=rKXreI zX?xXf=hQ22n}xE8h`Wn4wd{v%$O^l`njp?6l`fjc-9UkBO;Q#DnghTp`eHVUsvNxs zppG7|>pI6$#opI0c6VyWxkWx_ip=*G!V5uk8zP2meD*xLm9)qx0PI8B(?0uk!>xRQu zhUx%iORU~B8I3f}=BUvPv(Pb7oqGfL!w}0Use^Ho1ogAU%OTM-&p;>?{(~?me>*i{;)^R{ZDwz!4d>bovmz%@vfnY|1-ap;sBJ7|TupMYkpj}$d&^L}9`Y#Vu? zUl@w%F!7h7+-JJ$3f;iQGnZF}lG3eM))~#A%bfh?a)f^GEQfI|lVDPDWP+}m>l&vJ zedw!JGv;07bvp4zQ(@xo`v$Uw+#9$qmgiRr>nxz_4Bi3Oo#O#S4N;?s29hje-(}af z4iBgVzRexCI5pi0^wGP@b-ma`gNqJ@3##QqWK)B8bS-TA_&3RrM=atn*=C7%f{tdi ztd1)~vd)nlTfDr0Nh$l#sDJaZuWtjg)f`$-3Pd~BGMz`#_S?-Z{Epq z0S;=l`YFbSB{gYt)4Js9lIofka<7S|FxoP1^Iw4t4oK}U>`OnOYV11XJ}#z!lCqaH z{6w?)G0jG!<}&V!Rr{Jlre;B?+bafyNho_;|Jov8$XltF;LRdOJzGU{8dYun|F_QC*;6 zuXjFWm!evJ^8Dr?Ia((JuUcXPTV|9;0vX^~dK4dO5nyuu2!doJ zQY~~4+f|rFI~A(@m&dY>i}C*ew;kTU$PqVf9q3duzy`!by+;q5fn(eVChBO9l=qnL zOO>X1FBwdAx%g#8D3YpnEJlrJ&wAca3N-gJGa5|}7FxKtADGp0n&NBOXGQ|Z9IUQr zj+E+GXw>}_q-$+RdcP%1fi9`J3wERwf#LC!4Gmc@zy6Rrh_ThZC|Tk5FsigElFH zC~I8Whb9Boa2Bv;brPYaZU?aZN)d~;p$bMEgr6;`($umB%W7@cLo-s!#gyi%m9kvl z=gJmIeaZQfe=!JR*8}qp-WObGw(=nSkpW-D9itW0wOjE__I?pn;ZXQfv7Ip=c-D9- z`?RB`@Ps0r!Wfv**&GZNWmDq`Y;G5C>>?!HU)9sppcL$XV-UV@_Zc4EOX?GUZW+(GWx!|wTR8&O;8N**xvm+w!*edGH+l@`hcX*LYV6g z)b~5~#MYc_+s2|%%>I53#11Vr_f_9(7K40M{{T58zQcuw9q4E!b;T3v13K+ln?)*7 z3qmEb$tYqjDByD4jSE)$i`rYdMVxl6{6(TND(1SxjZ3YZ%J4fK{9UyjQ>rH#OM6Um zmmy zYhh{3trc|z-$O>NAd4~4kth{S;vluQC7cX19w0`8CGM#_3GFXz8o})_c67)=!$i}7 z8={E1UEqC~dt9?hVV#}T4xDuoFy*fI{FI5NXe)N^jdsU)DkE)wICMjJ&5Iu@VhiK_ zYHjevv=??Y>h6Wqp6%xQy1z2XShmZ@kA^sTi<&rO80Pv_MBLDOetRIz`Brl81~qi4 z)I^8Q6lyv!dKKjH#=Hko&8rN|?(=-O?r4r3ByU1mx>RCnL^-a0xb*_d!Jt{$rG$1X zK)SJuTEBxPK*a`gh4YbO=`nab28@FnD74c{(M$0IWs$Xf{{Yzmf*gc*R7TXZ z%durkDpRf02GOk|NClt`?Q-B+m7nJY-q`S`C+@2h%Pn&X2!b}xw;|bQaTgJ3N?mFK z=-lX72y_wHvDEC-{S#N*4os@)-vM18sbdQ*M?6=$o7#g_TsL9{TpKz@y0ZRsTRQ>n zI-+Y)I^%zzu$RCLAWtRfODKDSclJnPIRI!A;qOK@#q3agmtcF1Yjv35ntevNTCB@^ z5kajuDxNmp)!YtBvdQJ2Cxo^MHB7&%^z{^ytYfACHsPGTYkKz$NQJg9+n>C8!0FFK z{l`d&cJW*1E4fmn*_`}Zp;hp6b4w~^rNb(os$>U+ZH!pJ(2<2YiI8kj6N>2Rhcipl zV4gWTnKmPb+t*s{=w9=*DEX|+K3u1_{z&B&b~MJknmJ^7m)&bo<=acN6;ygOT4Gfi zoGrr&Cp$RO9|d_p8H3g3z9p(z+qF4eroH&GmfgQ`Su6?Rp*jMVdaJhsUqG0=0a5Wi z0EsLdwE3>R=?`?+d)#AF&DQRPyq9Lq{YA_q1u3?XAZ-%e?lst4xap%-dnFfK+PPx^NbTli#7-V9+^aDnMSz$v^>cHwnniS+-e?W+PoD9`5 z)kHr$_bX#l2n{H{g?gVDF^_}q1>U1i2CLhi=27|sH%s}PJOD^tS72Ct3R(F`=R^7c z4xo)r8-o4vcX!;nQ8yFv60{wRAxcJFgXURac8)(exo3m%2HW>~#OntoU> zRR(Re^-@-+{Q{WWP2XfudqG^Y_bi!0=8@bq@y6)QMjurGI*IH~* zf4P~{Kq&iF1sSMu2KMAI=h-dZ#P7VhArW`I-pS|Cm^s{i`chh(X7ZLPrl~`G*3Z`y zP;-BtNuu29m2$tE*Qt&};@Qo;c4|QPH5T`qEakPBaV&TE261c*v}kQGee1U~3|T8e zh?P(5Ry15dmKZcG^#pagMpb2Cst2`2+|BMi={8yD;z`Atp3c}e>n?oPHK-B_U|b#7 z-gWkE-lPT+(Y@Ce@ycA)=UIE3V=lW5NM;AR+8%NXb(tUO1a;df|Rp+&yw%CucuKKO)~!gycwEU5UteuW&y?7 zRm$*5d;wk`E!}P>QLl&x~pF( zz>kVG{Pn%|qw)2rCvXo_O-=Si$#lISaM-WIfBFPeyYBRNJOp};{4#@I^#w<$_Xwa} zA4JJn%kJZS+?wvRTB7b?^=U`0XT=RWW;!)PGTAQSbc<_i>lcv*6#=@WMl*pm%a@W? zSLoYsNtI1upRgON5|fvDlpo0>E_>knq21>a+PI+hN$_O4WIppv3o^jvQ(WYP`C?m9 z&1Le+=D_I@7vs&H3Y>Gp7-AKdunZhj`MP2>xIFZa;W8poFC|+a&1JaYBhr%lg0(S8 z4H*#jhph|H63Qwd4#u(uDp5E#r!Vsni!(E~jGjla7kY|oSUk3UpkQ8fu>PfS z7Np^gahusIrzw%o*2L7KA0!C{6l-RwQ_rdoz^F-1g`<#Cv3k7wky#wx2~>mMN3$`;4SS;D|)Uz zOpL<~6ZUsmXR1p0Fab))>L$0|#MVOoggz!7tN3!EYEf*^1)YKpA))AfBy1nLfo| z(=rvK7F6{{YMlHIdsY_pMSmaOT(CQT1tQRq*l1d+4Y{x2fqEFW#C32~ow0I(B3-Nj zgQs31QJSc`(C~$g3dS0T20Fh&4n8{d_PIcHET*Sc}S`FifcSLH?LNli-R531?;sXj~K)mv#+`6bkb02Y$=_W~} zU>90ptCN{+$-Bm0bq2Qrf|Mub!i(YZONpzm-A((!*xjs*;ccT*jZ1_R|`@yuO`t`pO`s1PsF4oz!8d9>Qm&%=e1L+BesAj(feN7j7a+z-# z#yfU~e(C86^s*cr=n5STbspLq0ex^XsfOkyZ+(jhGYv2nu`8kQ9mg<|-7NLeeO}iB zlUwo6Ga8&Y&XIa~1y{F+o4okU$x$f%Biu<&s;u(Qw3U8aFo;lxN*_Rd#e=NS_(a4T94~?E;RFyy zh?vsc_A#QMRelQ3EL~+-8(p^z?(U_yLxDn(;_fcRo#GNG?(XjHUfkW?-L<&82k1@T z`~AohnM@`#$zErzefB!ry?4`2AvG!a1TLjwX8ETyzQOlpuw4Xt)yd;*tDot3{srJ| zID{uI5QXteLQmc9^>?_9a1?383Lqh9tA?tSnf+Vl@iykTlA(yMKg6YR^nGfz)g3z_ zr9QOQWo|gaTJ!H$|0Er-O^?2?CSG=P^O+S)wjjub=l)Ot=u=%~y%{qYpoG0@&n#LHs(cl-IwhnDiL;PB zLaxD)%s9*Otj8fxa#10gT8#=)`wKX`%ZlUNzP$9>_^R|;zVuc~qu?OU&t5}EqKj%V zk^J52BYTfwgFkyplKbnGtaC!yqB#AHdsWRro`|#c? zR{RKpa|G4|Ywizm0?5ohQ_{t%jT6q0C5ypZ+R3kc*oE*FkG_PZFiJ@?U?(CtC9&UTYPTKmum<1&+IEHu9 z!a_-1Isw)UKawhisn1M6!JJOYT7y#w_JUa{&UlobY7@8`pM0!GLG0F-ij((U+FDd! zG=;5j!~S9*SPjS3gVicKAi)jUAv1&=I;TyWDk3-m%)4cCnL( zcbS8p1#NgjyUC9H`hY~|!y=Y%e_8QB#TB@wDmcwZW;ME|b^apf5>yfLxL~=nzS8U=J9g5a2r_XAqS zEn)-J2ZSHG)}=ksjSMGjaeLRdr!O`%rSU16?3{fsZft{CZF0gvFB~GELe+QxM^YF{lEyl zP*ktW#o5W4@};^@t*uGD9%o;|{oFIIZa~<1=^YT{HHhaywEc}5sPb)YyqQ!kE&20R zP=PyZ1)d3JG_-LN-%)$H7W>CR25ZwT4^3Vn^z=T-C*0X37V3Ctxq;JKm@%(~FJB>W zRiViYIxqG5tYRNEZ)K&`4UafI|ByGJS*kVIC~-7Wm@gp3Jj98ci~sPAB}`frP2sqr zvrPKDsn%2qV~e9V2W)VG5hs%Ika?0a^jFbq@sav8HWXy;bk5Z??w-ip5!CcK?Iu5| z&ZYPP_P1{7Xcemd4@+jKWEXI~MO{&awV{bW?v>Y|;%x52TEsg0G}|_-)!0TiD&yd^ z`3ov2N#apfrcy!+_m6|^MqhzRs9qbilVha`ZShBo$Q_ci4iPBr2}ck}^6-Rjnea}| zZx%_U>4vpN<6L-|K)-vNW6n2(&#k18ZKuVtcQ4DaCEtN3ET3PYm4vs_CtPhkpW&`J z(@C$h&{P?G}1X_9_LBV*@q(1LNb7o7k`{p`fSM^#4WvncGe7p2GGvB#AfLn zYwr@O&Pbxqy4R;l^VW&C>9?*KVXs}-v7C@2P0^`j(5HZexE+r`Aowg%ryU)#6I13Yg*?V)R^X5WCC8Tf*z_f@p8%IWpuHk4N7~MnB5S! z(CVo#ZA}6nX0Nf?Be&LyeYY$3>-I3;4-?Jk}Ginm#A`5+N{S; zj9-)EKjJ0482($shKB`-b)`BJR*r|-`zd!wSQ|$#frsvrK|fYxhhkyMr#}gIlb%%K zb#QZFXyKNJ^6qiv#%QaAwK*45Q0DXPviXV=yX`Lkz@MMa$@Ly|&Vnbi33dGzzyCJQ z=LeenWQ$#iQfZs=W*^qRuFvJsV&WRI0(HT9uBXmSL#Nt=65{R4hvm*EF!re=Sk5^H z`D4FD>DTc3ITk_f6fjZbcL>UKa9vhAME;%+%k2;Yzf3pXQ{;YY!;8Wezv6P*tq?OZ zbKiUXW8Kj$^yMX-Z(BS6ug=;>7Jc$P;qXVV0!e}H>f=MU4#E$8e>$m?m~aeeim>W9 zP%%QU*(^;SMxrIjZzuJWB2ioPGC3h@BK3#AaJz(TawP|u0Fs2Chs?*6hO zsOmLOs1@x95UlE$qW7(yMpHoIUjWdn06l^=SNDO|g=c$%yfD2QI17uk?eQ1Tw#55r z*43MIZvz?SSfHGEq?Y^8hQy4DcUy&939mVSE)}%@O-6~~vQU|=F@Q$}yQN@hJO$uBgfrf-yzP3B3heeEx6xZ!8o zR+(%3cXZ?KcWojU^yRubm%qxLoDAkwT(V_?{9|N=`v`@vkar&BMPd#}HT*F0s%f*Wf81bJdZqxj zYk`vOx1uDg&D*b#mVMtsse+vITCKI~aU=*R=Fonioey7qrGZ6isO^5Q$K9EPg~{Sb zE;+;s^LV%;AV=N}=%9igU7Gs0SQB91%s5=24CU6&51vf zLKz@(Oopp%kZ!vO*BeH!AVnPiNiPfM-+a5Y# zJS?(Gw@EU=N$Xo~{jpwmaou^`F`hQXz;ssik@tN7&@wT3w`<^QtC#z}%O%l}Ofv9L zDtCb{2uBa2XZY?5bAlfI8T8?<+OJTwe~-Pl_iu;2e`UGGC_2o#YPGTtXP(t2%xeAi zfGv!31^NoVTy!wb9I3hbs#x$bu#L(tiO@WzAze*Kp5hgK5nt8q+vl(h?3nMpfkgIM~*n z_1bQduyDn(0$)a{r~wycCCXjhGwvEHEpS#UjdjT|u63j5!idWG9eZ!_5ZU`s_f=&| zpDU9YFQM4%RdN-(a$6rCq+jRkEr6|189QEre$99L z0e7_y<=LU3jP~!aY<(y!+vJg6Pa#92^rG`u`Wrzq>S_;SK%YgXn94<~bfzmh1>(BK zVw6*xs)Ue7tTZ`mPLiSUXz|2GE``VA-~};s&a7qm+MK^@0Awf#03-kk005v57IQZ< zF0jS@I3}J&pMb%|LkZ-jM2@cS=h}~hW|*j{zwZ!7U%`$#2YrC>Q0g(ji1rUdm6P+ipU~3|pg0clPQaeZ z94M+-tW+zXj|H_TbAPT!3v0`0_CXc{1&;}&dhic^bX6`C=}OkuV(4k@FRF@i~fvTej^%1+P}pZGK>D523%~NTAMi z3T=TT^8&%|`{o`A-PeYMkaW327Ceegw&?IFHjj zP97K`j7|GIglP>dIaoW}i5y0=^ZJE0Pkp{6Grc(`w+L4jAw<*=)lq9mBq?!&_(j>n zKu6S!%>$KN3GWrLwFQl@3L(Yhu18P|5l8R`wJdD%CvLOpxff$wWxvex>D;66$R%DN z?B|GpKhP=TeI(zx9e*HH#yS@H^Aw3Lk7}M;Gkq4Q;)WP!$+uFQLs}gJ$VFBjilnsk z$vDuG384_@EaIX^eBlwjCWmGRKr~QATGU&{7Cjiuv$i%c@nH2$^?d=c`HEUqLee~h zcSh&NwD4cgX5PBP}f+&u6VYVy5LHeWfh(@$}njM4iaD+gTe%4wv{<^2^O45-R z#{66nvq`VstKHYM#$X>@31{Ao7w_pG2p{%I+d?>R_h8B74lh!9>e#Wf36814d$l_0fd8nH1*d3~C!IN4*$U#DPCl}h(hg`n_dcV1mtD*fV!OnU1_&mcWU zfZ)YNPtz-e2z@SoWC|a$+xx;XkRa%L@+31B5Qw@?pglUJOPV)~6=`+6PzJMQ>OS&? zfSloQ;lgpHpu>n76i}dx!af^eKt_#ygJ{fSJ}FZxw|-=p)`_p&R35;T-kIkGC>reJ zyDdpqCA2ATr=$?{(7eF6(T=DZdSF&TBdmExHXD!|REny@%k7cQZ5+6xU?(=Izj*o+ zlYep+5k>sU{)vVjqW`QL`U}mZ@@rnK*GOtknhl4JFSa2mOY**kv_a+EXJ`w#*Pa5k z%pUc4KSDu)hLK^DG6V3eKl|@} z?|nZ%5qcgn_zw#XdzO@U#!uQ>hvUoKwVHCe_>qy2#d zGA{UZ=y8;qLW0ouM))jq;n4ej6S;7&5UhOzLZ2~$1rL96w+H#$4%NM@U(gH8e$5*H{^$t$qScY<@Luk(A`&5VG&Yz}*osI5 zXp$aDKOijvr7Iyc)W~2e@(k#{Anq`S5Nu&TnBsK9v+zvLP$JZI5ZBJUt+}Daglu|? zCs?ED!msOrbi!Z}Jv0DB0)F-&M?&R7n-h*>e1rd#%W5?ChfG1Z{)BMKiOP{*V{lh5 z-Hg&`rWC`Q^^bNNmL&Q-zO($?#AG5Y)ytOGFnHRpO1+gX5Ei-Ecp%qnX$WM z4MHDmXKp?+d_L|o6ismC9Y^dh07>A%&-Rreu`!xG`CI5-X=(*7ws`U=75b2BX{FBf zJBu`bQ%EiIK#U9_N}xD0k_-AD4Q3;0!Bo?P%76>i7WqGTt%yc;^a3upXx&d@;anVU z%o#&b=8S>|BU&Y}TfXQH2aJaDl$^8Jx8qFy@fm%g=}KRn?TZu5`d^;hQfaG%x%_Vh z;W1eM%wof41QaSZbR0r_PJyHquK7bRuLsj) zfCxbuZqv|viebwUymfPs>_5(lHZ$pl<9LU;0&fQo5hB$WY^93$R!*A{)(|J+H#m4v zlIwPeYXvjzagsUY`7*l0` z0;C1|Q#=rplWS*@7E`bW&X|uCr%qFgj94WdYegW$k@2AKkN=r@+`6 zyv1Bz%>hGF(bF;5MAGsqXmKFI&-tHUmws;;wTM!o{~42Vkif;f`mKpTKtE5JAcOnB^xNlBLr3#!%@s?lrIDupesP^dm<+XsPHUH%Ezq zUYiU{bxO8?6~Nf+(MuNYjrC=`Bk?M{JTg?vA$;R2a!w zd{#n?Cu-V$Ff<(%)t;@)?Ux-8xx)Buc|;mJF534-&2ZB7X?4adm`Xy{P7nYI?|Y9Z zD5y02I|=$uJRHG_SPf`R_hKCpPD*@JN?($o%rJ7^i`aM;?KtY^qTLnbm(XddOpWQ1_k4(?1#5FtU~P5gxJ&%Xdev-^sz zm#7dM2esxW=Tn=G>%V}?*tI|ADLuThD@8-mxc7k@2_17hzG?^vB+AIB@q(T;d^=F^ zRJxz3eKo>!u`yhLj>ItQ_iS`WFkr$^^`noKXzw?_en%;Do3B_p(T|rH8Y!rZDA<;d zSwwFID9M^6h+nkGU;E6l-Bpk5h;gcpJ%R^C@X3 z0MGy#n0=WadDYuVzpjj6<7`>z#W;V5Lm9nWZ2^xm*aoFKyTUmmLYzK>ScIe9<7Y|2 z$>%rCq7fIDN+?mH3YtF4Xa%xh`a_Tp7dNLzn>4j6%bkIKkjxHQ_oB|6Jm6&#I9z0p&s6=c_Frui0>neKd=Zlp zX-zS+XG?bB{FU63OSd3d2(?TWx>RFdtXIBj8mNXmsx;gwEN%eTQF4d^C3?Bv=bcc% zzutmraX@S!ac$&ZoRDzf$ykxX9=$BAq6XA|UE8!(ZhOWltO-X@Ymc2OpqB|;qhW!y zjNnmB8B5r()^jmfTQ*bCC|ikWyyDzarAnI>cSo{WpOrs};jKZV6}t0p$YRj>>H@J8Esh7`)fRngFvyyrHU29tte*pOSU{RNBR5eu> zU;j=;CY?HUgdDrbld)N)6qkiDsXz9JqC0t?<}R|w95CP1+zq20(?&vA>@w2Nh=xOr zWrjB~VKHw^Lxc8B4brb1y8Bn6zlf2$ISs0JUF7&1BM()h38F!%zQ*c45jAs-dQKzpn)^fzHxA#J-_vc0vi`Nn z6JBg$QDx3EMC*c%!&dbTiGRYNF(2vUqhw+eYCuYX%O4GZbQwHUAW`)8RBfm^FOL&C zUu(LZLxXRe?=LI>`v;T9M0(_b-$H7R%{G443)%kaM&?+Qv29$Sb^$Tu@(=u^3n1i& zeAGuGV-+9Q$xk@}@5AB+Hed>r8P8m#R!n}lrP#vEHedS8prlT#m|+t9N(^@LL4X6ExGzCifef!sh6CZI9j8hvJPmYL*{sO=Y0#r3D)Nv-8r2ZWIqkWP70lh!zcVR>C(5ZZc zd0fN!4MK2@XzER@cFV)O8IeC+uU%CmPIQOemng?-#&47fr~$dupm(ht3_Ch>YSp+~ z3=!|f8*O1=i=fYGY;!_PcyB^%PVKJCf51&WkxZkzkt1_wPK*AQxJz`cbqq>@_Y+|hSw)N+?cMP=C;#!RfPYv95CI>Q&G!|vqNW~vgPoKopD~%uU zuk)(-(-e?sYuDpNXf>F1Ym*QiL1>euJpBP~yRVsv5PbI3S57dmt~rvN58nS}ZsxFQ z{tNZ&O5R43Q!90+xWMlG1SiA2OJLor3_trUOM$Z(L!i(#ZfWzNDSdLsomrgFrgZvn z=(Wy^K}x#cOYNvDtx@<8<01J~J4J?snxttjy6_^J;;7Ll^*I;O9p&{$SIED|)pw=A zD|}N7Z^MJ&XH__MzkO!!bm;#mNG<4zGLD|+lF=e~4vw6=FAs|9xP^SHK z85EP#XM}Ci4pYd@k;5h#wZ$#sZf-+0TQhIJ4O(7V$%7#| zFH}zKA8EbPB>}QgjmC&f%E!s_Mr1$m9pj=qb5C@b7QB^ywiC$D*`wvPe-F?gTLDFp zhlVI*`D|+dA3SFUZO9+mfQ{zue*tFRi$_3djloUwoCU6jrI2V`;)Sw`tG9w?J(@Q; zldU^Ex3TjxgIwv$yTQd_@*n1 zO@naQJQ~|Mcj-9&eENGY-G6pkk(w3^%>;f$B!QU&`TODog_F!lXm@ouN&ugMd*{<6 zH9=~99Vm?G>T;-eumKE|=x_Cu&|iKcWiZ^GaY;X7IG!Dq4Z^MPed1dr85kfq zT|K~09R$NTcM zgtUyErZtY^nd?)gx$!Z^38&oHbq%BgYN`kFmxV8RC5)(c1RbKUVz^%?;4jRFFSrNG zsrMVI)=-WX)&&;NAc^#Fz}CmLJZd>@{*YP-O9XB+mCH`|-z#ZLA!YZoB{c|W^B!JF zaZeCl2z5rO0llPV_?Ow4+_D^);$QX}kFyLi@{+?>4)SMA5uV692w5^vwA6Y2biG2{ z;jV?o4n1n*#9defVIg2l{snk^`&M|K6oTiXH*m)hgdN(Te5m8Pp^7?qUOM&j4{@n! zBK@W8Iay=Ik3bP~?TiF`cMGG40iNCH9@phxMMS2Z{l#94LIXr{eY+;adt61|N+q#^ zmms3PJ7|3z0ALJlA9kA~6cGm^zZz2{1jt(@l1Z*g=LGD8Jf`%8>TU`wiF7n?iN`$UE{{qH<94=QU zYMO<^mbC3Q-@ za97jU)?&!$y;1xH`!;y)v4A)D7f}1`$u&EFwbeSlLNfgJ7Z5p>(^TmB5kySeGrZk- z7BPM&Mu$JR8BV$mOgGo|$?4BcfLDBCkC#dOQsI~Jl*U7)pKftU`I3(T`xo###A}K6 z>I>Y$AbKe0yRx$GUw|BEYIEvCSy02~%Wk?T&qo;&-9Z&I^W>MY91rEpYVjKQNZp6_ zA-X2s?XFkHd1CS z-4Sj%h)3roqnNMsi(5mLN4{J5_dbeG?^@l^4?T*?P?NPaF`in1*eshY`SuMT>dZaieD{viR!+?0*t;2u1N!+~i4k%CY{Q&TLn6WnbI~(GZQc zGIy-K+1eRXiM+KzeyDKelt;9qauV^`ZQhmN;gvb444Hf~^U96-`lFGYTcqCpGDO;b zIk{!>cB!Ihs_8GlfKM%b!<8-cYIw@xO9oqo_U4F(a+n#dxdo_!v!_PC)@NIj%rT7U zJ>j`Xy*$R+(SA*4o#`)NpLo3>tO_8qmGGKn0{iLVK8zV1blGW+;W9r%&-gyx@+?9m z#A6TiwE)BIH}VcnXs5oUAN<`!Y7<3wSsu#x?kunWqSVNB`$2Y9Wm3p5a{B7!xi`^F z!TUZ$0&UX=-5{hKMWp_Cfv1aK;Ip27x+LA>J%q@`zxXbyr}I40TcGCQd`yQ3*h)L# z^J#>sIo|qodh=ezOfyM(*wWF>w%p|uqpkco``#Vh!o^HoAKU*!#*G0CIJ(T8zcs>| zmv6$B@8_76Qe1)@}8dyK8>AQR&C|%NS1Z|19 zhqEl?)eCgMUq^)0$tTF(&D(sig#U;wAl?_SAVf?7XJ9UBzSw-0E;K6V16sD0VVk+EsGkYcjxZ($lEIxf(h>ujYf%rt&an7a{Jk*>BpDyd4!YKP|zed>6M>Z3fZ@B#7w9Lat3u~Prt?P`9$5Fr z5Zu!Xp1CvzL@*w_5*|iU|{Is0dm`eDm5n?n-K{ODHA2MtM)8RP8#NiB^J4!x`O12cNJ9P*&Npp z(2@P@ZrOZjeQ^PD`6px^HINoToVp4R?DooZ;$FPZlLjg}J4>jpQ6Q;O} z&M#3J_YOqYNXD?fZ&p-S3el{D{U7FYC4T9TNLvcJRP@@<&9YYGZcA3NbDRXGtNodZ zJ-^+fS0rAPQImzzR$VB!S6V3&Mo8S=6JSG#1mf0AySO^L^b$E$#R?s3k&Zqu(Yi6~ z`ze0~qV+{l5N`Q`C3k%JV)kxmpArO1FCn!KIt z$6OiXFZ~0#p_LEovb_9!VCHDOLWk{4S+j zaLNzJN2tcKKP5Xn$k|Ax`wM2DK0)|{v4MSZ=Q&T7trhFxwLExmT))X?k`e#zN&s=m zT1K-QZskly^L_pwHECkiGmUNNFDQ7K{Eu;FOInZiu8T)r+2-2#`N0m558PGW9Cp;{0%7BNAB$p5eG+UT zV?DPwD5BaAfzXSB>P-t|KKyr8Fx=4+ihwGwiU4WOuQ=17%qzv6Q#~JeF7Pozj9ok> zO15WBDM48uNvNnLpeJmt?_n2RN73f$NKQ6Kw{XZ_K0m;0rj%tP;vaOxR1IDrldxkT%1MVXQU>{-ddu3zg6t5~Qd?ANE$r>!eG{F>kFt$i;^zYZl z(3t)cn@+AJg`O(h)Ag$@#hzcnUS(LcuS%wlkDw=Mo60TjDLIb9ewxd!A#yqKc)xp$M zY_ySh;5*}|B_-Mwp<{`Ptw$@mn^bkW9htrm@=)eAq4gI*j18RqqF1dV!*1fywn>@n z*7k&eOG`+AeYel0TVY?EPg6*DL}D=f!?47|e?OO^KQy=pxrM3b1ze4yD@*^4v?w)b z@AqcEyLD_)q?gGf&#We~zU*~~RNm&y-ON)Xqv??YkB*ISkAlc#$)J%F=t2rnUyb;X z*+b0wY9MfGu%rmKmA_u_;MrU5A$M4HZ$O zxa(wm*jQj$6XDTb)h<@J8z}RWQhtE`ShVV~I_1?Z2!UJoUe&^s4xNf z%rBA?Te3nyqE8)+pM*uXJ)eJO_%J}SKo8A#cYsX=-yk?PCJ_r6#jT4T9%JGoTh`Kw(I|v=b zp}owbDHi{c9hu?0o0or53@_y{dBEYS=xV*__%pG7dNzN~*pKPjmmlr_0Zu{t-fnQ| z8|nU=lk3OKBCwZ=eCeGXb~x|(AM5}I(QTTi_sWMKeQCWK!3@L)>*UXKsk7Ioe_N05 z-b#I@$6~FQXRw7Pg&c?%0!+c|fOD!rO0uzAPV@`G%29aqRZO@@(#?(Fu(eMV{boK& z)62j80SfIG@8%5}>&=xqrjO5Sag<-FzvE)wQ2eeZ=y`8=ETp`gg5?cMKn0B7 zNxLz1+vpQs`$XrA>2!9jr$=}FPfJbq;V-~S{HDRnbvhji%Qgq+ z9}-?ZKbQ5B+YK8(=-B7B+txTE`UVhuBThi=6-?hLSwnO0`guEDG2PrK`TdvPYmC<# zujLuz>&J<8cU6N}Trw5jmgQ!=PxeyIxsuKNw-f$#)$iOX>9EHV!hZqYH>f0D*^i*? zMCCYR;>0wOC;xT@p6U8Cq1Lm`J6>P|x7uBEaRMlsZijl!{pwb6izh9Zv3x%K0jjNY z)g#czH+tsCyxHCD{1d}w=BcHFUbE+Cr*1h5W(GDInbFzHIZSd`_>S6W@B2ce2$pi| zCJ@^r$4uBX;|sxuG$ig5m~1;0YR%Te88L0v6B(p>WN~Dff5{R=I>bxM@=&!4fMB+9 z1Nljj8%AEydAG1nVkpFmUi~mVcN|25YRwB|;bHl##0ir>d&-X?c0^dtWd%tr;JEPg zlh6{XsncHV`VarX9GCV(9^Tm^pH9|pzVb4(WM^=07GqrK45nRB0{4#&%x$;uRQdP5 zKdnD`A|@#TcL2`oZ$VL-s~f9S%g4b@h8^~bG>!X1pNyj$t^fn)y{NA-=oH3K_nBuM zmh+;s4HxodjjVsRWhQ;rpvud`Sgz~cUFv`Rg)QVVo!on3LRU`ZS`^&(VXF%J#)v4Iv2sA6#ja(vVZaq!rAiOU0d z7u*znv@?Fs5CgUs(irxC$12hisfwxMQLNBp_gfb|b8`pI9=!-c#ExF%bkU**!*8l^ zw)1f1MpCynOxeeL9wbFi5MpiZTs?DT9E3A{OL(o9mZEWsut_+Y>q(>0&#-hpg6IW4 zIR3nG@7$wBSV#J${KTK3w*Pcusq|?}!m8k)vW&P){>r_7iW3J!n!zxRFci-CV z?8}%FCOGk-~!lC`QjB=ZZ2>TVbB}75n*Yc^Oo>w_{OYurD zU7W!lGsScepEA?hm4}7D|CmRznsW3Mqky*6^_=`Rb|*O0$#-*~C#xd7{x%!WM1&-v z3G$#*=a{fPdYYeWRnR{$xChQxH-FFPQE#qlohc*@DZ%-dzih60E6;zbuZ_akEEaL`3fe{ER$%knPk>I4_M*aKi#{mC=+9unTtD5K@v_L zn=aWYorHfLK9pI*r`L&Gs~Y2oeh%DZJz5$@1RhLR>dy9K5K96lZ;DPTshT6SFS8Rz zTe5oW{Sj?ZyeFD2RC0rYA}sH!vW;%EY%9DQygcWdiMGvl)5Euk@0(5vn}fREo6Dlo zYoA_D)N34kd6Oe}lmx}6xSNN$so1bqnaF{y>vPduB;L%`FP45OjaOA`dTg6v<#tyS z?Pz54Y7O> z&K#ByxX%W)SHVm$Y_e*X>9JW5#eBz&U$v_9UeMi=|8IaV8}n zzeZgCu(T1Q7yE>GyVQro=25oe+`F1=c`oWVr4!T`)2Nj2T__?SlkMTfD;`07}Y zHmwaOs|88^$Q&sJuM1a8x<%2+QQlbWF|qv2dFrX9MF6#PWKmI4i!AQrVZly4a4FH* z`p)jOKe?=Bh@$-%e*)DM-@o2;qcS^CCTL<*ROPgT;=sE{=v<|p#u~0&N7afd$|{ni zU%prO99xynZQ7R66PuuH*Yd{Ov|3%$UegtX7QE&*4;JH?M#9 z{2qr{|Fl@JZMcA#&RMX1`nFPR&*DQhjcOgprrTN4`SDa$O|llh>I8(5aLM}@OG2Z< z$P|)%3GZ*X&3k@}q@<@#{^a#}$H_JzxF{hf31B$?6ukh2RFc)4^MVu@CH@~v z5FO)B`bR0mJfSG;a-ycyZJZg+2!1g9aLT0Oly zOC;X&|4S{&$QD@aItXarC{ie|xev>#7BV*Zf6(mBIaAaO2V2Y;KXk2o;C3jr;FM=h zMVp#Kk-H~undvphGa3X3^+6 zle?>wc>brL{>eh~tOTOsyw!WFt+Yp3a24mj9JyE;;o(%nUBub+KefkBvh(kE04Y5F zlVY_n)jToXTaB4x?tJtf+1?Cx_|v*(HW~jYX#@YSJ&Vi}!m}cwyseM>5(<;r&xTYl zMb^5NyjxrMcb{SKX0aOdoWnuASkQJUj%#tpBitrO_!f3fKFOdZfG{^TiA4;yUT(lB zMpHtRMH&?b1}eR4=Tq@|@8Nc}yT)OT_S0X0#n({rx*?;}*XsrROR*DYK>e{ zEo$c4t&p0R7|Se7W*?6u+bp}$?-R$`pv6K0<8;hdOEa2JN?53;Rc>CN_OuY<60Rl= z-^=pZnD713^os;F`ZUysQ{eF}{2Y?q~f3J&)49ZZkJV zeZ4K0Nj*6M(}#NS*>rt7NJfT1qe8Ie#?vMimUAj8tzp)J6T<7a607`YLsn83_q&;MWognh4QVV9( z{q-A<{&LFC_9t?E<6=v@5{Nm#=gF^aR!irdUep1mb!1;1)$$fn!%4sUk5<+aq|R$K z%XO4?C10g&*)Gu(ZtR(Whbp#pII#Y|DU_w$x&@`1GgCny#2K`b`80{v45wPd8x8G`@RJ+7)72QtQSduUdO$o32X88ku3MR7`M! zP1QGxw$8;k=1Ug1nLqE5zov+Rg!jkpw9{vNz{!e5uhKOJPqe^jo;#2pnCVT5QY4cZ&tYgp5`GRLGYY$(W zGP-${i9QQB{RO1DczPjOwT;y%Is7r?C?i&{@8KKi&& z8R-Dj^*#s>143WSh`!UyCphudo{+S~G%2PYKqoJGF0nnlSaMBQ!V$F=_czC#`j9VP zt#Z_ycM?Ti!Ga}ae*D_%R+l#w@@4^`ZJJ{&aXt+Ov?^SPjOs*7%%$j~} zUq*|=(TgldG_=lb;f^5e@?f2q)&H~|I{s9YO{wIs0b{D z7hWDb7d(U_k=>(?E`Aes%;cTQWEvU2J;YqAB&N)5Z8)%{!nNRqW@j|ewva)r2whT_ ziID-LMouN&qa*|NwnJV70iVJMk4^|J7nk;j-v7j2$xzmUH8j74%+y*p%knEk;;V4) zWRuHUJSDyp&DG?I6(h*k15tPJ8SdfgNSMH2fntsC_{ZFkhgWl^UO|K>Xr8h@bGSuo zlWVI|*3!h2oqw?-Hb8%!${-vb1Qa)P(obWB#~%VRl}%_awR|Q1U8!i#9M~O67Z3eY8(46L^w~ z5{=bLvFXRoOZ$+I4Zf^!Jhs(Cg^Kl1$^-_0i9ygnT_o_u53@EmRT1B{PBN&s zZ8f<+45f^^pT$d#v79y#=pi4j((f61ok;8RI&`HsP@tb^;!G z63x>Zr0%_pqC?$y*Wh%YJH-6qh>XjuEY8>p+gpf-S`<5-F^!KIzBc;nHU|G3*jm_z zVh}@}fYcAZM}%KeTsuzb%&RY()Qnr`xv%x6Y7~;gtC11!#LWv7%QwXaBH*t4A5C8Y z6xI8-jYvv&cOxM!-JOC;hjb&-v6OU|bT`u7-O}BQ0@AS5((wDv`u@Kec6NrDvkT|U zd7kUO>fn37hEJZQm$I%Jv>bnJ2M#Icf(sKEI9pqQL;quMIbTRCP|s~a*T!CsTqJWu zzFEX}oJkds!<_QH{FVaL{+R%Jk6&$5p^Eg*h#!4c7k);@}tMm*Bw1A3w+OMk>YLA_>~Nq!_|LqFhCLt;gv?)*^d9~ z>#GfH)xeH#i|#@Foa=BeT^M%O6aR+FLBg`%N_Tpx{Y{eD^ziv=mGJ5Vac2p=%}9naW(p zw{$wmfDNcBST@8zZVG0I7eTN2DwrYl5RPZPL=5tP=5@hL2kR4MQHR$W`0T{;&bmXl znTrP%I;W}f1+w3B4Bh_(bU8fR4-V}7vdTPPtckGLIEnE*mLOuhIQ{m$AGNB{$1#Jy zZTN?KO=v}XZB9utwq(t`nQk_#u=Vk+9v(&Jqw3qtDdPWI{nz|^BAl)+WN8Q^#xsF4 z)5H6790q%1#d_8zZ?!H?Uo!q3fh=K->yvxtUe1Ky>Cb;$EM#%;`B)reGbkwSK}5>M zYvzSbrg@%xStsqNW}%MuP_<#|GN@EH^a#81Ma_o#eKLb^hwpRzy(STnrSxrn*Ez=MYDYnC5Ln%~06K^W21G`;tSI0jZ^Zcc?7QNL9^VgLq+eX^+Nh= zrW*tVykA>`lfOyla8=VVeB|}5tP6tVCVe(vk|T&j{e+qz8nfB^4iHM-wFTTHt{;X( zpYr>XWa2ZAq?<}XWsxWt5pfDdC{dHK8CigXM=zelJzbUN7^|vG0{a|ASju_A`{&M4 zyMF0r`3g57jq+c&$(y}^X&q`6+*Rw|R}QVX~Q*&B7<5O%SUAL~P5}Y6pKf2+HJw7YNT` zbU}u4mgGv&(t)ETSpEt%O4($<4Zb`W2a**#$PcEpCv?%NCTo)jbXe>S(|JSkoRJ}MU7i~ zxoyn35p{mNqA2l$@GO!5LkAB>+m$2-f-C!W{z!!szrSBeP7}LyB*Y%4ILNqIN7>Ry z(6~AHJMwtZ8jOyMOwdXn| z_1^Dbrb)y=op)Yl%g?9E7EXn$#XV&=g+l!vw>_0tsuLm#t%E;t$(qCi9yMnziKyn+ zeYV>zd&Db^GvKL*XP1q|SR6sGw~KqGhgin1{jARiZhv;@!;~Bt0HF#%qY40)o4fz5 z2mdqqz;8Ll4FW93KaQJiDrd{@@ZKrg1~Znb{e?i+?mIZ3==4fE$Y#?4w0s8h2;E6e z@57K&9`IQT-+bm!!ueCT#FdR$0DWDmRlmZCw-NR4t>tqh&X@C#o!(Sbq4!u)aVp+D zTn2trky4EO`_DH}NkF6kqKh9fv79CVpANNwx`?eBn!TSLW>yk5$@^ z+K-96PhFhCKDy@FX2^u4?T{fv6myI$>#vDZP<(>WH@aY2VR@yGQ{E^y>VFTUupRuB zk01hTHRbHL0_Cx@N?FJ*fdeOXc1QVrVIxJd%+$EpNHK823~(lq^!&A=GwwGvSS1cm}iEZ-}OgY}XZ=MSBa>leT@Ou!d3JRahD$ zL7YzNdS2aIna8-4f*BbHB+bwhmXv1>nl!ShvW;ZO!d$Z#1$fDm~ z@}GmCz{F3;IK=Hd)*98?nn!5&OHh287_dV}*S?Boxv@WX)D5XrCV#KMAd(cW+W8O) zt{HrS2WxvZZ+xgDNy1qPM?qluv3qozTo9Wm%br%*1vB5z^64~!eR zEpG31*GyekL$6R4sO8L)dgIkXJGA1B;}^c_N2eA<=LEc5vRjVDsTJMl9+mf6JPfaW z+APJD|2f`!U5I)bwKsLO)Y4-9SpQzOqA+EvjPfI#wZr2cgm1xnYtsfZ;d&Le`MvsD zZhxV#7W2$=y9Cp=RH@JDSwm23J@pQ<qi+k-Dmgg1Kf&&t~%!*05#bGtB2;GYIjdi5l{JxibH9XxYS)4|8??3?P~5i zKj`N;Z`$ddR0g*48AH~WS#~0D`h?uq>#e#jTkUTk2=s}6V`63J1@(31h~2zm{X>a6 z=99WN8vU)}wKaLR;xlaP=0lwD@%xyzy!`w`UB7>p_C#YL()eh+)g|qb0YhAkanhWd z{wQs(x%0qXvzDmK262kDK~oVD;{Bd<0UQi^oc>)%;*@O(6UvG@;=C2zt*DO{#6A|s zPo3wWc@|KAiYiT*?uiVdWrtR#sqr1hE>z?$$`(2M-DW?Ed=O?7R#h(5YU=c>`n;n` zY1cr<>HBs|c?m(k9eGvc(`vTK^r>Z~PU5;$Mmba-OFdJm#kB7b^Zt3~6SXpZ7KcQO*rL(-A6yJ5j9eSM$ZL7<%#OPtEXBqA zD6IX-Q{mdYa`J?%cm0dD^(Mi+W{c1jeqU`V(!$K#K56G9lMu_=8uT@yko?H;IgIXl zs{Z;gw^1Vp;8>e5ngi{XrwZT#8!OB=OWNU4P%SmQGE zH3`0uTd4MCdqetSZr4w4+BF>Ut*UZOi4Ic`23LkMn;VZPqk5-Cj}(|ySWL_oj?k6U zdRi(@?^`zkT}v+_(%P1`S4qZX{<+`SQXP~0s#A~0Yh~`X81%cxR^fF^Xk9MVcRyh$&Z;KARjf;PJ z^r?U>x`Rfqoqgo08hA3Yvd>9dEP6hdI25{d;yT%BpL$`)_dd>t(Wl72fmB49gXJVVUHTYaN-vh%K*>5m`QuJZzU1Zryy^C( z?~9z-_?1fd@;YQw92>AoM+Ib5ynPj9B71FC`iKuI-3u^z%V4!=hQwb?lHBcRr~L>& z`^|xkUNj*onIijL5^Lo|21E%18>h%jG5^aS7Or-qSZ84Ff-R4L)?Fm@-tSl^+h0`~ zwNU$=vB67S{Wot?K=Y>mg)`rmNs91Ylam0+5*w3Be+qqWI7w9 zh%2$iv+J!jvBpr-sq9iPa5q}3f0h)iX8=|?D8{G5RJ+f1QkMf=gjVWKJ+76H|BOt( znGHC58XTFAcGKHc@CwnU?AR#TxY9!7i&aK!U+(p__BTa!w}9d-?iB3`Q#Q>+|!ttbqNz4bK9FzvOVxH0O=M!(hOf*3rw zma0q1dUHAiR#}Itfg?@dQCdeUuoZ}Y&RJa95GnY5vaqc&yP+Y{cPzQ&-sKfM3|EE% zPnj$3S?+SA;cO*6+}#9>;hJ1nT`Vx!ZVqNs1w;h}y)Uyc)I`+}7|L5abNK+7GcTW< zbanrq1p%Po7EHfX`N(~9#yM%*J}ep$$Q5aNze!Hl?>ZtfL%ZItuy%9Bw$NI^@oHZC z=OGt9l8N;vt0fR!lO(evz9`{?h=!*x6(9S2s`B;n-?cey@VUQu3gdSrs4 z&tlcC<>|bSnLtJncSIkQEoSpq&Ag_9r$J6y7?VQ!*6!5D7yaxqwRSPz1IszuzmdNo z*DK7&ouoMgy$=utv?Id~*~0hXY&>HQgAkizgpC(jT3)~JIAj_6sf?v*tP3!Kw7}CM zA?0Td6i4zxg+7GOaY{`{okBe5(_WCy{C8OcHi01C(39h#I4LZAuvy2hV*|4H*U$Ym zr~c)oK8b4S!|scB7`s!40>ehEqR^5PV4AhJWkm@YjFY=l<7L*u;T*O=k^6#XuoQrF zrG%v+_>0TY+JhjZeeYY2?v_**lNSO*{XBYAA$>&f>PtV3lhJKif)kt(i}^C&~>)5(AOGD89O{D+DQ;Gu#b}D0Ym3`JWkYXaz!~6}d8x<*Qv~EE-8D z@T)&BnN{P88|Vd$VbpRDc*zzDP_XBiJw_T-MvKvziT={_#zMPM>^ginu5q$m(>?Yq zMm$XLZva;vSoBP(L-b?<1n8Lx|4QiFRCYgGzee7s$dx~rVu-CeYu|I7enBzE(ztsB zS6M(w4o-+%)+^@jUK6*6`KZn4C5$h|En1)&mF5`_si;64oGqQ3^@3JfE^U28Nt^I; zd+Zkg?OaP_q(VJR2!^%|jl`S&L5E)r2IJl%B6DrxMzP&LCjZ<$@rHXmf1t6RwjSwF zvLC6q(A+&RvK7enfl&gnpZ=y`@?Jj$jlIqKr&&|S=w)}UZK>`Vlw94a4?%t&(9o_x znX$FTyWp6;Jf2*=UNqBP`Yt3K3dcb}P6UWuWXRm%Jr=Q*roiAzIRE-y=EO0e<^!pS zh|C%?@qpLJPq@gsvCB^Z3Gn&}+O@NShraSdT-YcGVlE-)#|7ItEEvqig~=kijh z>Opi~^R2y`aZAAO<)V`lW*WBX@`rkG_YD}@|L{M0v&RYCW}pl_HGXWlzsn=0>MUEQsPN_Nu3w|I2Ze*)t>h)Kj8IJ@QE=^HEz{=GHu?l zcY+Rmm3F#nBACwe`9Ke6Di*qah);e)z54s~R66{7rRg%Ix3-4J{d8$chG(g}o#*{0 zDEkMZrvBJMpU;Woc6qOsdBkoW=Ev%|o(*_$<;&tB>^DXNtoo%V>Rr0LS*pL09dSb7 z34)0+Wy~Iwh;J+v*G#ZvI%%%++pc2cjx&&i&7Q2$y*MjMi{F^W+rMDa@+F>#*#d$b ztA=tT^m$qers)BYNMmQQ!b}L!NGYNC$?8;m&5shaFJj8#X6BGgAtP!vE`_`L=-Az< zp~!+hD){HYPOhFzLfs6pQ7I8=9X%DNZ{jf6tugp|=*7Ay$(6D|KJ;#`CgR}Ljo3}G zfr^UZoSEJ%;SIE=gXE?OPDcoLKSgJ%+a?@^i|C>%E`?p4Wm$Q*|3W2)ZC|jD|KU^f z!B(~NQ~ro$W%aD!zCb}3O`(X(1%80`w{ZuDIO0P^p7?pl_(R!Sl0AwEAIfA#1bO|N zk+@AfTS3mbl{n)yOAf_;XFtFRAE|PP_BT~=`g1AHkvFzSWX$(3rqBDcF)jqvRGrz( zvbUKt1Js828aFX9iH7vLSE?*ZBb!X1JaV_$ZsNuegw?4E#XOC>OCk`Xqp8+S8 zu~K)szD4cg{ah)+eE7h_CxR6VhmiS7AS0*tJ<1VjHl?jrSgmH$kcBRdTD|}aygV7B z^Xn{3qB{6h3d#x{5V+s{)8;((pL5ef$I)l0hbN6xqmY%q?|bxuY6+Q)9Ia6ouq7&@ zYc?q=ZCWf!7@6`Zmz+U=2DIuiQ?G6n%NCq8Z^FE3-wbsIO+H*(tNoc;Z$H}ea2smW9UddF7Dcws)f3ts_mg_RH9D?Im8e;XJRN)#W10^p4}*AyX3yCHAPpfVb1_mA^RGNS_% z*PAtsz{H$XHMzR1oBIpXI%v4_>a)VcvzM)duQ)yuG6Zb>8WB)dU~`nhrH=;E8vx|Y z6#oLYF_;V-u3NGXHlE%B10*$TKQU@}&Tbs|2M>`=GVf@;bL6)!IZS1^VKRGiv5kJpD?c4AHBZ&pZ^GkN?>X;t zAb_?pLBO0>KYOdU(GzDI^r0^I?|*O}^XCWAiB80@JdLo@y3q9WGKA#HQP|b zldOy~DqcOkJ*}OAa>y0mUO?YW?k0S5)-1|*|3zF)cBgq5X|9o?J7T8+C)WP_tZ{g2 zro|seQ6``7yhHw{BqTFiptzy2OZw)Sh3)s!?W2D&p^TwDv@^%W;L9Ix?6a1)8t1-C zwoRHY%o9c&I-dzdA`26YN*E)R$C$v6hs+jL2iKIG+wni;c-rgU(7BZS29xg2#b9&jq{MWUEa-0;we0__SJoYsYfUO3&tZ<6vrT4Cxb z^*1^K%7HUZLm6KkmEZSf4zc0|@q9ZWA9U0#=?nREjrB$I$TOC#~Gox zO`JU4fGE05)YGkYQ>x*;b$_a{xmWmvUn++Dn|g$n?51yp8JQ#D0cK)QaQ*tc6mD}g z-MZi;b_2U+B@|d6P9;{7Heb(*xoBN>*Q(g-`%Y3z2^Z{W#2EutJM+RLo*{uEF8hbF z2hT2@h6i`Ck;qm!BsR77qWfa?pQl%Xc3#bMQVM0qj=KGpHbUh{7X+z+_kThCAOxv6ySKU-%)(0xyGMv_v zBQIof)v}W>mQL<-n=eEsql9`t6by4Y$A}gfsVLm^4ksM)^`Qwgf${8?-mFYzszi4V zyGZ>-Nx)?5cgRPuTu?pgZ4K2(Z4l^Gn0dh2swb|<2P`Q!4HYWkYeuAF)G4GLeioQ%Qq*XVKH|TA5?P7?!=j64kuKrZ>UP-)^vX z>)gu@nBbbckVc>w;M?h-Y zhTYiiJw6MWImIx#R-^gWZ_ihIzT=(@b5ODISLDR;-2s}4t9{S+mA6Y&g|WBg74D{@ z^k6c#7A41VKoaAU8_1{A$sDHmVt8un5VPm2K}=QMSm6S(j^hnu>SDyIPJmFdD*kvB zH?qqqb;IJB*Nhx(JbRugb3S?@g@rWbM!;8M%x|#P0i}KC9Lok31YdX!Dt{@v^EZx8 z-W5e|S%E%*o{xY81IrxaaL>;$6f#|(y(Y@+6YCY!MeU!)d@w%{jLdYr`g}F-b>TSO z#Q3SAzqURgC3UO9JEbp2P1lG7y8b9`-W);|ry3H$X4k$q)&6=3WH;ZB zpRDD^ATM4pr-1nRmgLmk_9<|bG>W+^WTeOTpVLU-{HoE6DZGFN8J{ydaEt0^!}n&j zb{0`}gO5hl%BMq9Fu!0Px=&MK&(RNE8%%ZYSMjb14GC0y2nqGtzov8hFOe5+lr);F zNS>uNODjQB+1IqCh()i?T_B&$TX(v#r*rX@&b=guGUGEz^7~~oL8d~|(GV%Jkm7Cm zB2#EFJJi%{oWOG(@fpKluVZ0E3`MX-DFHW03`b;$F`$Ws?|gF?Go-I{&kVBMB$Yi8 zd$PXW={$FK`>dhGF19kbMZBzZ$m)0LyI#uMp@m{p(dXpbR3K{QL$%Lt*d;G*ch)Z9 zQ0zi9EC@U*sY9TiaZ*8bJEw!vNdK zec{6P8My(mYP~C%Ea4nBb)6)7XRo-&1oE9#qI+Y`w`e(=FqVivHGD}o2dei{Nh&Jq)eh@b$c6ba7FKup__Mt zc1v{P&650OGkxx5wVbmB-_u>+5TYTplEybr#Mk4kAF-Zx^z%6*Y!t7kLEzYNUK5k0e@sq_q& zDT(ePWwJOvGj3D)qU;Oh2cdiTm$t%karW~k!hQUE4TNll?{*XAzloT*iE&1vr5Z35 zBFrPcvDL=VS(Ja_O>J;^T@>r6mw_m>Xt=}I`nSm~uW_}#&J{s%3amtg?C91I*MQ^v zf$^{n%?}BRQyq6mcc8d#@#!m?E?p3>xe~duL+B1%Ukvfd>fI$G5VARVn z>9t+@Opu(^9estv`zlntC;9u&Ta-^j%fg*5DlpIhTL9F)98nSQh&rVh9F)@=U%oPU z)s8#3#qn**2D_d=73$yoZq6Wi@BB$)TtI^B_@PYr)^cr|L*d}Tn9|LsUfoxq(89IbNsX;3$f*QK}1bALf>yzbnjg6UWp;eG2+3ELw-Z4rlxZHYh+>GB=tq{WbfDm2}IA9#-Co*tY_Ci&T9S=+B4 zXqMUwe|ppC&JpxSwl+bMa;LA6`1&I{Je19=ecKbwT|739`06s(LU^!Da-~8gGg}ZG zJ7e387N?7z`~tgsfe- z7xg!ORaxRRp1m_1x8RBB!%}_#XC+>QFXOW5-5*(obS+ZA>vk^<>#w1aG2CnIb&u~A z_tuXQ733yq>M^8T93|H|WUnIJU0SoZ*6@dJM#mi3J_zSPR|=Qa1ElmuxeNbm@4dCkA^>WlZw%-oJwh7QM_&IKlt z&2#SZR-{!L)b3uqrsp4HWY#P`9Q5Vl8Q8K^&H(azqyoTT|AI8ufS*-8S9?_+rjQ=j(wo8I9?Lm>FfRIBLHUyF|6%OgU!P#1 zu=@PSKLKW*aHlT|jsrihDxYj78GVJP!OK_P5k=|}{85AFMhk=}v}Vf&_oXJ7N12uwFS;KXjK9>QmZXXZ zmZeXrxYrYJlWU|P{}kLGQ#g_8s}6oYEM2TfldN^-{ShF2+20ia3N*L1Zl5a%272?J=8rhp&>&0bq ziLjrCS4DEAM5vVrU_}37XJnr0Pw+$#sc%L??VV|z(9>Z5f#Q_joopxh*{&UBxWyr` z2yt&E{k@dq3h5TJ;QNUZ{`gEZOfoJl5w4r-zg-0C^=PxwQe#9z-nGbO*1s$%dT%1v zrjF#37X6zrrsaod8$zS7Vzn|fjZ~i?x7N-_%c>LzPRY=AkCu~A3hsYi9oPlVwd=-m z>RbT0W;oO2U&1QRp$!7p$B>{1zlKMDYST2qC9$`7KEs}EPOt?cido1C|d$(Spp^q8!r~K6)-_6e3L+?&_vIZ`k2^%j)C!Z#9 zld_n{3dp^r1(sAic94CDU7^E4qcZ7(5wp~Li#v#&^L?yM9W-++=5(Ggn-Hq!=xJ?|^1N}f z5{&~f0cRv%K<=#F)MGuvc~Eyq6q1-#;pyGBfpKuyjr35HtwSC z?)-H2bsYHrYtmZ0+9>St5_e`3?Xls6;C$pCeV0qW0xZ@$A<7U*9+!f!ys*fO6t+n4 zz1mU-9t)!_)!QIFpqDfk?;bIvWY*VS3c? z+^kzXJU3iiX{2P^py#?f?ZD-(>ydlj578}C4N~SU{xL4{0dj}NYu;hgA9i4-2wouh zRlR7V-~qZlp6A0<`|8)z6E);=3AEd=6LAi6A9l%0)?c3eh42P5y}yp>aH(=L>5H}G z+?)%?CT9`MtTFygr_26#^W%tQ{%RvgiD+`Kv|=Q)R-C`ew?uO)e7%^6ocU%X5-IWP zi1xvxWnj$%2LEj(V|Ucc>ftf(tp!kq)EyC%J(JC=)grubdSeLh3znAKp3kKhIF8D4 z<5S!Z6&?~>_NCnNDiX@Uevnlc6|HrUAv1YKHqAUzB5*G5Uw8#i|4UiXsf;~@L&99W zbj&XpEQ(FX%@s2~Kc3=w-3zt6YSTfkG$r%!nMP`QyQd$7Fd>O0r%=r2d`$6XIqw@l zA3e!CQ1~574CP|r59%?&ItgHbzyJSw6(y_(g<*CJAb6-@Fw*4x09Y-oktjQC<{?$4 z@;Ls6I~a_JaXQf4wu&UI!z>w_5ye&}_=y(i#4`kwkVZX{rZi^zS|51ul&-__9E&4O zH`Zn3Sa+)D*SanNCEj`=HBko&4kUzW>)VHqL^=DzonX>2a%eXs`)7YAtyvrB{O?jz zX>Qj=$)fp>P{irz3t>BY69w-K*@3xdQWX^(stPK~20hfAAa9+RcNjKFiBkg3bD0(+ z-xs@}9bntF;h!1j)k9&Uo`1CjVvMDT_ws+ReQ`QUt@_hr_dSN8Z=!PM9r;}O?~k#& z1xJ16rHK%Ma-_)q5rZ4WGI$6;!@;Sf7KQPn z@oMQVHS$1QDY5C0t^D6vM*;R-=n#_Uj}V3#NhHg?S^PHhWC{8N*N^d8c<*otXc_{B zP%JWo(ollUNr%{iSruavak7+tNol4$NKL=*6zHwKo@8YV54mwoyOMtZTIFpot8=j? z)M;n4kNTQ1QKQ)%Sg9#SJ83=h&;k#9KlmE^oqhE1{$V02-k&fg)_UM~SAd{e7q*cv zr#=GAjp^pzjcH1}<=z>)vE({E!{7LqG9cr= z`BH2!m({%pOJG$>Phh_yt!$Z?5*0^Z=TVmIacO>c z`$r?EqmdHUJs)&RLptMAE;AB3L*6-uw7piN(n%X4eZ@FnL8#3F<@S>&I+T_Of6yn^ zTwDLu8Tk_Vt17?Ygwkq^Y~9CuM zFU1`s2fu{5CJEz|bMt#TMfr94zk#b&ipwWbX-Ln+Z}8A$vS#E<@A>`pk!kA+A-04< z?Y>Bc&OW-`YIb3-Wh=Hfv%+sb@_HJYb+;Xc5}>(7R_hK<{0pFuQHDf08GjE@>$Ynmh5&^yZ~K6ao2Yts?LWxdUq0at2D8M#rp1=iBvN+6K>Qf@!*HKMdjaadSle8BR0gPrFCYbEj|_> z+`Th(A#J8szZ){>sbwK$j4M;>y_THQxjF^&4E;vyVpL{J5qHh9xHdE>Laa@D+18^6 zV!nI!?yQIBIQ-2|L4HorjdEcTTrY$DaG`}SU5dSxU~K+z3Bn&ZGZNFt-}Mri8E5pf zRK7fGzB5D5bR%~2=4G_lur*&j0y+b5Q8#(zUNfU(yn(kwf7O^MP)D9zU{Sy4U>+JT z$f+P4O|UV}Bw<}B-Za)CCEQ|~p@{(hjQ-xGipbN5np5}WctXCki=8VCB;r2Ca&l1*LtME$u`2j zT?XaWoPCzOqW(_1#K?S3gnytJMsZ{hl`wva{gd;+fj4@KTkSJYe?O;peCEbG8jh|mPScTHq!^(aMgtiYhhpe-jpV; z34C44d)*jIECa2A3(on=i?X24Majrmg2vd=Ru^)q^a>&JH@yfCp?jIQ8-R*TLQmH0s>bh0&H876FFEmFzs0DGyAz6U@wHy$aM384OD@ z65$p|sP=)~$r95E-NT=2QLy#QnTs1Nsdc3Y`Aoj?)i@+tI+z(T?DLlW=77917Q6{G zE9=qr%HupcJlpriZ<92kkP?-Ksuu_u&-p>i{)0n=?MgWP*Vbo8eYX%kJCorN5kID9 z0zmKr>`D^vWZ_yhNfz_{w+Jx6*66Zy4Lkei5y>Ft;NTjgM>m0|bOi$m=Gx7$MxOL2 zz8C4eR;ub`{_kbas?=oYyBO7g6PDZI0~{IAw18Q~WRHNhi&M*0=Ko~m6V3@q`>l{f zj^Ve&)ZNcWx zb7in@wyJ#PYzz9;PV(WCIOQj}K8W@$<$rLQSBlru*J50<;ao3IjTP?2>2r-3+PSq-a>MI8VQ~m) zht>lzj`H>VQ6q767r)7zsA?7p{b8GZ21V1~@MJN{%%hvwCBYbmavT|3+oh(TxAYq4 zd2DYuE0!4=JRGAM%czn1_K==Wl~m|Tanf@a)&jrJ_`s(JojoI#Q(4<@t~+L`!kd;w zan4W5#b&->nB{+z>3iW1~k|v|>M-M&UC^AK* zzge3MNSI01R6;-9O=Ql$8Z-VGdj%r&iae7nj34{a?b(@)z&G&`mgWY=6xjXY#8c^4 z?q~*7S1?V2N*|4FfOdHF?PWGQOI+HWjE;IrRI2!-8&HmBb{4U$$qH zJay*?*nVDd@3>}%@_UXFF+v7Yp}A;j)Z$R{7LVYwlNNWX%thsN8sA!gxzIPc7N-uL z*3VzR2;@pFFRkBN2?#bdhJD&*Y_Q&)?z}&qplLp*^&y4~A)L2mdGAl&WF(jDfm1+{ zMsHK7b3f}lKCY#hP(-hdrW}$K2hc2iOpS z_nup&xW~HH{$3l05Xm^j`IMd;sn{ru-G5~(m>32}mq3hDvpjg9#dF0Z1(5ZJBr#F0?V@Q*2bp!d z%5_Lfm2$3B#4`UBJM>t!d?pA~0t_Xvf}?6Dtie zBEM1OlHBHFONyHA*EG}p%g7hABSJmgjN1Tu0+5~a*ocnSsx#A{d#KVFO|pKM@r%Am zjg#KaTe)WC6uNLi?BVyy$LqUz!MSNp_#${B}P zST!QhMwJi%v_3g~V^Zx{h~`?kd#Xfe@3@+u%SaVC34Ck)t6AB~cr2&kq_pLCHjbC+ zje(zi^ODik;!U@cqD5*xa$G;Al8sWEgzy9a-xorAopH8bpE%vk5e~aRD7O7yxnMfG z%sCM1>i@Q3SeAox9aQ&Ps5I&+fDLsrsMGn-Lptc4PU}2+`Y(lvX0;Y(1jO#+f99W} zch_c64dQdZ@XL7)BFjw@CUXaSKS%1!U2gjo^`F9XEI=sCp)d=udD3-s`iN(qL<)1D zM|~Gia4;{##E4g)&p^6hEGgDskP87TOV{}KS6hRv&ue?=B9`^C-V%oxdZos0vLGgR z&rV%+*J*LE*USgeX$ocz&d{Tz4vwYXYK9M#f{qkiDcf5_WNCs#ooAn2^p$}Is545< za5L<|2YUC&AAHrF;e<2%l2So(bTf~Xe=Q?J=3O^N4?Nu~n5?m!w+NyadmsJ4i%56~ zpa-A1bkS)iZ-t;ZBc2S4kjEarrr7`BcpeM#)AsitniBA*;=+Ms64NBR#pTPKF_)O@ zWbZ!YOov-{Fqd{Labt7~T!Pnzqn;q3G;(TsjzFz8SeS!3!9aW>PXw0@Ph|q3!lvKa>xA~*Fv_f%h8$O!N zjdrRpi*<1K09$@wzJ4Ixl2Q-o*QerJcR;NSOv)~ueadq_0c;;ENGR@GlPt-PYw-c0 zOv@D_kCSkwfd#5{)!c3c??KYB4f3@6$eT@l3NvdcHw+*o7If?bbSo*d?c&J}tCm>K zGkVcr6%htX2?0gu_d$bl=5EeJf#bg5ohr(o&y#F{n3-Vo^>JHK#B_<54Ag^njU@(Wl9#k{pc0lw;cG6y^*1`d|A$ z{5*zmwz+-lW;&n}x~~ga4?M(eA+^`iB&Wu7 zGVd}($3bos-!}~u{JmgxCTA-l{X_jNR69GLgUvjCdg_CrGXqtG`JtQQ48{eyLW5T_|m$Q2=ZL}5)+AV5x(--^N2+c z0b{m5kzu8}H~z>`02?eoo2HM`Tyu8axnJ5;I;3dd-V)S3@>lBJofy>nOZH1+{N!KH zn+joZtH0ZfQ;kwc^w0mnZBI%$RAB5zNUN+kzLDukStG^v%tQZxj#YY@(taoQ)^17R zQolyuD5yO)g@4>?esn3=%%avByl!-JEE#}8f{J=DgGMk#ATf&kHSU*DF=Y_&p<{69 z8d)YBe-t?L#!7ONqfiO~JI=R#@6hx)(fs)uQkY%{bfieY40m!!2s7ijQ@n$Iej}`~ z!IlPT8E&BJHg}Aqcw1QhCg9zN9~zL?f9Sg7Uyx4PwjirrN@N6JL+wQ7%fDU^arF|n7(GDqil@l6u<|4z>SScFEM|_VXVn$W>>!V|MaiV1YU;; z^nAzydSOPw@B;X6b< zq@2#_nNg>{qP)tGba?-GJ4i;jejL6NiP2arHrM|joOxb&G-&2+O-}NW`=Lg!3*&m$ScG?=Lc)7;X=eM{S-MGxe}KZUu1cK5 zM?P=ex_p9s#UVS0g~F^~IBx^qMhWRGbpOMuAqx`}JJ)gs`y z<#ks}+qQ%mkC3Doxc<>=Rd%fGZ!MPE!t&=w0Ymo|6r>sc`Ey!zW`Nt&k2>q&4DYhvdPWGfW?c^& z8)_zS$4+69?T8yA0%|8h#AP>X^XEKc_3aGU`z$>*I7U^41p+3AXXT4CiU<8}*LwNA zM?F0m3C4mk7obI&UR1jIz~Tk?5tLUOPDJAuTEhmzoxh;2a}f?>aQE6r_og^2IHv>p zNj@$t``AQogg4I)TBah*)(c`=e04eW9GN@G*qCm-bnJPS9ElegVmfU-8hT8V6qb6F z_9S+M2gjN=Omh5^J4%uXb1%+%c_-ah(!}Txe|yk&@%U_`Mzww++1>SBuX^s{Qy4xj ziQ5BJyC~j~52N4&%xx54a{ENRS2V`4?p2^JwOLRYS91gNCjeGB=2)Usa0u5!9*Px@ zdbKE32Ed$|r#f`bM03i8a2bHHB9)B&BS$K(p5ONh-$ z)T5G?y<1?OvIC=7oK~{jBFZ45`R zDi@GYo|H|#f$ry((Bnm^y87GV^kiR|mDI(xJSC|mfz{VHX?Ym8+HFZvD8a)w$GKyQ zRkuNF+q@2-stjaAkH8arR2L)9r!;v>a;Bl>t35yRY`VtjgQt?A+Eem54da$_Q^qah zL0a}2&Jr^!)25kbEmFaVHEB<)7vL|L(7P-ihLP;Hdw*hL-#?Lt4q~){7|f@&FNcDu zGl#{UOTa&ZgRNxcy~D4$pO*kTGViFU!_>7I4Tgp>7x?Olm6}aO6raP;ZQb@u9dfA? zm^sPL)`_Ru=j9dQ#X#7CH}h+%!1VYPy627Q#yo(o1-{I>Iksk((Q`gSVBq)(7C(N4 z*xqaQV{MzoLM5a3nQ`Sz^I)ww!(_a5HVRWW$q{7K=#$u3yRyMFS*{Sfc1qy_eye>V zjv2Wh0xeygo7m!CXE3tyH^7h4iSh{};D7DZ?wK)5bM`R#ADmGmE{%ikokyP1q~Zz5 zfXOvrzSfrcx>-MQrBUoyUp?2k2Bj3G;*}*t1%eDqbsy}FLLGVYg5Cu@Ce2 zQ5Ao?VcP()J!zUFAaP*1~BC{jk0b3&t-F=!8=W?wMsV{fwI=N?jdsC9O|s zgQ6*B!jr8)zqegl%ue*b{sCAA0j&QGww3^cl5$eMS9Op0^av1PIr001DXIt1M89!_ zHc#CPN9FBrn*PnMfoZ98^Fp9bw~nAv{|#^;rn3MqX8M#VKdO_*;G=sJuL|Z$rq#TA ztx0rl^{w3`56xW2`JX>=f0t_MkuWTu41d{iz+>a|PZc&OO!@^cdkM>vFX0Fhn64yT zzLe2zD)~EBpR#)9&kfhTYEjB+o}891^huVdGacw-;~HF?vjtN^>}|lPn9Vd>fsh>{ z9AOTly>fZH?jLWkwCdToJiBUSX}rw69CNiDvOk1EA9?# z)THVxW%RE5>ALhZ9i4K|2-hOlx~@06%=SO?@hf2D(G}GUG@+ul=O5-oJK$-zsusq^ z-{#s4OCV_gkym%t`a#-|i2moftcBlvwI3ovG2nCgW4tr7wMXAd)}fuv`)|HYBAG8S z14_n@3LAFz(su9e^7|MEyRR;e@v~5mbehk-ZRBo(58B#^lCGDfs^hyBu}sD|0A zTfKhJ^IiELx6^dU+&{1O>)^+=sKX@P0&Lf7os0XrP=#*nPrIM29MZJki6gnUGd!ZHyoY-C_BB z$>?k*wmfMrCq+2bEzMXLd>zJfy_dJYc%P5#sh2q@*F2SQFVMcz_yaI7C0Rt+8CgoX zEjl0GX)h)J2dCyt6@c!iXpY9tkeQx_$ToFD`yX7?fbz?<#S`wc)y<3Kx7(a^2MI#) zw_ok8LL=sRUm=iz4!r%|m29gdMd8-@wC!9FUo&v2WN#?-jdr6$hb*t3e*k+|F^BW} zIYII%uKQpGXa4K&*z1O@|K@rdX3q6lM%undSm-Jq_gh4ZzVT_>lT>s}o<(Lb+irA? z-r7TI?9$rX6+ca6!V|}Drcj`(Xa?rz$b%=Z*m$^-uIuQgn}-~U_bIZ+BGLa1RsJY4 zLO>aiMZ;u-mGH}Y^%*~v@f!`usG`2oY3~dLmZ47pAMlrC^KX~M zq*w7Zv90Pd!J|RE$k3Ow!No<6>d1kT+@*2J99CMaH)x8r$GloLeuou}a$MS#R>Dte zEj@qN&w+ZE?1*QE!_haR?t?Xn;tZ!xq*lApQa?3HN^3P4Zw) zBJijQm^oa$4;ECqmZIK*D)8GXRuA0`JwM_PBphEBhs(I%7sw%l(}aItSkr)at*p5Q zHJmW-h9Otw-iF@7>bhXQWAA!`AXKsqnQMr1-U)a3(FV%|&Hv-+EF7ZjqODJdAYFnW zUDDm%Eh3F{GmLaMBHfbGEiH|7Bi%7D2uKboF$lu@J>$Li`vV3ho^#GVd$09dQt8_i zzHL*TTN(4LR`qV>Y)0n9XAE0u}K*Iq$Jov2@_n|m|9#0r1**QL5=%Wc@D z)Z!xEi%K2ijg-4Dxr{{yEz#iOOZcmp9^N(0UO4;mXN;nM z;e7c|aogXc9_y)RRzL|NO|88143%HB>wzY{f?>ZsYu%iE=uhD`DE{d3q0sgk{}*D5 zLFg&duz)U%$*2HwcqoTXIi;&s$8c*kkI)dL+Han@DO+2)4Ba9aUg$_u`E;aS9OLRG z9g=&nas$L}Ab8OK0y;WdQ>WF1X!Bh`f3Sw@R14%rMe;op4xn4%{u<|Or!*nF&t8a; zHR91N%fvT6W66 z+gpokVCQc#O8ys+ov?LV556NoSz*2Nwtai`!dgN zY>T%vL|eU9%*_YC3r{3|30gmA6c~8X5*??7W4M0E_=(~MmZZP@7pZS-!5N3m+uIHv z-+~6Auh6m{^-ly!XccH#kk@d{kPZdMF610Mm`cCxm(^O*nD0Rp#ki{ z5zqwuqTmp-yE0acLQj>}`3si4k0;MZo%_z(W4@81DVV?%GL59Y{#|ui>NfONm;%qjPU%>L;&S5gS?+78om`*Wa%E--VUFi5VL>utD6K zwqqsFr}Dht_{=^!)OnQt5fo=Te>+UU`g2>(+6`ID;3+hu0c5v+HQ%NB!0*eJ$S?0@ zCz77gt`>3Z5PBZ0IF=SzEo2{+`jjtB9cXvKEBr#3+EERp-ihGHb6Aj5gN^ZC_Vy6a zjgTa&Zq*XkLeo37C#dNpR}UA$cFiyUL-GS4_O^WQk1K4-L;;?$qceBr?LUkjLQeUK zruLu8$n6+$75e`{49Jmy_aAYc0ct-@i>BNw0JnMVgkw3^B~bI|C)v0YIh161r8@-y zTl6ORBf0pip9-We9-mm*1U`LL?tT@ye&@Qw_DPnA2~NXKRCn&jC|sye6npGrPN~+o zr#jKZ6BfP< z_tlF+9AVv_cd5T`hOr%}g5T~L$@#wKGn(()75L2sy5WaHVvqJ!{qwrt{oTjGJql@5 zP+fUoxGAO+R?L$TN7sJAVPAxMljvE7@iTNrmYS#*Zf-7ZPFI2rukB}hH{^3oi9sdGcTY`z$*;4Sr0H*;(E z^46#H`}5rr@;r~X#alB&vU!;j$&ayL@Dki$RFVkCwZU;akFl$@X@`HXh9=>Q%%(o$ zeX<3Ajg(B#KG*90h0|K;Wb2=#o~2-#OsZr^?TdH;@=b#omA?l^VC7@Sm;>!z54By| zEz#^I7ImWz>^D?gEO4}!j6PczWyQRF4;mn2;|&`78=u(tD~zC5J+vNf!5A~k$trFe zw}0cl^TMP_5Q@vuPI2%!;J;0#J?r@>53~(3Xkni(LqKd(`_H!24HM`EGML#JLf*kc zC{9`#^T01P@>3L}8A^&pE5|Y5VG$~{%UZd?^mFBGwQ*x=O-PD!&1eiY1(B)!Rc#@g ze_QCrusLwL2;3@+2JZ+sp3Bc-n#NhVyw}jUz!8bl*qL%HTEq~YbS%kd<;PmGrIaT> zqGm-!`e1m?xqC5&v7;W48}o9kGkxCMn0)l^6+11^t6Evxx(xh#H`+qb_^bR+(NIV8 zQ=?{Ds5VbUhS*!S*N~I@x6E(gEm#(|j`63IDb72m1Yr%RhrRi<-J%SzQke9wW-=6& zm1X<+!cOidcJ7^^AG9t(IkOs1vlm}+^qtDScyt)|QLqvIvItwhb^rUOhFi2}I_pv+ zrE!2|02S(p9xhuNAk@nQ6qkGp7`fJPor{x!b+yvII%b8bRq;QK?{PltmmpivYL*)f zP|m-q+Woz$`Fpmyy-P&RRcO0#Esy6vB>A*5S_>VPOd-=r6a()BF%c$Td!0P}+-_Ge zb;ANR?)+6hi7LfTP_^UNSf7`U$onx;xa{i+pRir@A4WseXa-J&PLaP%KC`jK_%K4T zH}jRD@}-92XL}s$QiumibN-)j2tBf;XB=VbQ4uXhQ>OKQJXcdO4}P>hTp*iU zW*J7dv7Geit(Y=Po#n`TBLS`=(Q`{T?HKCrBo04>H}bfiHN6325cPcR@os*%?PvNy zlghYpS~wY-d}6)~CXTa8z0+b6pNli7bPJ8n5q#Z6Ek(#jc zb^7YfqxRHm&b5?%RjqAY<}ar`j%6eitElF^VsAG&^@Aur2>X_({hV=u4oIqbE}#i> zZv4G6sQj(=;zFTWPJ_31sWc_l}NG#1GTENNh5A1v?1?5@Oo9d0j6Ua%u>Y9iNy~zye27#qZqX_?HDGznfsc9v1~&b;L~W>hO|%znde;lN1Wd^ z{ooJV&9*Mn@FW}DQ78Id*w$Geo<9fpeVbKxF1jZT*0wHiO|;$2m-sS)v3w}SMQKV8 zEX1v?7I(8RYR>w zU1PvW82czS9uB4E0q}xYqi+vm0RmV8Ag&Zr;AgJl@xhFi&Tp=Jd+rov=M#S3YCTs$ z;6ae2(7g)N<@N0a<-RW#FImu{W`e&I?6g>K(6@CDRHEPo) zGNyEf==&c{+3g&^{2|sbkJSRYdN6b4t``Cc(R6$?nf51p9Vphc$=7+~Yh5Ro(Cfpl)dVl2tH(SxmpmUEh;iHYAYbO1SnCkX zKy^&B^$4^Zd;ChlU!IFTM5#}m&-|T1-Q0Wy1C5oW{)MxS^7||NSz#?!R@p)Bv>)jc z7<;Df_0BH^bJSwowIfGFa%9$Mwh0ecnEVsilHN&nJ4SQ4L@eR&zTs`(_^qw#*Tcs6 zviJED%;}BaWT3aPa8D<=h7hRSi2{DCMP`d#2xI>y>M3PHbwf;Vq_RH^~|| ze`3B+RxRbiOxL-Jh}WZi@2;HKq3`Xba?2*umG8q8ZW!M0j%_O-e5~|w=DLvc_O906 zypC7HEM_152rHvyw;M5{`#%?9XIDrFz|8oUd&Bdf_r0f9ZYPKt(6%FfJA}j?7f518 zlmHorU7862I_OD*62Ov;l0ySo6y({plu%6tpw@sWA}QI8hPT}H2JIoV`HV)&icp8;1rwwHNW%VrGg&1oRI?2%t1>WZgMLRK|(8!yh3$Ut~G zRJQ8J^4A5XI$JC!hd|SK20KP}h*@7|imcE7Rg4>0ey4nCRd%15nK>T=1#YSFe8)%@ zZA&iR0uZ8}J}yXC?tj}Y+31jPxM>m0V4U^Okla~5RSbO5rL`7g?zrTbLkk1f*2muC z4-2vzi*OJ#+EcE&-C4Mhja2tdWQ=s_#c50ffNSG@|5v1FUxh^yTQ7sYvTM#_ebx(& z`_b^KHGaMrV;%Pc5nQ?9&Y~egnK9@tR4d75Q_cg|lZB<-91`%v#1)En<9H z7`;$^p3|;$lWXY<(VY7I9z!U%`^UqxcD5G(*|yV}$56O9>Jc=*+*^WpJNmCyCj*xCFesHBDFV<@PTn=J3Ngts~2 zR+daiK8v@d5|u9KDzPVzQ|98$DbUPtL!9Tb@@dmhcE|7h-5>DkPw#cy->zV4vIO05 z$E{qyd65R*M00pKDn4!1yw+we2!LJ$i6_NRyPBiqAb>@FiheTT%HEhyxsf`aHfW@y zL@wD+{j^5g7+tun?S*v!s!EQMlBSJ#a7%SR#D4x27$G^9dU<#RKR1q9KhU&m0p$(6j$6{$=%=vO37;f}Nh zX%Fn5<*1@05QpdL_N4?8G5LiVwK`O)mZM$E!C1X&iw62Vqj@E~q4fvmEAy`^1Z`In zmKR!01FcdwShOT{$jx`Eqol|Md4A@2s^Gu1@AsPA2O0+xm}&dKH|~6E@}S}SkBTSc z^aFBy`HuwpX6;&!Lh@iPs4|7pjq~mg6hR@Go<&%@5iG4sgs|F!rf1Uo6HI|j@E`}Q z`_SD<@i?q*H$#390Mcz(A`@2+;d=I+^D-aooHeQBXZ$se;x;V{a!`?re3k-(SaUdY zt3V(|{sx?V>-2K(2R{F4?-k9IaThFPRXVwzDqp9$ZlVg`7dQow#m*^-pj;2uX3D9r)y%0Tdc8g)QRfzH?Cby7xVIfMVe3Jw~_$wkGJAoEM)i? zO-3l}=o5E&v0`_x`t)b9PW>F?p4#TnjFarv7z2k@RS@%AG%B5!+i)0(jWP*zLt2(v zo>S04&YXBMzitr)EzH>W*e&Z^W+06hb%l6XHH#&n`)VDZ1UdE};aR^}Frs0|{2;cg z4lXWRHGhMvUH%k5^rxDV3D$GNz%BpSd(!H(BM)nXk>nP7F~RCL^lBGene9B{yh>lD zy^5z6J31E|S=jV7MQq&i`9FVU z0axfH?-PEvsSUYNP@|jz@&ruSr_QHhAhqzTV%<8-8NywyWL8{bI`(g7Rch-c_`%b_5&X8}Ojh3hg zAuPUS2wgAW+5$3Q7XhR$Vn>nf`xD{?pj(VN0{kBzlL_fWo@ohz*4adL`a}&!4EUWi zeP)`i`RFwa33QRuM(&U#AKZzO-=Eg@xpg;VaIWq zHzBbN@JJgMlp3R~vl=7r>-4NfHEyK|`&4$Dl409_90B@NYXTjP(=g6_T9mM2%6I5K zch4j8b*I)?iwPYnV@}ickFLMqGb`emApecL6R8@JUp>LqG+&%R7Ctpqakgk9Yk zEEOm;*V;a6m}$EBFZ!a#9CM10jnp1is@mfr}yG zpFLD3jxOP=NQM4X^L57kk)U;tS0qWulg_~ z*WXPkU7b7qFQF9J77wmah^crU680(w`+cd2%;n%-M*_<&)gI@YSC&C zUmJbv`m7zh1LNKr#AcMB(jN$gvSVqH?S4~_$z4xoC8MQg6jxOBJ$w-GQG)3He$`+v zOLH4fc{*eGZ6)(f0?i3#nedGIEM4yW)TQ~CKx8KOix=~#c$_MkS$@y1Ca>NSei*d# z?9OU^n9UBG(8dyuUR$o~SfX`vl#u*Dj6LI_+DR5nx<#6NO=WKGc%HAYpd$e)!?Rd_ zzM=RZ(ohG--bTjt;d+2^w!7KFv5<*mAbO)>9oBzHWzI{6_2_1>puLyZtT0eb zQ|ES5Z2sdV9K7;sUNRE@{c|AI?RaYpw@nh#{ThWHOIwac39L&vZ6VHEq4m*5qwOky0|Ft#e}lO6X)_w>AD3IPG-L z?wiB(jdrb_-uZx!$_1pcgb>%MO#0y7cfgAa`)TUCmYsF2&#lT8?8Jg{FQ+_wrKGXe zuVuGYA9DIkj+IY@01>cmIMKKx&FTzIbM{GNkOZVw19T{LurGl%JTN_+XHxDg40cDn ztFPSY=iFPT$C@*`Ju$^NK`Q`unL%D^kM`gK2?ZNgA<2#auBzIOmZzn@oW+ozxVsj* z_8PYHeC&;WbWCg_j~I5?gV94)^H<$>__nW%Keh`i~1LVjK1AVNodlPDho6DWdXcsuCgkw%-eJqw zZi#pRKC*f?ujf7xN+~$IZ>}};R)=UPGzg7plcj}Fa3|WPZ`Y zNYQ3ysh#wG$JG z))GN9diHY2gYuF0orCYx9`aV6CGw|JyL1CV2S_Rc>UB8~WNtnJ0aMr9HDJJvq5kMP z=ep^jQnUOk@we}(5mR@^o0z3`M{#{?a0th4eK*9}%ev-%wSvl`v-HM%?WNuuuJF^Q zu}kl`WY_k4Y~PK=%0J#3pf=a1u+RY=-ertO$MFV+Gq)Co0#u0WHSx1B(cB~hrFD?x zm1IwuFcfpZe`3DT|0#CbR+8p(q_WS!MdJIK;ID||9B7n=4g~#cN!=k8-7Ya7RQ-p9 zHMNEL6S2}S0bUHSAVPk$nr|Xjdmnl%0uSh{9I|AV)oW0Wh&!(a5@!&gP^4i2&fY0h z{FM%ty`77PO$vN5^42@(#>9FNE%&cCB0?dum~=ULLG$s@LT8 zbd*GjcD8lei#SANZJD}W0m4(@e+LU#ZC;&IPA>`?M~x}KUK0+IBy4yn7u$E zqb`;;KHH*Z&z09!gKg+3h39eih8-g=q@Z)BR{jtsLUR*@Ja`@4iwPXPi2E+^0qkysbPAiOF~}S)k_Ii>1mv_xjojVO?l23Q zR$^lc2v@A(=Q<|sRcEF5V2$5hOAWlzTplu(Qn!dN!gi^HC4BuLs&LdDf9-ZF6zEqW zMx0h&kBF9nK%MfrKO#X4(HJJRzkDq73;GWU1&9eE=`i{pphJdp1G;0xYoe~#*|`$2 z?$MDP&8DI7N=y)r!PISH-|rP{$dKw#wk_S3i@qlw4x$$%-P?mQEiwdUK~ zP3GF~>zK6JHigyWJk5D^pO=zkDe3DY?7D~`JQLPKM<3Vc&q(cW9^R(@BnNW_w$+N3 z5J57Q{t(N^sZcB9^92I6*}r!Ou=h#HLtX*dd-*^{&=PQNI3fD;0J{w_T4}}wipBuy z^>-QXKlaQ_(Utoa^whU7&7AKvW^ALJd}<%3s2WsKm7mSFz@4UVW|vd0GkX-HFv<@a z)eT|()cNtMe!*$k@Ix@S@+H|zo(r7LqoY-1EKdQ+97WN~H4M@*Jb}FO^wb>@8}xM+ ze`cFR7f!cX_!mX9`VaR4O)Ynh5aJlgJHz<4t7%8Yjp(+OiV9g~ZDw-F;o_g6eV3kU z{{uk|q_wEk#P_-UI^zxU3rQNCDF%E(v_r=%;fcJZ5J|^R_f>JAomM&_f$r0861J$b zOj}7efk3y!HW0;;So3tDEz?NxhWxMs*|hRfYp_A-0hPPXvqb5vO2*RzTint}tY~M? zu+Lb`1dL>dOmPA{Sbg79MdzoBp~nCbVW*!Kv#zTaiD%7L!uFnHwvwNpJ}ud#!Ht9! z{fud}=N@*%ltr_$ZkPLn?1t>h_IXB+n3|W+MU(Xy>;Z%vWns|Y-B3Y{6I6cY-X4#- zvY(gF!ZpP9BeRY}Y^X=?Ox}oI^^Y3MEgbFK8{f5%jqjWXN+Lj8^X@a~Krv^>_M&!I zXw{iRthwOZkiu=%+KEZX{=+PK?EXbyFlX=59gy5RaXI5Nx}!Ps_BV2PN0&4SKojR< zVNBJVv9Mv$rF1D@y_V*GiUszPt&jTV2vc@hcJ3pa0Vhrr^})#-MxX3UKp30U_YuD} z2WXc&BPGIGVU36lgsl8F-X0(ZmIQ<9x)ifgCxj*o(mS}C&ZROQ^@_V~Fu7zNf)I74 zvz7?X<)_hIGYGsOq>puV%{5{|f_KXaNgGH94#8VkdQ#r;-Ido5ywn*P44os)H#)N@aYLAiR!Ji=y^mh1ZN_NK9fTF7k)=Nt6t?;lO7Dr>Oh69X<5exV) zQ92~3N~0!#H~UW|P*j;=7WfnCnr%zP06yyo)#&ssaNyEnvf9u03%K?hU5M~a0B$oy z*D=6+NhvQz60P?&nt8kA6x@`NvAi7FH`Y`zw3x){(8ej^ z;yuw{;;!Nx>x8Oma*g+kwnXNtNo>zgAwG2wI$O%a)}?rXF_i|L=< z^RWKto$G8;;u(BrOwwOgcpJU{eJg3hCUBk9h zL%kU?=-in$0k^BKS`?X8-y&}`FWB>JN!JA~2`pOQ9p%-Ub5TJ(tEtG%!idME5Q}X6yxWmgGdbw~O?;}48pI3$ zT2}Lw-{I@fJJ|22oKDV2r7O8cXQeN$l{HznJ;d#Q{7Cusum`5mNnH^Q1ux9sFr(}i z(y(+jkAL9rg_rjx0>{+{V6*>aJgeo4ArO^5a25o1D4R4UHenM}xggdO9%pujn zh0YykD18#D!GM4xUnryA)a|dppKgMoTzV<n2 zf^<=u%aCp&FGuWG_2+3qduZHg71r^_PYdWZORjIkp}12>cq-8&IrQdA_NgERo}syS z>s&5`ZNwz__;WYYqNT<&dXH_0zCqdeE1G^)H7>Yaz)i+eiwLVzIe%-Qa}g_`rK>}G zF~%q7YUPy?Iq4?2ud$%sc|Z(eOSRD$I94#%pRD~n_6A9DxNY)EOkXA;%i5icvt9)J zvN<8l+FC~=jj7LlH4lji+v~F~0WAx_bY%`uOx@@C1(5XVo@c{z!fB7TM)R06-cwHX zc9ey-3CHc+(|D+Lc2k6ec<6ldVOUjZ(`50b&q+QWus4|JTm4Q-TUX=TO>8&30a3M9 zwR*@+kF~9SFS0>hz66kp<(GeL^(A0)7n_IlmXdl5IWyPpNdLeJ*IJui=DbV8%f8cI$+(3eFdnTPJ%!(7aqeG-_>fOROL2vs$uJ$D#5k;Qmv8AI%Jly6CK`+}vdK{1* zk?$)VI?7Zx?&+Z|jC1i`UK=4T2Uc6)Ej#tSFlok{N)PB zV%W>BCT>dqr*{Vcpc7F(+HkJj`_sO$upC*Bl(x~siPT}1VVG56F%aY+;Qq?ys<29@ zwCK(a;GaBUT3@IMs+oWOu;M9_R+-%3!%BT1WshtmjpP%WkZ4UG#-N%A5GZc4c#na4 ztl3XbuotztqD!CfjG3RexNV@sdJ;<1M#Pm@T*_|Ov3~JOx6a9IOCM1KxSp@}pI*sX z-kL{r%YQ2CO`PnO6|WlBVkuu1wFbXa0X{SG+RF|9dH633$_#bhpb}%n3<^n~6YxTjxUj8{ zA;x)%21J!74qSPyNi6lvE5J0j`;pBGG9Y%??Re1Oc~kmgf!YDNxldr|wWxA<(pTKc zD~Xvu7H)972t$A9`nc|!In+4R!}s8fY=-Gig)8JUIqj>Bba^Tg#L(|C^ODbV`wpEx z&dcHbEcv|9p+j8v-e(>@%$(GX4zZql{K`X>AXTyejvWY2mcXoDk8i3*4wxkIaP^uJ zl~m$Wh^DZN{Wy^x)iHOMEH6hmw4QK_1pY1&6r0%U)oGnw)D93*zs;mO3)ChN%+wzL zb|!1YES6C4%z2Bt%I4Q6ZdXFM%#s@~5hHc0yEIiVR&PeT9vPLI_*_@G%-9&WHb4GR zJkj!`)6xff|)cbUqMhsh?q2kFHR1PP-Z8X`I)p)>2UpD;Z6#gpoouS%PJ}2dU z-`X=2vghG5|b|o$mpXa{POJ{x{wt27n{A$Vyvk1KMB4)WR zVhkt@sX+zC=zyG_d@_a4&IT-Vl=`couc4-Pf}NOYtvp(@bY8dEJ+-c$|5-m-^#oaG zeX+v5wvi1~#l0V-67{E>>(`*VM^*4{fN60Ubu@Wg=2f+?3!^6NK`4EL#f&Kx2lVCc zlZT|$;sz37hmD8G4{&-MvC4C#0S0E2tSG&YJM@K`wAsUFt1*L(Ul;o=rzN0QWgp|L zRDoJs*TRvhaNTiI3taF#@AX0+U3d0-*#60i>fA;g4tturiRfa}JT`79GJ7Gio3oWe zxC{|J=T}GV5ALmvFsP#kQ*Twg1*_r0*NNIsw9PzBr-YgXStif=xT=O~FF6~qNO{94 zPKAV`FWzOZiS=Y^6AJu|>%{!_{1U}Ae(IB8y$TXF-^?d*9Y#C_ve_X# zcoxiLkhh;&Vm~Yt+jeR8J6j$1W(T{ELFXxm`7+*_cRP#3GukU|+ACH<7v&^tNCi6; zoRX6rIU7nA(=VcuHEIm{=uKyr!yBW^Eraatyheci4P2~$(i|&JAU}2*pqn~Tk6yejH~^b1VTTK-H5S?D>E z^QKWbT`i-~DtCb(D=-)r*l3FPc+8^p$L z&fb}I*2K7O|DGNnIv8}#qoGo6*lt2At)HHhNS{YcomRAf)W@1V%uTiuG%1~VVU}mj&PUi3Po7AaZxf> zh`y}xi6$FizAFzEi6ERS$U7cfCEgZ#oH9@RSyDQ2%T6~+GNab&(V~#Rk2&|fYkirW zi;AaxZJV7;LTzfX7j(?hFk@B*sv5`1z70}4V@6ZG*(EWeMs|Tvhpd}SeDt&Jd`;H8 zc#89*Q9>JX`8HGx*Sl|j^|KL?|FO4saCyo0`ivV+%cu3!vD>ao^B~;?C$DVZiI+|M zP+t~VBE_+-TljjdK~5$_ID_GAMl@hjqrFr3oTLMAw~AZAp}Diz3FmtNLt&Q5V2U$A z^?~^9Hk9x%KmJk-Jt11NQ$F~r?fDYq^N#}Og)-dyLoEX(@ZV3#5yZI+@g6!3uhvla z?n+azElmVvk<-(MNO19XuMk7M`3_<4Xn^oPZR|Oz%>Gv6(C^_dv_|Haa%s#}?4n~= zk_P_Nxl^#bMZDsfQll9lGR(ve>ShkOsqC)H`+hLsGaOzRJubG30rBdW(mcWhjVc0 zUK3I#dsK+eUg$(K>2uiXSrdmxQ4EhyzX-Mz`$&(Lmv^5foTr8ekgdo+Uml z&acuz4*8XPTVRjdrDb|;*sH#490not<@4=YBoYa`?UiZ{=k-J{ZR9gvu~MJbvoaZ< zI`Mz`F2jG-^gTuJ$VX;(w@6T=@&ZXb{z9F->1G;UI9`9sd{bkQC?Y0e*DJ=?q!oLf$Z z;j19SDGZm!O9U zd=`9<`y)rcpiVPWasFzu?#vc4msH-|Mwha{Z|O-UUU*%vkmK{x+k?=Uf>7hHS}$@O2*lNf+ibLTjZ^W{!=PC6Nlk2#vWO3~M?fI8)85_^W%w!178P$SIgNlKSrscKs`e*Mvb9hP7hUB?#w%i(RR$R{dj01s!D+SsAvL$6$B+ov z(PTvH3~+g1h|?2$<<7M;$&6NzE#u{)_;*YG_17tF<>Y+S7ir-W4w9!ESW;m8vY=*N zwuiwVuEWUgKp#EuQWjea>sZ%x@1|BM=>~H8SEpu@YRIn_!vT8B#yj`8o72dcAxxOA z9uF@noMk-55N=daeya#ou!81njC0^+%(2sp`3OB$~K2D-m>&iiXkIHM9 z$aOUV>{qaKXtspYm3*nB;JIomj`5(kOtAH#?T@tMSF%rZ?dFq;rUOf;A^4~MdY>Cc zF|vc7T;VR&9vx@nFRCKwJ_LR7EfpoqKeX0_Uc0@FLXw-&U?}~0%sk~1Ws>YNPKT4K z<+PJs<8Sh}y|i)FZFJM8nBoK4pPAtM=94b8%R2B%56g!Gt~~fAH1`)~53h)IOEm%A z{uN9r?_M$bOrgS%p4EuYK5-c+h|11`C!rw9#ZF=k_=nc6ieA_a&qKcUfECsva%X~G z>q(}qv+L3wcm8f>9XGTU*X)$8s;tKz9>u6_Qo8wWfv2IbN;9`siUv{e1T|pA{ObgFuzZUf9_un!`IXWu(!cV}9?NJs{ldD8A&D^Cw*d&`SSO|srA(a(8w zRAlxn3v}0iJ~3;;t^2{G1WYXrZgKD|w%0KtXxiBL9-5Ajghc@CWG1@2XcI}&h&r1D zhAC=|J+>`SO04o-&I-g#nsgtH;9~sLuuY~*mNKqtX-05TFK@sWZ?r7N_lUQe`d}$PV8_vf&pdCkyA{YI-(^9@^6- zX0sAEO(cSXFAuVdMtFJZbc{mFhGoMV8Jr2V!Wr~&M8dG3t1Bd7DBJx0gv7)3F3<{$ z+qp0D7P3r37C&03XyX+ij<)$Mu`WucI2f6OF1aC?omCtQ6afkK-LJxjCj!UOXJO5C zj`2QxP9F$Nm@fIr(CAeqTD$vw=cv^BNzxlXJY1tvYFP@($<=rh_+$%RBjR#X`lH zC*vK&*Iy8HL4qQtMa%V#`{G?#x|X_l8|cMGq=pPpn4c&6kBEL*ux1cSXieUpB0HqN z49ezjKZu19Du4au*%)FR)$%1Q5i4zEp^TZgx!tXag0R^XwZ z?bL355UDIhzhvc;^>EA}0e$An>JzsBOZ<&d^A5W~^Py?s()zex&$X9DagxLVQtSIe zbiEq?lk+9R!50}xSqxlb4rf`HW3-I!zQ9h6d3hP6qfljss-+i9yFyW_#-Wu7mTkG5 zZSQjyJVnD!@Q{J8g{L~LZR1E02 zpw2YM5Ay2JfNDldD~NKpW^_YSz|H$Nf7{yQ+OQ#C z_H$P8kO+yx^0@s~k@r|i3(SkZSdE;k|2b0A=&TCjD>xOM2RmT)S?8w{qs;z1z4)|7 zDdL#Fqbw`jg0I*)WKS03Q|JKtdu3E`n?TtfI?vY403-mhWb|L*rIu+9;H%?VFSVfC zG{{ED5$Ve7Q&)`-{UQw5_EAizHTb!cgyj3a0cl5SCjQiDZRF)a;O!IwuTp0EK*B1U zQ1dWfg;$-Q(52twi3(60&rVyXd3?6T2v)!W41!&DkmrOl#~IL!(Qd(P@5i|=PwuOW zH@2O@@-9guu}(5a5wL*heigD%akk`A>8y&k@{zZlv*Ps;#{8q?_-Ca zdA>AC7Qjc$@V>@(LVyyJJyyU@S^ptnLgQ#hRS%3xMcF4fiD?{gdFRCfbjwwJ{zH28 zF|QjHa>+qt(ch-2(Y-ds7(TS~Jm)8%{Gp=x>|_nYRH45;AbKAqUHI@1rp|t_S|$pN zK9g3AQPMLP`q7Sj%1RKPQ1eIi%G~@$E|Rx3=y-sB%S2P2oVTUZ4&m!@#FeCY)QN1o z1P2$y5#pQN56$DL`QTVvxxT5m&1bTN-|*5ZI}lP1 zAidY1`~~bJ3n6oLB5`bz`cu^$gl|_$C5CgHw%;@Sr}F&zNY(xBX_n^`P^bpKmQ$Jq zzeuVl<~MccIDjMm?5G>sx^rO7%R6u$c?^53-|}M zL+(Sp!9MG8_}2HuMOlfVNzd+$Ct;5z#rNfOUH&7#UcVwP-gKj&m^=-baSaZ;N>{jK zxk#D*+vUbkhX0TRadfE#MT{7j**e|Gt=qq;wwZ;v~vxUR=)RxAj0!mypvsJNb zEVRZnYX=bRctah?Lk(o8RaC(9M7@$&x+<1xkt485J?Grj`)#|Z1u~xMNHAAF)H4lW zI_+ZcQ4vThil-_Z9|i*LXw$d0qT{f;pLPpW-xb#+Q5xEm2{N>h7ut8z9aC27`-k`@ zo~yqw4JybKyN|BBP`e|{;}eJ-G?WUPww=6kg#PV)^rX-n!U$8G`4}!fK!lgQ; zkX$!R<`!BISiCBY(X!t1OW`~b6ky^KvS#CGvE5a+LL*@&8q}1gH^k_H7jZb>Ie0cs z6boTtVBrmy@J-85aZ<8~CvaR>rZ+nPd_WKs1E;jr(SRo=1uJ=mGt9Qm<2FX8sx_HG$!Dj>F+e>yq3PJFT?!xzw(W(3ANE@Ap z`+iiKq4IF8Bmo4;_hxz-xcs%Rx-w*o*WRCRX5S?IrtgXy-Wr2I5j&6*p~nw#u)dg> zzkILk#eDwzWQ5I3t*KCq2VBmMWdUo%bEvD&@+W~4k|fOdiSF?bkLb8B7illDh!1r_ zCM?O}qvVv%iZ-zJ;3A~g(1%dnH%AYC#Z;DzF3pglRpk?v^VE!d}YuX~{T zee0{;FY$KdTW6Y6T|_XfA@D+g^xg>U;(D%9aNhP0$&$S>`IxTqjbp8r=)JwH_R2zQ z=o}#6_cHX7LaSajjxZ<-;|o&#QaD{BxM+M zg{#!*E8FtZrvy16RSeFJT@E;MqT^8{l1R7vD~#t^-q_5g+laTALLU!ccpmhf#LcWM zrmxb`*~p`|-OKw;T!(8RI)iJ=;*|jpBBW!RDVzkqhnatEZ;@(xqX+$wE4F;=7vTn1 zg#GitDvQPNsf1>bz<%xrbh-f;&bg*Hepg%T{pLnVM^z2Bi;Fe4S&Br5?9;z3+f-Mk zj!7IR!m+Q}vPT-!O?W&CKktKC{SK*of}xo|25r1 z+dcX`XJpaSML^`v6*A)NSL*Jq{Ki*7aiwb_vt=Vnn9}ADx?tL3QqUON=eB`EmWJ`& z!)xlBrglsZT(PmYsc!ZunbC%vw~{%xHpCoDuwye|lU!2M#+t&KZEp9vjk=f?{O;NW zZ8X41M+h$&(j$iCH2EO^hj9n-kGI_Bl4XOBl-*95sqR@b`Ih2#+)ul|6~-~h5gB#O z0boM^(4;MNi*DJMk+m%g{y(&LDEWd&&K%2#2$J`iYCPU1`M(f)(ETLxEIHw_9*wmB z?MJ#mEv5@rEF`ufHLiUm5Yb27EfxI-7%I9hgjdviCpVJ6Z-0Pdh;4so4XOH3Jws)d zpB)4R|4$U4{IbvMfA*_!C9}<4mdksnQ0I6>)~u@~ufjjG=u6un9&`KIz91R`x(7=} z4Zbh=x4(93xp%+%BQ?{w5=)ylr(xV;chj3MeJ{(Ym?0q|Q?s~Ql9F;Urj_ej6Z;f! zP2(WBR;2dHV1Mx(^+LslES zdUN|1njxKi@4x!Yc1H>0F1B>K1d?_oes|9~|G4&+PVVy8d2fq%80P*VnA8wMK03!t;^wy|mJf&n5$sBw>j%Dxr-4w%6xJG0^64~L`B$lU zdL~`S1qJr925Z5sbb0WNwPN4uNQql)atT}}JBDF>u`JK?{^!H!8&!`$Zb|1?cZgVx z@vGp!`oDmPH5-Hp&X}TSM;o;iyp^e-8OIb6i`70^yI=JD zTmxdeGICk{vTvsNc;_-1-s`ngLRw7EeCr4{8?aeOTk1Rca5BpatN<+D>$UQE0Df2! zcbhKeU1y5c4}&xsIEL$>3nXWH()E|Dr*Q^@UL5+s6`dbsT~A49@mxmyr^Rz!F>cLG zfT9Yuf`Y9*I4^>@@vs=#lb^WoztxR&m*}vp7-RTWT*bt%K{USmhE32(ZFD9zJH4}m zVz>o|WZktHGYn$T%gImdNowfgF&2xYB3@)h^h|f9*YgYJ+ETtfq2MVuHM1J=!*Al- zKKzjVL?VVQM62Qnk@nU&dT5s~N3C%>7bpI2MROO;m^!%y8>iR_(v$X0i>_HLNYLYl zZX^KwcOd@nMxRlX2^mJ&(V`??cw`z}4YIB`U|)6*2pmilE%p$Mf)!+%V$mQmv0E)> zUq9l%IzYUqPwum6E9!@=rz7jSg$hU9zQ1a0AU@J0u0^vdmh6y)q257TnBKd$I$)C7 ziWby-{(ur@IpI=YeO-G%pn8a;pC|_O$TS%JwMuxtt%~Gzg&E4|3?*rej-KnOE2p!X z`71WbiS+CG`@bhgi3qiaXmvJ`QuVW(c|`k`&igX+;E;@`%nb0UGt~2;lpcR7?H*(~ zsg|pHWi#lZpH=-z{!=qK{oXSH6JdY&oDWF@Vt1y6!F#jk^q%~(@!MiN2DyId`&X86?tn?bCM zN!sd;2-@-W3L#^ciQVs~ajI=n%LJc1?wf4e$?@oiepz{mu0@Yg4M7u|5yyr%V?2i; zeK{3>$eW=dStUwC4|+9ksy>(0g$7ThTa)*!OvM%!+gW#G6H6L=O$&fNzS(ORx9>D}n4JjYYej&1gB-)=xUgN&Y`7xxfYOQ4348dm$~}*GX#VVy*GN3#2Jls5+vp zXG#ZNb$qckQ_Subn4BLCHX7k^tKI!<$`(@*UT;W?op$`n)9W+V|l0)2`~oX z+DhBod%b<5YHA7zq}X_g$kpLCwTK1|0$;mkCkKW+k5ALYULmwh*N@#}4W1_$_9PS5 z3pM+P#>x|%>Kt;!?6wPL==9{ANAGRCdsk#t(M9qCnWp9-Z-Y5_d(tW$2Irs5sw|-5 zCrmw#h#+>pGO*@)5g zOB<|X&>UfA?lSYPU1^)u8`4;1S zp4{XQ9%4=*rketK!4*Zv(s|{x6)<9gSu{1#>Y8R(#N&g1e}Xwb^(Bk(iGPcZGbeU% z(<+8+tYGt#(gSP9Nrem5xo(l{S6Tqxk@vNiMbu3ZxK1q$D*eIGKQ;VL$x5NhRloYgub8?@oFi*V4QeDm>)^AD?n-rh^8B;Fb=Ldd<&rU^0JI zcwiy1X*Sc7Pa;a>x{#`=&<}{ypmZ!+-}9r~G|D1}XlTc4qj7RPgnmyt901sL? zyLfzy^e~#qZr(4qjGtB3?pgz0%{##_Lmct=jayL^Y9uy9TAlx`bIvE7lM&M2pQ$t^ zloPJvL(U;?A~zZ3LT$wb@5^~G@=eQ;GG5a^gu0f=rIdY$bK<2IpkyH7N*{Xqi&uWO zg^^Bbx%D)FkjKWSrKi@YP#9; zt|FG{Jhn{hc$+Q2lJncl*U(YQ+!N;=wx;D!@eYp3(O0s$jmA|t-`&4ZSXLO?ESxI02aY))FwU~!3bw^hG* zIvLS~{2vxv#->0Q+t7o98|gnpRKpy9EOQN-bXDLpvo^7=gTAuZzcpzY)n;So zAeWvNsODO&^DeQe?FE5(=g5jW{-Kp3xJ`ZQ_2?VzY{ACa{@|+1y)%#2i`TPWz|85A zKdcAY0mWrc<)tm9mA4nzC{1dIS`DQaM!xc#Xo0PZ&?JErU#kej!L35a>U99je`u64 zhk#kOE`>-1f#Oa5g%&CAh+vy`0&dKse!2}dm9av{F=L7j75?b9C-sI>7#z)55UvW? zP_bH;?Txs~7e)w)U)Ovcdy}2iw)n**;HJ)C`4)9L0~omPLI0Nlu&Ezw3-T_0i7a=J zYS#2Fz6SW%kfwcXXtFLCsXInZGgP8Y;B%pgUL5j0`g<(Jq#$ zANF#AelpUvL~Pr*DO}4js?U!*!~eD)Ue52_8z&%d7vNle3s)=>o65tB+>ns;w)29s zz4z*@Dwjesn{};t09(-{h{CD=8m8$P@E_dFO9>skpZkYKj;wUdk~E$(1(>6T8J3E3 z(m+|Bs>7Qv?icNUOrGKa?W^zhI>0aktpe$k!~juF#FnF6@WOi8ha}R(EYKnM`?J*- zi5cD!gA8biV=7}*2^Q3Sh~SJdT%ra5R$HrdqN&#Km<&Nqxu>bu9~~EXMW-lTryA@t z@y-(03tV3d(qk~8bt&OclwHQk~V;ZY$BpWyEyUFYxez3b<gFk$0vh1iaJ**`J69DR+c|&OJ8-6QgWIsI3%hVtxae4)#7VA2vAeoW$f$eu|!y zawR^LTq}2KtXetny303{%pR8YxxlG0Wb^JQvhPt=qUyF6v$r7~!Wi&G97z<0Xzq3k zL}@+bV6hnwc3-v|OD!I2_R&n=I0|o=`kVHRXdgJhvs=ICjL@`9WJQcly9xJ%#Z7Mc zLz6B!CF}|%X(`0Jj<1KM>}3r1XvXZ=rIVUX6TlP_NiR zIu{l1gj$g3+>!Qi6=v0b60spYU_7VkWTTZ70<=3l@ic2IN^ zLl>bUuBfOKvYU**U;O@qE`E=l4I|mcKQ>m(74RoC$y7r43pOjhM0?DRRe271I-bL@ zOjM53aTuIl5U1a`uifg{}xM_Y4JgC^9n1lDG>GV zFDKPI4-F~`Ja4<-ASJHyc*VVGNGGE;kdz|mBJK=D0@X8$$^#N789yj|f}PGL z>ZIB4ENdX4rnYetYA^R2THK3|tEqJ~UGo3C8{}{IdRol`zrRLqAeQefz1+-zdaK=T zMTk}}VPqjwGBCF}=7>KscYgR9S$J{N_RK`<0|Q?OgiFQu_I-;W3(+VmYY7(#d}oh-t)cUsN)2b z>;$1CN=NAZ{&ucHS*nVjmDEYl+GI@pP#2;s;-BT6f0<@HGHHaiE(rLw^8)>uwVU)V z49q(q zAUs1O8L~+TVSAVB7r}Y|O~(28Tk^x99tS`!4SR}T0I0V$UhI=v{sxd(gwy!EXY!A- zo}OV;uK+Z?l)#GMO6?bGEtC3W#EMU10*&rFhi}piKsMGXN3$odMob9Zz>VH)xddJ7}tW7UQXV(qd20vh^s#Ye2o}466<% zfgdSSBkemwuF^UpfX=nr&qDek?h_Cy7#+7pqFnkxIVGN$9aj?rw)QN<6Dsk=U^~ew zlwfYIK*A)lHmB3_*;qmd7KcrS&Fh42Jw@ytnV&0gw8!INI{n_oe%a6qo#X%C?4IfWf08FM&=E@IS%%LPy$p>_N{z$>j*0+~K^= zA|+$cZkF!gFg)I+73WiK%OrCIUq#mI`o!c6+>x!iy_Sc(ycw;XY88(XWHcs91Iq-R zVcr%1q)T&{$^;4FL&O;PGl6XWeZTaxI)6gG4KGd6q8taEg{qE`spxd+Xk!I`|Di3R z+NOTwl;s~6Cr}p5OdgThy2#o zQ`ucj`=lS4GDQFwiv5sz_<%haNRilzvTu7Yi%k=}->R|$uR#A!<0?G2AMfXct{{8; z;8XnT#?qs{C4{R{?CSpU9Df%d=i@ojh00L2fY86!zowFO5oWM@_`kS|ums?}JCIqgBI>;OXWGNMH^Aoi8Zd`c6 zxuqch>2dH{JrnYm(Wi2g8bJJJXBKG}bo2NYna1&U#ci%1TCs8I*gkBkIx=^OMJ2X5 z^dzdU|Ib_TGo_pdR>*~(KeC`hYp&7XVey^bbUe)5sqKJmRW7HVF+4Q9`%WK{e%ae- z-Sp!Sx*}Nrr=uGKD&7R@S+H8I;+2eci@Mau12C$HIX>*`)jgI@p8$%t1Uf4L+l>%j z`|I;NW$XgbRgro3yDgv8o5rOLA$cN%#Sr6a#Q|Q%Kw##iU2@hVBS0za;y`@!V);0$ z#`*eWA(74AOxZt){K<9`;H9(1Zu>eMrhqBNqFEOy>@BvGgXztv zs~U8-plo^o7LuOFlGna+Kd(2c6foAs>A9NMEC%VT0=x|LBqkU=SooSJB^$q=@_hWW4AzD=$dDGumB!)U- zM0SJFOdeXhqO$H8LW&zF3D_l)UbOgR^pE3%f-Pz4W>;$&J$bvw<37vK0=G5HAP)c zN+s|l@mXs9^}n4c%BVWMrf3*!`E+C>f*OGmVrOl)C~qq49Ss5(I)Q!J zXKK6`gP@zg_G_R614h}4nKmRvfv?%^H)^4cT&Z$mN%xXpzEE;Y%B4V(6W?CdV)$lA z*Z)~T&RsR?hkRvplZ2yWEq@~C9wKrFWi$fyn>i0DV<)~}#bh+prQ4s#3M`fNm2D}0 z2NE9^F8u%T3o0H$GrQD_cnR8erX;wdrqh?YdQkw zT|ReV1wLHY9ox8XYq@V zO^*6hxpo=zY+21Qi7X|5ml)W%cS6{8EKpudTRNq2EsgJ(jfamhT#-TPjmn%Y<00YZ8_bP+TCwPob|N=pXP^&wOx#Y~sSbzqahk4v2e_N<#=s*a;R zK^^AfD)>UtjQiQR<3K_0BoXG0I*6I$q*cbE;W8qqy~37G;6ohqjtRwgaany3AF;7` zci_#pnP|bWkX-mU)T^0)4Se}^A zsvfn#^PfBvgkbR>F$k@-qVFEbi$ly7inWyJ6ii*+a#Q!|EXsTWYWm>M=|7lCTCiVG zQ;n9{S)mD5Aev_AV=>xhv>ffem+p3M`Yk!V(uyI*Hr7E4 zd!eL|^xnG}^g(WjVy97-%HFs)bE%Giw{+^~E+9!r+ZJc=*wa^p4wBIR|LlXS@ zS@Rb`Bf!qEpMDmtRb~_=@h?hPJAAePJjK@X+sI@kilt6A;XUMPRWPUKxjS$oRy^5* z$t;=)JbF2yknuK*0_R!GvRbqL?@mfJm8fvs>dpp=jo-hu3QfPm#oB}JrsP+MQi!=O zaF5bKCm)U$lSIOCt>rZTq4ntVlek|FKH@6T73WU_k)x)ffdc}L;M;^vCydT|>9@)g zoojO+pLk6z{Y9FGgbbq~uR&?k4{+c=Vi`D+I~yrPe18}C)@65P|39NuDlAS{9{!=RFYL)FhK~C^tK#XA zQ+~h7BR77K018^(kb5fzMD4qWHS4k^nkwR6-V{D=5#fyj|q`%Tei-Q~FC3Tw^h zE{m-3<1+(^`+^|9FVAh-?i#-uh5Iq`?gvZMe4JKanYiv*TX0vxSS@Trv=WYD`?l1p zuEM&PTPf+7`i7s>6O%gqJPNxlE(Fr1j$#GQtL>=A?$Bag;)?S}YJK>cqq*OJd2FaR zJlyY6=s~?!73f$bA2@C9T*6e)sdAyA{<5ZN?**i8VekTPbFrw)t{B;GXC1{W6^U}| z$Q=ppOFBC!azQmcUp_7#=aqxiy@8WJtZ19!!lgAYc^wzA5Zh});Dx$W(u#Poy)MI) z95jQ*vMcZ|{;G_{vA8zBci5T0J3@v~BMtg=RI_I)!&!Q>sWHi5F%ixHFqiWVmj$4q( zu!GuBSr^mUox=&HCr-rdS<&o|5)()`>EpnyR8??lFkuy01=Asr%FL)QM6)kjb67f%ZR0BqB%gp5#;#zHM0)9 zY{}C6*yIQa1Fhwn&6`KSw;y>GI%TMLq|e`fT%EkHzTXrcZZ+mP5!}@()J47!>B+1vFdebnEhl1LB0xIUAD^^`2FHkKM z5UXSv*xMn4vu(8{~v zwZh6^D}r*ugS^h7>5O&)R?Bo{a9A*JHe{}ANF20^PEp{``B$y;Cq1Wmx2j^N-YcI95rG!-N7C+W2%X!xiQ>_=U zJ2$Fn+5KSkPrit36^+A;5Fg-sx#3uH60bRTz9-mUOQ5JIuU+T1ZpxYN3E~{#ZR*U? z^Gy(T@+=t(U|vj#P*cI+r2!IrlN{XVm}}UbTI5{8`!qPQ-j)+T$3cnkg$T|J@)!*{ zbZH+q&OIAkP$^3=JXnR5d;WOBIj;4i#O{weMK>9J?3A1H9^;n+FEt*C(CchGdft}m zNc`l3CY5nwM*z;-v2=K@NVbYY#sjL!OC-;4Ne+UY*Cj`&iyAdQ9uKSz(41AC08M zZSWw+@+kJ*|6czMpio0?GI2R5(y+{_aJo$GR_5dtU?GJ?@g+3A|78*tbV@T6kH#i)XP+y5(D%Skn$jT|E%V#t8!?+N`o;xw%lG8;7-T>Bs-C9YNkKX4fNA7yA9H#DrI07Y2U!Ss=}dJ zzqXJ*z|1}b{zTS2NzDcJ&4cEo7i$nzyQTk=i#NZ9o&MB-)V`}r%W&3Q7@{@Qw(+OPgyKpK$Zq52gYa zbKAiOf;+&t)HOuu{#QLd0=_@*+r)?Dt9)g{y@?BLzc}QJg?!@%#$1g=(e738nwZ0Q zeKkUV6L&kt9aCmjtw4R#=w&kw=s21{TG|2iNu^~wb#ujKp%Nv9b9lW<~(@(S&Z z-)IDkfeW&bdcn?rXkKI;>eZpQ)R#+THbS1qUl6jF{jd1j1*qbd7IFQySp)3Gh7|R8 zY8r^ohjgfYUSAhnZhlBn3mca$4q9-JsJK_|YoDYJHSU$OuSLWrv9e!@T{u%6oANv3 z6m`8Fit$Yx1k71;zA^p*gSxInwWQ-Fz|K@_Es3OxUfl)IX!Dqhd&}vPdk1f^4Jr^I zLywH;p}gO2%Q#KP1mQ9v8B@c|xrb>*i^NeIztn%tEoe|J^&jby75r9bExZO4#a@ga zig_1NOe#hF@Lk0|Trf}Q(LO0$70;i0NqZ{oo^O|qE*y!ylu}<@{mFc$lrb3+>-V(4d z)88(Lt_GC8z7}XT_PSDL$^K{@0|F-adcj<3fG3JS++%2R{f;pVU zRFbCIxs&~QI}tCpR-Cd;0gDhzhAkBvk8#UJU8ke4b^!ZnH^`*wI|t7U0%j4(7t{ZuD%w=5}f zQMs)-T%#Jaq%ZuYXi66Gfx>oS_$-qqN3DSLnh|P}PN(StZxPVvqx+_~+mdM?ED2HM z!Kn|&@*_Duou$P5WH$9FZ;pD+F}vW0`Nm^?HV@y|(cE9uf&TWQr`JR>Af9TZOtHLi zsP9|46#BmY^`SF=y6KaHkbxk$qWBkfYh*)x86^jhXIAD{zUb1;-?vBcFh140HN)jc zz+lTor6iT~-)9eBUCs<>{ji~^N;s4ayIhX(XXZ5dPgMEW`iFE6nlV_o5mMkN(FXz+ z^>PZb0AV408dsUY$Ri6&s{fKgbyn-YFK6-Y+d>eYuE|W)&s}g32}20Xc(<FT`I4%YB0p|;7XErg^i)egb3_C8{QWlQ;-aD% zRt<8E_c92-{03)T1>S|m7bLhif0Q?KO)BR$O}#f1)~U90^MS7|+_O&2ha$932Zs{) zpBl+|@5fA4G15P9hwSo#+b!L48wHmZC-OjTPTY=-0_{DQLt>04`@8Q0=*>&dt}6ss z3Y7jf|E<20#BdJ9gS^np8Be;fSeto?>$+2eWYf7Sx#Ot-9VO>9E@ZAD0e%PK&QY%qtKVFTCsyurU=)L4|(T0Xh^7UkGNv_8-Q{aSaOyOeb_&gm6 zb^?v9-!MekOMG8Ea|B&0pvw4N(K|~4vUDsTFr;-aw1d{YtM_yPSH1b+>;fDvmUsDi z|M<`p&}(qAlRHIw)YVBHRbK&Ar*y7!i@z6kOxtrW*jUPJWI4z^vIKJx5wGnpNzD^^ z6N7)N($vbh?_MUn!x)HgZIn`JadjgG%kLk(_EnWgJik9Rhf%s;?(Hl!Yf15s7*r5q ztEJ|ZGwG75Yq z8co9l=8vChCvAv($*GfE&%17sy2&OFMr4CmZ!RJ$)VoLjKF@l#CD&mAnZ?vv z&%$!;z5Mc}sT z01exHEM&M$Juaz78{ayXAa;o!!Jz0ZGyOQj(EoZ{RUPVjc@|{QCcpz|6}^mI+LE%r zbd}U`jAmZH)9M4hc?qO%$*K#Q_XA9A%qP0(rX70k1+Q18d_UgC_TMfFW2oM;e1TQ= zEwFxrn~rFv==I|9l0&ZMIS)f^?+_DiH+?{2qZ(^0-fnUZqRL4*sp2{aq9IB@@nnlo z6HZRnn%%qBggDh!%aZCp{^U2J5$+~kFm8Ykck^1N zwKV4bffanqrKTBdH!QNvH`c%#M&neie)r5*O3mBp${aq0cm@F`awNh*oer%+e7~oQ zZ{Tkz3_aXt^VGi7BXD{$A-_rPGw0kR)*r89WGy3mgu}JbmX_=zqvxO+-=$WfS=K%_ z$G%C&RS09UWkoOXqfnW#`rM$F-eB@Bp}FwbPpZMI+G%u8h%N!b>jKdXH-E8$??3JI z2IQ1LEXpT=u}@C%D(mU)hOe-r^dvnuAZ6lAa+0KE^t(Dizn?dFi(9&I7xPy>-MU$n zh^RY6(oCSWEpD-i$kClLatzJ`aMRpa^z=W=mY!EnZ(!=HX@1bdP}=+DdM<+tHF=Ju z^}}aqKv(lMmyz}y^$Kf1TOC(iW}{nsgF%jq^S~RPh_`m=i`<4m(&LW67kuMZPi?>m zKN^#q3O2q|ig}nFKQD_XMSkxe$C}#H&OGy(JebZ3+5|JiizDDzBio5cNm($ns~)az ztcrf^DbH^r+?p#-jromg172NX{9O$FA0dV?lZjJj-FM8GO=E?CkZ{v|b8m-&Y7e+-WC*qNsowfam z^F|D2^b|yR0SR6#>)RATD*UE@p+bZ-#!mI1;93OZ#F8TI4unyMO^{I=u^rMH$sp@A z&S3md)$=w!?wd+rK`Ez8Rcq48`!y$xiqq7qb_h^vZqJuHk8NfJokmX)vEFD5#&<^q8;3~_F8`$w7?7c?BYxf3HHKWlSmEQ*?dpjO#+zbyZW@y)$D zHHqL`U}1Wx3*rbZaLz~#E-_mKuBJlds1~i=8IHj>?~BArek#;=P zR^1CM-;6EUBA!TV*Xn1nVn)Yv%=Xn1D)mf@CU)6f*h1W10Y6=Oh<6*`J&e&e#~K=x zz7L$-+-)gt_V_uz%q@8*p!{M0XRCHPV6*nLeZB9S-<|^(f88o#=ze1>EF6+<0MJME!AyYXEJLRQX+>1?8V>q*UoEYGc zr+it+s`B7lRy?SYJryez3}bRvShG_)g=K1>b^1U;qH~`=fd%&G`!4P1rPNIP>G5)k z#@c_H?l-)M%vn3x#O%u`^RBrN9@?oj&E(s?X_obBeeO_cFL}&EIeW0cWU_RAys6=| z;<4ocki7bOuA;(nFZ`P95<>Vv%iNTY$_LsW~ZrW-M( zx(K~-&^1guosM2<5k<)=TT-9tY=8NNj0nK>@!fY<71&i+v~2V0dI;SP>CMEx$l(Pf-LoxT_=8|Jd)-D7I!a>vUOlURJ|Lm4pf zyR_28*xEb!KK-v`s?4EXtQCHr-&d!_^3JjsO(K!ya zXI&cQI}=v>ZRF1#lBSs#hm@~HN6J|=$;xmF^^;t6?$;QGe!yV`Mau#ft!%LD&Dd^l z-;_rh8Nal8RZ(I4I`++vRh^GH)Wy7(wr~2>D>C;2F#Ruw1XI7`;T?=>c2d|YEf^6M zoQB`82?!b3)Qb7$^!~XhY359&d5%dyWKNSZ+Gk14n%CPBP4#^oCym0n7~rqcVA!DP z9p>Wm<&*nq^i;T|EnJ=^i@(HH)Nh}BB}F{v2k8&-x%kk@qd>p&Nhav$Q>j?$jfC~t zNkb(X6{;PEi(zb1w4HRj5D;e@`&wu3cT*~c)nbY`z8jO=4py)OL|#w#T{&dAK1E@%Y- z^H{j25Le#ET%*rV@?3_WvYD=3yOOHkXbuEoiFMLBv1`0&t=ZLZnQQ}dkC~caOQC(O z!SmzE2lr=RCgn=!tl9H-R`9xZXwI-+?1TVpp;sQ)8j*se0UH5r_T@vace{KnGtc0{ zE+cwSp+{>c;{^^2xIOmO(NZksu<7l<5FM8*PaWZBT0Cq~0ztXH#V3nR;(NdDcZ-@V z+jK?#Tlg~qS_*qR>Z=p(yf4P_UpmpeuCx?x4f-}IOpXvQ*B>mtTCOKm*I;PYVNPHUnkNPM)3I2VPXiM~OCH0f zWeylrd>;7P;5{BnHGdqzH#WxXXF3PAo9buAY&}VNH`}?&_*K`NNkV#W0xt9@la^XU zwGiz2o8vKKdP&4?`Y?{VwYOLK>sLQ}8)14xqQijfHI6#F(y2nvgot~X2JvB%Sirtm z^{)$a_mHj`8xdR4MVFib>ZRV~uW88n*3T{ilnLyxwPNi60W?L|ayr+FTm=abL5;OkcLp%_PF^NW?1g`;uv9h%hz54TkE z{%&PwX4T^|YxzTSY8G*sThfy_{Mq-=(L`zeo_z{0Sb2YYA2) zPlOxvRywlp+KEm^j)5O|JgD-Htg@2wmz~slG?S>SMk;0T;6)qb95p78PI$cuVu>*M z+-|tNnAk+#^5vcGnd6(ls6od;dVZSgHc3WolbnlQ+L7B@<3HJi$tQo8>U8Ut#;A*g zCnDBQ2Wa4{Wq7b~AGRco2)s6-MEzfRk8r)V5&G{za66jij1od7w+QD2a=_2V0-OCw z3R|efQrA=$z?_|ltM3etES&1R>?M5_fOFH}`RwA>)q+s9PsfG1XeIv!mha0ioVY7I zJ{%6`${z^-Ec?LcRv+%BtYsXH-?4HAJa9?=<%bUlyQk2y(_07iE<|gK{5rlbmhj#< z2wq+K^a<7$donHy5%I>K^N*AyujFs=f41rDTTb@eDJfAPr#iiV;q?5~W>OiJdsJ4f}+)s*Is{PC#0Scs;lNRxapqMIF z+enl1uIUnEp+mpsVdK2*R-YT4t~0pfBH4(aVElA9xEfDq`sA~#5cNjS*>7mBvboYp z@PSpCZ9R9dV-7Tj1AYxAYmj*B_wEmHLAHQpo({w^pJX#6hbWA=U7>s;M3i( zw8=@UM;$4@|A*l8(SV5(7vmstnhX+8p#=-it5Tcs?aMhvIw()6fdCf$kG1y{Jd;(1 zR5I5)G?c@f_nTCuL#?>WglBqO@zcbT9OKX|{7LrfuB%mq)8dP8V(`)+@-O{rpzekJ zr>*8T$B+oQ_gw$b?2K4H1~xqmzG1-cQ)x)@( z69AlTShQ>3JN#`1r05lDcN;~#T8*T|oFFK4#EG{=8+pZ10`bWEd6+iF*G(l_``xzP zPEQD=iECs+_T(%3zK6i|$FzJijMLQsJ8ytJ=FclnfVZ!0&R&)+a1uMHz+N)p3!DB1 zVc@(G6_^LpEkOJ<^v~D#+cTT_u&X7<$^KsMrhqXV8i@IY{fp_14_U1g1k6egr*`IaAcfXj>DOZ}X zs{s=bH^x+RPqY++^wiFJ)NsU2CD$!fAX}RGz&rurtr6487Se7hyJ3WF{uYBLB4}u^Y{|W`rkDiQ(X}2)*C;1gNX=ghBN^SlK zo+}F6y*-!gocopzzdiTr32t|E#Vpow6&$|MdDfg?jU-z@8EdBlQYC~C(;ClP@ zz(tl>+I*`{vbh5E_~txw)_IkYdBw-yU2qrJ`r9wn=vn#Hx8<{IJqoY6`W|$}Tu(Z_ z7Vs&mo4)CXt&aMX)%lxTp5`uu<{Dgb#jTR;@y3A0Aa#L$FxyYH>P)l9fjJT?d)gW3 zV(6gmm(;65?$bu2Ec_nYe`q;4>?6|G*3a8{{E93% z3(e13WP@cN5*W_U9{V?VtKXYQi4#iwvIRTOQuGUhs;wCF@dJX4#NW@{xH;*D5-aXQ z#x(dn3cEK4ls~2JV%ms4jeL3<52TpBoA%KDV;lG|CFy2MGNF3X(RG|(-Rq^7FUq{8 zwQO8}0t3JtX`k(FdQ0?-veHw@GP~cMeB^J-EGtm=E3o%@d3}z2BiZW5bQ|{zvRvT_ z6b0Qk29G*>dO{9Jlog6re+YFb<>W9fb^UZ|Yb@@R8lM9-i{_L?tf9gh!vCGW+wsDz!xJQL7r?nrF!xuM==#H#ZfbSWXr zN_iRD>{Sfu1lI)&N z9mqp_h@LuD+T`dC}M(f?}>^PxC-O_*)x;@OIB44)xIciQDJYXUVRL8A^rk3HrMP zT|~(~S_7LQj0cLxy2)_YD%5gqc!CTJm6S$4+IR^-G7_nb zgCE`6ayX5Qr%RGs@YzQ>2;`4|T+KqQt-n9x%JbF7Fsd=;e64Z%n`MY?wy{*&6$ZA> zb1?8*7f5&o^ou}WRbzhhfk2V}`j>~dB1N^je0{CbGiv+xFK8{7?ClUoSe>59Blkuh zajeW|@6RutMZ`_VTJ8Q0R~P$@r3lT<$05wdBNpqtaA&Mk809|+#VI}1Tk-v3{wv#w z3jVYDivk&m!bnGh4svl$C#O{&OQfE@v=L!c+?@WrkzI;7>A&VY+K8m{?*<}WmeEQ`PP5z6cW$-7!U}Rj1g}5-qeCZuvn2Qf{~h=KU?S4qTIP!mn9*a^iY%i4 z)#hwo50F^LZT9`y4@C7UXRP5$lzO*rxOtDE+*ALy`ugepY4|v$HiwBB9{dQi`}qv+GzzB|2iJ2YVG%Ha7x&q8`Jei_@d*o=ekys$aqQadvT;u&|RuYbA2- zA!{tbzxT}FKMH2_=^J>hL-Jardf zPv3MHeAl62q=7@D5y$mMAZKs})=1ZFnz}arEY}>;PZHuDG;NVaGXmFYQqTnGNct{0 zxkL#+Y!)`Dc{VOp+;w0%jp>HE-&`$$M{&?TN^*=|e!3T>hw=CmeH$AN@=5Se2HCbA z^E`|t%hNx&_FvI9K+kFj2+QL(!=Q5yOQiPm<{C!EZ+GdMkpaiDQfFAXTrhw6B+VoZ zNSf@NkH$N<>U@}*le2L?-@W%vYqdyndmTK_%(LjnS;#Sn8fLF`Li-*BZmyt=2WP%`G%5(hB;Is+)rqV2-9i@M*h>>%c|WP_shIL;aK zmHO&e%=%|76Qz323Udg*D)btqdlbyh@vEn#yWe%O4afBQ(t~^qyKBcrjg!`^Ed!&%zlr=s^2hR`sYBh4K{WJOTL*BO^9 zUmaeYMOdIUQZxKTp5UrBt)=BRb$sIDMC81*5#OQrb;UZ{=lWf52w>_dJF}s_8YEEa7|+(ox*nKGOg5(a6rA`Cd}$dR*B=8#y}-?J%|Z z@Gg+ZVeAnF;GehQlhihn?TAjyerRa+cf`Oma~~_ z7L*TFzKy(k4l&5p{Aejv*-P{wKqehP*l)B8DU<7xJiNLnZ(|yT9Gp##nzH@k5%wM#Tx@2YSHn8ECbTF3=?|5iQ z0)y{pGj%3tj^wV<3b+zuQbth_BC_ayww(rI+zu775s1T(^dFKL*^rT$7KXWLa|;g8Phprb36#?N4(9d{0$zgve0*+W;jGBYHw-dnEt_#ifSTYsYkB{$ zLg79Z8CtnhPVe$Flv+sYonHsz>n1W^m*WqyI6H-#-6((c5#SVPuaA2ZelPD`d7@Lo zX-Q+G9`cD3?{t07&Bg_47;CReTQk0*SWg_1MFF0>M|j<@)rM>Ty%dEC&8c^QLR}+tU$H(`+zFZLR^?`Nnm$tdX zWFm%Fjvk3JbAMV_YwKFc+*D7lN-Z**-meX?1}?eL3^>etpVWL1WO^**8Mb6ae}BR4 zYRA>|o{ylc?>09x<-R3MiNvz~b=+8wV5{33+ItHoL|FDg6Fa03EP{$vOZ4EM^x)+5 z_alSaV-5st2K~s!TB6Zj7=3uSeGr^37k6|Zd zIA1bl^C(dV=^>NDQFOz#Qa=Z)GOb8=XDo!c=H6?OzfyAcAop2~jd~^TdS}IRT~zY6 zQtQ`2Tjv}>r-U?cy4&0+S#BENR_v}}C^}yv@U#v07l$9t@D)hf>@UDiysx?ssgQ2{ zWL-@jm#_C&Gfuo%MEq%K!`6C$l7nrQEV$ByP9Ur4cdOd%ZLE>jdXIe~Prk!1G#^Cv z-~QEks@c|<&6m&<7kTa=p-Q|)vjVeKGY87`m^+apEpJ9ce8^`7<6G^W>Yzg6z2aN-G-}lVB;#B-Hym}NNoRJe{PWqr#*++EfO)ZsOg>QXBlna zFTq4JVRpq%0+a+l%=T4VcXdUKFU&PbKaxv>fmH^U%bwkr9gCLb49pvrGi^tju!^gV z$wCqZI*EIj`znS>r}}Ga3H(@p?T6=7r%TL>yy81?=CQWzok|Rs ze(uubftiQ!vG zK(jY#RkRyf%U!{*dIkAT%Dkv&-ciptukQI7-q^<*8e}g!gU7=^ja4F1`dGD;-7!oJ zOMuBbAJO^2h~a)5UEq1#uE4g3SdEe!W1|)McNB}`8#ehX;P$J`r$00&fh#H${jJ|7 zb4!o0q!Vo7ru*B?Y*1s0Be1Z4Xt>Yc1}4l9t(ld!s~(CgCSo+CF14Ok+-EXorO~)S zjZ3ka2TmPC`S|K^zwJ4urEX}~ z*Q~+jD@Ehvr*v+U5+Q510&>`|N>eL#M%O-OLlwz=Q=*8zdjjpFi;Oe22i(2EuERr? ziQQJFxuP~DR&Mgiu$5~Evq+DAOV^H1{8~JzgHKg*^At>cf*I7g#{v>@k!C{z3s#C5 zX;)rK;`o35jL6lcS}cCfLu|N6vDf`7I|!)X@lh5MSSmS*>hf-W%cLn<46f8;6?sMC zN~3gs^QWLWg34a-qW<+rqJG4qoO<)m778~CSMs*@=aULId?D?{M*ef6>SgC5*G90P zGzT<|XP?5{mjy@TI{J=wE(S8+I*UQyvKJ5S&mDBAv3%2a%&$1**)*~ezF<8B4lR`k zOM1MmBy^hetpg|&e3RtM;a22Vh?2r~qqWQ7gmYe>35;Y;7j2Oy)6Ol)BU}HW#re5Z zyr1D$7KDB8Tjp_3E^9E}h&mFg8?$+LU-ycJJ0~O7DlA?WuN~WRE|pLK3m^NPmvKcn zRP)Cn;f!aGBv7?mF^%h};pQjkFg}p6>CvYc4O=Wd@sf)YKr+Z7Y`+7iB8wLlT_$1} zm$w4v0R2UJNJUls^@$`uaKRMp@O3}`R)HYD>bm*GqVVkIpQx(WiP){SbX(^`)7fvDH^^?M#>$E~tm9P6!qlq`%}i<5Rtnr})t2 z5O2{@pRhLas~l!k#~fWmuhzHARX2+9wG+{DrrcC;+h+fRs>RCGzQU#VRsYP=oWq<) zo&O4!M+Tzo3lsGNWCzcdL>a>hFoa%_x!?H9LWNLpIKlE>-a(x+4ec|hc9UCEiVfy+ z+F#F}cV141hGidm1x9#ndmx8(^J_srE_S7Y9qI+6#A|*Ndylm`m@z@-TdgmL1ubwO zS{oO7?lxa~vv(06Q@EWyTRcIE8X;YWcBguQxIxN6xn>b>ouNF(fEqKS#`&txLbYCV zZj9?YP%$sb!N%@;w55`VRAwLMDpXo66Bgioq8|^LcavJoo!&Lk=9*n9W*u0!Jf(*C z2DrBboia!cWwiMIXqjTa1DO@NHA}?6Rh=pi;mnW>M<)xfGjQLL@77~zpsoL;Mr62% zdB-ujjcorUk@wrBSml3cr&E$S^T!Fdd#FK2kQXx(!bRy+=C5}!)SG|j>gNaVcIv~c zn7J!zrYQ*|MVrW+;hA+t7mZ!%tC-Ybfla{KtODM(}`6h~8#rp~gDybJyTA z#O7kgr{vU33BNMa;2(&>%Ywdt3{Rdt3 z{*o7-eszE**fmte!oRS4>IFpUtj{it7w6~9XW5|9582E4o*KrEqnJ{qr4M~C6y(sa z1jr8iVfEb2=MRGtE|tY4Nv!?VQx6RRN1=MYk`-5vnPymq&YRai!(A9=-mV|d4e01D zRi$-cP6^L5M;Csn(^|8QP&@6CF}WdD7xi{{PrUZ7?Ud29DW)vFZxbgm!(UHI!Dwkc za0#NTi)?7>;&99e|PL&05Zr*eFRA%G+ z@{L-t?^idc!}Na%`|6=XLUWo0g5swhyXg4T<~V=siE=7BuH;Z5To*pIO%N!r)nC7( zGAfep``As>$05DtOx?k`5Q?pKOIJyyX$3B^3V|<1pxtcm7%q`s7qj{<{W1H8#-$Ox zw}mTVE>fvTXV%sf;->TE=_DY4yhl-eF5?WpP~|7RB4>ZggHN0HyrMBoLfLs^%ja`c znJ&4t6Rq)0L60_VRM1(?RYL#$nnG0twz#6Y6`ywwk9KIZG1^Hpu*3EZ|J1K8(&D1n zw4WdCWEMNI$Tq@=F}7Nm4A`0YV+q)JC>SL|3fL%Zj&E9EB>Z1NKkk|gUkM~8RsLba zPe+ur3-R_~mMr8w|IU*4lRZt9VzN&Qu5*pfxW@K4?pyf0=yX=nuis8f1ePV(Qi{fGij~IVe5+y~A$FiVGwcUyPm_)o zHW_s6GixJBGT{U3`Bkc9R4>eGI1plP2<5c&@07XWW(!A(>m)4o+$A+{EGCn5Kb+;8 z+AGWAyfbiRCTmrz*co#i^5Zjw0c&J<5-WnF+wWOb+CJau=Bi)qaWv$u%1JsBu_o~k z@KeWA_4aO2p_J?2L}t9j(h=QgBVKa#_f|27RU8q0y^~))sOIq?&m8!Orp%)z^ldwF z^Bm}&DL03Aw{KlQjekRLz9mIV3!6By!xl=PT@+y6f#9-55zD^#99EKT!q4PwlNx{( z->=Bo^UNp%&NeDLrdF)`K3X;b43s2{$HuaYg*ux19bwJUbz>NBc64SN;C&Zrgjz>qnLgHSW;7h6v&j z)Fg}7;nfaV6r|*{826EooZ-;v1EnY$zdm-D8%)ob$XYbAp$PM58%DZ>i+0SaUG^=! z{<-Ykm&yQ6KIyNDK=EVz?VN!0a4(@5Z(t z<>7uST!z|qchrgBS|xaht*R0JFUSXpMHUz;WADsQU6g*WU(}I5ovQcQy?9}|?qdjo zt6Ja3g2!Kr58-J~kFkwVv8xF%53S^ z|8S9YC>HS?0YWIFmjyGs+lvxju*=@y=QLq+VwdKSO1&pzQ9Fe~OAKUFE9dn$@EbA+ z5;r@!Z6z4;baiY;p3llZ=bOCt7bbE$WMVgW|28#%2dF=7GnqG-^-}v}u4K-vbdaBwmY;sSMG|*m)o|{y({bA@&yb=-3`qf=o+Di?7ud;03tN!sE zZ{=(*;F1N{0ULh>BtS9w_N1aOiJ!TI+M>cXIGvT5?IMDSIbOrxQ>KZxa6$f+vwHIy z;SHjsS=H!dRt~unkte+-`d-IQm!qnpHqpKabdvo6m8LSrxZj6MU=-1tX;)Prr41$B z^U#!O9Ilf~nk)N=H>0m9n>z3B_%bDdsnkbOq!e$T-NkX)GS}Pk00$@}AyOI^>I=rq zmW2s|tu$cvV?yaHLRclN?z!Q_uoe zOy0Hd7Dch8XR$DjQvXaHJAg6D!JFdnVkVu=?wm(Pd?1~c|7|SP$t+pqJ0^{E7Nui?gR9#YqQV)OA#9xs3$fCq=~tbb4D$lKXCJ?KUs z_@}MyqnA;E5Hrf(BcAR*^pY!UmF+skF7apAN%!|?MKjwf6B<1d5!w*)WBhy4Y4Ae#r@2ccAmq(0 zAXY8(gxSV-FL55D(D|`jPC7ParEhDAlWkxA!m|UXFsFqydU>C36Rs-NL>HPE$l$2n zFK{K2ATDR{eq*w|`3r35$^v^+Nusc$LJ&{$?tZqG1&jWHlBlesUqV50B{vr%-|Ko( z%@INO=FR-PrsYJ6{_4|(Jb2v4N>x)t!HJe7TgANe70)1)f2?TdGdQ;MHHof~&B}+w(h~ zmdojrMqQD?P;T=R6w%7btpb&8Df|hA4DHr6@Q$)iR4^hY@CTB4{mK=9n#1XIzm+u8 zk21{cZ-0!kKi5<2maMT<_Y_>NhABLL!CR&4iQwo5=)cxvN1eWSV*8DkCIl1j#1W&4 zzW8~G9_miP^m5}+p!UwV@Gj=U@ZAGyDoYD{5Ivh%6{IfqdBb=MF|(U%=ZYV@zXSp)Yy&v7)V?2 z1~n0XEo~bwea7|=Eo_}xoEiGq_hH@F17x0aR}R0u%275CsY&(dQryEm4+$qQXRb_m zmNY(AHbaNJ)ZguQ+|bBz@KpGRmM^|1Ha}2{kalbeng$D3*ki04K;E+|RlYg$18WYMg;VI#u;g2RaGk?DwAM=x_p#6&8i0=l5 z(BBKLQ^pLDaLx9Ymu43Cqjn8nYVHO)TgO1n*aANRVmywm9J7If4>`FTBYg4uf#Vhk z#rQxvlh`kb^C}ynp2pHt!zv)>ouGh*f)sC8I+B(c=OkHp%%0TGjM>#-&UAj~(aynJ z8$115rBuV$U-%}CQef5v8z=KCv@f{CmG0T5XSpFa{k_2ry;||-_CyqWedeEBTEwUX zG8ztVG_19>oySEJ#D0WSmd_7*T-Y&xxGC%kr0UFGYpgn zbNwa#!vivQ)5>~#c5=~H8u)@)MhnN*w1|a;ez~QWwzqRW##msp*o9K#Jre_uK*`BE z$~t=ymo_uXn|5(Qhu(W$&t&C@Y>(_Ylq101Tv;Ts_g!~yDg2O6$s)3TcZ8Om0$vo@ zmWdYndda*=$?=((iiygq;c>2x-8_yqDug0sMezLqd7-T;fBb;b382XztmGAO4fm&V zHxuZw?Y>&UISG#XSmFBoIXJ@H!yxG(>44^$rPB%NU(&~g=y>4WZLJ#%6b4w`4>zY8*n&MFg2>T0?{Ft8zv6 zF#d+}zv}No01+Z7rwI8U_cQQ%$ZzJR zLl&*%$kLt`shiao`FK^{KQxdp zR!B{TjSC7K{4zEF;{JRm1%!&WwRJmq=)2RzPF;Q|BQ=5TfA3sPc2F$2%|?ztmW$v6 zyy9lI%4>q{Vs;y0EC0}ZH}GqmDv~9DhQ0mDB-YjLmmvoyi~FnoFk%{eJaha7BIZCu z$oP%A`NL7V$6*zVs+C}pZOHkvP`~V5ZD$|W@CI9T0JrY69}v}I>B$;d`lA$Fm3u%5 zIql}}rVs8yd-1TL%IlkNkkiRNBb=K`;?mN_8l8-oS7rkm{=2@CwPw{bwi%8ITleMk zR^cyCh$nIsBOf|uXH!RcO^$!id{s~hiGEf*Lj7#0m?x&g%&REd{g!A`q=>@w^Z^sk z6Ni_$`S#$fC0m0z9qnfaC1pCN+}3IYC;qnx3Rc!2WX+MMalFrf1cye< zfXaMs{_n&y5a#F0AbsY-JwX9f+&r$aI{bDwi3Af1VT5|FD(yY=9^|=-yn{6o>iggG zl}_j620t_ig&mcvoXs(-C)fR%z`dhua`wztnYNmvsI)wj#nR4u>((XNsC|a`?;{OD zQAg#I6aUZ(TTrv#2&uteVPQPN4bWU5c|x~aPA2WBN|NQ*Vdnx*xJD$191v`8a!dD# zrjX7n;TMFiakN^yr|hY}Rvic?&xZK!q-1U$KN7#DUJ0Bpw+;CP)EZf>@L&JC)AHNr zTY1;oef^`Ie=;P`cRR;L`vNyNG%q;TMO_59g)JoVE(Ye9OKM01;_E> z=n^pBQDHVUQ!i;eHL zn`ehMJF0K9W8yp$gb!gd%|5g3z9`6mXoNpWwjrVq4xVGK2JO0 z?8sxjoz`dTXZAX;1CVeN7pw&*6flNghvS~z4J`A9}~reTT94tw3Z1J$;WFcVFZ`=LJhZxlPjkWu2Jqc3)W2L0)ZgyJ zWaDEL0;r)>UHWZ>5XiSliRS?%k+iCTI#Qs0Zp&+oIMjb1yqGK}f5N;M{^xH0K8tD~Vi{hL_AyS~_cL>xf?#szyM%o)LG1Z(sqNV%q2h?p z9|4R>pyx_6{uDR~%MgfB>UP9XNZg}4J#N9vL=K;yVeC^iB2tgqyxRs>p=XHN8s2Dq zE1!e=jZzD7!rxEjsOVP0M7@+NCcF@d5@C;m))61Ij`wNImRa7{cM+#+>dk+u;@CI{*vKsCBf0Xsp;Qq z1;V?ZCnV%v*bl(5y+cf$H+3Rm4FXlInIW&C8!Z$5v>cNTsc56T@Xf`hz$H#`!l93Q zOL}1{9`8GOJ)W>ka3&k;xcx2Wc;P%jh*g!F3jt5b$*U+Ot`MFot2n`R1NjmEpG`RB zSv(L%B(rLyHRodW2&d!7?ltdZQ*@^P-=>VjEqt~u4I4(Z{l@hOsRKY0?A6zj;+C&ZCdShHX; zh96>f7>RDIc=>4nU-x^Q8!_cKtYV3p!3k;(5D0s~h6U^T)i<7XoA020N=K+S)F7$@hA>j0NN7h* z*lQckbn9gPL=DyJkpdgVHD05z!@*CZ>-;g-Qp)>$vv%kh@0wy_%0Ev>JoU1Ww``X& z?@_k_06TcWeL6P$_&EjRh25X5H*9=omCqAbz3xFMh?Q)_cumT55b|m8{k8M?uJ+qW zUl{*#wg<(w)-L~G2+5n2$JII9!t-|i%35=Dx{~l=;EkW2EmuaS?&G6ww)Lh9Ng8uV zH|4^LMCpw%TXWCA*(XfV7MF7!5>$=f_6vquynu}LKKq|=6P-A?peVwL^t)Hp<~>hR zQgXTrVZp>2rFi2rE2hwSs_o?W&`7HUm*Kvp^WsGRVx>Xdx!(&3u}s^$)*uuVDEmIi zC7hT(ad4-#y>cx>x{nf9ELwMj2z}E!4I@mfqzyYAlEp8p`>9`Y9cSqHE4gZReS>T3 zfYpu1_tm@;{Tpn?;yUb=A*Tkj!TxT0n&y}h<<&xBX<6$}-HY&v4o>r&X9i}HTK6ew*R{eLx@1yzeY6;1Q6 zBspXY>hC33Nzd|xwYi(e0{7N?fewDzGtbR8J2Q>Qe~vQ*y0jVnfimY$ia_k_vw^+E zGLPb#>nkVjA?75L=&=DR_Y2mXF~-`H2kF&s-$JzcEP_-U6mf2*tOs|cErWpFU zzF4KQeB!WVz4Qd!L!d7R$#MwXcDMQ_DesLcHr96>Ze<2L{j zCk^_@wrnPKW%W(n4jZ4Hd7PZz zp8DD`PoBw2bvoVMx^QfMZ$v2Sx=G@|KVz@mj8*{t=4ITt(Avz+UEMyox3Qb>a{#}T z+^TI9wpoGoj;YWX-;kLBly>c)|HI2Jt~mRd2&FT($hFjg!%GzA4XkX2ij7qIT@D$O z0e;zv-c*iF?oil`sjkKQhxWDuM^}>;tx``f=W*#fN#CkVzrCUQXx}(Mx8cj5`zroB zgr>#02D<|&SVhzA47Cl?1|PaI+oB*>y_bMJNroBx4ak4p!Th$!!_p&e?hMtf+e6>~B8<|zL)Ogs ziCSmn2Iw?^HL(o~FYX9t%HS|#7z1BQePM$tWN zk-;-}fGf=kN|NFKUsABDf1Fttyq&Z|ML2Ysj3?>-N?#f0ZELZ%=rj z+^ctq-)X;nPvJhz-V=H%H_Pt7c&tc!|Lq@IUEt1q3ZDshf^gHIEJkZzzJlzkuO{e) z!ej>^Ho0egZTB5)td$e|>lwVBrRQ2O8I08VZowhq|10OtF>KmaNbcKiY(y&S_AO7m z_7Bf@X^^F?{)zG?Wd+yiTzb!*EW@%u7Uo%n)G#Fgy$#cWq5HRt6hElg~vZ4ST@=cZ7Tp zeT;#3nD3##d|MWot<4<8GM7?<^CkNzF2!FBjeZccJA?U7sNVT_^Nt3%YVBWHRh^Q# zA!aMOsZ63NayRx54Uz4s)=7Qx!guZp9@me@DOmOGfNICgu%A^_$ML4{?w0-B4~o+0 zX#~S!$Tr5zTso4b*9tDO^Xu!3^G;BI9Tq6hFfKj~K8V3Qm#%RYs0CNR=Y73{ zbi}jvga7N?|J%WYro1IWn$|C_HBH8Ah&~O=EbjfMh{t3dj}fORa_>OlU;58kgOR>E za$S|DA5dFgwT;a`v{-hJVh@_m{W#0ezfkyP0BX-nubUKyqEO$5yyKs5tJx1WVQV6R zAr4|a{Kx~uBRcv+y#jv7>3bd~%iEa-)Zs&Bo205e`0+obqi(J%^_8*qG)Jk}_mrv=1vD`aS(a6IPL3 zA=o7H1PRqJ*Xh#oCEURDzD$PMb)9{+xOgnh-o`u?++T*P$ppmH5vBtt2!Z*s@Hd3*xT`% z?zhoz7DD!EC*V(%2Xp)=@9f8hs*U>voaKVQln$Bf2EkUseQEs??NwT=P5 zSAM#JM2TgFtDqx0r(hCct|~DDq-4a(qI;E79}_pMI*bhOx|?P}xZTJ1mKbDdIR0{a z!1Fsk+P4uv3rPIpJ6E%laUx{iu*QJy9r>{W{VQ*b^BIw2bFkR-gYT}xdyOgDAbXFX zqRjel+=LG1pIpxkh{<98e@Gjp;5?Z4by^8$C8eb3iv^9@kA4oBWQZuoH zfBNy~k4_7>xMI0zn&ivEde2e^kJ3rPK$x(V%GXqFWD38vuv*DS3oKv2U*Bp_!fo&6 z=7EXPz{kg`PMx^Px{*hyOoGP;-is|5$%`^|XDEDg=esNzY(`f=UiO`PxzK@0z(MuB zK-8~%XffeqYbLCLvI)H!PVJTxlgK)Meb%7%=hvP)?66NEZ=$7r6t*1u>7Ap{k9Rqk z4QZ;Ct)g~VB;&E9!dwevn4#_-`&Jju6DISYwmXx1p=Y74PMm5QnIISEcUf-e4F_!T zG3#P&T%jNRIQ0@|i4TBw+$3A*I{~8e3A80QS_jN3P;&W}58cRiquo2nL$A_qMB#>9 z1Y$kTyOsTk$kuTQ`>| zMtVtHkhGtrI3%o`jsh?9V* z+6aa!a>7S~ABx;pD-*=$vkyU4x!i8O*K?_;s658-V94G?pSxwDCe_CICN6 zs4d6x|L1I)(IowMP=aK1Yc;R%IXo^e>;I_8@z91+B>(%P4@Q1GNyPBYM?2$??!h!$ zjUfA&-AdR><$LNO1p-N?M%DG=-SFQc~)ucaClb5Hxv1BUhLhH)#{kcu;kylMJK((4qj^J($#NuQJ-Y_gJIA*yk)Ob&T36FP?ahZRfBmh4NYCI+0pt5W~ zvCHQCL(8?g(nkpbDa{b0mfXUuaTHbk>;#-TbNTbZ`KjC!6w*!&W8G2wJ;Lu$AQaQeatmxb(yZIa(= zV1#%2ZeloUFNy3L1)euf(ETX-+X*lxf54eVxHWuL^qNdzX#&-&rzZPL9dN#%+Rq_> zwkxoQU#o`#j9jd}TQwO? zcopU_UNa7C0#B!ZH<0?MG|bqDSEF`X_^j>G=1R(-#b&Kcff2P*i~M8v(2KyFK>gNU zoyqolw}w{E?#&O0f^}d0@_bA4pY5^iqL1WPD`|GFHZ}EBE_`6>DEO#|fJKMRopK{{ zZj0+)^(A=+jsYEuWT;)v6%iCih$p=JbO0gMm#dQdRYvicJ!j~pX-z~ zJxtn@883VwPWo0nQZ=RVMO$&uP|TVo1c}A6j;;rGlk1gW0yJ8~O`q8JWaWjGO%b@@3 z5gLR1$iBB(!f#p7F$C6W4^Amjdm!s7voLawjLMxuCM#qN6}RYR)A9h;5Quf;Jw@5L z_zUP6zQ7rC$e+^pWOPZ@)vh!%s5qsylC(JLn-V_0?0yC&p4`jDaD$LRa+KTd_+-nk zB62&bl%aNWp`-%4HjfFK|A#g!<%+U6+_eCWE~9R!eZBR!VM3cEQ0gyntN$f*x(z5c zh=sj6{`y(iI(NI|=D95%WiWik!6fG5LfM@*_*ii6BH*;=wwhy%Zy03h@lekK<>*G` zkDyA_Koafk>?8UaW4&RR?chNFQe&`m`z$S+R-rNHSjJNyG12YglaJ#aFD?!ek zpdUc;mK!QZ+T6QxJ|%C*T@67VBYA({-WQXb7Iy1tSO(V$(G-4toNY;3S613TgocGI z{#KB_KCX9NuYR!-0Ml?RN3qqTq85r2nes~JW`iDk8uJ_*|F-l_mXUv#i*!4Zn z?A-(vv;}+3Z>zGG_#_{tqAV1X4M6LE=@DKi8Kx=wLgXKsgXzX8>f|SUykqsGgovvz z!IqRy?x-{_^jzmV?vIDUoPzY$u!pnkm4Z=qC*}Z{*qCG201Tpg8sgP(bui7dyQ5v2)W0D0x)lNk`3Ov!0)WuKm>`!w@0xi;cjm&f7<&VYpbCigsdZpB^_giau zH|V?$QiI7S@m?g#T?i`-#+?`ao>WXV-l(o2ZWZ{_X#)HTw52B9TKK9Djytxr>(Ael zd~Tj`T_q&;EBI0C3ir_s_^@1NsIJvYSy}$uComG9WeI8X?I%Z_PY?3=J=Zc#LNgVu z_KjIiH9#zeaoFyRAn=c zz?w6!(Fo#ToDXz+=P_tjVwx^FIcX?`b95bRb~ zS1C(d^6f{D?k#sl+(~x(vBM@xA9jcnz!YWLfeZGK4rkb%{LNV#96t3)uO5b#wkL5veE;mJQ9s%3Ra*3Rq zf8Bh%3)eVeGv6P<2018D*^mzn8i3z6OZ$TdH?0_0*R=`T_YD+MRzX*LSr=;QJqB@s z7gb+EsK(f^v#w=vlPU&Y71<>vb9hS~>-yd_pGA?b`=5J6TUKuNZn7=VP8E za>L23R<~RcF}G8#Vj}tTgiK>0#_yZc$ooy1-RGDq+E$wDTanPu?4v`37pgv8(mSiu z6mOIKqd9f?X%n>o&_SNor@U+crX76Gk#FPjQJlR)PH;&T{?c_f$wCn{6|5xiyfpMd zpf{|uH4x6?ZVeE3UL_~Q!8HLk4vBj$fH+p8ya~X&JqHM)R~1f~vR3c}=e@A+Evk}` zZAS0vLIns7cWI?D*BBkW6u29--?(V&|mRM-YAZSQDE*_4|F^&1Q*(iO`R=H94PJUyq=j{Hx@^-K65=G=qN zxIW1ebkyQ*!yFv(`Kna){#odT>-)<31fV+MTCsfg0qL4CDqgjUTGl@v+#Uxh6wM-} z;5LE*2KA$)ry_i~vk{SMo$Dn*SM;v&FVgQQD_HHoOBL9zl?iGWpUa1K8$0gt8}#Hn zwU0AJt6aIwPBRlT2KavWbwZW`4lm!H4>D^+%-k zQB{4?64(XSDclcAfVlcg^W-OM{%5xVqBPrvF7CY_hpcLq*qI2NT?qXap|MmeBWn}c zA5pAk87?ID2xn5w7}CHoODCzx&+;*F60-V5?5V2OU_{=F`9jICvgAFVYIaCoWdf&K z)@fzPWGO;Y?Pr?>O|x$wGg1#AINOOF#l}h3P}%v43g*tD=*$L374j^F;Vm^S0wpBR zwbeG+dYidrh@{(?FD_G)lOqxZF5slf*9nPYrc9^)kdf%GW*hqD_`mx^~ZfN59SSgPFGeBgSq#cP~gnXR~cKf zkHKoB-Pg_C(LBIf=MW5Slk?7f1`fYIY`cQlU-ZX?V4|lE&&oGhuFf=RFc9D4NvItp_|U+ zsCRt1)Q@;o#@0LmU_tC(l6-+#Zl08!cZIUF{#M5$+fr_P|KV@cHQ+YkWZYkw{>4bQ z=evRvR$cK)F6(Jrb0$h;v2h>x0Ce@A(Rm2Rj0c$Tx?LrB>Xx>E?WUE&1+7S+HT%_M#W^?v3r-E%3 zI7L0SujiOz)zoVL2|3!8opl+WVz?vn&7m7XWZh~uj{Na!&y%<;eb2c_9u_(s?PB1n zHs@vVjAWzJW_O&v#01GHdhZ>r3980TB1kwIT%Vpo+D1(v8D^#1Y0gL!A^T_+To>UY zPe?0aAN>iX-{E{7eby$hOoB3D2`p>e%x&m zgCIWB-pXf;rW}QwqULKy2h9f~4nNP!naH{1!m&=V%W$N`1qyYOJO_7NTYny;X~V!@C|0ZXY&eg zwE($2&-qZ^s^-$vL|oRxJJ~ba+P(zfQ5wiT zKrQ-Ku5HfEnduW4xE0~OUn{FR0S-5vF{1?AG`@Z;(u+Hy0h&8SG+yhG$h2h%UH>E6 zr~ui{6tlX%DdWX1{13qsKmjgkCx~jd*ljAoI!AF&Rzt-xhbtSST<8bSBxKh^kF+dH zG<>u6A=&J#}pPS$_W|J=Lsfw-5T!J z4D@KJA}o(>3?sIS^wEY4y&LO?ZNjx|%IO`GxfS}f>Z)^_V|OnmU0e(tbcn~6&!rh+ zi};0A2Ez}CDb|~TCeQf=VH7kaE9Av4b+V_@b@&6^Lt*&^X0u2pBW+HyH@b5h<;!bm*W(>5%C9T@tvalJg_;PVn8di*X!3LLi~YSBQ)SP~>Eest4Ae6+5DS z_8Jp1q-gPk=2y1UE7^Gm8m00oZ3GhxGu&5=cd;T_$&DwbRkF1s@ofcgX^HYml)05M75Q3>U(P5^H&9cYik!tdB)Ec3c zb@kX1P(DFXyL}cz#li9VnkU>Sb&IsLNG)wCs)zX^8>Bv7eB79G)hW`4;w2U`cI#6|>kb9zghX%zOQd zl}~#^TdvGHAQEvD-cyBjJE=gjflw=pT`A(B(5kGEY(N&#;gDH$D}bn66zh|HSgr&W)G|)B(E6Uus0Yt-bFJt%%?pZ z%>`R;_C$Qw4^rz4I1vl>Jk=*^C5?03d_&lA7a;|$A7;A2mb>7Hr zp^UxiwO4cJmR}nG3EXKM?Bys_dy#{(+>Ds-EWH{ya;<);oS!lG)OvF024QF@j{L=G;?%gZ9i=**9 z>X=FO$i8mf8hN9eKg03 zyCTZ0qO%rN{oZFfq|XkYiW2#~h%RN*^sr}eifW1e^EWBQM`H*)S`axeg+PFbI+I-E z>Yq|lCOoE&B}`69+8BVmEI-}Xx2m)2VtTEo-nD{ybb!(U?p*5;{HJ6pQ%>mV6hsO8 z6I-(f^{+&{KZ-K!Mu}pfWd0jcF0X)b)vO1M7@*k7Q%DIe5xBcNiVTcJ6J4Z?O-T~{ zfE>OxC5tBP3*#$YF_-(Dbud4+w_M7URU7*kuF2(?)F__G?Oe9|y*|p_3bqMpr-KC_bH8}>`c=BDZ(F-B!E4#jpGb%tN>dq4p zump%i=WG4?mD%gk#U?do^o3)*bymy0_B0P2gXdyj$be7L4&~e_BHl4FTcTqZ0)C5U z)Yc)({aXU$Mbtl#g|rl^uBnlPm1AK9azn~qXXq2E>!EnvHQYjJAqkC27hHI|I@t!X z>Phh|TNH>0>UNO^2#LPTF;ZV=Y+PSB zVz%5}E;-Zpy}1=(rB1wNvDuEAJ$g86-aSZ;X&>g&skm% z7;bTiKffsRU5&f4y5{Sa$qt|Q`;WW$S&2gRw#=wSXn%3xy&cjqObFYb+X&&CVWa=a z93{1Rj5Q4!o0&5nG!&lbBw8WX=P2Xo;6eUnGq_S2a!J7j^+qJ0fG0Nt$pfipW%mtc zX)tr}UWeS8g*|~cQU#+oR?FZ=gNGugJ?0m(+H^p}{Bw+fs)P);2X_G`10oWBf<9{XgY+u zz9+ddrGEM1=ZyWHH+@ zq*q)6G5e`1E%d(1hibGY4W_2=Wey*xjwCdq{7+QTb?f6_7n&uf( zb+v0KyA^`Xe)12bJo1QnE&bcR@wxHY44v?=D zUB|hW+xax`8r<$e8Xp4ERgx>4O&$3=&-nl;JN4&fxH3JY%U#FX!JTzKG2lSecz)MVpc2sCktnlb%ZFH z{lLCd33{^jXr)Rr)D|h~HiW<(PlReMd-=K8xi!xEAn*`N%>RXe|1RhGIj0?l0VV?| zPq@(tDC~Gyb62L-Z943-|48!uI`=mW{Q48MeOeW-#%j=6ziDc))1-9owL&^gw=L44 z5Em|9Kp%Bi*#&oW|LI3=SK!#qxp&SY$`rb{$C`0xxX)6S3sJLCubNVH$Q&b1as5;T z*etHC%(MtId~JTfTgNb7E}|NLgnr&p+sbj9_{F|BiObOaA(zeT!Zl#4pr`gIa3Nvy zb-dV->v%X9K-K?kr#5VbFVkhZb^3+ckrs|2fEDLGi{P{8h_L5SYJc)Ro6!Th?&TuP z^gFXCezXX6bu!1Oj;qNn3wD+u0QeF5lzUD8)V@vd+q(mx31xq;7M>#ILkKF@8vv&Q z4*!9SoKrK5ZlD|-u9+juLKx{ZD?QtqvVV+Fd|o#W`GA#{;McKOpeacx1I|t#Pp+V! zm7e%EYTxf?nc~taX=rbDen*o_f*9CHE@)Q_uM+OFxy?~PoOeXiBxdtP;%s)K#{_dO z?Bxo2bR2zcJ@3e2F!s8e4-QF_s@3^)jb(gE0UyQNmcTBn)gfI?&E<54nEU!`+)B~7 z>c|O$+=y8A$3miCIDTGM?v}-l$%6q`eFtY>@Y#bz<_}b|bA0LRK;w#g>U<`(I)@`W#TODTrq!o1juPH+`RpRI5De~=q(;C4u(ES8E-M;TzW&3{~r&szGz{IQfw z1IZ1K$>Ap~a;U~e)l3_-PIoJkRXrkP6`KwGekgOl!-5-}K|u1Gzf*Es>sq%ro4Y-_ zT+o@$o0ebTd1`G3AUf0|Z{&VmXOva#T?o7(7I4(~xN)Q4YRuluYWc^b+~p@@2v^%g z4wfOlvA)wUTE3Aj0xPw`#@CXGbdjNF{VByWK=ep%{v+t|)8_JCVPWd|d7%3rNCqTQ z$!UYmX1jMIgFM8Z&RhCGpAfS&B`LYVh3*R7?8^NQ{;u>9=ZXUtuTv8QmKVj zOi-Kl>5l`4a1WmN=c3GG@YI_(l&GC}wCgcT6>G)F+$SsEH~VmNt%c{}KAT$aH6E># z+V7ZcA!RvFbO@`>VAWsARnQqM=g+K0zM28lufX?nlF0NJa1THt3^Q+Xs8jfSujT8J zo%2{EqH(65;r3ttrprDk>PzL-`m3a=;9hxL>a&Ly9`Y8vj-iG>X@QiCJeda}F;4(8 zu7b=f+(DjoFAtS=$S^Xg(f{G^(s!EYQ`mN1cRIBFz1&e)byZ~|3ITY)+o}AFsKZj& zaXC6h{zp-vA7|$}>P3@Amf3xYcrGUYdJH>RjKci+J07CMdUZz+`EREHek4xsd^1{C z=wo3dXAQS9VT{487T}nhK8*SNom9bg?z}Iup*=hAQR*RX6ilWw^K7n)w|SZ?u!R24gkq>0N zX)>0+>>}+8J5sKEcAB=3ia2K;A#99~HDoISj{NP5E@q%0fzi-(Yx=rit78o2+I_EQ zRG4K~RsLM`WgR4lHuR!q_spl!n|!{nyNYs~^aWB=`l0#(x|TaVf>8$|fFUqlc_qaB z<{v0b$q=*s>FV7=@ora^(cRc>VC(zih z_N4kw(3KiGG{h8#;b^X|&R{W-RCky|LPSdJy${5Pi^uju(O5Y z%fjoJwOfHL;NKR1w&e_=D2$2f9tzhcR3*eYHb>mh;x8Vl$2|`#e=tV#H2hwhQbe(- zOVIA75BiO#MKFIl$LPf%At7rXCr$8H5A<3c&}LHQkDlaA3n~W(n%i{;{p5 zkz=LU-Bw3exE$osh<0|~h%bQyHAH?Q+M1v5;<(4CklV@isR| zt>Z`RGqwEr3Pjxr)`f_jc}v5lTD$F>mQX<~yXxMysq6LNzfX6i1F3EjW!#F*@#hf7 zdWAQIJD2}JBu>A6gljZvL{DMoI`r?noqipVNKg-btPSZIdKU4Y3t)I_*RRUi#bX4L zhtl?SC9b^(o5Iyx+HQq<#>#m7fO}Y~4zF)|m@ob2r$!SRm$eoZcg_*LYm)de8Rdbd zvUbKIjw^xPsP~veh}qoxpUCs2>%FIOy^Le?t*9chEs%zueHrvr)nFL?D-b*I0fZ&cDc=!Yuow(p-2@ABs5n^=3uwR`yTGevBd`Z_{#G-{(UTzO=( zM0oElM@e*qKcu~LU&{is*-rqJ``Iqk4ed2+2^-83fL&y#&oq96I6Y+FLmJDN%LFnPU+mT&~wz_>qgjX*7HOx6ugvosh~UON~aE z?L4cid{Xc6*ym$QtUkK^<2jH~vnRmb(RQY0)iO$xu9E$w=wnpz=0|hQvoGvdTUiE} z=Q8>8GPEG7b|^*Wuy*uMM?OC8Q}i2t(X7;>n>=~8qYg-L=+K3u7N}trso(KBtR%J9 z;f|%%dROoWt&dmT6)y-i`ISSy7F6p9CC82F?Jve)PHHAXXeN(ZR(^-w-;wy=_tU6a zE=o|D111Ked3|bqW7u?jWz!l5XE=Uh!yklbAL7)fGRwQGH)~gnwhyNMQ;DPd}sDTdBr$Quhum^ z4A|#XyArteGekX4l;9ycR=CK4FtzFM>GmTQm2lR@{my9b9!(xL^T2cQr^kGq7kh9t zKRFErLLOD7kw=~4X{ftz*q(8bK9oRUaRY-B9U?kY$z06jWYl&z5vTb4D|lrMobe(?=Ppf~@{;RDXN78G0CP_@Lxg;BI7 z)d3S+TQ*3%_m30gz>5lrQC~`W37s*ynkr%T7rQ$zk8(eM! zmg-7(8Mr*3xb_K12%px5m)LAdsd3g92??`k+i?>~l3UhZj;3EEV5U{(7uT|}ZZw%< z;5?P~=TA3&{%A;7?}MX&`C`sPBvEL06Fx+Y;~oSRP!k7ad!*6Ay|=}F0|vfLsARK{ zQ}evdH}5;_{v^g)@Xn0l5B|n~ARCr)ujh(lr<`;$-|PD~twoBshR0lMg5RR9nuIzT z)mZPmwf;_ccr+1m?Z*TWGu)+Qu9%vz3wJZ#pV?#Xj`(oo=Uo?pO8Z zewN!J+1B;vhJ;(K7>-xtKIlOEud@>2iTH9GtH~1Rix#pcoMde@2#8XwxOjy{0S5AE zM90uX5=#ZZbq(4&emxrxonJybsd`iFky77FgPB&6PXhTrWo#F`Ee55&wVlm2DjeiE zo6zngW~8Yw=)yMk88Tcn<|rWlBp`aiPP83|qHK5b4`gvIzQ(>LIxt}(fKI~$wImv0 z_+FH3QB(kvS*%1hBY*{-CVezsOpXoz2>_L@p*`;yiY@q>T2gYpdk*NDEiOsC zb%sbw_?ZR?A}obYlN9%*@vf&4S&r)ofs8&mR^%IW*!&0q8Y_wTjMUhlTBMf0S-_7L z8UcSEo^8ANAap4;q{rMX#>5t9Jif`GlGhSduO|Mff^29}p3342anlbWi@t`*jXgbc zGVQ1P1^sl=WpE2AH;sIGpqXEViUaCnVIi2~B0p)+Kuha@fX3DJtvqog8Wxb_JoM}7nF9_(T`bL;)-Qcr~A;c_(9 zVmlH78(Zf<%mpKu(eVvQj`273^GNV4!}st(huCf9rk-Lw(^Qg4&W!ukg$gQFl48kP zBhv^stzwnRBd79}y4qq19F*^L$FTDr!isUQM!L>NWuPh2%&}=*d1(k^cK%+E8Fa}M zgw}&_3^*dQQHnR)Nf^|VN+oxwS>+EB(SvvySH;UX>-iPpjl)rjCZ}EGM=XhB< z$r8*Xn)$WgcZYjs2yK0P@^RcKZM zndisuimRw9AGYN-9op6=hvpJbIkLfrUoL(3M~4v(V+wJ)yPaQ;d=o|YVi3c|80j-I z?4uQ(_f;rc-D-Z=*yM+S@JZ&gUACqk7bONoFVfG292b7)E+_hx-Mv-F zI8g6XAAuQDCL(hCOp*8HxPhNVGp4QG8M@UPIAZ3u=6Byyf*0=BkEKv7HqNa5$N05k zmfo69XR_+Ntj`2vofuzr@=xzyP@Hy0FlMp=gC&+wPt;4}Yk%%9V*@=nN4MTdJn3`C zS#YR7jb%M8CIM--hHlO5)3X3O18DsdVZ^O>nQRq?z7syh_Fj3dRCFRNis;LvVZdJe z2a^rn%lZ#J-ePTvl@<5wyU*_X*qz5dO}y^;qDP&`-iT7xmzsbKIP9mR@grrzy>I`* zC%AiBbFTMx0y{!>{M9I*YXRUHP^?L793d#H3(v}xMNWhvC_Hi-u;D?HFhT9$AJE zU*0iCbWpbZ5zOkKd?ZFos90R6*fb+17JAwBmw{I)rmu!x7E{2OBvRRtOGy;g zX2s9;>$#C%ej<8xl#;`A24G1_-#&TF7=$EBSl_0jRtMh3dO26pTFLirLU6!G{DqiT zd>}3o#btqr-WD*ifR1e{vFos0okp=u|Eei^y|>|Ml`a(nK0b;IN4z_wCs?YSNWH~5 zIFtt$AsrptRF6ARBJbT`{>Y-WqRWVs5fbiY3qBtENK9Z?Mab&i+6LLrk|CayQBso_ zNBKgB&%J4F=YeQdTZp4t3WY8SMv#Mzn^Wx2?cxR{Lq`H>$0MmmkB4Y}Hh1glLWyAo z=10lkcD&B$xFSwGb&!P-5O?Y*;Ekl+g+8LwrN|S{O<32j z%cCYU+VBuT5)wo~9}a>W_#!~}7T`!M3(ng+P2g+bwjjyRJw1E9YYcr91_h@GP)L0b z&1NCozjP}Z#rNl*m|*N$9fF&(Ws?VKUB)h_pw^b(q;-+;p2<s^hQ&BZWN<~YA_`NG zY^uFXWbu-VYmbSE(-6z)_(>R=uPXX-l7)RHJkY0Xh40%Pm;ZR0&)+_MW4mveL#AY# zQs2ml%_K)8u}sh%$W8(sWBbkNbN%_s^>j>)9%pcTR1+|8JMk<&sF5pKo$O()n6p~r z<-LkqUXG^^kWJ;6Lz=0~ALHoFVMomd{2^u$*U~|cStW8;3>1#+8gTHF{CaneI{#~C zgH15fMtv7NAn!+nZ>;qjSs4 zgDmxuILffTIDfD()sHC?cFCr=U!@ZxLRsLSMoy2>H#`4Zh5qpT457i_u}D^eNbJpoC~vB|DOMB4wk?`(7i zJ6W`Sh*(RWN-FL{Q)s;06xw(xBtyPDSo|e26p7Ie!W7h-1Uq1qlH6atjvGGfItep~ zi<6dt#sPHuuUEZUP@;D+X;X?`!@`sc77S`k%qPg9H{(ZYXc#>WFY#2WP1Y>dc+S7B zc(?fEkJqfzYE_zBHVfM&4||;8r%u zx9ww1{9df@78$UblG=W6U7!}tHX7*AluT719`6`}A2 zd)B_e$`~j*3`1)SQ(8hYn&@GS70|hMB=MF-&`{mCPt_%av1x3l4ckNF_o72F(V=4! z5wU%`exifOxJMZwrul>xjIJq(MyGXJ$R?sfDQMsnahLnKO;e&pwpqA3!fRe|48zc0 z!v-TgFDzbFlst@*=Fr=5uA&!{L{?*crCW&!GiAd|(XnaCRaC6W*`o<`-P z;Ehr)Gf#?3q+F485L#|{Lu3FTaM3=yKr0zxVI>Sd;bn4nC=Tog#^qW;&$~_btF%I|%DZ)N^=`S9N zeGS~%f$mz^5;NV>j%qUV;*GWbj=50@BG65?L;%HERP9K zTTZI<7sPH}B>M$n$c7#!TpHK9*RIZ2A+LZi^ zFbT!ff^@%_4eSD$LbWwUPIY$9ZY1@bC1VyYJkj85JP3e5^bBGiaO#5li~dm%{IZoT z-o$>8FeZVgf@8@eK;}9lcYHYl`rQtY#F}5H2rw7R3k0Ah-{p^w(Nqi;)BasAyF$Og zY@f~Q>Ed0B8tHrA=C49iZE%ojaI1IoqA()}hWu@{p0v_mSKCY+FDrBsJBZ#I3n|Gt z-;(A&*ZSyX63jFTMx;KzVh!c;o2-|#JBk~+%XE|Fb&xxaN?_AXHPZfITDzZ5TO|LX zzpytQ6oK|Pt%IfYUY(~W{HB51;b?NVOSd1?H`eASVYrQUhS!O0oK2vMQN5iCzV34BDMqxQ7z^SmPk}Gi0a-itnZKq!vSG5 zsS*3eWJ)B9It^QoMJ|G=+zZ!wUh|J~eESu4KiAaHpVV?TXC7c>aDR#>a9|p-0v$uGYc!kxEs2H!iPI&sx>uv$3@??Nay(*`z&Z?b zPEQiEGmwO+t3rL~X-96ssc(`P6CC%}SDm1+E-C9Huwkq%AGPVp0y7?PtZ!J)qTSv9OtFc)IS2!M@IW7YNYfl=KcT;kP)n!*{y? z{-AKQjM(%lC5KrjZzicH}2sE*Cwqf|{y3Fv)R9+WaS z9f}84%8-uPgH$t#S`7}cu5?&+bkz{>5K^gRp*KYP{(;y;of4v}ro?*U-Z)I5=@dXB z3wEGNeCSUm@PB_(I7lL|Ajh&C_ZQugT}zHC_`te5&R`YRPzX`{)-J(^Ha(l*=UGcb z)SUE=Sp#C*7E)vc+HbR=0WOBPNc-&h!wD1HSd{&Q6ImX&ryY$7C_wbmWJgNUsQp?~~@p}MFGFm9Z5+T+WN;&6?0I$L3m?Ed4k@LD`ugG46{3k{# zxn6Ow(F-;2w-#smvrrBF^ko)?b%DmgP^CXObrv?YXFF4oqfZJgLDO4Q`fWD@_-KDJ zM~{>VzB%6eSi(ks1$WfSnz*X+VK3Mws_6_(c*~VwA;3(-;f(NiiqXAA5P&m1zn$s@ zIG>&ds~!qH!0b)SAPZ0S3CZos{!OsP(L}>xO8Jy^UVvLISYJ%R$Ic5~J5~Xmrz>*C zkog;nYGagX{^L;$20O>y{NH75weoe&*6dr9^O&mt{Ms%a{~4L-=Cuo1Z!^93-z1d~ z%O{A$6Kk5+h&>kmS4i;*)zI8qzCR=1Rmq@E4?*Re(&-Sa2d1VI+cxh%(8xL#wyw5B z`g{EBD~&se*1Z1*dcx3;B4bioUgxn`gDzc}#c zqaJ|t7v3mlO=l*EO0-fmSEo52f1ZjB1^W<8qgYJV z9p=zJx3dkOWXJr+Z5V-%?_al}{Tu~HJoTkjQs%za{w?B^sFgl@nLUhmd6+199z53r zGL2zT_tP#9%UH=aDWxpSuhA5-X7Q>DGf#zpl{>|yUAXSLv-wmK-cx%Bbw2UG1RI?p z6+X~l3n}c38;duB!q9kRob9~Yskxh;D4rqoOh-;e7{2jSw7LbvhbB-Vlt%nHy!(sO z1LHhGxk#OXumK*0Wb@yV*9Hv)Lwl|bhiHihsbVFoupbF9feG~>;}-}~jd=MKCHxL5T4l&MS6){uo|QDvsxJ(YJ{ zsLOc`=lZCB4RAt!iJtP=f=0eV4l?Om)7E?Abh0{OX;czQWU-%9--{Une%stYA57Si-^qv*X%HUfcaxJ)kZ<{JZv4N$!N?}78 zovVFg`&O2fE8(lfy%1T7)kNdHv=yCeS8H|EhxXFW{cHCGfO(T&xYshQEkWTJ6vB}X zhCPO;9Q2ZL%MCq)@PJL8;k6Pfg~4dMx0(K8PNSbD(w5$8neh*_-}sr4^e?rj{rq@C zEP?ouP7HLyLBKs-CNFbsWhv9+^lW?qj)K2q`apL;QDsyIo;}SaQUfw{OXnh)_IMwwL-g$F(Y34Q(ece|ye zyy1fWvq-8Xfs57lW5y(tL|_U3Hoo|~P*SWc)loU0*2+`FV_NU@n47ERyq||tVCvDz zZwVrBx?d|)Vl+!CAgrFwp4%9JC7`C3z3{8g(8)d`8 zXpzOO^|u*!-Z@?(Xwn!%9cJDlwb_xZ{JS_bp67tClup3U9L)91Zf()ooM*RNx2@LU6PS=M(& zI67DBmfzz}MK*$N^|Wr6`|R2}eRgmSc4Xv&GL``h@~Oe*CEeVNd6g|A>o#gT>rpWg zm1*Swu@@h|j@(lB#1p3S3aB6Zg{^ntu-bHd)~WPT3FO&g6ZKcWxJ{xEwXpI018t}C z1gUjv!VE#CI$Gi{c2}upeKWPOST8yM_~-iOy;NwDfpLoH`{TT?!C%qBVDdltrtHqY zY%atwtztvlEVnE4`wOMMGuC(Yd8pPkzi5poCA)9j(1DDqn~lvDo~;Qy-$-bIcyw`c zZ>rct+t+FVwcmQHYC>q}#{AlsIPfHm; zJx#jpAhB1j867C@DA=MF))DBHd6?CMSiNw1=&l~K=pCNGka4;d7m#hg%zQj=(zJ9N z@SEICHH9c_le52b*eRxl00r&aN#fbSib|{zOH5jZ{aT6S$kc1Uyo%Ds_`R)@WXF_R z?y`=@@li&X)mNJU@S}__0Z9}46r1yAtx{f*a?5(Jc6Np$f*=a|aETR6T(X^5ZILdj z=b$9tswv~gNuDgu=B?-NMa6x1J7T-?CD4%mgim7!(0>~xAqlTw@We9Yd;0n)>nI~_ zF^57Fc^c}wa%-=~IxO*hpiKL98gNIs`2t1w|jcGak@U21a^%&ZE?IqC(1Ktm)8+3SSRtuzWS`A z6NiF|YP2z^bW64^L6iI~Z8YMd`3myd&+;usd)5Qe!Rp-4!Jbws#$hNdow6j?3e4oI z(JFA4$~xzmRRj`WtyP5}Q_u?ILPMxbvp;7|yoA(MTjU0}h4i$RlgP97)mHuVG0N=C zYL*~euJ{QHxf*jg;1xOl#G|=s^u<#p&M`PEr-cBUJ-h8-nbwX(Y*{a{S$L&)-&(F= zn?)TfG6Jz8Loi6Lz#pYeqNK%0&ZRXeo)0CXo{HHMdMY(ELO13+qVINwwI?m7${cbr zDZW@;2_CIg-d4HhB!^LC7y%^uZ>YUD#RSNGoEoxiw6E$Ls|=r(Hg%s5av`HXzg`w> zT@KqXdtuajQk ztjEz|fTveu3Rq{uOcrn2>w$>Uj1-Gg=9V7G8ELlNJg57?RA4hB?TAjcPtvE%=hx>z zJcl1xZ?9?pKp31uB+=}vS_4us{;irF3p7w9n_L9oYN5{$Z>mjqv^pKAWeGnBjomU1 zODpeweOC1itl?&l6442HrL>RFA56oLif8=}qCAg|#;)JL(Ca0zxqmP0n^(u0FhV+R z(|$GWF$fkc)H=4FR%{I0)Jtdct$+pA=E7thS5LP*r^ANiv<^q!!}LVFjL8WzWPgqb zpWrCUsSbBp<)GDU>}oLKrBt}h+S4Hxh&y0j@DJp8hy8RRVCPaC^H9Zp)=Nr*mulLw?8Uz-DAqr zE-rz8KQ~VkM(T=%swqRI7IZ)==$jDS z$dgG-Qxf2|K)*WGl&zgfo*kiGf0nzxJt_*#u+fY-wld?L<-+WBk-2%B9n-Vlks#^O z`W?Eb?*epKN;HjxBD`kG3h^PgL8E)spGPv_=}s=IXA4 zJO-_eH}N5m+Pac6G5PVr9i$nfl=e-h>XgGRH(ilJc628G=@{vyTpjhyV?QtKtKsU4%vG&B$=1z*5lf726^RGx7=LyMREz zu;n7f=lK&WOy6;UcHJE`Fveai^g1<3hk&Q&ej0<1T~FAJ!r3TUe!x$$EjVhM><$=Z z3C=U;brn>TU_$qQ0S4Y-j$V_7V6_KuS*VRzwpa!F72r(XdOf!ojHs&(#03+uMQ+k^}hl zk?oBmmNURciG7`HDTw8ufc!KOZF*0ak(;dud)i2e)r=u_RL5k9WXn5(MG|>AOg>(_ zS$&h$$jFJ52(1szrg8n@&>$9J<)bVVW(|fkh_vt4^u=*+$tS!pmMp;-UP!w)kN`nx zPBMh_w??lPi_-G;k*`4^gsE7SwOm4hMcaEh)WYMTt6*P4hs`PL5;CILX~~nhAC(Si z)M%}#3FmbD2k-Xxp}eEz_T>N?2cy4x3x5BT!B5od*<10WY4^X67Gyfhu(h{uNsd;c z2R^i7%WkJY3g*(M^CEDC))s~VawmQ{boIL}YuTEbd>rHT8x4k)FJGa@{Tng4ycvse zE;CjhK_r6(I;3ne<}Y_DUWJF~J)}0d{O58DuR_2SNSYadV8A1y%Ok7&oqU2hxTpjC z8Vl0NMu^BaOu%e=*2{_IFL(^G?MfJX2duR}( zyOah2=|;M{l$7r7mImqW?(Xh}p<{rdBm`-ZE(xE_@Bh9ZIQTkqu(|hjuWPMyi8M0% z<6NuIE@da3RYQ@emLFWXhfbD%2ZruUe1hoqk+)0imyQX<-yrzCg=78txPEhC3y};% z={rDfh2?>_m0vT4u`^a^N)c|2R2ch{}w0&#zDlJh7jtB>UG%5GcYJ1YF6@|s>y zXc5M;k)zBte9c#Bpa)z>)*wUyOl2a9yIyNS0ilYI2Fi->2aMR2EtO(ua`JARTyU0t z@6-Ow*w#UQEM_X~8jzvt4^3?x@?%`u!OG`aqWeBBp-*kIlxINl#hzx`cFm=7UV(Ow zj#o5$WgWLC^AO4Xs(6cA({W-@_qTkDXO!#l;isspv1?%X4)}p7A_ACSal_~@dpTyq ze4%Jbp}R_->J-9-{Lf&8DjfxiN16j8HdlUqIg}hcqoR|UHde=kRuu4q7g_tm1eK$b1#$sVd=TCE7&TXRIV+vSz35**h)GsNTSy9PsOXG0*33%4gc- ze1?P@?qU<`&28Cz_(IE#B=WIz*;3;Z%qM2#!*1fWF8&C@A&~EW$H{oN&jc;C0`~lPdb$o2DOX7>`^v(y94N)uN@^zC z{j7~>I^U_1Rl-;M#dw`>WJ=D^iOa^&A3GFT-aHov<<%)f>^&Zoyx;JvU4(-D|a zWqQ~n_?g-3=wiGnY@BS>2NS#DBmv9>FR$5Jz*zI;g_G-fCk^ceeCGDLls@?NZZBVf znB;OTjL#hrU|j0)B1~QG7T*Bt%Rf^7l)#3y zYjrvnPyJvHU`-vP{NFHvbxhv3J(8M?gOwa(;e#gNU+zuf8Lp^1j%aRc-Jn7M!#Z`* za=qZQ2*irj|A8dgG~MODWvh-WikGSA9;2o^!-f#ejG$;^1wyP+Bp14!Rfnp$-V^~{N=vnp{hJzj$I4I)Zc3efX-4cB$P{9 z6rE?=H173B@`NN88%?c8l>;>+JLpom;YgIr6;s-2(s~CmZ%~5MRL^bjdd4>E$nHl@ zb@;VzomrjO^g1HYAwHW;17(EOLh53sA7lG{gvw?9flx0`JyqCDd8U_En6C3O+Nxop^D#LkL9cby)X278&?y|uNl{G8h})aIj#gmJo4>4 zVu9R7AIpRMS{&`@fs(*im^hY$2mA@{w7bitWF*l$&MM{yf(uc2`+=jRffD`B$ARus zqT(BISg$+`P%; zLZ%WxVit}@O*|JkPReOig~vYe9xOvsCZivWssCjAB>2Wn!<;L_n`N3Ivc0bLM?0Y< zqQI$O$70A#Y#s6afsOlsa0w(vIV9K$YszzRM7A4i1>D;pVT`^{)=u_D6*T~fo{YN? zYcZdvJ-3&0GKV5t2_^T(p~;;tXC5&2<6+Mh{BI1W^@K)+GjZA9|%`(#LWMy)zQ zzb?$qv0uK(w zofX77&Er(Ew@B@&06?29fb*s$dQ_K`*Dtbzb#~WVJZZw)3Q;L`YClEWnk;vkA^90n z)^TtQ&=>kHG&x^eet;B+3frHJt;pA#{j>CCQD2iJ-@6(AosKHX+eiN&{M={DVks{F z)o&Qrmk9nOl}WqgSmYm!9G|_j0hITnk7Dy)9p4qmGME*Kj8adF61?VD8D8}sgSV{A zE7%gW3dE-`MLeD#sRBqu^?zy}Dohmx=N$MhW~QZuHgz=gh~<6~$?6}0vl(f%s^imx zbq z(vcXgm5#x25qQ4m`l5epEhbyv(>x|$@-TwqtR0?c;?2Lk>7BUA5*{K%ni0TybONmiwx$=!?*z zV{kaDZO}y1pkp4*RKiYetm)6p&(o%Lb3pi$>n|`#W9Oz1o)4wT0BY9kIUHS*S_u;- z>MsY0KIEuwOSpG_M}h;eSCGx@BbAQ))q4~S;-Z2bD^_WTnou-N=Iu`LajQFnpKVw~waNN#)XBI=%au}$Zm9uz>QE%jtk$=Sc*RD4ScL(_XJ zo@l4ji76C|9)VMW=zE#Q4{}zDRfH{;85}a9McB=OjbYqE_e436;dGBvnb!?AMzxUN z`qnv2lIvIb8Vz`rqEbyAvTc^1p5*^D9a9V1i7^3XcZ_M^+k^}fU`a+!mZ#Aw-f?41 z%p!-RemzQ8@*0>C|DI*}`t~ge^suDPvJ?f^`~&G^k>)TTSEmaugc&&-;`Q*b&07LLV zuz$2)`O;2qwKm|CwM+4Z9{tG=&4XmKnuw9rB-Vs@cQ7<{fU#!*y_+LJLU9=HVKWTW zBg=Y*_~SN`(@`XwjP7PWUgb}Oeg(hPW;2dcGVfQg8-(G=6NQ_dgSmGJ^|#aK!M_p* zHWKP=f4;y)ue5bSvOJ1FNBPj%XFcqlE(Zu8$*yC2J0=k{@ z@Lexd(v@lY`T%_Mx*5y-QA8x-`}3D)pD+6Lzg@2E`}V>^?C>v@4@YE^w`^{y)=MPA zuORM!TORRW<)q#h^ zyMkSQIw=0v&*vH#&tUN{04zcSU=95L)*)D)#`c6cPWx53p zU$8m8IdtKtF%0aqp9Vb{Z(M+a5;3>!}ZDO}`A&%xKlrp})xMdDs|1ov}d3QlO4X>wesm3R6 zjgHWyuHAfwbk><(pP!G-^yBwfT10~I0>>t@2eDM3jHAbWUt<{{xDtUBDyh?TDFU_i zh7c6E%M*%!#Izp0h(_Ign}VQ=*Lws#t-*nQ3+<XfHWq7BCmyCL zlxOvi?#g~V7%1?)=#e(U;GW)hsQw3% zZ(svJ+7;TL*}GAz412C#s1j+4Fp%@jFPXn9V0s9xx8 zh|-fu>Db@&!quigAuXYZg((vrVh^ImLSx5(M<@tdL>T&HajoG&q$R;YDPlw|UOr;= zImW|O9)A2I9S&{~4_wEk_@IL$$Z88sf5^}p{}~O$vyM?r(WCznt2^)7mDpJKfDHi zO!s{AdXF+OkM&=iB!CP70}&p07)4H&&JjcO1qO|yhlZv&>zZ=iMhsgD>NDZ$zNaoI zPA?$;!1adY)adT9g=T=jhltN2&)|GTq0@FxM0O$4(he7(OjhDn&s(HAU0DaQntbHf zVOBClQFj`JOQk0^rltD!etXjff-}r7;L<@7Ius6CBg#B$@cw#kyWTQ>favHbQeY>4 zq$xmsQ*+2VOmN3NJ|Ag{cw{!Ur;#&a1a+EWlJbH$X2G5_r4)`wE#+^#O~#<$UUK%i zN}{~7OS!4(d%-ZfJt6P44AgYa{VVeNmlOHGB8rzj>|ZxtCIK zvDNdxr3S!e17I5^5fTy-Mz2okS3|A*P{WM~Am{%BxdXwzo99w5vV|o$&m?uV4DGL5 zeHTK4vr)b(xYd&#r9(F&NEnry{B}LkcN`q!NhOtI)_UolsBPJ!$xmOxu4YQ8e0TeU zJau?=sAmxa(|2)Ja|%=9SHYW`o+>45`)3j+x8`ZM=0-V9(N+YzVzS5$a2t~A+5BpR zvOGgu{QK6bgbkr>&gVXxs!hZ^ODtulpvL{SAk>lYk_-WAq?ouzu4qCp4^%(M(g)ey z!kNP?*vCa1W0175_D+{t3;uCip38NZOQhSjG&r)Avk4>(;xDvFk(6wO^%>rQT9Orr zR6CoJIO#7r#Lh{cdSaY-hv0HXnnci_*tS^cUUKv!wOPN_J5%fj6!+-=xFY+c$YbN8nW|}H^JSfnna``( zHo?Px5A;5vIq&vAH}{v01{a|%h%`H`S%;ATosVqE8_Y}hWlXh3{<60?X_emmy?lQE z@%@vyR!n`f?@8%V5guCe|Fg@gncjW+z9EdH-0Lb0H)6gDL4CQIQ^y@O0m@c;*ak+-p9a zwJ6W<8EeJYQ&UrMydC(Upm5~=Z3m;1we@e8%PTQhlXj-54|nM~d{B4N(GsWp!y)1f zjG6o=!9%riP7_eLw-wNNHaA{|0a@XVO!?6Gx4`?tJXyV(#9 zGTMZaRMDj*A3yE=OC{{wT=-280%sFP`L=xxszW(^ zJ(_;X-vY3&Lxnv*X{b0wXp0OagarF2aP1+-|0#Jnl4qXRl7Q`G)uL?9pdo=4QDmGw zwiu#rxKi><*^qg#N331Q7_G(YiXE)Ex>y}@CyI%Efdp5hu9#Ds;b3=6G1uM|nUrCt zpIT%;kAA)DA?ojvbv+Uz33F2%OEqTph`Hcm*)V};LL=OG=mwGU7o4k_Mjp})^Z2d;_ z*u4iC>|p$Xot}rvw8MvAHL%0)OA^QSZiOoMQo6Gvj;G8PN_jp>GxbMO{&FR~YY*JD z+bXOWBYIX;T+#EH5}hfDmalXlnOknFC84=$8^UbTKVZ0%*lPp29VDCmZRu5w0y~o! zifLyovahS7t)f>|enhtrhSNgZNU$Dj7$}fW{+Jh`lGu{j>J(4XK6<{s1LI}Q-O28V zf%=hupnf)zbD)45k&tI=1TIB!?-gqI8n|C(i93}w?TS0?Wv-F-a1NYY{{5HX_4Y>g zD3p@XQ)Zvn4dLLvz1>=Z#cX@ZdlYshL~=;Y4CJVWxm4nCwV7lSAyk-5;h1Sn!otIn$m6D@_S9^KahK-dEFCML?>7flk%E8pXQ0jrql@;uv*5&ZJHBunp)vcs zLPlT?5u%UY>pDGpvWX#}vkyNOiunTHDf^=|&=5D~kKtvHBznI%G2)D_TFRT_a`*SK zDF}pSl6mwd;cFZ!1dOamqB}2J;_vHSK6_5aHWPwYkRvj{3KrUB8v<5?EdD?-#n(7t zp~@lFb?w(B^k@_-unnpaWibhQxs#p}#9oS#N+&tVO+Jh+b=|W;ZzhTE7 zxQyK-Ss&v%WVo=`mG>)^Yk&95U>hGf zEgIp3c62byEcPcRn^LAF9H3FfCVi7$>ELmGiys>q5qA2AYys?cp`O7*#gNfJu>3M_ z-n2*%L&Ps2$p}XhmRu*$qReEZj1bTY7i5zj%E%KQLwVb|{O-|Q*KCIc&rCyq!AFoK z&P8TI07F#0&?SpRkTFK0UQRuAhJmw=kjZChH`~l>lT%iwM{|OKTN&a(o)fMi z->#rL$OuRik3TvEK;QFndD>Vo!z zjTb4F8IK#aQD(2f#w6z>-yvK~gxhMj#vn<1UFjy*8PjfsN&Y(FLb2rO462^iL6iJ0 zPw#IVp4%XdPpkrKusj%S1i#P&R+U7CB~PI9cay|m-gEwmC%3StGfnK(?+IUGu0*c? zf%xxM*9ufLbG@V`?jsiv$3Vq91XjUcnI)oONPnQ~9>h@Q9JE|v+K_Uno`awk7wWq| zFf`O0n(zZ;|JRz{>oikJu zX1a=fB*zemK`nf9N(0TNs$j)2dRw^1{heHVwB4>Z?vNZM%+iBLF>9wSaTl&iQ`NAb z^PNItuTT}x{ZrP~Axg=;?y^bMBx2O2%6*X67LjkD|F@8g@(p!udjcGa!%Qb7bEaY7wSe&ZvxgShVCE#KqgBDQWO#T>S|j& zFCz%S!hQab5?d;h(&hWj9*28b+us2|_7hoLkUH|GaP~3TMxUR9+SD}AbXFz=SWlvi z9qrG7G#{LBgB%4X%>ps^k4LKpn1y|P^OSo8ZraSAkA0rY9476vWp~6OlDlO;=FQ2g z&eT>SRRRT4(kx!w@|kb{zRsyyx3OtHCd4D{5N+NG5$9&o!$N|jk~i|q20wc`{#VSE zhAARpwtj1caPw2Hx&`1GBhEMPq%fpFYwUu}EYm%3dNtfo*axmPao8?)hlA9OMR_a<W@g1mk3j^@LJ2gc_Ix(6W0qP_||I`69Hx4yZ*+?JQ4qRuY42yj-<(t8Pa zX%sND&O=8kY8w9Xo>ea@6hA+5F}2u}US42vmF~hyt+o9mb>cD#$QsR*=fHwwxfV(X z@oewY<36VkUh2sY---T2x!5xIA*w1k$xj23q2nrHeT=&5V##A&n@q2j8cYsKxh)`-DgDhH5H01mv0Q0bc7>2sNXi&p}wNOgUVKyMAMa ztld+iIkYLX7U8JqeRGWBK0nGuMgS7iWbq-}=mD8cfJtG8!{!g#4Awi`Q>u}(!BhpW zNO!3zqH^t36W=zWr!YH-#v;-%mpWq>+f(7L`KpplzMdrjs`T|A0%+d>rXqv?N-kfy@&KU# z&~jcWn_ns8P`b+~&~AzpuExGgEZyOt^xdbGUu>M^H0%tV>L(6vy5R??5W zcf2c%X}2=PxGB;<$B(&p*>=l>W%Bkv>y$(|x07BF|NIOoK&u%( zu~l_@IN_h+`2Z|KzBNyo8@s+310|_?o7W0H+oMzwxttI)OX3TQM@*fh#MfT2iflwE zbFUKl2Q@8u1*Cw8*0u9K6Z;v@0tIh+iZGAj#2!Py(((n3$(>+(hISFs(^@|*cAS8xZEyTbNDBmoB}SYRjdVDdpkr1ejqUc>8`eevr% zPSk4Rn^|8i=@87*2pP*;UN=t!2lh?PWaJxq8Q93^7|qX@nok{Ghc^3-klw#|=A+?N z_ds2GXc&3`?AFIDC&&}kf7uBU8EkY=PT7gluzXISsEW z4@`vcyPE=P!nLOgO=cI*7pM&h^C|7^neNFMnfv-R1h!JkQpv7PyV!!n8qD$_CXqhn zcv7v)zrP4e$sEP6I!d^3_k3V&ysSjR*=lCM?e?mS{wJ|%FfZ71ur1uj3a6Bw*qy?p|x9$(9ITcqgxgUsr=ovG{@p0*gwkP}%+2%E!;Y%;kR(c3LJq(e8XejqVVQidW|eVXA7MY{8@2P_-@e$A?&>L+*gv<} zoJXAu6c$tOr4jF5mEf(F)ckbpJDKJz7}8(e`1GeJ6n-4XFmfHO>+>}aOf6J4D%aYs z3oVAZp?8S0-jn2e2uU+-NH0}aQ*f1s{B-4gsV7{$>_E=?v}_MTw+^%$uaIqqbkwwU zPVwVV%BPx}Gu;v*7M@)?;)bFwCMPk{o7}_GO_fjaE^Pvz$HNSt-s;Xp$j5k^HO*G5 zxV;@{qo<73Q5#d*nMDdM%vE>98shfdiiv>zEwV0ej$UKU+`e&ZQSW3ax*g!t*@GK} z(z4#e_-pZ&V3%syBjgi-ODI_{EF`9TLMWdtUuLoW=eXBV%g z)g^&50vyjf;L`hD;<|=+W#Hu9qkemJI!vo9jS)Y6PPhK9wgA14M4-6kZ*#aRGOD2pBD_HexalfK$wT29jU@~T4(1eND1?RSq%x*Bc zw(1$CHo-(URKa3|Ad_|E^FC3&OZSdV3JniazKsA}}JS+PE zxGDQC8RG8F{pS?`Y;#rU9w3}K@jY=OGM(B$GRBYWpJhtD1D;+p$z2}HfuUfW?OG0fU?OR1I z=VF8EAt^FWZISBOm))c!>&vKf3LC$6*UL7rQTGT>=+3ZV<_RAlQJ5w# z&nRt{yg-JV4!VBKgR7RXE+d%C3`;95W)@#YL8YyU$-@|ldN0S&Q)yO zCjgmR7IW>2hY53?XHVH^)8i{$ZhL_Un5Jd_j+5)+)tb9tmreY9ePFGSS?3Rh7Pa8~ z14%zJ_DM|5#=0W(ZfQ^?^xhtb?B%ZR`<|CL3%k7nbze6_9v%!tCwYHgvomVUE25`Z zJ24Z(0}QD_d`>Ev@hh|%lSNGT^O$XunS}cTEk{3_=Em}v`7~gJ18gYWh%<3OeKrW1 z@`Yu<+UoUt2BVRj?WExVMT5-Yzr9P+i2jz0i4)gm>+JAt-8xL%0Aho5!Y708Vg*gB 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