Compare commits
23 Commits
release@2.
...
python@4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
03aef8fd27
|
|||
|
31931b234f
|
|||
|
a7987eb161
|
|||
|
1b5d7dbb20
|
|||
|
a2ed371e2c
|
|||
|
dbb5a6f7b3
|
|||
|
5e3d3c7c7d
|
|||
|
b45e143c84
|
|||
|
d2b974e1b8
|
|||
|
75d31aeda5
|
|||
|
a83f7c8b57
|
|||
|
b29699b763
|
|||
|
1f1b03c6a8
|
|||
|
62be4c9127
|
|||
|
e7800b29c9
|
|||
|
9c5b9e331b
|
|||
|
05f3855b99
|
|||
|
6d35f01303
|
|||
|
049f44e697
|
|||
|
a52e82933f
|
|||
|
5a65de21d9
|
|||
|
3ce9cbc84f
|
|||
|
38620d7e71
|
2
.gitattributes
vendored
@@ -9,4 +9,4 @@
|
|||||||
|
|
||||||
# Binary files should be left untouched
|
# Binary files should be left untouched
|
||||||
*.jar binary
|
*.jar binary
|
||||||
|
*.gz binary
|
||||||
|
|||||||
52
.github/RELEASING-PYTHON.md
vendored
Normal file
@@ -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`. The default manual mode validates
|
||||||
|
artifacts without publishing. 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 <artifact> --repo leogalambos/Radixor
|
||||||
|
```
|
||||||
6
.github/act/python-models-standard.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"inputs": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"publish_existing_to_pypi": false
|
||||||
|
}
|
||||||
|
}
|
||||||
6
.github/act/python-native.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"inputs": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"publish_existing_to_pypi": false
|
||||||
|
}
|
||||||
|
}
|
||||||
4
.github/workflows/benchmarks.yml
vendored
@@ -10,6 +10,8 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'src/main/**'
|
- 'src/main/**'
|
||||||
- 'src/jmh/**'
|
- 'src/jmh/**'
|
||||||
|
- 'models/**'
|
||||||
|
- 'build-logic/**'
|
||||||
- 'build.gradle'
|
- 'build.gradle'
|
||||||
- 'gradle.properties'
|
- 'gradle.properties'
|
||||||
- 'gradle.lockfile'
|
- 'gradle.lockfile'
|
||||||
@@ -56,7 +58,7 @@ jobs:
|
|||||||
test -f gradle/verification-metadata.xml
|
test -f gradle/verification-metadata.xml
|
||||||
|
|
||||||
- name: Run JMH benchmarks
|
- name: Run JMH benchmarks
|
||||||
run: ./gradlew clean jmh --no-daemon
|
run: ./gradlew clean jmh -Pjmh.includes='.*EnglishStemmerComparisonBenchmark.*' --no-daemon
|
||||||
|
|
||||||
- name: Upload JMH reports
|
- name: Upload JMH reports
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
26
.github/workflows/build.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
|||||||
test -f gradle/verification-metadata.xml
|
test -f gradle/verification-metadata.xml
|
||||||
|
|
||||||
- name: Execute build, tests, PMD, coverage, Javadoc, distribution packaging, and SBOM generation
|
- name: Execute build, tests, PMD, coverage, Javadoc, distribution packaging, and SBOM generation
|
||||||
run: ./gradlew --no-daemon clean ciRelease distZip pmdMain javadoc jacocoCiReleaseReport cyclonedxBom
|
run: ./gradlew --no-daemon clean ciRelease distZip pmdMain javadoc jacocoCiReleaseReport :cyclonedxDirectBom
|
||||||
|
|
||||||
- name: Upload SBOM
|
- name: Upload SBOM
|
||||||
if: always()
|
if: always()
|
||||||
@@ -156,11 +156,14 @@ jobs:
|
|||||||
test -f gradle.properties
|
test -f gradle.properties
|
||||||
test -f gradle/verification-metadata.xml
|
test -f gradle/verification-metadata.xml
|
||||||
|
|
||||||
|
- name: Validate exact core release tag
|
||||||
|
run: ./tools/parse-model-release-tag.sh "${GITHUB_REF_NAME}" .
|
||||||
|
|
||||||
- name: Build release inputs, signed Maven bundle, and SBOM
|
- name: Build release inputs, signed Maven bundle, and SBOM
|
||||||
env:
|
env:
|
||||||
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
|
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
|
||||||
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
||||||
run: ./gradlew --no-daemon clean ciRelease distZip pmdMain javadoc jacocoCiReleaseReport cyclonedxBom centralBundle
|
run: ./gradlew --no-daemon clean ciRelease distZip pmdMain javadoc jacocoCiReleaseReport :cyclonedxDirectBom centralBundle
|
||||||
|
|
||||||
- name: Generate release changelog
|
- name: Generate release changelog
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -177,24 +180,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
CENTRAL_BEARER_TOKEN: ${{ secrets.CENTRAL_BEARER_TOKEN }}
|
CENTRAL_BEARER_TOKEN: ${{ secrets.CENTRAL_BEARER_TOKEN }}
|
||||||
run: |
|
run: ./tools/publish-central-bundle.sh "$(ls build/central-bundle/*.zip)" "org.egothor:radixor:${GITHUB_REF_NAME#release@}"
|
||||||
set -euo pipefail
|
|
||||||
echo "::add-mask::$CENTRAL_BEARER_TOKEN"
|
|
||||||
|
|
||||||
BUNDLE="$(ls build/central-bundle/*.zip)"
|
|
||||||
HEADER_FILE="$(mktemp)"
|
|
||||||
trap 'rm -f "$HEADER_FILE"' EXIT
|
|
||||||
printf 'Authorization: Bearer %s\n' "$CENTRAL_BEARER_TOKEN" > "$HEADER_FILE"
|
|
||||||
|
|
||||||
curl \
|
|
||||||
--fail \
|
|
||||||
--silent \
|
|
||||||
--show-error \
|
|
||||||
--request POST \
|
|
||||||
--header @"$HEADER_FILE" \
|
|
||||||
--form "bundle=@${BUNDLE}" \
|
|
||||||
--form "name=org.egothor:radixor:${GITHUB_REF_NAME#release@}" \
|
|
||||||
"https://central.sonatype.com/api/v1/publisher/upload?publishingType=AUTOMATIC"
|
|
||||||
|
|
||||||
- name: Publish GitHub release assets
|
- name: Publish GitHub release assets
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
|
|||||||
37
.github/workflows/catalog-release.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Model Catalog Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'models-catalog@*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: model-catalog-${{ github.ref_name }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
catalog:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: maven-central
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: gradle/actions/wrapper-validation@v4
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: '21'
|
||||||
|
- uses: gradle/actions/setup-gradle@v4
|
||||||
|
- name: Validate catalog tag
|
||||||
|
run: ./tools/parse-model-release-tag.sh "${GITHUB_REF_NAME}" .
|
||||||
|
- name: Build only signed catalog metadata
|
||||||
|
env:
|
||||||
|
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
|
||||||
|
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
||||||
|
run: ./gradlew --no-daemon verifyModelCatalogReleaseCandidate
|
||||||
|
- name: Publish only catalog metadata
|
||||||
|
env:
|
||||||
|
CENTRAL_BEARER_TOKEN: ${{ secrets.CENTRAL_BEARER_TOKEN }}
|
||||||
|
run: ./tools/publish-central-bundle.sh "build/model-catalog-release-candidate/radixor-models-catalog-${GITHUB_REF_NAME#models-catalog@}-central-bundle.zip" "org.egothor:radixor-models-catalog:${GITHUB_REF_NAME#models-catalog@}"
|
||||||
147
.github/workflows/model-release.yml
vendored
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
name: Model Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'model/*@*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: Model tag to validate without publishing
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: model-release-${{ github.event_name == 'push' && github.ref_name || inputs.tag }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
name: Validate selected model
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
model_id: ${{ steps.release.outputs.MODEL_ID }}
|
||||||
|
model_version: ${{ steps.release.outputs.MODEL_VERSION }}
|
||||||
|
gradle_project: ${{ steps.release.outputs.GRADLE_PROJECT }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Validate Gradle wrapper
|
||||||
|
uses: gradle/actions/wrapper-validation@v4
|
||||||
|
|
||||||
|
- name: Set up Temurin JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: '21'
|
||||||
|
|
||||||
|
- name: Set up Gradle caching and instrumentation
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
|
- name: Verify reproducibility inputs
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
test -f gradle.lockfile
|
||||||
|
test -f gradle.properties
|
||||||
|
test -f gradle/verification-metadata.xml
|
||||||
|
|
||||||
|
- name: Validate and select exactly one model
|
||||||
|
id: release
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
REQUESTED_TAG: ${{ inputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
|
||||||
|
tag="${GITHUB_REF_NAME}"
|
||||||
|
else
|
||||||
|
tag="${REQUESTED_TAG}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
./tools/parse-model-release-tag.sh "${tag}" . >> "${GITHUB_OUTPUT}"
|
||||||
|
git merge-base --is-ancestor "${GITHUB_SHA}" origin/main
|
||||||
|
|
||||||
|
- name: Validate one model
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
project="${{ steps.release.outputs.GRADLE_PROJECT }}"
|
||||||
|
version="${{ steps.release.outputs.MODEL_VERSION }}"
|
||||||
|
|
||||||
|
./gradlew --no-daemon "${project}:clean"
|
||||||
|
./gradlew --no-daemon "${project}:check"
|
||||||
|
./gradlew --no-daemon \
|
||||||
|
"${project}:validateModelRelease" \
|
||||||
|
-PmodelReleaseVersion="${version}"
|
||||||
|
|
||||||
|
publish:
|
||||||
|
name: Publish selected model
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
needs: validate
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: maven-central
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Validate Gradle wrapper
|
||||||
|
uses: gradle/actions/wrapper-validation@v4
|
||||||
|
|
||||||
|
- name: Set up Temurin JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: '21'
|
||||||
|
|
||||||
|
- name: Set up Gradle caching and instrumentation
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
|
- name: Verify reproducibility inputs
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
test -f gradle.lockfile
|
||||||
|
test -f gradle.properties
|
||||||
|
test -f gradle/verification-metadata.xml
|
||||||
|
|
||||||
|
- name: Build signed model release candidate
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
|
||||||
|
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
project="${{ needs.validate.outputs.gradle_project }}"
|
||||||
|
version="${{ needs.validate.outputs.model_version }}"
|
||||||
|
|
||||||
|
./gradlew --no-daemon \
|
||||||
|
"${project}:packageModelReleaseCandidate" \
|
||||||
|
-PmodelReleaseVersion="${version}"
|
||||||
|
|
||||||
|
- name: Publish one model
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
CENTRAL_BEARER_TOKEN: ${{ secrets.CENTRAL_BEARER_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
model_id="${{ needs.validate.outputs.model_id }}"
|
||||||
|
version="${{ needs.validate.outputs.model_version }}"
|
||||||
|
|
||||||
|
./tools/publish-central-bundle.sh \
|
||||||
|
"models/${model_id}/build/model-release-candidate/central-bundle.zip" \
|
||||||
|
"org.egothor:radixor-model-${model_id}:${version}"
|
||||||
141
.github/workflows/pages.yml
vendored
@@ -10,6 +10,8 @@ on:
|
|||||||
- 'src/main/**'
|
- 'src/main/**'
|
||||||
- 'src/test/**'
|
- 'src/test/**'
|
||||||
- 'src/jmh/**'
|
- 'src/jmh/**'
|
||||||
|
- 'models/**'
|
||||||
|
- 'build-logic/**'
|
||||||
- 'build.gradle'
|
- 'build.gradle'
|
||||||
- 'gradle.properties'
|
- 'gradle.properties'
|
||||||
- 'gradle.lockfile'
|
- 'gradle.lockfile'
|
||||||
@@ -24,42 +26,43 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: read
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: pages-${{ github.ref }}
|
group: github-python-pages
|
||||||
cancel-in-progress: true
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-pages:
|
build-pages:
|
||||||
name: Publish static reports
|
name: Build static reports
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out source repository
|
- name: Check out source repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Validate Gradle wrapper
|
- name: Validate Gradle wrapper
|
||||||
uses: gradle/actions/wrapper-validation@v4
|
uses: gradle/actions/wrapper-validation@0b6dd653ba04f4f93bf581ec31e66cbd7dcb644d # v4
|
||||||
|
|
||||||
- name: Set up Temurin JDK 21
|
- name: Set up Temurin JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: '21'
|
java-version: '21'
|
||||||
|
|
||||||
- name: Set up Gradle caching and instrumentation
|
- name: Set up Gradle caching and instrumentation
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@0b6dd653ba04f4f93bf581ec31e66cbd7dcb644d # v4
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.14.6'
|
||||||
|
|
||||||
- name: Install MkDocs Material
|
- 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
|
- name: Verify reproducibility inputs
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -70,7 +73,7 @@ jobs:
|
|||||||
test -f gradle/verification-metadata.xml
|
test -f gradle/verification-metadata.xml
|
||||||
|
|
||||||
- name: Build reports for publication
|
- name: Build reports for publication
|
||||||
run: ./gradlew --no-daemon clean ciRelease pmdMain javadoc jacocoCiReleaseReport pitest jmh cyclonedxBom
|
run: ./gradlew --no-daemon clean ciRelease pmdMain javadoc jacocoCiReleaseReport pitest jmh -Pjmh.includes='.*EnglishStemmerComparisonBenchmark.*' :cyclonedxDirectBom
|
||||||
|
|
||||||
- name: Prepare gh-pages worktree
|
- name: Prepare gh-pages worktree
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -88,6 +91,9 @@ jobs:
|
|||||||
cd ..
|
cd ..
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Prepare staged MkDocs source
|
||||||
|
run: ./gradlew --no-daemon prepareMkDocsSource verifyModelCatalogDocumentation
|
||||||
|
|
||||||
- name: Stage published reports
|
- name: Stage published reports
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -191,11 +197,8 @@ jobs:
|
|||||||
COVERAGE_BADGE_LATEST_LINK='<li><a href="./builds/latest/metrics/coverage-badge.json">Coverage Badge Metadata</a></li>'
|
COVERAGE_BADGE_LATEST_LINK='<li><a href="./builds/latest/metrics/coverage-badge.json">Coverage Badge Metadata</a></li>'
|
||||||
MUTATION_BADGE_LINK='<li><a href="./metrics/pitest-badge.json">Mutation Badge Metadata</a></li>'
|
MUTATION_BADGE_LINK='<li><a href="./metrics/pitest-badge.json">Mutation Badge Metadata</a></li>'
|
||||||
MUTATION_BADGE_LATEST_LINK='<li><a href="./builds/latest/metrics/pitest-badge.json">Mutation Badge Metadata</a></li>'
|
MUTATION_BADGE_LATEST_LINK='<li><a href="./builds/latest/metrics/pitest-badge.json">Mutation Badge Metadata</a></li>'
|
||||||
JMH_BADGE_LINK='<li><a href="./metrics/jmh-badge.json">Benchmark Badge Metadata</a></li>'
|
|
||||||
JMH_BADGE_LATEST_LINK='<li><a href="./builds/latest/metrics/jmh-badge.json">Benchmark Badge Metadata</a></li>'
|
|
||||||
COVERAGE_BADGE_REPORT_MD='- [Coverage badge metadata](https://leogalambos.github.io/Radixor/builds/latest/metrics/coverage-badge.json)'
|
COVERAGE_BADGE_REPORT_MD='- [Coverage badge metadata](https://leogalambos.github.io/Radixor/builds/latest/metrics/coverage-badge.json)'
|
||||||
MUTATION_BADGE_REPORT_MD='- [Mutation badge metadata](https://leogalambos.github.io/Radixor/builds/latest/metrics/pitest-badge.json)'
|
MUTATION_BADGE_REPORT_MD='- [Mutation badge metadata](https://leogalambos.github.io/Radixor/builds/latest/metrics/pitest-badge.json)'
|
||||||
JMH_BADGE_REPORT_MD='- [Benchmark badge metadata](https://leogalambos.github.io/Radixor/builds/latest/metrics/jmh-badge.json)'
|
|
||||||
|
|
||||||
if [ ! -f "${RUN_METRICS_DIR}/coverage-badge.json" ]; then
|
if [ ! -f "${RUN_METRICS_DIR}/coverage-badge.json" ]; then
|
||||||
COVERAGE_BADGE_LINK='<li>Coverage Badge Metadata: not available</li>'
|
COVERAGE_BADGE_LINK='<li>Coverage Badge Metadata: not available</li>'
|
||||||
@@ -207,10 +210,6 @@ jobs:
|
|||||||
MUTATION_BADGE_REPORT_MD='- Mutation badge metadata: not currently available'
|
MUTATION_BADGE_REPORT_MD='- Mutation badge metadata: not currently available'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "${RUN_METRICS_DIR}/jmh-badge.json" ]; then
|
|
||||||
JMH_BADGE_REPORT_MD='- Benchmark badge metadata: not currently available'
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat > "${RUN_DIR}/index.html" <<EOF
|
cat > "${RUN_DIR}/index.html" <<EOF
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@@ -239,7 +238,6 @@ jobs:
|
|||||||
${SBOM_XML_LINK:-<li>SBOM (XML): not available</li>}
|
${SBOM_XML_LINK:-<li>SBOM (XML): not available</li>}
|
||||||
${COVERAGE_BADGE_LINK}
|
${COVERAGE_BADGE_LINK}
|
||||||
${MUTATION_BADGE_LINK}
|
${MUTATION_BADGE_LINK}
|
||||||
${JMH_BADGE_LINK}
|
|
||||||
<li><a href="./pitest/">Mutation Testing Report</a></li>
|
<li><a href="./pitest/">Mutation Testing Report</a></li>
|
||||||
$(
|
$(
|
||||||
[ "${HAS_JMH}" = "true" ] && { echo "${JMH_TXT_LINK:-<li>Benchmark Results (TXT): not available</li>}"; echo "${JMH_CSV_LINK:-<li>Benchmark Results (CSV): not available</li>}"; } \
|
[ "${HAS_JMH}" = "true" ] && { echo "${JMH_TXT_LINK:-<li>Benchmark Results (TXT): not available</li>}"; echo "${JMH_CSV_LINK:-<li>Benchmark Results (CSV): not available</li>}"; } \
|
||||||
@@ -254,7 +252,7 @@ jobs:
|
|||||||
|
|
||||||
cp "${RUN_DIR}/index.html" "${LATEST_DIR}/index.html"
|
cp "${RUN_DIR}/index.html" "${LATEST_DIR}/index.html"
|
||||||
|
|
||||||
cat > docs/reports.md <<EOF
|
cat > build/mkdocs-source/reports.md <<EOF
|
||||||
# CI Reports
|
# CI Reports
|
||||||
|
|
||||||
Radixor publishes durable CI artifacts to GitHub Pages on every qualifying run of \`.github/workflows/pages.yml\`.
|
Radixor publishes durable CI artifacts to GitHub Pages on every qualifying run of \`.github/workflows/pages.yml\`.
|
||||||
@@ -271,24 +269,38 @@ jobs:
|
|||||||
${SBOM_JSON_REPORT_MD}
|
${SBOM_JSON_REPORT_MD}
|
||||||
${SBOM_XML_REPORT_MD}
|
${SBOM_XML_REPORT_MD}
|
||||||
|
|
||||||
## Benchmark and badge metadata
|
## Benchmark reports and badge metadata
|
||||||
|
|
||||||
${JMH_TXT_REPORT_MD}
|
${JMH_TXT_REPORT_MD}
|
||||||
${JMH_CSV_REPORT_MD}
|
${JMH_CSV_REPORT_MD}
|
||||||
${COVERAGE_BADGE_REPORT_MD}
|
${COVERAGE_BADGE_REPORT_MD}
|
||||||
${MUTATION_BADGE_REPORT_MD}
|
${MUTATION_BADGE_REPORT_MD}
|
||||||
${JMH_BADGE_REPORT_MD}
|
|
||||||
|
|
||||||
## Historical runs
|
## Historical runs
|
||||||
|
|
||||||
- [Browse historical build reports](https://leogalambos.github.io/Radixor/builds/)
|
- [Browse historical build reports](https://leogalambos.github.io/Radixor/builds/)
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# Retain only the 10 most recent numbered builds to stay within
|
||||||
|
# GitHub Pages capacity limits. The "latest" alias is kept separately.
|
||||||
|
mapfile -t EXPIRED_BUILDS < <(
|
||||||
|
find "${SITE_DIR}/builds" -mindepth 1 -maxdepth 1 -type d -printf '%P\n' \
|
||||||
|
| grep -E '^[0-9]+$' \
|
||||||
|
| sort -r -n \
|
||||||
|
| tail -n +11
|
||||||
|
)
|
||||||
|
|
||||||
|
for build in "${EXPIRED_BUILDS[@]}"; do
|
||||||
|
rm -rf "${SITE_DIR}/builds/${build}"
|
||||||
|
done
|
||||||
|
|
||||||
{
|
{
|
||||||
echo "# Historical Build Reports"
|
echo "# Historical Build Reports"
|
||||||
echo
|
echo
|
||||||
echo "The following build report sets are currently published on GitHub Pages."
|
echo "The following build report sets are currently published on GitHub Pages."
|
||||||
echo
|
echo
|
||||||
|
echo "To stay within GitHub Pages capacity limits, only the 10 most recent build report sets are retained."
|
||||||
|
echo
|
||||||
echo "| Build | Published | Link |"
|
echo "| Build | Published | Link |"
|
||||||
echo "|---:|---|---|"
|
echo "|---:|---|---|"
|
||||||
|
|
||||||
@@ -308,26 +320,91 @@ jobs:
|
|||||||
| while IFS=$'\t' read -r _ts build published; do
|
| while IFS=$'\t' read -r _ts build published; do
|
||||||
echo "| ${build} | ${published} | [Open](../builds/${build}/) |"
|
echo "| ${build} | ${published} | [Open](../builds/${build}/) |"
|
||||||
done
|
done
|
||||||
} > docs/builds.md
|
} > build/mkdocs-source/builds.md
|
||||||
|
|
||||||
- name: Build documentation site (MkDocs Material)
|
- name: Build documentation site (MkDocs Material)
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
mkdocs build --strict --site-dir .mkdocs-site
|
mkdocs build --strict --config-file build/mkdocs/mkdocs.yml
|
||||||
rsync -a --delete --exclude '.git' --exclude '.git/' --exclude 'builds/' .mkdocs-site/ .gh-pages/
|
rsync -a --delete \
|
||||||
|
--exclude '.git' --exclude '.git/' \
|
||||||
|
--exclude 'builds/' --exclude 'python/simple/' \
|
||||||
|
build/mkdocs-site/ .gh-pages/
|
||||||
mkdir -p .gh-pages/builds
|
mkdir -p .gh-pages/builds
|
||||||
cp .mkdocs-site/builds/index.html .gh-pages/builds/index.html
|
cp build/mkdocs-site/builds/index.html .gh-pages/builds/index.html
|
||||||
cat > .gh-pages/.nojekyll <<EOF
|
cat > .gh-pages/.nojekyll <<EOF
|
||||||
EOF
|
EOF
|
||||||
rm -rf .mkdocs-site
|
|
||||||
|
|
||||||
- name: Commit and push gh-pages
|
- name: Prepare read-only publication candidate
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd .gh-pages
|
rm -rf .gh-pages/python/simple
|
||||||
|
rm -f .gh-pages/.git
|
||||||
|
if find .gh-pages -type l -print -quit | grep -q .; then
|
||||||
|
echo 'Publication candidate contains a symbolic link.' >&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/simple
|
||||||
|
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/simple' --exclude 'python/simple/' \
|
||||||
|
candidate/ pages/
|
||||||
|
cd pages
|
||||||
git config user.name "github-actions[bot]"
|
git config user.name "github-actions[bot]"
|
||||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
@@ -338,4 +415,4 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
git commit -m "Publish reports for run ${GITHUB_RUN_NUMBER}"
|
git commit -m "Publish reports for run ${GITHUB_RUN_NUMBER}"
|
||||||
git push origin gh-pages
|
git push origin HEAD:gh-pages
|
||||||
|
|||||||
337
.github/workflows/python-models-standard-release.yml
vendored
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
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
|
||||||
|
publish_existing_to_pypi:
|
||||||
|
description: Publish the existing GitHub Release to PyPI
|
||||||
|
required: true
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: github-python-pages
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build and verify standard models
|
||||||
|
if: github.event_name == 'push' || inputs.publish_existing_to_pypi != true
|
||||||
|
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.10'
|
||||||
|
|
||||||
|
- 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}" \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" >/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 \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" \
|
||||||
|
--verify-tag --draft --title "radixor-models-standard ${RELEASE_VERSION}" \
|
||||||
|
--notes "Precompiled standard Radixor model distribution ${RELEASE_VERSION}."
|
||||||
|
gh release edit "${RELEASE_TAG}" \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" --draft=false
|
||||||
|
|
||||||
|
publish-pypi:
|
||||||
|
name: Publish standard models to PyPI
|
||||||
|
if: >-
|
||||||
|
always() &&
|
||||||
|
((github.event_name == 'push' && needs.publish.result == 'success') ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && inputs.publish_existing_to_pypi))
|
||||||
|
needs: [build, publish]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: python-pypi
|
||||||
|
permissions:
|
||||||
|
attestations: read
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Select PyPI release source
|
||||||
|
id: release
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILT_VERSION: ${{ needs.build.outputs.version }}
|
||||||
|
BUILT_TAG: ${{ needs.build.outputs.tag }}
|
||||||
|
REQUESTED_VERSION: ${{ inputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [[ "${GITHUB_EVENT_NAME}" == 'push' ]]; then
|
||||||
|
version="${BUILT_VERSION}"
|
||||||
|
tag="${BUILT_TAG}"
|
||||||
|
else
|
||||||
|
version="${REQUESTED_VERSION}"
|
||||||
|
tag="python-models-standard@${version}"
|
||||||
|
fi
|
||||||
|
[[ "${version}" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]
|
||||||
|
printf 'version=%s\ntag=%s\n' "${version}" "${tag}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
- name: Download current-run release candidate
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||||
|
with:
|
||||||
|
name: python-models-standard-release-${{ steps.release.outputs.version }}
|
||||||
|
path: release
|
||||||
|
|
||||||
|
- name: Download existing GitHub Release
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
RELEASE_TAG: ${{ steps.release.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir release
|
||||||
|
gh release download "${RELEASE_TAG}" \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" --dir release \
|
||||||
|
--pattern '*.whl' --pattern '*.tar.gz' --pattern SHA256SUMS
|
||||||
|
|
||||||
|
- name: Verify and stage PyPI distributions
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd release
|
||||||
|
sha256sum --check SHA256SUMS
|
||||||
|
awk '{print $2}' SHA256SUMS | LC_ALL=C sort > expected-files
|
||||||
|
[[ "$(wc -l < expected-files)" -eq 2 ]]
|
||||||
|
if grep -Ev '^[A-Za-z0-9_.+-]+(\.whl|\.tar\.gz)$' expected-files; then
|
||||||
|
echo 'SHA256SUMS contains an invalid distribution filename.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
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
|
||||||
|
mkdir ../pypi
|
||||||
|
while IFS= read -r artifact; do
|
||||||
|
cp -- "${artifact}" ../pypi/
|
||||||
|
done < expected-files
|
||||||
|
|
||||||
|
- name: Verify GitHub build provenance
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
RELEASE_TAG: ${{ steps.release.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
for artifact in pypi/*; do
|
||||||
|
gh attestation verify "${artifact}" \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" \
|
||||||
|
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/python-models-standard-release.yml" \
|
||||||
|
--source-ref "refs/tags/${RELEASE_TAG}"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Publish distributions with PyPI Trusted Publishing
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
packages-dir: pypi/
|
||||||
|
|
||||||
|
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
|
||||||
561
.github/workflows/python-release.yml
vendored
Normal file
@@ -0,0 +1,561 @@
|
|||||||
|
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
|
||||||
|
publish_existing_to_pypi:
|
||||||
|
description: Publish the existing GitHub Release to PyPI
|
||||||
|
required: true
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: github-python-pages
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare:
|
||||||
|
name: Prepare versioned sources and sdist
|
||||||
|
if: github.event_name == 'push' || inputs.publish_existing_to_pypi != true
|
||||||
|
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.10'
|
||||||
|
|
||||||
|
- 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.10'
|
||||||
|
|
||||||
|
- 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.10'
|
||||||
|
|
||||||
|
- 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.10'
|
||||||
|
|
||||||
|
- 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' \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" --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}" \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" >/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 \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" \
|
||||||
|
--verify-tag --draft --title "radixor ${RELEASE_VERSION}" \
|
||||||
|
--notes "Native Rust/Python Radixor distribution ${RELEASE_VERSION}."
|
||||||
|
gh release edit "${RELEASE_TAG}" \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" --draft=false
|
||||||
|
|
||||||
|
publish-pypi:
|
||||||
|
name: Publish native distributions to PyPI
|
||||||
|
if: >-
|
||||||
|
always() &&
|
||||||
|
((github.event_name == 'push' && needs.publish.result == 'success') ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && inputs.publish_existing_to_pypi))
|
||||||
|
needs: [prepare, assemble, publish]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: python-pypi
|
||||||
|
permissions:
|
||||||
|
attestations: read
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Select PyPI release source
|
||||||
|
id: release
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILT_VERSION: ${{ needs.prepare.outputs.version }}
|
||||||
|
BUILT_TAG: ${{ needs.prepare.outputs.tag }}
|
||||||
|
REQUESTED_VERSION: ${{ inputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [[ "${GITHUB_EVENT_NAME}" == 'push' ]]; then
|
||||||
|
version="${BUILT_VERSION}"
|
||||||
|
tag="${BUILT_TAG}"
|
||||||
|
else
|
||||||
|
version="${REQUESTED_VERSION}"
|
||||||
|
tag="python@${version}"
|
||||||
|
fi
|
||||||
|
[[ "${version}" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]
|
||||||
|
printf 'version=%s\ntag=%s\n' "${version}" "${tag}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
- name: Download current-run release candidate
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||||
|
with:
|
||||||
|
name: python-native-release-${{ steps.release.outputs.version }}
|
||||||
|
path: release
|
||||||
|
|
||||||
|
- name: Download existing GitHub Release
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
RELEASE_TAG: ${{ steps.release.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir release
|
||||||
|
gh release download "${RELEASE_TAG}" \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" --dir release \
|
||||||
|
--pattern '*.whl' --pattern '*.tar.gz' --pattern SHA256SUMS
|
||||||
|
|
||||||
|
- name: Verify required model dependency on PyPI
|
||||||
|
run: >-
|
||||||
|
curl --fail --silent --show-error
|
||||||
|
https://pypi.org/pypi/radixor-models-standard/1.0.0/json
|
||||||
|
> /dev/null
|
||||||
|
|
||||||
|
- name: Verify and stage PyPI distributions
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd release
|
||||||
|
sha256sum --check SHA256SUMS
|
||||||
|
awk '{print $2}' SHA256SUMS | LC_ALL=C sort > expected-files
|
||||||
|
[[ "$(wc -l < expected-files)" -eq 5 ]]
|
||||||
|
if grep -Ev '^[A-Za-z0-9_.+-]+(\.whl|\.tar\.gz)$' expected-files; then
|
||||||
|
echo 'SHA256SUMS contains an invalid distribution filename.' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
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
|
||||||
|
mkdir ../pypi
|
||||||
|
while IFS= read -r artifact; do
|
||||||
|
cp -- "${artifact}" ../pypi/
|
||||||
|
done < expected-files
|
||||||
|
|
||||||
|
- name: Verify GitHub build provenance
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
RELEASE_TAG: ${{ steps.release.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
for artifact in pypi/*; do
|
||||||
|
gh attestation verify "${artifact}" \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" \
|
||||||
|
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/python-release.yml" \
|
||||||
|
--source-ref "refs/tags/${RELEASE_TAG}"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Publish distributions with PyPI Trusted Publishing
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
packages-dir: pypi/
|
||||||
|
|
||||||
|
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
|
||||||
32
.gitignore
vendored
@@ -37,6 +37,7 @@ local.properties
|
|||||||
.settings/
|
.settings/
|
||||||
.loadpath
|
.loadpath
|
||||||
.recommenders
|
.recommenders
|
||||||
|
.classpath
|
||||||
|
|
||||||
# External tool builders
|
# External tool builders
|
||||||
.externalToolBuilders/
|
.externalToolBuilders/
|
||||||
@@ -87,6 +88,9 @@ local.properties
|
|||||||
# Typically, this file would be tracked if it contains build/dependency configurations:
|
# Typically, this file would be tracked if it contains build/dependency configurations:
|
||||||
#.project
|
#.project
|
||||||
|
|
||||||
|
# Eclipse and Buildship create project descriptors during import.
|
||||||
|
**/.project
|
||||||
|
|
||||||
# PMD plugin conf
|
# PMD plugin conf
|
||||||
.pmd
|
.pmd
|
||||||
|
|
||||||
@@ -94,19 +98,35 @@ local.properties
|
|||||||
.jqwik-database
|
.jqwik-database
|
||||||
|
|
||||||
##---------------------------------------------------------------------------------------- Gradle
|
##---------------------------------------------------------------------------------------- Gradle
|
||||||
.gradle
|
.gradle/
|
||||||
**/build/
|
**/build/
|
||||||
!src/**/build/
|
|
||||||
|
# MkDocs generated site
|
||||||
|
/site/
|
||||||
|
|
||||||
# Ignore Gradle GUI config
|
# Ignore Gradle GUI config
|
||||||
gradle-app.setting
|
gradle-app.setting
|
||||||
|
|
||||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
# Avoid ignoring the Gradle Wrapper JAR
|
||||||
!gradle-wrapper.jar
|
!gradle-wrapper.jar
|
||||||
|
|
||||||
# Cache of project
|
# Gradle task-name cache
|
||||||
.gradletasknamecache
|
.gradletasknamecache
|
||||||
|
|
||||||
|
##---------------------------------------------------------------------------------------- Python tooling
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.pytest_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
htmlcov/
|
||||||
|
|
||||||
# Ignore Gradle build output directory
|
# Workspace-local Rust/Python build and test scratch directories.
|
||||||
build
|
/.cargo-target/
|
||||||
|
/pytest-of-*/
|
||||||
|
|
||||||
|
# tempfile.NamedTemporaryFile-style dictionary scratch files must not survive
|
||||||
|
# as repository candidates when a process is interrupted.
|
||||||
|
/tmp*.gz
|
||||||
|
|||||||
22
.project
@@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<projectDescription>
|
|
||||||
<name>Radixor</name>
|
|
||||||
<comment></comment>
|
|
||||||
<projects/>
|
|
||||||
<natures>
|
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
|
||||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
|
||||||
</natures>
|
|
||||||
<buildSpec>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
|
||||||
<arguments/>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
|
||||||
<arguments/>
|
|
||||||
</buildCommand>
|
|
||||||
</buildSpec>
|
|
||||||
<linkedResources/>
|
|
||||||
<filteredResources/>
|
|
||||||
</projectDescription>
|
|
||||||
181
README.md
@@ -1,17 +1,19 @@
|
|||||||
<img src="docs/assets/images/banner.jpg" width="100%" alt="Radixor banner" />
|
<p align="center">
|
||||||
|
<img src="docs/assets/images/radixor-logo.png" width="160" alt="Radixor logo" />
|
||||||
|
</p>
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](#)
|
[](#)
|
||||||
|
[](docs/python/fast-track.md)
|
||||||
[](https://central.sonatype.com/artifact/org.egothor/radixor)
|
[](https://central.sonatype.com/artifact/org.egothor/radixor)
|
||||||
[](https://leogalambos.github.io/Radixor/builds/latest/)
|
[](https://leogalambos.github.io/Radixor/builds/latest/)
|
||||||
[](https://github.com/leogalambos/Radixor/actions/workflows/build.yml)
|
[](https://github.com/leogalambos/Radixor/actions/workflows/build.yml)
|
||||||
[](https://leogalambos.github.io/Radixor/builds/latest/coverage/)
|
[](https://leogalambos.github.io/Radixor/builds/latest/coverage/)
|
||||||
[](https://leogalambos.github.io/Radixor/builds/latest/pitest/)
|
[](https://leogalambos.github.io/Radixor/builds/latest/pitest/)
|
||||||
[](https://leogalambos.github.io/Radixor/builds/latest/jmh/jmh-results.txt)
|
|
||||||
|
|
||||||
*Fast, deterministic, multi-language stemming for Java, built around compact patch-command tries and measured at roughly 4× to 6× the throughput of the Snowball Porter stemmer family on the current English benchmark workload.*
|
*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:
|
It is particularly well suited to systems that need stemming which is:
|
||||||
|
|
||||||
@@ -23,6 +25,68 @@ 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.
|
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.
|
||||||
|
|
||||||
|
## Choose a runtime
|
||||||
|
|
||||||
|
For Python, one installation provides the native runtime and the separate
|
||||||
|
standard package of 20 precompiled models:
|
||||||
|
|
||||||
|
From PyPI:
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.egothor:radixor:<radixor-version>'
|
||||||
|
runtimeOnly 'org.egothor:radixor-model-pl-pl-unimorph:1.0.0'
|
||||||
|
// Or: runtimeOnly 'org.egothor:radixor-models-standard:<catalog-version>'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
final FrequencyTrie<CompiledPatchCommand> polish =
|
||||||
|
StemmerPatchTrieLoader.loadCompiled(
|
||||||
|
StemmerPatchTrieLoader.Language.PL_PL,
|
||||||
|
true,
|
||||||
|
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
|
||||||
|
```
|
||||||
|
|
||||||
|
`Language.PL_PL` selects the documented default `pl-pl-unimorph`. The optional `pl-pl-polimorf` model requires its own runtime artifact and explicit selection; adding it does not change the default. See [Model Selection and Loading](docs/model-selection-and-loading.md) for complete executable examples and [Stemmer Models](docs/stemmer-models.md) for artifact concepts.
|
||||||
|
|
||||||
|
`radixor-models-standard` is a POM-only runtime aggregate: it brings the 20 default model JARs transitively but publishes no empty aggregate JAR. `radixor-models-bom` is the separate POM-only Maven dependency BOM for version management; importing it alone adds no model. The root CycloneDX SBOM report is unrelated to that dependency BOM.
|
||||||
|
|
||||||
|
```java
|
||||||
|
final FrequencyTrie<CompiledPatchCommand> polimorf =
|
||||||
|
StemmerPatchTrieLoader.loadCompiled(
|
||||||
|
"pl-pl-polimorf",
|
||||||
|
true,
|
||||||
|
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
|
||||||
|
```
|
||||||
|
|
||||||
|
Complete PoliMorf construction is supported but unusually memory-intensive: the dedicated verification task uses a 6 GiB maximum heap. Applications should load and retain the resulting immutable trie during startup rather than rebuilding it per request.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Why Radixor](#why-radixor)
|
- [Why Radixor](#why-radixor)
|
||||||
@@ -50,18 +114,33 @@ Radixor is especially attractive when you want something more adaptable than sim
|
|||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
|
|
||||||
Radixor includes a JMH benchmark suite for both its own algorithmic core and a side-by-side English comparison against the Snowball Porter stemmer family.
|
Radixor performance is best read together with stemming quality. The English dictionary coverage benchmark builds contracted compiled patch tries from deterministic slices of the `US_UK` dictionary and then measures both exact-root agreement and changed-token runtime.
|
||||||
|
|
||||||
On the current English comparison workload, Radixor with bundled `US_UK` reaches approximately **31 to 32 million tokens per second**. Snowball original Porter reaches approximately **8 million tokens per second**, and Snowball English (Porter2) approximately **5 to 5.5 million tokens per second**.
|
| 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% | 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 |
|
||||||
|
|
||||||
That places Radixor at approximately:
|
Column meanings:
|
||||||
|
|
||||||
- **4× the throughput of Snowball original Porter**
|
- `Used rows` is the requested deterministic percentage of English dictionary rows used to build the stemmer.
|
||||||
- **6× the throughput of Snowball English (Porter2)**
|
- `Actual row ratio` is the selected row count divided by the full parsed dictionary row count.
|
||||||
|
- `All exact` is exact agreement over every word/root pair in the full dictionary.
|
||||||
|
- `Changed exact` is exact agreement only where the word differs from its root.
|
||||||
|
- `Root preserved` is the share of already-root forms that remain unchanged.
|
||||||
|
- `Speed ms/op` is JMH average time for one changed-token benchmark operation.
|
||||||
|
- `Error ms` is the JMH score error converted to milliseconds.
|
||||||
|
- `ns/token` is average nanoseconds per changed token in that operation.
|
||||||
|
|
||||||
on the current benchmark workload.
|
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.
|
||||||
|
|
||||||
This is a throughput comparison on the same deterministic token stream. It is **not** a claim that the compared stemmers are linguistically equivalent or interchangeable.
|
|
||||||
|
|
||||||
For benchmark scope, workload design, environment, commands, report locations, and interpretation guidance, see [Benchmarking](docs/benchmarking.md).
|
For benchmark scope, workload design, environment, commands, report locations, and interpretation guidance, see [Benchmarking](docs/benchmarking.md).
|
||||||
|
|
||||||
@@ -101,6 +180,9 @@ Compared with the historical baseline, Radixor emphasizes:
|
|||||||
- **frequency-aware deterministic ordering**
|
- **frequency-aware deterministic ordering**
|
||||||
Candidate results are ordered consistently and reproducibly.
|
Candidate results are ordered consistently and reproducibly.
|
||||||
|
|
||||||
|
- **contracted compiled patch tries**
|
||||||
|
Uniform patch-command subtrees are collapsed into accepting leaves, reducing hot lookup depth while preserving preferred stemming results.
|
||||||
|
|
||||||
- **practical subtree reduction modes**
|
- **practical subtree reduction modes**
|
||||||
Reduction can be tuned toward stronger compression or more conservative semantic preservation.
|
Reduction can be tuned toward stronger compression or more conservative semantic preservation.
|
||||||
|
|
||||||
@@ -121,7 +203,7 @@ Compared with the historical baseline, Radixor emphasizes:
|
|||||||
- Compressed binary persistence
|
- Compressed binary persistence
|
||||||
- Programmatic compilation and loading
|
- Programmatic compilation and loading
|
||||||
- CLI compilation tool
|
- CLI compilation tool
|
||||||
- Bundled language resources
|
- Independently versioned language-model resources
|
||||||
- Support for extending compiled stemmer tables
|
- Support for extending compiled stemmer tables
|
||||||
- Reproducible and auditable engineering posture
|
- Reproducible and auditable engineering posture
|
||||||
|
|
||||||
@@ -131,23 +213,60 @@ The repository keeps the front page concise and places detailed documentation un
|
|||||||
|
|
||||||
### Getting Started
|
### Getting Started
|
||||||
|
|
||||||
- [Quick Start](docs/quick-start.md)
|
- [Python Fast Track](docs/python/fast-track.md)
|
||||||
A practical first guide to loading, compiling, and using Radixor.
|
The shortest path from `pip install` to the first native Python stem.
|
||||||
|
|
||||||
|
- [Java Fast Track](docs/fast-track.md)
|
||||||
|
The shortest Java path from adding core plus a model artifact to getting a first stem.
|
||||||
|
|
||||||
|
- [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)
|
- [Built-in Languages](docs/built-in-languages.md)
|
||||||
Overview of bundled language resources such as `US_UK`.
|
Language enum values, default model IDs, artifacts, and optional variants.
|
||||||
|
|
||||||
- [Dictionary Format](docs/dictionary-format.md)
|
- [Dictionary Format](docs/dictionary-format.md)
|
||||||
How to write and normalize stemming dictionaries.
|
How to write and normalize stemming dictionaries.
|
||||||
|
|
||||||
- [Compilation (CLI tool)](docs/cli-compilation.md)
|
- [Java CLI Compilation](docs/cli-compilation.md)
|
||||||
How to compile dictionaries into deployable binary artifacts.
|
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)
|
- [Programmatic Usage Overview](docs/programmatic-usage.md)
|
||||||
Entry point to the Java API and the overall usage model.
|
Entry point to the Java API and the overall usage model.
|
||||||
|
|
||||||
|
- [Model Selection and Loading](docs/model-selection-and-loading.md)
|
||||||
|
Default, explicit, dual-model, ClassLoader, dependency, and troubleshooting examples.
|
||||||
|
|
||||||
- [Loading and Building Stemmers](docs/programmatic-loading-and-building.md)
|
- [Loading and Building Stemmers](docs/programmatic-loading-and-building.md)
|
||||||
Loading bundled resources, textual dictionaries, binary artifacts, and direct builder usage.
|
Loading bundled resources, textual dictionaries, binary artifacts, and direct builder usage.
|
||||||
|
|
||||||
@@ -157,6 +276,9 @@ The repository keeps the front page concise and places detailed documentation un
|
|||||||
- [Extending and Persisting Compiled Tries](docs/programmatic-extending-and-persistence.md)
|
- [Extending and Persisting Compiled Tries](docs/programmatic-extending-and-persistence.md)
|
||||||
Reopening compiled tries, rebuilding them, and writing binary artifacts.
|
Reopening compiled tries, rebuilding them, and writing binary artifacts.
|
||||||
|
|
||||||
|
- [Migration and Backward Compatibility](docs/migration-and-backward-compatibility.md)
|
||||||
|
Migration from serialized String patch-command application to `CompiledPatchCommand`.
|
||||||
|
|
||||||
### Concepts and Internals
|
### Concepts and Internals
|
||||||
|
|
||||||
- [Architecture and Reduction Overview](docs/architecture-and-reduction.md)
|
- [Architecture and Reduction Overview](docs/architecture-and-reduction.md)
|
||||||
@@ -185,7 +307,10 @@ The repository keeps the front page concise and places detailed documentation un
|
|||||||
Engineering standards, validation posture, auditability, and operational model.
|
Engineering standards, validation posture, auditability, and operational model.
|
||||||
|
|
||||||
- [Benchmarking](docs/benchmarking.md)
|
- [Benchmarking](docs/benchmarking.md)
|
||||||
JMH benchmark methodology, Porter comparison, and result interpretation.
|
JMH benchmark methodology, dictionary coverage trade-offs, speed, quality, and result interpretation.
|
||||||
|
|
||||||
|
- [Benchmark Results](docs/benchmarks/index.md)
|
||||||
|
Structured reference for methodology, corpora, environment, English coverage, and per-language result pages.
|
||||||
|
|
||||||
- [Published Reports](docs/reports.md)
|
- [Published Reports](docs/reports.md)
|
||||||
Entry points to CI-published reports and GitHub Pages artifacts.
|
Entry points to CI-published reports and GitHub Pages artifacts.
|
||||||
@@ -216,3 +341,19 @@ The goal is to keep the Egothor/Stempel lineage useful as a serious contemporary
|
|||||||
## Historical note
|
## Historical note
|
||||||
|
|
||||||
Egothor showed that stemming could be both algorithmic and compact. Stempel proved that the approach was practical enough to survive inside major search ecosystems. Radixor continues that tradition with a modernized implementation focused on production use, maintainability, and controlled evolution.
|
Egothor showed that stemming could be both algorithmic and compact. Stempel proved that the approach was practical enough to survive inside major search ecosystems. Radixor continues that tradition with a modernized implementation focused on production use, maintainability, and controlled evolution.
|
||||||
|
# Radixor 4 artifact architecture
|
||||||
|
|
||||||
|
The established `org.egothor:radixor` artifact remains the algorithmic core and contains no language-model data. From version 4 onward, applications explicitly add individual `org.egothor:radixor-model-<model-id>` runtime artifacts or the optional metadata-only `org.egothor:radixor-models-standard` aggregate. Polish defaults to `pl-pl-unimorph`; `pl-pl-polimorf` is opt-in. See [Stemmer Models](docs/stemmer-models.md) and [Migration and Backward Compatibility](docs/migration-and-backward-compatibility.md).
|
||||||
|
|
||||||
|
Radixor Java software remains licensed under BSD-3-Clause. UniMorph-derived model data is
|
||||||
|
distributed under CC BY-SA 3.0, with upstream attribution, the canonical license URI, Radixor
|
||||||
|
transformations, and Leo Galambos's limited contribution notice carried by each model artifact.
|
||||||
|
PoliMorf model data retains its separate BSD-2-Clause license. There is no project-wide CC license
|
||||||
|
directory because the root artifact contains no model data.
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.egothor:radixor:4.0.0'
|
||||||
|
runtimeOnly 'org.egothor:radixor-model-pl-pl-polimorf:1.0.0'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
BIN
Radixor.png
|
Before Width: | Height: | Size: 318 KiB |
25
build-logic/build.gradle
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
plugins {
|
||||||
|
id 'groovy-gradle-plugin'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter:5.14.3'
|
||||||
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.14.3'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('test') {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
gradlePlugin {
|
||||||
|
plugins {
|
||||||
|
radixorModel {
|
||||||
|
id = 'org.egothor.radixor.model'
|
||||||
|
implementationClass = 'org.egothor.radixor.RadixorModelPlugin'
|
||||||
|
}
|
||||||
|
radixorBuildSupport {
|
||||||
|
id = 'org.egothor.radixor.build-support'
|
||||||
|
implementationClass = 'org.egothor.radixor.RadixorBuildSupportPlugin'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
build-logic/settings.gradle
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
rootProject.name = 'radixor-build-logic'
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.file.ConfigurableFileCollection
|
||||||
|
import org.gradle.api.tasks.Classpath
|
||||||
|
import org.gradle.process.CommandLineArgumentProvider
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
abstract class MockitoAgentArgumentProvider implements CommandLineArgumentProvider {
|
||||||
|
@Classpath
|
||||||
|
abstract ConfigurableFileCollection getAgentClasspath()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MockitoAgentArgumentProvider() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Iterable<String> asArguments() {
|
||||||
|
return ["-javaagent:${agentClasspath.singleFile.absolutePath}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.file.DirectoryProperty
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.InputDirectory
|
||||||
|
import org.gradle.api.tasks.OutputDirectory
|
||||||
|
import org.gradle.api.tasks.PathSensitive
|
||||||
|
import org.gradle.api.tasks.PathSensitivity
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
/** Prepares the two POM-only catalog publications for a Maven Central bundle. */
|
||||||
|
abstract class PrepareModelCatalogBundleInputTask extends DefaultTask {
|
||||||
|
@InputDirectory
|
||||||
|
@PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract DirectoryProperty getRawRepositoryDirectory()
|
||||||
|
|
||||||
|
@OutputDirectory
|
||||||
|
abstract DirectoryProperty getPreparedBundleDirectory()
|
||||||
|
|
||||||
|
@Input
|
||||||
|
abstract Property<String> getCatalogVersion()
|
||||||
|
|
||||||
|
/** Copies permitted publication files and creates Central's required legacy checksums. */
|
||||||
|
@TaskAction
|
||||||
|
void prepare() {
|
||||||
|
prepareBundle(rawRepositoryDirectory.get().asFile.toPath(),
|
||||||
|
preparedBundleDirectory.get().asFile.toPath(), catalogVersion.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prepareBundle(final Path rawRepository, final Path preparedDirectory,
|
||||||
|
final String version) {
|
||||||
|
if (!Files.isDirectory(rawRepository)) {
|
||||||
|
throw new GradleException("The raw model catalog staging repository does not exist: ${rawRepository}.")
|
||||||
|
}
|
||||||
|
deleteTree(preparedDirectory)
|
||||||
|
Files.createDirectories(preparedDirectory)
|
||||||
|
|
||||||
|
final Set<String> expectedPoms = [
|
||||||
|
"org/egothor/radixor-models-standard/${version}/radixor-models-standard-${version}.pom",
|
||||||
|
"org/egothor/radixor-models-bom/${version}/radixor-models-bom-${version}.pom"
|
||||||
|
] as Set<String>
|
||||||
|
final List<Path> copied = []
|
||||||
|
Files.walk(rawRepository).withCloseable { Stream<Path> paths ->
|
||||||
|
paths.filter(Files::isRegularFile).sorted().forEach { Path source ->
|
||||||
|
final String relative = rawRepository.relativize(source).toString().replace(File.separatorChar, '/' as char)
|
||||||
|
if (isExcludedPublicationMetadata(relative)) return
|
||||||
|
if (relative.endsWith('.jar') || relative.endsWith('/stemmer.gz')
|
||||||
|
|| relative.contains('benchmark-pack')) {
|
||||||
|
throw new GradleException("Unsupported model catalog publication file: ${relative}.")
|
||||||
|
}
|
||||||
|
final String pom = expectedPoms.find { String candidate ->
|
||||||
|
relative == candidate || relative.startsWith(candidate + '.')
|
||||||
|
}
|
||||||
|
if (pom == null) {
|
||||||
|
throw new GradleException("Unexpected file in the raw model catalog repository: ${relative}.")
|
||||||
|
}
|
||||||
|
if (relative == pom || relative == pom + '.asc') {
|
||||||
|
final Path target = preparedDirectory.resolve(relative)
|
||||||
|
Files.createDirectories(target.parent)
|
||||||
|
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING)
|
||||||
|
copied.add(target)
|
||||||
|
} else if (!(relative ==~ /.*\.pom(?:\.asc)?\.(?:md5|sha1|sha256|sha512)/)) {
|
||||||
|
throw new GradleException("Unsupported model catalog publication file: ${relative}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Path> poms = copied.findAll { Path path -> path.fileName.toString().endsWith('.pom') }
|
||||||
|
if (copied.isEmpty()) {
|
||||||
|
throw new GradleException('No model catalog publication files were copied from the raw staging repository.')
|
||||||
|
}
|
||||||
|
if (poms.size() != 2 || !expectedPoms.every { String expected -> Files.isRegularFile(preparedDirectory.resolve(expected)) }) {
|
||||||
|
throw new GradleException("The prepared model catalog must contain exactly the standard and BOM POMs; found ${poms.size()} POM files.")
|
||||||
|
}
|
||||||
|
copied.each { Path artifact ->
|
||||||
|
writeDigest(artifact, 'MD5', artifact.resolveSibling(artifact.fileName.toString() + '.md5'))
|
||||||
|
writeDigest(artifact, 'SHA-1', artifact.resolveSibling(artifact.fileName.toString() + '.sha1'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isExcludedPublicationMetadata(final String relative) {
|
||||||
|
final String name = relative.substring(relative.lastIndexOf('/') + 1)
|
||||||
|
return name ==~ /maven-metadata.*\.xml(?:\..*)?/ || relative ==~ /.*\.module(?:\..*)?/
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeDigest(final Path source, final String algorithm, final Path target) {
|
||||||
|
final MessageDigest digest = MessageDigest.getInstance(algorithm)
|
||||||
|
Files.newInputStream(source).withCloseable { InputStream input ->
|
||||||
|
final byte[] buffer = new byte[16 * 1024]
|
||||||
|
int count
|
||||||
|
while ((count = input.read(buffer)) >= 0) {
|
||||||
|
if (count > 0) digest.update(buffer, 0, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Files.writeString(target, digest.digest().encodeHex().toString(), java.nio.charset.StandardCharsets.US_ASCII)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deleteTree(final Path directory) {
|
||||||
|
if (!Files.exists(directory)) return
|
||||||
|
Files.walk(directory).withCloseable { Stream<Path> paths ->
|
||||||
|
paths.sorted(Comparator.reverseOrder()).forEach(Files::delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.file.ConfigurableFileCollection
|
||||||
|
import org.gradle.api.file.DirectoryProperty
|
||||||
|
import org.gradle.api.file.RegularFileProperty
|
||||||
|
import org.gradle.api.provider.MapProperty
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.InputFile
|
||||||
|
import org.gradle.api.tasks.InputFiles
|
||||||
|
import org.gradle.api.tasks.OutputDirectory
|
||||||
|
import org.gradle.api.tasks.PathSensitive
|
||||||
|
import org.gradle.api.tasks.PathSensitivity
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
/** Builds the isolated Maven-layout repository used by consumer resolution tests. */
|
||||||
|
abstract class PrepareModelConsumerRepositoryTask extends DefaultTask {
|
||||||
|
@Input abstract Property<String> getCoreVersion()
|
||||||
|
@Input abstract Property<String> getCatalogVersion()
|
||||||
|
@Input abstract MapProperty<String, String> getModelVersions()
|
||||||
|
|
||||||
|
@InputFile @PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract RegularFileProperty getCorePom()
|
||||||
|
|
||||||
|
@InputFile @PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract RegularFileProperty getCoreJar()
|
||||||
|
|
||||||
|
@InputFiles @PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract ConfigurableFileCollection getModelPoms()
|
||||||
|
|
||||||
|
@InputFiles @PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract ConfigurableFileCollection getModelJars()
|
||||||
|
|
||||||
|
@InputFile @PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract RegularFileProperty getStandardPom()
|
||||||
|
|
||||||
|
@InputFile @PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract RegularFileProperty getBomPom()
|
||||||
|
|
||||||
|
@OutputDirectory
|
||||||
|
abstract DirectoryProperty getRepositoryDirectory()
|
||||||
|
|
||||||
|
/** Creates the repository using only declared task state and Java file APIs. */
|
||||||
|
@TaskAction
|
||||||
|
void prepareRepository() {
|
||||||
|
final Path repository = repositoryDirectory.get().asFile.toPath()
|
||||||
|
deleteTree(repository)
|
||||||
|
Files.createDirectories(repository)
|
||||||
|
install(repository, 'radixor', coreVersion.get(), corePom.get().asFile.toPath(), coreJar.get().asFile.toPath())
|
||||||
|
|
||||||
|
final Map<String, Path> pomsByModel = indexModelFiles(modelPoms.files)
|
||||||
|
final Map<String, Path> jarsByModel = indexModelFiles(modelJars.files)
|
||||||
|
modelVersions.get().toSorted().each { String modelId, String modelVersion ->
|
||||||
|
final Path pom = pomsByModel.get(modelId)
|
||||||
|
final Path jar = jarsByModel.get(modelId)
|
||||||
|
if (pom == null || jar == null) {
|
||||||
|
throw new GradleException("Missing generated publication input for model ${modelId}.")
|
||||||
|
}
|
||||||
|
PrepareModelConsumerRepositoryTask.install(
|
||||||
|
repository, "radixor-model-${modelId}", modelVersion, pom, jar)
|
||||||
|
}
|
||||||
|
install(repository, 'radixor-models-standard', catalogVersion.get(), standardPom.get().asFile.toPath(), null)
|
||||||
|
install(repository, 'radixor-models-bom', catalogVersion.get(), bomPom.get().asFile.toPath(), null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Path> indexModelFiles(final Set<File> files) {
|
||||||
|
final Map<String, Path> indexed = [:]
|
||||||
|
files.each { File file ->
|
||||||
|
Path cursor = file.toPath().toAbsolutePath().parent
|
||||||
|
while (cursor != null && cursor.fileName.toString() != 'build') cursor = cursor.parent
|
||||||
|
if (cursor == null || cursor.parent == null) {
|
||||||
|
throw new GradleException("Cannot determine model ID from generated input ${file}.")
|
||||||
|
}
|
||||||
|
final String modelId = cursor.parent.fileName.toString()
|
||||||
|
if (indexed.put(modelId, file.toPath()) != null) {
|
||||||
|
throw new GradleException("Duplicate generated publication input for model ${modelId}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indexed
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void install(final Path repository, final String artifactId, final String version,
|
||||||
|
final Path pom, final Path jar) {
|
||||||
|
final Path module = repository.resolve("org/egothor/${artifactId}/${version}")
|
||||||
|
Files.createDirectories(module)
|
||||||
|
Files.copy(pom, module.resolve("${artifactId}-${version}.pom"), StandardCopyOption.REPLACE_EXISTING)
|
||||||
|
if (jar != null) {
|
||||||
|
Files.copy(jar, module.resolve("${artifactId}-${version}.jar"), StandardCopyOption.REPLACE_EXISTING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deleteTree(final Path directory) {
|
||||||
|
if (!Files.exists(directory)) return
|
||||||
|
Files.walk(directory).withCloseable { Stream<Path> paths ->
|
||||||
|
paths.sorted(Comparator.reverseOrder()).forEach(Files::delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.file.DirectoryProperty
|
||||||
|
import org.gradle.api.file.RegularFileProperty
|
||||||
|
import org.gradle.api.provider.MapProperty
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.InputFile
|
||||||
|
import org.gradle.api.tasks.Optional
|
||||||
|
import org.gradle.api.tasks.OutputDirectory
|
||||||
|
import org.gradle.api.tasks.PathSensitive
|
||||||
|
import org.gradle.api.tasks.PathSensitivity
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
/** Generates one model's deterministic resource tree without retaining Project state. */
|
||||||
|
abstract class PrepareModelResourcesTask extends DefaultTask {
|
||||||
|
@InputFile @PathSensitive(PathSensitivity.RELATIVE) abstract RegularFileProperty getDictionaryFile()
|
||||||
|
@InputFile @PathSensitive(PathSensitivity.RELATIVE) abstract RegularFileProperty getVersionFile()
|
||||||
|
@Optional @InputFile @PathSensitive(PathSensitivity.RELATIVE) abstract RegularFileProperty getLicenseFile()
|
||||||
|
@Optional @InputFile @PathSensitive(PathSensitivity.RELATIVE) abstract RegularFileProperty getNoticeFile()
|
||||||
|
@Input abstract Property<Boolean> getShareAlike()
|
||||||
|
@Input abstract MapProperty<String, String> getDescriptorValues()
|
||||||
|
@OutputDirectory abstract DirectoryProperty getGeneratedDirectory()
|
||||||
|
|
||||||
|
/** Copies bounded inputs and writes descriptor and index files. */
|
||||||
|
@TaskAction
|
||||||
|
void prepareResources() {
|
||||||
|
final Path generated = generatedDirectory.get().asFile.toPath()
|
||||||
|
deleteTree(generated)
|
||||||
|
final Map<String, String> values = descriptorValues.get()
|
||||||
|
final String id = values['model.id']
|
||||||
|
final String resource = "org/egothor/stemmer/models/${id}/stemmer.gz"
|
||||||
|
final Path dictionaryTarget = generated.resolve(resource)
|
||||||
|
Files.createDirectories(dictionaryTarget.parent)
|
||||||
|
Files.copy(dictionaryFile.get().asFile.toPath(), dictionaryTarget, StandardCopyOption.REPLACE_EXISTING)
|
||||||
|
|
||||||
|
final Path descriptor = generated.resolve("META-INF/radixor/models/${id}.properties")
|
||||||
|
Files.createDirectories(descriptor.parent)
|
||||||
|
Files.writeString(descriptor, descriptorText(values,
|
||||||
|
versionFile.get().asFile.getText('UTF-8').trim(), resource, sha256(dictionaryFile.get().asFile)))
|
||||||
|
final Path index = generated.resolve('META-INF/radixor/models.index')
|
||||||
|
Files.createDirectories(index.parent)
|
||||||
|
Files.writeString(index, "META-INF/radixor/models/${id}.properties\n")
|
||||||
|
|
||||||
|
if (shareAlike.get()) {
|
||||||
|
final Path notice = generated.resolve("META-INF/NOTICE/${id}-data.txt")
|
||||||
|
Files.createDirectories(notice.parent)
|
||||||
|
Files.copy(noticeFile.get().asFile.toPath(), notice, StandardCopyOption.REPLACE_EXISTING)
|
||||||
|
} else {
|
||||||
|
final Path license = generated.resolve('META-INF/LICENSES/PoliMorf-BSD-2-Clause.txt')
|
||||||
|
Files.createDirectories(license.parent)
|
||||||
|
Files.copy(licenseFile.get().asFile.toPath(), license, StandardCopyOption.REPLACE_EXISTING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String descriptorText(final Map<String, String> value, final String version,
|
||||||
|
final String resource, final String checksum) {
|
||||||
|
return """model.id=${value['model.id']}
|
||||||
|
model.version=${version}
|
||||||
|
model.language=${value['model.language']}
|
||||||
|
model.displayName=${value['model.displayName']}
|
||||||
|
model.resource=${resource}
|
||||||
|
model.default=${value['model.default']}
|
||||||
|
model.format=radixor-dictionary-tsv-gzip
|
||||||
|
model.formatVersion=1
|
||||||
|
model.sha256=${checksum}
|
||||||
|
model.rightToLeft=${['FA_IR', 'HE_IL', 'YI'].contains(value['model.language'])}
|
||||||
|
model.caseProcessing=LOWERCASE_WITH_LOCALE_ROOT
|
||||||
|
model.diacriticProcessing=AS_IS
|
||||||
|
model.storeOriginal=true
|
||||||
|
source.name=${value['source.name']}
|
||||||
|
source.version=${value['source.version']}
|
||||||
|
source.project=${value['source.project']}
|
||||||
|
source.repository=${value['source.repository']}
|
||||||
|
source.dataset=${value['source.dataset']}
|
||||||
|
source.revision=${value['source.revision']}
|
||||||
|
source.revisionStatus=${value['source.revisionStatus']}
|
||||||
|
source.license=${value['source.license']}
|
||||||
|
source.licenseUri=${value['source.licenseUri']}
|
||||||
|
source.attribution=${value['source.attribution']}
|
||||||
|
source.verificationDate=${value['source.verificationDate']}
|
||||||
|
transformations.summary=${value['transformations.summary']}
|
||||||
|
compiler.radixorVersion=3.x
|
||||||
|
compiler.radixorCommit=unavailable
|
||||||
|
statistics.groups=unavailable
|
||||||
|
statistics.forms=unavailable
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String sha256(final File file) {
|
||||||
|
return MessageDigest.getInstance('SHA-256').digest(file.bytes)
|
||||||
|
.collect { byte value -> String.format('%02x', value & 0xff) }.join()
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deleteTree(final Path directory) {
|
||||||
|
if (!Files.exists(directory)) return
|
||||||
|
Files.walk(directory).withCloseable { Stream<Path> paths ->
|
||||||
|
paths.sorted(Comparator.reverseOrder()).forEach(Files::delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
|
||||||
|
/** Exposes typed repository build-support tasks to the root build. */
|
||||||
|
final class RadixorBuildSupportPlugin implements Plugin<Project> {
|
||||||
|
/** Registers build-support tasks without inspecting project state during execution. */
|
||||||
|
@Override
|
||||||
|
void apply(final Project project) {
|
||||||
|
project.tasks.register('prepareModelConsumerTestRepository', PrepareModelConsumerRepositoryTask) {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Creates an isolated local Maven repository for model dependency-resolution integration tests.'
|
||||||
|
}
|
||||||
|
project.tasks.register('prepareModelCatalogReleaseCandidate', PrepareModelCatalogBundleInputTask) {
|
||||||
|
group = 'publishing'
|
||||||
|
description = 'Prepares the isolated POM-only model catalog input for Maven Central.'
|
||||||
|
}
|
||||||
|
project.tasks.register('verifyModelCatalogReleaseCandidate', VerifyModelCatalogReleaseCandidateTask) {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Verifies catalog bundle contents, checksums, coordinates, and dependency semantics.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.model.ObjectFactory
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/** Declarative configuration for one independently published Radixor model. */
|
||||||
|
abstract class RadixorModelExtension {
|
||||||
|
/** Stable model identifier. */
|
||||||
|
abstract Property<String> getModelId()
|
||||||
|
|
||||||
|
/** Radixor language enum constant. */
|
||||||
|
abstract Property<String> getLanguage()
|
||||||
|
|
||||||
|
/** Human-readable model name. */
|
||||||
|
abstract Property<String> getDisplayName()
|
||||||
|
|
||||||
|
/** Whether this is the documented default for its language. */
|
||||||
|
abstract Property<Boolean> getDefaultModel()
|
||||||
|
|
||||||
|
/** Source dictionary name. */
|
||||||
|
abstract Property<String> getSourceName()
|
||||||
|
|
||||||
|
/** Source dictionary version or explicit unavailable marker. */
|
||||||
|
abstract Property<String> getSourceVersion()
|
||||||
|
|
||||||
|
/** Exact upstream revision or the explicit legacy-import sentinel. */
|
||||||
|
abstract Property<String> getSourceRevision()
|
||||||
|
|
||||||
|
/** Upstream source project. */
|
||||||
|
abstract Property<String> getSourceProject()
|
||||||
|
|
||||||
|
/** Official upstream repository URL. */
|
||||||
|
abstract Property<String> getSourceRepository()
|
||||||
|
|
||||||
|
/** Upstream dataset identity. */
|
||||||
|
abstract Property<String> getSourceDataset()
|
||||||
|
|
||||||
|
/** Whether the source revision is recorded or was not recorded by a legacy import. */
|
||||||
|
abstract Property<String> getSourceRevisionStatus()
|
||||||
|
|
||||||
|
/** SPDX license identifier. */
|
||||||
|
abstract Property<String> getSourceLicense()
|
||||||
|
|
||||||
|
/** Canonical URI for the source-data license. */
|
||||||
|
abstract Property<String> getSourceLicenseUri()
|
||||||
|
|
||||||
|
/** Upstream attribution supplied with the source data. */
|
||||||
|
abstract Property<String> getSourceAttribution()
|
||||||
|
|
||||||
|
/** Date on which the upstream metadata was verified. */
|
||||||
|
abstract Property<String> getSourceVerificationDate()
|
||||||
|
|
||||||
|
/** Material transformations applied by Radixor. */
|
||||||
|
abstract Property<String> getTransformationsSummary()
|
||||||
|
|
||||||
|
/** Model-specific data notice input file name, when required. */
|
||||||
|
abstract Property<String> getNoticeFileName()
|
||||||
|
|
||||||
|
/** License input file name. */
|
||||||
|
abstract Property<String> getLicenseFileName()
|
||||||
|
|
||||||
|
/** Creates the extension. */
|
||||||
|
@Inject
|
||||||
|
RadixorModelExtension(final ObjectFactory objects) {
|
||||||
|
defaultModel.convention(false)
|
||||||
|
sourceVersion.convention('unavailable')
|
||||||
|
sourceLicense.convention('LicenseRef-Radixor-Stemmer-Data')
|
||||||
|
licenseFileName.convention('LICENSE-stemmer-data.txt')
|
||||||
|
noticeFileName.convention('NOTICE-model-data.txt')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,505 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.file.DuplicatesStrategy
|
||||||
|
import org.gradle.api.plugins.JavaPlugin
|
||||||
|
import org.gradle.api.publish.PublishingExtension
|
||||||
|
import org.gradle.api.publish.maven.MavenPublication
|
||||||
|
import org.gradle.api.tasks.Copy
|
||||||
|
import org.gradle.api.tasks.bundling.Jar
|
||||||
|
import org.gradle.api.tasks.bundling.Zip
|
||||||
|
import org.gradle.plugins.signing.SigningExtension
|
||||||
|
|
||||||
|
import java.nio.charset.CodingErrorAction
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.zip.GZIPInputStream
|
||||||
|
|
||||||
|
/** Configures validation, generation, packaging, and publication for one model artifact. */
|
||||||
|
final class RadixorModelPlugin implements Plugin<Project> {
|
||||||
|
/** Applies the model convention to a project. */
|
||||||
|
@Override
|
||||||
|
void apply(final Project project) {
|
||||||
|
project.pluginManager.apply(JavaPlugin)
|
||||||
|
project.pluginManager.apply('maven-publish')
|
||||||
|
project.pluginManager.apply('signing')
|
||||||
|
project.java {
|
||||||
|
withSourcesJar()
|
||||||
|
withJavadocJar()
|
||||||
|
sourceCompatibility = org.gradle.api.JavaVersion.VERSION_21
|
||||||
|
targetCompatibility = org.gradle.api.JavaVersion.VERSION_21
|
||||||
|
}
|
||||||
|
final RadixorModelExtension model = project.extensions.create('radixorModel', RadixorModelExtension)
|
||||||
|
project.group = 'org.egothor'
|
||||||
|
project.version = project.providers.gradleProperty('modelReleaseVersion')
|
||||||
|
.orElse(project.providers.fileContents(project.layout.projectDirectory.file('model-version.txt')).asText.map(String::trim))
|
||||||
|
.get()
|
||||||
|
|
||||||
|
final File input = project.file('src/modelInput/stemmer.gz')
|
||||||
|
final File generated = project.layout.buildDirectory.dir('generated/modelResources').get().asFile
|
||||||
|
project.sourceSets.main.resources.setSrcDirs([generated])
|
||||||
|
|
||||||
|
final def validate = project.tasks.register('validateModelInput', ValidateModelInputTask) {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Validates the immutable source dictionary, metadata, version, and model-specific licensing material.'
|
||||||
|
dictionaryFile = project.layout.projectDirectory.file('src/modelInput/stemmer.gz')
|
||||||
|
versionFile = project.layout.projectDirectory.file('model-version.txt')
|
||||||
|
modelId = model.modelId
|
||||||
|
moduleName = project.name
|
||||||
|
shareAlike = model.sourceLicense.map { String license -> license == 'CC-BY-SA-3.0' }
|
||||||
|
metadata.put('source.project', model.sourceProject)
|
||||||
|
metadata.put('source.repository', model.sourceRepository)
|
||||||
|
metadata.put('source.dataset', model.sourceDataset)
|
||||||
|
metadata.put('source.revision', model.sourceRevision)
|
||||||
|
metadata.put('source.revisionStatus', model.sourceRevisionStatus)
|
||||||
|
metadata.put('source.license', model.sourceLicense)
|
||||||
|
metadata.put('source.licenseUri', model.sourceLicenseUri)
|
||||||
|
metadata.put('source.attribution', model.sourceAttribution)
|
||||||
|
metadata.put('source.verificationDate', model.sourceVerificationDate)
|
||||||
|
metadata.put('transformations.summary', model.transformationsSummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
final def prepare = project.tasks.register('prepareModelResources', PrepareModelResourcesTask) {
|
||||||
|
group = 'build'
|
||||||
|
description = 'Copies validated dictionary bytes and generates the immutable model descriptor and index.'
|
||||||
|
dependsOn(validate)
|
||||||
|
dictionaryFile = project.layout.projectDirectory.file('src/modelInput/stemmer.gz')
|
||||||
|
versionFile = project.layout.projectDirectory.file('model-version.txt')
|
||||||
|
shareAlike = model.sourceLicense.map { String license -> license == 'CC-BY-SA-3.0' }
|
||||||
|
generatedDirectory = project.layout.buildDirectory.dir('generated/modelResources')
|
||||||
|
descriptorValues.put('model.id', model.modelId)
|
||||||
|
descriptorValues.put('model.language', model.language)
|
||||||
|
descriptorValues.put('model.displayName', model.displayName)
|
||||||
|
descriptorValues.put('model.default', model.defaultModel.map(String::valueOf))
|
||||||
|
descriptorValues.put('source.name', model.sourceName)
|
||||||
|
descriptorValues.put('source.version', model.sourceVersion)
|
||||||
|
descriptorValues.put('source.project', model.sourceProject)
|
||||||
|
descriptorValues.put('source.repository', model.sourceRepository)
|
||||||
|
descriptorValues.put('source.dataset', model.sourceDataset)
|
||||||
|
descriptorValues.put('source.revision', model.sourceRevision)
|
||||||
|
descriptorValues.put('source.revisionStatus', model.sourceRevisionStatus)
|
||||||
|
descriptorValues.put('source.license', model.sourceLicense)
|
||||||
|
descriptorValues.put('source.licenseUri', model.sourceLicenseUri)
|
||||||
|
descriptorValues.put('source.attribution', model.sourceAttribution)
|
||||||
|
descriptorValues.put('source.verificationDate', model.sourceVerificationDate)
|
||||||
|
descriptorValues.put('transformations.summary', model.transformationsSummary)
|
||||||
|
}
|
||||||
|
project.afterEvaluate {
|
||||||
|
final boolean shareAlike = model.sourceLicense.get() == 'CC-BY-SA-3.0'
|
||||||
|
if (shareAlike) {
|
||||||
|
final def notice = project.layout.projectDirectory.file("src/modelInput/${model.noticeFileName.get()}")
|
||||||
|
validate.configure { noticeFile = notice }
|
||||||
|
prepare.configure { noticeFile = notice }
|
||||||
|
} else {
|
||||||
|
final def license = project.layout.projectDirectory.file("src/modelInput/${model.licenseFileName.get()}")
|
||||||
|
validate.configure { licenseFile = license }
|
||||||
|
prepare.configure { licenseFile = license }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
project.tasks.named('processResources', Copy).configure { dependsOn(prepare); duplicatesStrategy = DuplicatesStrategy.FAIL }
|
||||||
|
project.tasks.named('sourcesJar', Jar).configure { dependsOn(prepare); exclude('**/stemmer.gz') }
|
||||||
|
project.tasks.named('javadocJar', Jar).configure { exclude('**/stemmer.gz') }
|
||||||
|
project.tasks.named('jar', Jar).configure {
|
||||||
|
archiveBaseName.set("radixor-model-${project.name}")
|
||||||
|
preserveFileTimestamps = false
|
||||||
|
reproducibleFileOrder = true
|
||||||
|
}
|
||||||
|
final def verifyDescriptor = project.tasks.register('verifyModelDescriptor') {
|
||||||
|
group = 'verification'; description = 'Verifies generated descriptor identity and checksum.'; dependsOn(prepare)
|
||||||
|
doLast {
|
||||||
|
final Properties properties = new Properties()
|
||||||
|
new File(generated, "META-INF/radixor/models/${model.modelId.get()}.properties").withInputStream(properties::load)
|
||||||
|
if (properties.getProperty('model.sha256') != sha256(input)) {
|
||||||
|
throw new GradleException('Generated descriptor checksum does not match the immutable source input.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final def verifyJar = project.tasks.register('verifyModelJar') {
|
||||||
|
group = 'verification'; description = 'Verifies the model JAR checksum, layout, metadata, and dictionary-free documentation artifacts.'
|
||||||
|
dependsOn(project.tasks.named('jar'), project.tasks.named('sourcesJar'), project.tasks.named('javadocJar'))
|
||||||
|
doLast {
|
||||||
|
final File archive = project.tasks.named('jar', Jar).get().archiveFile.get().asFile
|
||||||
|
final List<String> names = []
|
||||||
|
final String resource = "org/egothor/stemmer/models/${model.modelId.get()}/stemmer.gz"
|
||||||
|
final boolean shareAlike = model.sourceLicense.get() == 'CC-BY-SA-3.0'
|
||||||
|
final String licenseResource = 'META-INF/LICENSES/PoliMorf-BSD-2-Clause.txt'
|
||||||
|
final File sourceLicense = shareAlike ? null : project.file("src/modelInput/${model.licenseFileName.get()}")
|
||||||
|
final File sourceNotice = shareAlike
|
||||||
|
? project.file("src/modelInput/${model.noticeFileName.get()}") : null
|
||||||
|
final String noticeResource = "META-INF/NOTICE/${model.modelId.get()}-data.txt"
|
||||||
|
String packagedChecksum
|
||||||
|
String packagedLicenseChecksum
|
||||||
|
String packagedNoticeChecksum
|
||||||
|
new java.util.zip.ZipFile(archive).withCloseable { zip ->
|
||||||
|
zip.entries().each { names.add(it.name) }
|
||||||
|
final def entry = zip.getEntry(resource)
|
||||||
|
if (entry != null) {
|
||||||
|
packagedChecksum = sha256(zip.getInputStream(entry).bytes)
|
||||||
|
}
|
||||||
|
final def licenseEntry = zip.getEntry(licenseResource)
|
||||||
|
if (licenseEntry != null) {
|
||||||
|
packagedLicenseChecksum = sha256(zip.getInputStream(licenseEntry).bytes)
|
||||||
|
}
|
||||||
|
final def noticeEntry = zip.getEntry(noticeResource)
|
||||||
|
if (noticeEntry != null) {
|
||||||
|
packagedNoticeChecksum = sha256(zip.getInputStream(noticeEntry).bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (names.count { String name -> name.endsWith('/stemmer.gz') } != 1 || !names.contains(resource)) {
|
||||||
|
throw new GradleException("Model JAR must contain exactly one dictionary at ${resource}.")
|
||||||
|
}
|
||||||
|
if (packagedChecksum != sha256(input)) {
|
||||||
|
throw new GradleException("Packaged dictionary checksum does not match the immutable source input at ${resource}.")
|
||||||
|
}
|
||||||
|
if (shareAlike) {
|
||||||
|
requireMatchingChecksum('notice', noticeResource, sha256(sourceNotice), packagedNoticeChecksum)
|
||||||
|
validateUniMorphJarContents(names)
|
||||||
|
} else {
|
||||||
|
requireMatchingChecksum('license', licenseResource, sha256(sourceLicense), packagedLicenseChecksum)
|
||||||
|
validatePoliMorfJarContents(names)
|
||||||
|
}
|
||||||
|
['META-INF/radixor/models.index', "META-INF/radixor/models/${model.modelId.get()}.properties"].each { String name ->
|
||||||
|
if (!names.contains(name)) throw new GradleException("Model JAR is missing ${name}.")
|
||||||
|
}
|
||||||
|
[project.tasks.named('sourcesJar', Jar).get(), project.tasks.named('javadocJar', Jar).get()].each { Jar task ->
|
||||||
|
final File documentationArchive = task.archiveFile.get().asFile
|
||||||
|
new java.util.zip.ZipFile(documentationArchive).withCloseable { zip ->
|
||||||
|
if (zip.entries().any { entry -> entry.name.endsWith('/stemmer.gz') || entry.name == 'stemmer.gz' }) {
|
||||||
|
throw new GradleException("Documentation artifact ${documentationArchive.name} must not contain a model dictionary.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
project.tasks.register('validateModelRelease') {
|
||||||
|
group = 'verification'; description = 'Validates a tag-supplied model release version.'; dependsOn(verifyDescriptor, verifyJar)
|
||||||
|
doLast {
|
||||||
|
if (!project.hasProperty('modelReleaseVersion')) throw new GradleException('Model release validation requires -PmodelReleaseVersion=<version>.')
|
||||||
|
final String recorded = project.file('model-version.txt').text.trim()
|
||||||
|
if (project.property('modelReleaseVersion').toString() != recorded) throw new GradleException("Release version does not match model-version.txt: ${recorded}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
project.tasks.named('check').configure { dependsOn(verifyDescriptor, verifyJar) }
|
||||||
|
project.extensions.configure(PublishingExtension) { PublishingExtension publishing ->
|
||||||
|
publishing.publications.create('model', MavenPublication) { MavenPublication publication ->
|
||||||
|
publication.from(project.components.java)
|
||||||
|
publication.artifactId = "radixor-model-${project.name}"
|
||||||
|
publication.pom {
|
||||||
|
name.set("Radixor model ${project.name}")
|
||||||
|
description.set(model.displayName.zip(model.sourceLicense) { String displayName, String licenseId ->
|
||||||
|
final String material = licenseId == 'CC-BY-SA-3.0'
|
||||||
|
? 'See the packaged model-specific notice.'
|
||||||
|
: 'See the packaged model-data license.'
|
||||||
|
return "${displayName}. This artifact contains Radixor-derived model data licensed under ${licenseId}; "
|
||||||
|
.concat("Radixor software is licensed separately under BSD-3-Clause. ${material}")
|
||||||
|
})
|
||||||
|
url.set('https://github.com/leogalambos/Radixor')
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name.set(model.sourceLicense)
|
||||||
|
url.set(model.sourceLicenseUri)
|
||||||
|
distribution.set('repo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id.set('egothor')
|
||||||
|
name.set('Leo Galambos')
|
||||||
|
email.set('egothor@gmail.com')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
url.set('https://github.com/leogalambos/Radixor')
|
||||||
|
connection.set('scm:git:https://github.com/leogalambos/Radixor.git')
|
||||||
|
developerConnection.set('scm:git:ssh://git@github.com/leogalambos/Radixor.git')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publishing.repositories.maven {
|
||||||
|
name = 'modelStaging'
|
||||||
|
url = project.layout.buildDirectory.dir('model-staging-repository').get().asFile.toURI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final String signingKey = project.providers.environmentVariable('SIGNING_KEY').orNull
|
||||||
|
final String signingPassword = project.providers.environmentVariable('SIGNING_PASSWORD').orNull
|
||||||
|
project.extensions.configure(SigningExtension) { SigningExtension signing ->
|
||||||
|
signing.required = {
|
||||||
|
project.providers.environmentVariable('GITHUB_REF_TYPE').orNull == 'tag'
|
||||||
|
}
|
||||||
|
if (signingKey != null && !signingKey.isBlank()) {
|
||||||
|
signing.useInMemoryPgpKeys(signingKey, signingPassword)
|
||||||
|
signing.sign(project.extensions.getByType(PublishingExtension).publications.getByName('model'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final def checksums = project.tasks.register('createModelCentralChecksums') {
|
||||||
|
group = 'publishing'
|
||||||
|
description = 'Creates Maven Central checksums for this model staging repository.'
|
||||||
|
dependsOn(project.tasks.named('publishModelPublicationToModelStagingRepository'))
|
||||||
|
doLast {
|
||||||
|
final File repository = project.layout.buildDirectory.dir('model-staging-repository').get().asFile
|
||||||
|
repository.eachFileRecurse { File artifact ->
|
||||||
|
if (artifact.isFile() && !['.md5', '.sha1', '.sha256', '.sha512'].any {
|
||||||
|
String extension -> artifact.name.endsWith(extension)
|
||||||
|
}) {
|
||||||
|
new File(artifact.absolutePath + '.md5').setText(sha256WithAlgorithm(artifact, 'MD5'), 'US-ASCII')
|
||||||
|
new File(artifact.absolutePath + '.sha1').setText(sha256WithAlgorithm(artifact, 'SHA-1'), 'US-ASCII')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
project.tasks.register('packageModelReleaseCandidate', Zip) {
|
||||||
|
group = 'distribution'
|
||||||
|
description = 'Packages only this model publication as a Maven-layout local release candidate.'
|
||||||
|
dependsOn(checksums)
|
||||||
|
from(project.layout.buildDirectory.dir('model-staging-repository')) {
|
||||||
|
exclude('**/maven-metadata*.xml*')
|
||||||
|
}
|
||||||
|
destinationDirectory.set(project.layout.buildDirectory.dir('model-release-candidate'))
|
||||||
|
archiveFileName.set('central-bundle.zip')
|
||||||
|
doFirst {
|
||||||
|
if (project.providers.environmentVariable('GITHUB_REF_TYPE').orNull == 'tag'
|
||||||
|
&& (signingKey == null || signingKey.isBlank()
|
||||||
|
|| signingPassword == null || signingPassword.isBlank())) {
|
||||||
|
throw new GradleException('A tagged model release requires SIGNING_KEY and SIGNING_PASSWORD.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ensures a required file exists. */
|
||||||
|
static void requireFile(final File file, final String diagnostic) {
|
||||||
|
if (!file.isFile()) throw new GradleException(diagnostic)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects a missing or byte-different packaged licensing resource. */
|
||||||
|
static void requireMatchingChecksum(final String kind, final String resource,
|
||||||
|
final String sourceChecksum, final String packagedChecksum) {
|
||||||
|
if (packagedChecksum != sourceChecksum) {
|
||||||
|
throw new GradleException("Packaged ${kind} does not match the source ${kind} at ${resource}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Validates complete source, licensing, attribution, revision-status, and transformation metadata. */
|
||||||
|
private static void validateMetadata(final RadixorModelExtension model) {
|
||||||
|
final Map<String, String> required = [
|
||||||
|
'source.project': model.sourceProject.orNull,
|
||||||
|
'source.repository': model.sourceRepository.orNull,
|
||||||
|
'source.dataset': model.sourceDataset.orNull,
|
||||||
|
'source.revision': model.sourceRevision.orNull,
|
||||||
|
'source.revisionStatus': model.sourceRevisionStatus.orNull,
|
||||||
|
'source.license': model.sourceLicense.orNull,
|
||||||
|
'source.licenseUri': model.sourceLicenseUri.orNull,
|
||||||
|
'source.attribution': model.sourceAttribution.orNull,
|
||||||
|
'source.verificationDate': model.sourceVerificationDate.orNull,
|
||||||
|
'transformations.summary': model.transformationsSummary.orNull]
|
||||||
|
required.each { String key, String value ->
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
throw new GradleException("Required model metadata is missing: ${key}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validateRevisionMetadata(model.sourceRevision.get(), model.sourceRevisionStatus.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Accepts an exact recorded revision or the explicit legacy-import sentinel, but never an absent status. */
|
||||||
|
static void validateRevisionMetadata(final String revision, final String status) {
|
||||||
|
if (revision == null || revision.isBlank()) {
|
||||||
|
throw new GradleException('Required model metadata is missing: source.revision')
|
||||||
|
}
|
||||||
|
if (status == null || status.isBlank()) {
|
||||||
|
throw new GradleException('Required model metadata is missing: source.revisionStatus')
|
||||||
|
}
|
||||||
|
final String sentinel = 'not-recorded-in-legacy-import'
|
||||||
|
if (revision == sentinel && status != sentinel) {
|
||||||
|
throw new GradleException('The legacy revision sentinel requires source.revisionStatus=not-recorded-in-legacy-import.')
|
||||||
|
}
|
||||||
|
if (revision != sentinel && status != 'recorded') {
|
||||||
|
throw new GradleException('An exact source revision requires source.revisionStatus=recorded.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Validates the model-specific attribution and ShareAlike notice. */
|
||||||
|
static void validateShareAlikeNotice(final File notice, final RadixorModelExtension model) {
|
||||||
|
validateShareAlikeNoticeText(notice.getText('UTF-8'), notice.toString(), model.modelId.get(),
|
||||||
|
model.sourceRepository.get(), model.sourceLicenseUri.get(), model.sourceRevision.get(),
|
||||||
|
model.sourceRevisionStatus.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Validates required content in one UniMorph model-data notice. */
|
||||||
|
static void validateShareAlikeNoticeText(final String text, final String noticeName,
|
||||||
|
final String modelId, final String repository, final String licenseUri,
|
||||||
|
final String revision, final String revisionStatus) {
|
||||||
|
final List<String> required = [
|
||||||
|
"Model ID: ${modelId}",
|
||||||
|
"Official repository: ${repository}",
|
||||||
|
'Attribution:',
|
||||||
|
'License:\nCreative Commons Attribution-ShareAlike 3.0 Unported',
|
||||||
|
"Canonical license URI: ${licenseUri}",
|
||||||
|
'Radixor modifications:',
|
||||||
|
"Revision status: ${revisionStatus}",
|
||||||
|
'Copyright (C) 2026, Leo Galambos.',
|
||||||
|
'Radixor-specific selection, verification, cleaning, normalization,',
|
||||||
|
'to the extent protected by applicable law.',
|
||||||
|
'The underlying morphological data remains attributed to UniMorph and',
|
||||||
|
"This derived model data, including Radixor's protectable contributions,",
|
||||||
|
'is distributed under Creative Commons Attribution-ShareAlike 3.0',
|
||||||
|
'Neither UniMorph nor any upstream contributor endorses Radixor.']
|
||||||
|
if (revision == 'not-recorded-in-legacy-import') {
|
||||||
|
required.add('The exact UniMorph commit used for the original Radixor import was not recorded.')
|
||||||
|
}
|
||||||
|
final List<String> missing = required.findAll { String value -> !text.contains(value) }
|
||||||
|
if (!missing.isEmpty()) {
|
||||||
|
throw new GradleException("Model notice ${noticeName} is missing required content: ${missing.join(', ')}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects generic license files and foreign notices in a UniMorph model artifact. */
|
||||||
|
static void validateUniMorphJarContents(final List<String> names) {
|
||||||
|
if (names.any { String name -> name.startsWith('META-INF/LICENSES/') }) {
|
||||||
|
throw new GradleException('A UniMorph model artifact must use only its model-specific notice for data licensing.')
|
||||||
|
}
|
||||||
|
if (names.count { String name -> name.startsWith('META-INF/NOTICE/') && !name.endsWith('/') } != 1) {
|
||||||
|
throw new GradleException('A UniMorph model artifact must contain exactly one model-specific notice.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects UniMorph licensing material in the separately licensed PoliMorf artifact. */
|
||||||
|
static void validatePoliMorfJarContents(final List<String> names) {
|
||||||
|
if (names.any { String name -> name.startsWith('META-INF/NOTICE/')
|
||||||
|
|| name.contains('CC-BY-SA') }) {
|
||||||
|
throw new GradleException('The PoliMorf artifact must not contain UniMorph CC BY-SA material.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Memory-bounded validation statistics for one dictionary input. */
|
||||||
|
static final class DictionaryValidationResult {
|
||||||
|
final long acceptedGroupCount
|
||||||
|
final long acceptedFormCount
|
||||||
|
final long ignoredEmptyVariantCount
|
||||||
|
|
||||||
|
DictionaryValidationResult(final long acceptedGroupCount, final long acceptedFormCount,
|
||||||
|
final long ignoredEmptyVariantCount) {
|
||||||
|
this.acceptedGroupCount = acceptedGroupCount
|
||||||
|
this.acceptedFormCount = acceptedFormCount
|
||||||
|
this.ignoredEmptyVariantCount = ignoredEmptyVariantCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Validates GZip, strict UTF-8, and dictionary rows without retaining decompressed input. */
|
||||||
|
static DictionaryValidationResult validateDictionary(final File file) {
|
||||||
|
long acceptedGroups = 0L
|
||||||
|
long acceptedForms = 0L
|
||||||
|
long ignoredEmptyVariants = 0L
|
||||||
|
try {
|
||||||
|
final def decoder = StandardCharsets.UTF_8.newDecoder()
|
||||||
|
.onMalformedInput(CodingErrorAction.REPORT)
|
||||||
|
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||||
|
Files.newInputStream(file.toPath()).withCloseable { InputStream source ->
|
||||||
|
new BufferedInputStream(source).withCloseable { BufferedInputStream bufferedInput ->
|
||||||
|
new GZIPInputStream(bufferedInput).withCloseable { GZIPInputStream gzipInput ->
|
||||||
|
new BufferedReader(new InputStreamReader(gzipInput, decoder)).withCloseable { BufferedReader reader ->
|
||||||
|
String line
|
||||||
|
long lineNumber = 0L
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
lineNumber++
|
||||||
|
final String trimmed = line.trim()
|
||||||
|
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('//')) {
|
||||||
|
final String[] columns = line.split('\\t', -1)
|
||||||
|
if (columns[0].isEmpty()) {
|
||||||
|
throw new GradleException("Invalid Radixor dictionary row ${lineNumber} in ${file}.")
|
||||||
|
}
|
||||||
|
if (containsUnicodeWhitespace(columns[0])) continue
|
||||||
|
long acceptedRowForms = 1L
|
||||||
|
for (int index = 1; index < columns.length; index++) {
|
||||||
|
final String variant = columns[index]
|
||||||
|
if (variant.isEmpty()) {
|
||||||
|
ignoredEmptyVariants++
|
||||||
|
} else if (!containsUnicodeWhitespace(variant)) {
|
||||||
|
acceptedRowForms++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acceptedGroups++
|
||||||
|
acceptedForms += acceptedRowForms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (GradleException exception) {
|
||||||
|
throw exception
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new GradleException("Invalid GZip or UTF-8 model input: ${file}", exception)
|
||||||
|
}
|
||||||
|
if (acceptedGroups == 0L) throw new GradleException("Model dictionary contains no valid rows: ${file}")
|
||||||
|
if (ignoredEmptyVariants > 0L) {
|
||||||
|
println("Model validation warning: " + file + " contains " + ignoredEmptyVariants
|
||||||
|
+ " empty variant columns; the production parser intentionally ignores empty variants.")
|
||||||
|
}
|
||||||
|
return new DictionaryValidationResult(acceptedGroups, acceptedForms, ignoredEmptyVariants)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Detects Unicode whitespace in one bounded dictionary field. */
|
||||||
|
private static boolean containsUnicodeWhitespace(final String value) {
|
||||||
|
for (int index = 0; index < value.length(); index++) {
|
||||||
|
if (Character.isWhitespace(value.charAt(index))) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds deterministic descriptor text. */
|
||||||
|
private static String descriptorText(final RadixorModelExtension model, final String version,
|
||||||
|
final String resource, final String checksum) {
|
||||||
|
return """model.id=${model.modelId.get()}
|
||||||
|
model.version=${version}
|
||||||
|
model.language=${model.language.get()}
|
||||||
|
model.displayName=${model.displayName.get()}
|
||||||
|
model.resource=${resource}
|
||||||
|
model.default=${model.defaultModel.get()}
|
||||||
|
model.format=radixor-dictionary-tsv-gzip
|
||||||
|
model.formatVersion=1
|
||||||
|
model.sha256=${checksum}
|
||||||
|
model.rightToLeft=${['FA_IR', 'HE_IL', 'YI'].contains(model.language.get())}
|
||||||
|
model.caseProcessing=LOWERCASE_WITH_LOCALE_ROOT
|
||||||
|
model.diacriticProcessing=AS_IS
|
||||||
|
model.storeOriginal=true
|
||||||
|
source.name=${model.sourceName.get()}
|
||||||
|
source.version=${model.sourceVersion.get()}
|
||||||
|
source.project=${model.sourceProject.get()}
|
||||||
|
source.repository=${model.sourceRepository.get()}
|
||||||
|
source.dataset=${model.sourceDataset.get()}
|
||||||
|
source.revision=${model.sourceRevision.get()}
|
||||||
|
source.revisionStatus=${model.sourceRevisionStatus.get()}
|
||||||
|
source.license=${model.sourceLicense.get()}
|
||||||
|
source.licenseUri=${model.sourceLicenseUri.get()}
|
||||||
|
source.attribution=${model.sourceAttribution.get()}
|
||||||
|
source.verificationDate=${model.sourceVerificationDate.get()}
|
||||||
|
transformations.summary=${model.transformationsSummary.get()}
|
||||||
|
compiler.radixorVersion=3.x
|
||||||
|
compiler.radixorCommit=unavailable
|
||||||
|
statistics.groups=unavailable
|
||||||
|
statistics.forms=unavailable
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calculates the lowercase hexadecimal SHA-256 digest. */
|
||||||
|
private static String sha256(final File file) {
|
||||||
|
return sha256(file.bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calculates the lowercase hexadecimal SHA-256 digest of bytes. */
|
||||||
|
private static String sha256(final byte[] bytes) {
|
||||||
|
return MessageDigest.getInstance('SHA-256').digest(bytes).collect { byte value -> String.format('%02x', value & 0xff) }.join()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calculates a lowercase hexadecimal digest using the requested algorithm. */
|
||||||
|
private static String sha256WithAlgorithm(final File file, final String algorithm) {
|
||||||
|
return MessageDigest.getInstance(algorithm).digest(file.bytes)
|
||||||
|
.collect { byte value -> String.format('%02x', value & 0xff) }.join()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.file.RegularFileProperty
|
||||||
|
import org.gradle.api.provider.MapProperty
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.InputFile
|
||||||
|
import org.gradle.api.tasks.Optional
|
||||||
|
import org.gradle.api.tasks.PathSensitive
|
||||||
|
import org.gradle.api.tasks.PathSensitivity
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
|
||||||
|
/** Validates one immutable model input without retaining Project state. */
|
||||||
|
abstract class ValidateModelInputTask extends DefaultTask {
|
||||||
|
@InputFile @PathSensitive(PathSensitivity.RELATIVE) abstract RegularFileProperty getDictionaryFile()
|
||||||
|
@InputFile @PathSensitive(PathSensitivity.RELATIVE) abstract RegularFileProperty getVersionFile()
|
||||||
|
@Optional @InputFile @PathSensitive(PathSensitivity.RELATIVE) abstract RegularFileProperty getLicenseFile()
|
||||||
|
@Optional @InputFile @PathSensitive(PathSensitivity.RELATIVE) abstract RegularFileProperty getNoticeFile()
|
||||||
|
@Input abstract Property<String> getModelId()
|
||||||
|
@Input abstract Property<String> getModuleName()
|
||||||
|
@Input abstract Property<Boolean> getShareAlike()
|
||||||
|
@Input abstract MapProperty<String, String> getMetadata()
|
||||||
|
|
||||||
|
/** Performs deterministic metadata, licensing, and streaming dictionary validation. */
|
||||||
|
@TaskAction
|
||||||
|
void validateInput() {
|
||||||
|
final File dictionary = dictionaryFile.get().asFile
|
||||||
|
final String id = modelId.get()
|
||||||
|
final String version = versionFile.get().asFile.getText('UTF-8').trim()
|
||||||
|
if (id != moduleName.get() || !(id ==~ /[a-z]{2}(?:-[a-z]{2})?-[a-z0-9]+(?:-[a-z0-9]+)*/)) {
|
||||||
|
throw new GradleException("Model ID '${id}' must equal module '${moduleName.get()}' and use the safe model-ID syntax.")
|
||||||
|
}
|
||||||
|
if (!(version ==~ /[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?/)) {
|
||||||
|
throw new GradleException("Invalid semantic model version '${version}'.")
|
||||||
|
}
|
||||||
|
final Map<String, String> values = metadata.get()
|
||||||
|
values.each { String key, String value ->
|
||||||
|
if (value == null || value.isBlank()) throw new GradleException("Required model metadata is missing: ${key}")
|
||||||
|
}
|
||||||
|
RadixorModelPlugin.validateRevisionMetadata(values['source.revision'], values['source.revisionStatus'])
|
||||||
|
if (shareAlike.get()) {
|
||||||
|
final File notice = noticeFile.get().asFile
|
||||||
|
RadixorModelPlugin.validateShareAlikeNoticeText(notice.getText('UTF-8'), notice.toString(), id,
|
||||||
|
values['source.repository'], values['source.licenseUri'], values['source.revision'],
|
||||||
|
values['source.revisionStatus'])
|
||||||
|
} else {
|
||||||
|
final String text = licenseFile.get().asFile.getText('UTF-8')
|
||||||
|
if (!text.contains('SPDX-License-Identifier: BSD-2-Clause')
|
||||||
|
|| !text.contains('Copyright (c) 2016, Marcin Miłkowski')) {
|
||||||
|
throw new GradleException('The PoliMorf license must contain the complete BSD-2-Clause text and upstream attribution.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RadixorModelPlugin.validateDictionary(dictionary)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.file.RegularFileProperty
|
||||||
|
import org.gradle.api.provider.ListProperty
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.InputFile
|
||||||
|
import org.gradle.api.tasks.OutputFile
|
||||||
|
import org.gradle.api.tasks.PathSensitive
|
||||||
|
import org.gradle.api.tasks.PathSensitivity
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
|
||||||
|
import javax.xml.XMLConstants
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
|
||||||
|
/** Verifies the contents and Maven semantics of the model catalog Central bundle. */
|
||||||
|
abstract class VerifyModelCatalogReleaseCandidateTask extends DefaultTask {
|
||||||
|
@InputFile
|
||||||
|
@PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract RegularFileProperty getBundleFile()
|
||||||
|
|
||||||
|
@OutputFile
|
||||||
|
abstract RegularFileProperty getReportFile()
|
||||||
|
|
||||||
|
@Input abstract Property<String> getCatalogVersion()
|
||||||
|
@Input abstract Property<String> getModelVersion()
|
||||||
|
@Input abstract ListProperty<String> getDefaultModelIds()
|
||||||
|
@Input abstract ListProperty<String> getAllModelIds()
|
||||||
|
|
||||||
|
/** Performs byte-level archive and semantic POM validation. */
|
||||||
|
@TaskAction
|
||||||
|
void verify() {
|
||||||
|
final List<String> entries = verifyBundle(bundleFile.get().asFile, catalogVersion.get(),
|
||||||
|
modelVersion.get(), defaultModelIds.get(), allModelIds.get())
|
||||||
|
final File report = reportFile.get().asFile
|
||||||
|
Files.createDirectories(report.toPath().parent)
|
||||||
|
Files.writeString(report.toPath(), "Bundle: ${bundleFile.get().asFile.name}\nBytes: ${bundleFile.get().asFile.length()}\n"
|
||||||
|
+ entries.join('\n') + '\n', StandardCharsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> verifyBundle(final File bundle, final String catalogVersion,
|
||||||
|
final String modelVersion, final List<String> defaultIds, final List<String> allIds) {
|
||||||
|
if (!bundle.isFile() || bundle.length() == 0L) {
|
||||||
|
throw new GradleException("The model catalog Central bundle is missing or empty: ${bundle}.")
|
||||||
|
}
|
||||||
|
final Map<String, byte[]> content = new TreeMap<>()
|
||||||
|
new ZipFile(bundle).withCloseable { ZipFile archive ->
|
||||||
|
archive.entries().each { ZipEntry entry ->
|
||||||
|
if (!entry.directory) {
|
||||||
|
archive.getInputStream(entry).withCloseable { InputStream input ->
|
||||||
|
content.put(entry.name, input.readAllBytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final List<String> entries = content.keySet().toList()
|
||||||
|
final List<String> poms = entries.findAll { String entry -> entry.endsWith('.pom') }
|
||||||
|
final List<String> unsupported = entries.findAll { String entry ->
|
||||||
|
!(entry ==~ 'org/egothor/radixor-models-(?:standard|bom)/[^/]+/'
|
||||||
|
+ 'radixor-models-(?:standard|bom)-[^/]+\\.pom(?:\\.asc)?(?:\\.(?:md5|sha1))?')
|
||||||
|
}
|
||||||
|
if (!unsupported.isEmpty()) {
|
||||||
|
throw new GradleException("The model catalog bundle contains unsupported files: ${unsupported}.")
|
||||||
|
}
|
||||||
|
if (poms.size() != 2) {
|
||||||
|
throw new GradleException("The model catalog bundle must contain exactly two POM files; found ${poms.size()}.")
|
||||||
|
}
|
||||||
|
if (entries.any { String entry -> entry.endsWith('.jar') || entry.endsWith('/stemmer.gz')
|
||||||
|
|| entry.endsWith('.module') || entry.contains('maven-metadata') || entry.contains('benchmark-pack') }) {
|
||||||
|
throw new GradleException('The model catalog bundle contains forbidden publication content.')
|
||||||
|
}
|
||||||
|
poms.each { String pom -> verifyChecksums(content, pom) }
|
||||||
|
entries.findAll { String entry -> entry.endsWith('.pom.asc') }.each { String signature ->
|
||||||
|
verifyChecksums(content, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
final String standardPath = expectedPomPath('standard', catalogVersion)
|
||||||
|
final String bomPath = expectedPomPath('bom', catalogVersion)
|
||||||
|
if (!content.containsKey(standardPath) || !content.containsKey(bomPath)) {
|
||||||
|
throw new GradleException('The bundle does not contain the expected standard and BOM coordinates.')
|
||||||
|
}
|
||||||
|
final Element standard = parsePom(content.get(standardPath))
|
||||||
|
final Element bom = parsePom(content.get(bomPath))
|
||||||
|
verifyCoordinates(standard, 'radixor-models-standard', catalogVersion)
|
||||||
|
verifyCoordinates(bom, 'radixor-models-bom', catalogVersion)
|
||||||
|
|
||||||
|
final Map<String, String> standardDependencies = dependencies(standard, false)
|
||||||
|
final Map<String, String> bomConstraints = dependencies(bom, true)
|
||||||
|
final Set<String> expectedDefaults = defaultIds.collect { String id -> "org.egothor:radixor-model-${id}" } as Set<String>
|
||||||
|
final Set<String> expectedAll = allIds.collect { String id -> "org.egothor:radixor-model-${id}" } as Set<String>
|
||||||
|
if (standardDependencies.keySet() != expectedDefaults
|
||||||
|
|| standardDependencies.values().any { String version -> version != modelVersion }
|
||||||
|
|| standardDependencies.containsKey('org.egothor:radixor-model-pl-pl-polimorf')
|
||||||
|
|| dependencyScopes(standard).any { String scope -> scope != 'runtime' }) {
|
||||||
|
throw new GradleException('The standard catalog POM must reference exactly the 20 default model artifacts at the model version.')
|
||||||
|
}
|
||||||
|
if (!dependencies(bom, false).isEmpty()) {
|
||||||
|
throw new GradleException('The model BOM must not introduce runtime dependencies.')
|
||||||
|
}
|
||||||
|
if (bomConstraints.keySet() != expectedAll
|
||||||
|
|| bomConstraints.values().any { String version -> version != modelVersion }) {
|
||||||
|
throw new GradleException('The model BOM must manage exactly all 21 model artifacts at the model version.')
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String expectedPomPath(final String kind, final String version) {
|
||||||
|
return "org/egothor/radixor-models-${kind}/${version}/radixor-models-${kind}-${version}.pom"
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyChecksums(final Map<String, byte[]> content, final String artifact) {
|
||||||
|
['MD5': 'md5', 'SHA-1': 'sha1'].each { String algorithm, String extension ->
|
||||||
|
final String checksum = artifact + '.' + extension
|
||||||
|
if (!content.containsKey(checksum)) {
|
||||||
|
throw new GradleException("The catalog artifact is missing its ${algorithm} checksum: ${artifact}.")
|
||||||
|
}
|
||||||
|
final String expected = MessageDigest.getInstance(algorithm).digest(content.get(artifact)).encodeHex().toString()
|
||||||
|
final String actual = new String(content.get(checksum), StandardCharsets.US_ASCII).trim()
|
||||||
|
if (actual != expected) {
|
||||||
|
throw new GradleException("The ${algorithm} checksum does not match ${artifact}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Element parsePom(final byte[] xml) {
|
||||||
|
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
|
||||||
|
factory.setNamespaceAware(true)
|
||||||
|
factory.setFeature('http://apache.org/xml/features/disallow-doctype-decl', true)
|
||||||
|
factory.setFeature('http://xml.org/sax/features/external-general-entities', false)
|
||||||
|
factory.setFeature('http://xml.org/sax/features/external-parameter-entities', false)
|
||||||
|
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, '')
|
||||||
|
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, '')
|
||||||
|
return factory.newDocumentBuilder().parse(new ByteArrayInputStream(xml)).documentElement
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyCoordinates(final Element project, final String artifactId, final String version) {
|
||||||
|
if (directText(project, 'groupId') != 'org.egothor'
|
||||||
|
|| directText(project, 'artifactId') != artifactId
|
||||||
|
|| directText(project, 'version') != version) {
|
||||||
|
throw new GradleException("Unexpected Maven coordinates for ${artifactId}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> dependencies(final Element project, final boolean managed) {
|
||||||
|
final Map<String, String> result = new TreeMap<>()
|
||||||
|
final Element parent = managed ? directChild(project, 'dependencyManagement') : project
|
||||||
|
final Element container = parent == null ? null : directChild(parent, 'dependencies')
|
||||||
|
if (container == null) return result
|
||||||
|
childElements(container, 'dependency').each { Element dependency ->
|
||||||
|
final String coordinate = directText(dependency, 'groupId') + ':' + directText(dependency, 'artifactId')
|
||||||
|
if (result.put(coordinate, directText(dependency, 'version')) != null) {
|
||||||
|
throw new GradleException("The catalog POM contains duplicate dependency ${coordinate}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> dependencyScopes(final Element project) {
|
||||||
|
final Element container = directChild(project, 'dependencies')
|
||||||
|
if (container == null) return []
|
||||||
|
return childElements(container, 'dependency').collect { Element dependency -> directText(dependency, 'scope') }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String directText(final Element parent, final String name) {
|
||||||
|
final Element child = directChild(parent, name)
|
||||||
|
return child == null ? null : child.textContent.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Element directChild(final Element parent, final String name) {
|
||||||
|
if (parent == null) return null
|
||||||
|
for (int index = 0; index < parent.childNodes.length; index++) {
|
||||||
|
if (parent.childNodes.item(index) instanceof Element
|
||||||
|
&& parent.childNodes.item(index).localName == name) return (Element) parent.childNodes.item(index)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Element> childElements(final Element parent, final String name) {
|
||||||
|
final List<Element> result = []
|
||||||
|
for (int index = 0; index < parent.childNodes.length; index++) {
|
||||||
|
if (parent.childNodes.item(index) instanceof Element
|
||||||
|
&& parent.childNodes.item(index).localName == name) result.add((Element) parent.childNodes.item(index))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.testkit.runner.GradleRunner
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.io.TempDir
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
|
||||||
|
/** Exercises catalog publication filtering, isolation, checksums, and semantic verification. */
|
||||||
|
final class ModelCatalogBundleTaskTest {
|
||||||
|
private static final String CATALOG_VERSION = '2026.1'
|
||||||
|
private static final String MODEL_VERSION = '1.0.0'
|
||||||
|
private static final List<String> DEFAULTS = ['alpha', 'beta']
|
||||||
|
private static final List<String> ALL = ['alpha', 'beta', 'pl-pl-polimorf']
|
||||||
|
|
||||||
|
@TempDir Path temporaryDirectory
|
||||||
|
|
||||||
|
/** Prepares exactly two unsigned POMs and their checksums without changing raw bytes. */
|
||||||
|
@Test
|
||||||
|
void preparesUnsignedPublicationsWithoutMutatingRawInput() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
final byte[] before = Files.readAllBytes(standardPom(raw))
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, prepared, CATALOG_VERSION)
|
||||||
|
assertArrayEquals(before, Files.readAllBytes(standardPom(raw)))
|
||||||
|
assertEquals(6L, regularFiles(prepared))
|
||||||
|
assertTrue(Files.isRegularFile(prepared.resolve(relativeStandardPom() + '.md5')))
|
||||||
|
assertTrue(Files.isRegularFile(prepared.resolve(relativeBomPom() + '.sha1')))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Copies test-only signatures and generates checksums for both signatures. */
|
||||||
|
@Test
|
||||||
|
void preparesSignedPublications() {
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(fixture(true), prepared, CATALOG_VERSION)
|
||||||
|
assertEquals(12L, regularFiles(prepared))
|
||||||
|
assertTrue(Files.isRegularFile(prepared.resolve(relativeStandardPom() + '.asc.md5')))
|
||||||
|
assertTrue(Files.isRegularFile(prepared.resolve(relativeBomPom() + '.asc.sha1')))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deletes stale prepared content before copying current publication files. */
|
||||||
|
@Test
|
||||||
|
void removesStalePreparedContent() {
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
Files.createDirectories(prepared)
|
||||||
|
Files.writeString(prepared.resolve('stale.jar'), 'stale')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(fixture(false), prepared, CATALOG_VERSION)
|
||||||
|
assertFalse(Files.exists(prepared.resolve('stale.jar')))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Excludes Gradle module metadata, its sidecars, and Maven metadata. */
|
||||||
|
@Test
|
||||||
|
void excludesModuleAndMavenMetadata() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
final Path module = standardPom(raw).resolveSibling("radixor-models-standard-${CATALOG_VERSION}.module")
|
||||||
|
Files.writeString(module, 'module')
|
||||||
|
Files.writeString(module.resolveSibling(module.fileName.toString() + '.asc'), 'signature')
|
||||||
|
Files.writeString(module.resolveSibling(module.fileName.toString() + '.sha1'), 'checksum')
|
||||||
|
Files.writeString(module.parent.resolve('maven-metadata-local.xml'), 'metadata')
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, prepared, CATALOG_VERSION)
|
||||||
|
assertEquals(6L, regularFiles(prepared))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects a missing standard publication. */
|
||||||
|
@Test
|
||||||
|
void rejectsMissingStandardPom() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
Files.delete(standardPom(raw))
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, temporaryDirectory.resolve('prepared'), CATALOG_VERSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects a missing BOM publication. */
|
||||||
|
@Test
|
||||||
|
void rejectsMissingBomPom() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
Files.delete(bomPom(raw))
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, temporaryDirectory.resolve('prepared'), CATALOG_VERSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects unexpected binary publication content. */
|
||||||
|
@Test
|
||||||
|
void rejectsUnexpectedJar() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
Files.writeString(standardPom(raw).resolveSibling('unexpected.jar'), 'binary')
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, temporaryDirectory.resolve('prepared'), CATALOG_VERSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects dictionary content in the catalog staging repository. */
|
||||||
|
@Test
|
||||||
|
void rejectsDictionaryContent() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
final Path dictionary = raw.resolve('unrelated/stemmer.gz')
|
||||||
|
Files.createDirectories(dictionary.parent)
|
||||||
|
Files.writeString(dictionary, 'dictionary')
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, temporaryDirectory.resolve('prepared'), CATALOG_VERSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Produces and semantically verifies a nonempty ZIP from prepared files. */
|
||||||
|
@Test
|
||||||
|
void verifiesRealPreparedArchive() {
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(fixture(false), prepared, CATALOG_VERSION)
|
||||||
|
final File archive = zip(prepared, temporaryDirectory.resolve('catalog.zip'))
|
||||||
|
final List<String> entries = VerifyModelCatalogReleaseCandidateTask.verifyBundle(
|
||||||
|
archive, CATALOG_VERSION, MODEL_VERSION, DEFAULTS, ALL)
|
||||||
|
assertEquals(6, entries.size())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects an archived checksum that does not match its POM. */
|
||||||
|
@Test
|
||||||
|
void rejectsIncorrectArchivedChecksum() {
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(fixture(false), prepared, CATALOG_VERSION)
|
||||||
|
Files.writeString(prepared.resolve(relativeStandardPom() + '.sha1'), 'incorrect')
|
||||||
|
final File archive = zip(prepared, temporaryDirectory.resolve('catalog.zip'))
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
VerifyModelCatalogReleaseCandidateTask.verifyBundle(
|
||||||
|
archive, CATALOG_VERSION, MODEL_VERSION, DEFAULTS, ALL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Repeated preparation replaces restored or stale output deterministically. */
|
||||||
|
@Test
|
||||||
|
void repeatedPreparationRecreatesValidInput() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, prepared, CATALOG_VERSION)
|
||||||
|
final String first = treeDigest(prepared)
|
||||||
|
Files.writeString(prepared.resolve('restored-history-stale.txt'), 'stale')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, prepared, CATALOG_VERSION)
|
||||||
|
assertEquals(first, treeDigest(prepared))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a real Gradle ZIP, rebuilds a missing output, and reuses Configuration Cache. */
|
||||||
|
@Test
|
||||||
|
void gradleZipRebuildsWithConfigurationCacheReuse() {
|
||||||
|
final Path project = temporaryDirectory.resolve('testkit-project')
|
||||||
|
Files.createDirectories(project)
|
||||||
|
Files.writeString(project.resolve('settings.gradle'), "rootProject.name = 'catalog-fixture'\n")
|
||||||
|
Files.writeString(project.resolve('build.gradle'), '''plugins {
|
||||||
|
id 'org.egothor.radixor.build-support'
|
||||||
|
}
|
||||||
|
tasks.named('prepareModelCatalogReleaseCandidate') {
|
||||||
|
rawRepositoryDirectory = layout.projectDirectory.dir('raw')
|
||||||
|
preparedBundleDirectory = layout.buildDirectory.dir('prepared')
|
||||||
|
catalogVersion = '2026.1'
|
||||||
|
}
|
||||||
|
tasks.register('bundle', Zip) {
|
||||||
|
dependsOn(tasks.named('prepareModelCatalogReleaseCandidate'))
|
||||||
|
from(layout.buildDirectory.dir('prepared'))
|
||||||
|
destinationDirectory = layout.buildDirectory.dir('candidate')
|
||||||
|
archiveFileName = 'catalog.zip'
|
||||||
|
}
|
||||||
|
''')
|
||||||
|
final Path raw = project.resolve('raw')
|
||||||
|
write(standardPom(raw), pom('radixor-models-standard', false))
|
||||||
|
write(bomPom(raw), pom('radixor-models-bom', true))
|
||||||
|
|
||||||
|
final List<String> arguments = ['bundle', '--configuration-cache',
|
||||||
|
'--configuration-cache-problems=fail', '--warning-mode=fail']
|
||||||
|
final String first = GradleRunner.create().withProjectDir(project.toFile())
|
||||||
|
.withPluginClasspath().withArguments(arguments).build().output
|
||||||
|
final Path archive = project.resolve('build/candidate/catalog.zip')
|
||||||
|
assertTrue(Files.size(archive) > 0L)
|
||||||
|
Files.delete(archive)
|
||||||
|
final String second = GradleRunner.create().withProjectDir(project.toFile())
|
||||||
|
.withPluginClasspath().withArguments(arguments).build().output
|
||||||
|
assertTrue(Files.size(archive) > 0L)
|
||||||
|
assertTrue(first.contains('Configuration cache entry stored.'))
|
||||||
|
assertTrue(second.contains('Configuration cache entry reused.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path fixture(final boolean signed) {
|
||||||
|
final Path raw = temporaryDirectory.resolve('raw')
|
||||||
|
write(standardPom(raw), pom('radixor-models-standard', false))
|
||||||
|
write(bomPom(raw), pom('radixor-models-bom', true))
|
||||||
|
if (signed) {
|
||||||
|
Files.writeString(standardPom(raw).resolveSibling(standardPom(raw).fileName.toString() + '.asc'), 'test signature')
|
||||||
|
Files.writeString(bomPom(raw).resolveSibling(bomPom(raw).fileName.toString() + '.asc'), 'test signature')
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String pom(final String artifact, final boolean managed) {
|
||||||
|
final List<String> ids = managed ? ALL : DEFAULTS
|
||||||
|
final String dependencies = ids.collect { String id ->
|
||||||
|
"<dependency><groupId>org.egothor</groupId><artifactId>radixor-model-${id}</artifactId>" +
|
||||||
|
"<version>${MODEL_VERSION}</version>${managed ? '' : '<scope>runtime</scope>'}</dependency>"
|
||||||
|
}.join()
|
||||||
|
final String body = managed ? "<dependencyManagement><dependencies>${dependencies}</dependencies></dependencyManagement>"
|
||||||
|
: "<dependencies>${dependencies}</dependencies>"
|
||||||
|
return "<?xml version=\"1.0\"?><project xmlns=\"http://maven.apache.org/POM/4.0.0\">" +
|
||||||
|
"<modelVersion>4.0.0</modelVersion><groupId>org.egothor</groupId>" +
|
||||||
|
"<artifactId>${artifact}</artifactId><version>${CATALOG_VERSION}</version>${body}</project>"
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path standardPom(final Path raw) { raw.resolve(relativeStandardPom()) }
|
||||||
|
private static Path bomPom(final Path raw) { raw.resolve(relativeBomPom()) }
|
||||||
|
private static String relativeStandardPom() {
|
||||||
|
"org/egothor/radixor-models-standard/${CATALOG_VERSION}/radixor-models-standard-${CATALOG_VERSION}.pom"
|
||||||
|
}
|
||||||
|
private static String relativeBomPom() {
|
||||||
|
"org/egothor/radixor-models-bom/${CATALOG_VERSION}/radixor-models-bom-${CATALOG_VERSION}.pom"
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void write(final Path path, final String value) {
|
||||||
|
Files.createDirectories(path.parent)
|
||||||
|
Files.writeString(path, value, StandardCharsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long regularFiles(final Path root) {
|
||||||
|
Files.walk(root).withCloseable { paths -> paths.filter(Files::isRegularFile).count() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File zip(final Path root, final Path target) {
|
||||||
|
new ZipOutputStream(Files.newOutputStream(target)).withCloseable { ZipOutputStream output ->
|
||||||
|
Files.walk(root).withCloseable { paths ->
|
||||||
|
paths.filter(Files::isRegularFile).sorted().forEach { Path file ->
|
||||||
|
output.putNextEntry(new ZipEntry(root.relativize(file).toString().replace(File.separatorChar, '/' as char)))
|
||||||
|
Files.copy(file, output)
|
||||||
|
output.closeEntry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target.toFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String treeDigest(final Path root) {
|
||||||
|
final MessageDigest digest = MessageDigest.getInstance('SHA-256')
|
||||||
|
Files.walk(root).withCloseable { paths ->
|
||||||
|
paths.filter(Files::isRegularFile).sorted().forEach { Path path ->
|
||||||
|
digest.update(root.relativize(path).toString().getBytes(StandardCharsets.UTF_8))
|
||||||
|
digest.update(Files.readAllBytes(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return digest.digest().encodeHex().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.io.TempDir
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.util.zip.GZIPOutputStream
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
|
||||||
|
/** Tests model licensing metadata and packaged-resource validation boundaries. */
|
||||||
|
final class RadixorModelPluginTest {
|
||||||
|
@TempDir
|
||||||
|
Path temporaryDirectory
|
||||||
|
|
||||||
|
/** Accepts a known exact source revision. */
|
||||||
|
@Test
|
||||||
|
void acceptsKnownExactRevision() {
|
||||||
|
RadixorModelPlugin.validateRevisionMetadata('6e63b53', 'recorded')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Accepts the explicit legacy-import sentinel without fabricating a revision. */
|
||||||
|
@Test
|
||||||
|
void acceptsUnknownLegacyRevision() {
|
||||||
|
RadixorModelPlugin.validateRevisionMetadata(
|
||||||
|
'not-recorded-in-legacy-import', 'not-recorded-in-legacy-import')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects a missing revision-status declaration. */
|
||||||
|
@Test
|
||||||
|
void rejectsMissingRevisionStatus() {
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
RadixorModelPlugin.validateRevisionMetadata('6e63b53', '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects a missing model-specific notice input. */
|
||||||
|
@Test
|
||||||
|
void rejectsMissingLicensingInputs() {
|
||||||
|
File missing = new File('build/nonexistent-model-licensing-input')
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
RadixorModelPlugin.requireFile(missing, 'Required model notice is missing')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Accepts a complete model-specific UniMorph notice. */
|
||||||
|
@Test
|
||||||
|
void acceptsCompleteUniMorphNotice() {
|
||||||
|
validateNotice(validNotice())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects each independently required notice statement. */
|
||||||
|
@Test
|
||||||
|
void rejectsIncompleteUniMorphNotices() {
|
||||||
|
[
|
||||||
|
'Copyright (C) 2026, Leo Galambos.',
|
||||||
|
'Attribution:',
|
||||||
|
'Creative Commons Attribution-ShareAlike 3.0 Unported',
|
||||||
|
'Canonical license URI:',
|
||||||
|
"This derived model data, including Radixor's protectable contributions,",
|
||||||
|
'Radixor modifications:',
|
||||||
|
'Revision status:',
|
||||||
|
'Neither UniMorph nor any upstream contributor endorses Radixor.'
|
||||||
|
].each { String required ->
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
validateNotice(validNotice().replace(required, 'omitted'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects packaged notice bytes that differ from their model-module source. */
|
||||||
|
@Test
|
||||||
|
void rejectsIncorrectPackagedNotice() {
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
RadixorModelPlugin.requireMatchingChecksum(
|
||||||
|
'notice', 'META-INF/NOTICE/test-model-data.txt', 'source', 'different')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects UniMorph CC material in the separately licensed PoliMorf artifact. */
|
||||||
|
@Test
|
||||||
|
void rejectsUniMorphMaterialInPoliMorf() {
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
RadixorModelPlugin.validatePoliMorfJarContents(
|
||||||
|
['META-INF/LICENSES/PoliMorf-BSD-2-Clause.txt', 'META-INF/NOTICE/test-data.txt'])
|
||||||
|
}
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
RadixorModelPlugin.validatePoliMorfJarContents(
|
||||||
|
['META-INF/LICENSES/PoliMorf-BSD-2-Clause.txt', 'META-INF/LICENSES/CC-BY-SA-3.0.txt'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Streams a large dictionary while retaining only aggregate counters and the current row. */
|
||||||
|
@Test
|
||||||
|
void validatesLargeDictionaryWithBoundedState() {
|
||||||
|
final int groups = 250_000
|
||||||
|
final File dictionary = temporaryDirectory.resolve('large.gz').toFile()
|
||||||
|
writeGzip(dictionary) { BufferedWriter writer ->
|
||||||
|
for (int index = 0; index < groups; index++) {
|
||||||
|
writer.write("stem${index}\tvariant${index}\t\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final RadixorModelPlugin.DictionaryValidationResult result =
|
||||||
|
RadixorModelPlugin.validateDictionary(dictionary)
|
||||||
|
|
||||||
|
assertEquals(groups, result.acceptedGroupCount)
|
||||||
|
assertEquals(groups * 2L, result.acceptedFormCount)
|
||||||
|
assertEquals(groups, result.ignoredEmptyVariantCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects a source that is not a GZip stream. */
|
||||||
|
@Test
|
||||||
|
void rejectsInvalidGzip() {
|
||||||
|
final File dictionary = temporaryDirectory.resolve('invalid.gz').toFile()
|
||||||
|
Files.writeString(dictionary.toPath(), 'not gzip', StandardCharsets.UTF_8)
|
||||||
|
assertThrows(GradleException) { RadixorModelPlugin.validateDictionary(dictionary) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects malformed UTF-8 through the strict incremental decoder. */
|
||||||
|
@Test
|
||||||
|
void rejectsMalformedUtf8() {
|
||||||
|
final File dictionary = temporaryDirectory.resolve('malformed-utf8.gz').toFile()
|
||||||
|
new GZIPOutputStream(Files.newOutputStream(dictionary.toPath())).withCloseable { OutputStream output ->
|
||||||
|
output.write([0x73, 0x74, 0x65, 0x6d, 0x09, 0xc3, 0x28, 0x0a] as byte[])
|
||||||
|
}
|
||||||
|
assertThrows(GradleException) { RadixorModelPlugin.validateDictionary(dictionary) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects structurally invalid rows with an empty stem. */
|
||||||
|
@Test
|
||||||
|
void rejectsInvalidRows() {
|
||||||
|
final File dictionary = temporaryDirectory.resolve('invalid-row.gz').toFile()
|
||||||
|
writeGzip(dictionary) { BufferedWriter writer -> writer.write("\tvariant\n") }
|
||||||
|
assertThrows(GradleException) { RadixorModelPlugin.validateDictionary(dictionary) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Preserves the production parser policy for Unicode-whitespace items. */
|
||||||
|
@Test
|
||||||
|
void rejectsUnicodeWhitespaceItemsWithoutRejectingTheSource() {
|
||||||
|
final File dictionary = temporaryDirectory.resolve('whitespace-items.gz').toFile()
|
||||||
|
writeGzip(dictionary) { BufferedWriter writer ->
|
||||||
|
writer.write("invalid stem\tvariant\n")
|
||||||
|
writer.write("valid\taccepted\tinvalid variant\n")
|
||||||
|
}
|
||||||
|
final RadixorModelPlugin.DictionaryValidationResult result =
|
||||||
|
RadixorModelPlugin.validateDictionary(dictionary)
|
||||||
|
assertEquals(1L, result.acceptedGroupCount)
|
||||||
|
assertEquals(2L, result.acceptedFormCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Streams the complete maintained PoliMorf model input successfully. */
|
||||||
|
@Test
|
||||||
|
void validatesFullPoliMorfInput() {
|
||||||
|
final List<File> candidates = [
|
||||||
|
new File('models/pl-pl-polimorf/src/modelInput/stemmer.gz'),
|
||||||
|
new File('../models/pl-pl-polimorf/src/modelInput/stemmer.gz')]
|
||||||
|
final File dictionary = candidates.find { File candidate -> candidate.isFile() }
|
||||||
|
assertTrue(dictionary != null, 'The complete PoliMorf model input must be available to build-logic tests.')
|
||||||
|
|
||||||
|
final RadixorModelPlugin.DictionaryValidationResult result =
|
||||||
|
RadixorModelPlugin.validateDictionary(dictionary)
|
||||||
|
assertTrue(result.acceptedGroupCount > 0L)
|
||||||
|
assertTrue(result.acceptedFormCount > result.acceptedGroupCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeGzip(final File target, final Closure<Void> content) {
|
||||||
|
new GZIPOutputStream(Files.newOutputStream(target.toPath())).withCloseable { OutputStream gzip ->
|
||||||
|
new BufferedWriter(new OutputStreamWriter(gzip, StandardCharsets.UTF_8)).withCloseable {
|
||||||
|
BufferedWriter writer -> content.call(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateNotice(final String text) {
|
||||||
|
RadixorModelPlugin.validateShareAlikeNoticeText(text, 'test notice', 'test-model',
|
||||||
|
'https://github.com/unimorph/test', 'https://creativecommons.org/licenses/by-sa/3.0/',
|
||||||
|
'not-recorded-in-legacy-import', 'not-recorded-in-legacy-import')
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String validNotice() {
|
||||||
|
return '''Model ID: test-model
|
||||||
|
Official repository: https://github.com/unimorph/test
|
||||||
|
Attribution: UniMorph and upstream contributors
|
||||||
|
License:
|
||||||
|
Creative Commons Attribution-ShareAlike 3.0 Unported
|
||||||
|
Canonical license URI: https://creativecommons.org/licenses/by-sa/3.0/
|
||||||
|
Radixor modifications: Cleaning and packaging.
|
||||||
|
Revision status: not-recorded-in-legacy-import
|
||||||
|
The exact UniMorph commit used for the original Radixor import was not recorded.
|
||||||
|
Copyright (C) 2026, Leo Galambos.
|
||||||
|
Radixor-specific selection, verification, cleaning, normalization,
|
||||||
|
to the extent protected by applicable law.
|
||||||
|
The underlying morphological data remains attributed to UniMorph and
|
||||||
|
This derived model data, including Radixor's protectable contributions,
|
||||||
|
is distributed under Creative Commons Attribution-ShareAlike 3.0
|
||||||
|
Neither UniMorph nor any upstream contributor endorses Radixor.
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
674
build.gradle
@@ -1,4 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
|
id 'org.egothor.radixor.build-support'
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'eclipse'
|
id 'eclipse'
|
||||||
id 'application'
|
id 'application'
|
||||||
@@ -7,9 +8,9 @@ plugins {
|
|||||||
id 'pmd'
|
id 'pmd'
|
||||||
id 'jacoco'
|
id 'jacoco'
|
||||||
id 'info.solidsoft.pitest' version '1.19.0'
|
id 'info.solidsoft.pitest' version '1.19.0'
|
||||||
id 'me.champeau.jmh' version '0.7.2'
|
id 'me.champeau.jmh' version '0.7.3'
|
||||||
id 'org.owasp.dependencycheck' version '12.2.1'
|
id 'org.owasp.dependencycheck' version '12.2.1'
|
||||||
id 'org.cyclonedx.bom' version '3.2.4'
|
id 'org.cyclonedx.bom' version '3.3.0'
|
||||||
id 'com.palantir.git-version' version '4.0.0'
|
id 'com.palantir.git-version' version '4.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,6 +19,8 @@ version = gitVersion(prefix:'release@')
|
|||||||
|
|
||||||
def benchmarkReportsDirectory = layout.buildDirectory.dir('reports/jmh')
|
def benchmarkReportsDirectory = layout.buildDirectory.dir('reports/jmh')
|
||||||
def sbomReportsDirectory = layout.buildDirectory.dir('reports/sbom')
|
def sbomReportsDirectory = layout.buildDirectory.dir('reports/sbom')
|
||||||
|
def jmhIncludesProperty = providers.gradleProperty('jmh.includes')
|
||||||
|
.orElse(providers.systemProperty('jmh.includes'))
|
||||||
|
|
||||||
def nvdApiKey = providers.gradleProperty('nvdApiKey')
|
def nvdApiKey = providers.gradleProperty('nvdApiKey')
|
||||||
.orElse(providers.environmentVariable('NVD_API_KEY'))
|
.orElse(providers.environmentVariable('NVD_API_KEY'))
|
||||||
@@ -28,6 +31,11 @@ apply from: 'gradle/maven-pom.gradle'
|
|||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
mockitoAgent
|
mockitoAgent
|
||||||
|
stemmingQualityJmhRuntime {
|
||||||
|
canBeConsumed = false
|
||||||
|
canBeResolved = true
|
||||||
|
extendsFrom(jmhImplementation, jmhRuntimeOnly)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@@ -38,6 +46,10 @@ java {
|
|||||||
targetCompatibility = JavaVersion.VERSION_21
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
options.compilerArgs.addAll(['-Xlint:deprecation', '-Xlint:unchecked'])
|
||||||
|
}
|
||||||
|
|
||||||
tasks.withType(AbstractArchiveTask).configureEach {
|
tasks.withType(AbstractArchiveTask).configureEach {
|
||||||
preserveFileTimestamps = false
|
preserveFileTimestamps = false
|
||||||
reproducibleFileOrder = true
|
reproducibleFileOrder = true
|
||||||
@@ -63,6 +75,11 @@ dependencyLocking {
|
|||||||
dependencies {
|
dependencies {
|
||||||
jmhImplementation sourceSets.main.output
|
jmhImplementation sourceSets.main.output
|
||||||
|
|
||||||
|
modelProjects().each { Project modelProject ->
|
||||||
|
testRuntimeOnly project(modelProject.path)
|
||||||
|
jmhRuntimeOnly project(modelProject.path)
|
||||||
|
}
|
||||||
|
|
||||||
testImplementation platform(libs.junit.bom)
|
testImplementation platform(libs.junit.bom)
|
||||||
testImplementation libs.junit.jupiter
|
testImplementation libs.junit.jupiter
|
||||||
testRuntimeOnly libs.junit.platform.launcher
|
testRuntimeOnly libs.junit.platform.launcher
|
||||||
@@ -70,12 +87,55 @@ dependencies {
|
|||||||
testImplementation libs.mockito.core
|
testImplementation libs.mockito.core
|
||||||
testImplementation libs.mockito.junit.jupiter
|
testImplementation libs.mockito.junit.jupiter
|
||||||
testImplementation libs.jqwik
|
testImplementation libs.jqwik
|
||||||
|
testImplementation gradleTestKit()
|
||||||
|
|
||||||
mockitoAgent(libs.mockito.core) {
|
mockitoAgent(libs.mockito.core) {
|
||||||
transitive = false
|
transitive = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def modelProjects() {
|
||||||
|
Properties topology = new Properties()
|
||||||
|
rootProject.file('models/model-projects.properties').withInputStream { InputStream input ->
|
||||||
|
topology.load(input)
|
||||||
|
}
|
||||||
|
return topology.stringPropertyNames().toList().sort().collect { String modelId ->
|
||||||
|
project(":models:${modelId}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def defaultModelProjects() {
|
||||||
|
Properties topology = new Properties()
|
||||||
|
rootProject.file('models/model-projects.properties').withInputStream { InputStream input ->
|
||||||
|
topology.load(input)
|
||||||
|
}
|
||||||
|
return topology.stringPropertyNames().findAll { String modelId ->
|
||||||
|
topology.getProperty(modelId) == 'default'
|
||||||
|
}.sort().collect { String modelId -> project(":models:${modelId}") }
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('projects') {
|
||||||
|
actions.clear()
|
||||||
|
doLast {
|
||||||
|
logger.lifecycle('Root project \'{}\'', rootProject.name)
|
||||||
|
rootProject.allprojects.findAll { Project candidate -> candidate != rootProject }
|
||||||
|
.sort { Project left, Project right -> left.path <=> right.path }
|
||||||
|
.each { Project candidate -> logger.lifecycle('+--- Project \'{}\'', candidate.path) }
|
||||||
|
gradle.includedBuilds.toList().sort { left, right -> left.name <=> right.name }
|
||||||
|
.each { includedBuild -> logger.lifecycle('Included build \'{}\'', includedBuild.name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets.jmh.compileClasspath = sourceSets.jmh.compileClasspath - sourceSets.test.output
|
||||||
|
sourceSets.jmh.runtimeClasspath = sourceSets.jmh.runtimeClasspath - sourceSets.test.output
|
||||||
|
sourceSets.test.compileClasspath += sourceSets.jmh.output + configurations.jmhCompileClasspath
|
||||||
|
sourceSets.test.runtimeClasspath += sourceSets.jmh.output + configurations.jmhCompileClasspath
|
||||||
|
|
||||||
|
tasks.named('compileJmhJava', JavaCompile) {
|
||||||
|
classpath = classpath - sourceSets.test.output
|
||||||
|
setDependsOn([tasks.named('classes')])
|
||||||
|
}
|
||||||
|
|
||||||
dependencyCheck {
|
dependencyCheck {
|
||||||
failBuildOnCVSS = 7.0
|
failBuildOnCVSS = 7.0
|
||||||
failOnError = true
|
failOnError = true
|
||||||
@@ -121,9 +181,10 @@ def splitTagExpression = { String tagsExpr ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(Test).configureEach {
|
tasks.withType(Test).configureEach {
|
||||||
doFirst {
|
final def mockitoAgentArguments = objects.newInstance(
|
||||||
jvmArgs "-javaagent:${configurations.mockitoAgent.singleFile}"
|
org.egothor.radixor.MockitoAgentArgumentProvider)
|
||||||
}
|
mockitoAgentArguments.agentClasspath.from(configurations.mockitoAgent)
|
||||||
|
jvmArgumentProviders.add(mockitoAgentArguments)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Bundled dictionary integration tests compile and reload large real-world
|
* Bundled dictionary integration tests compile and reload large real-world
|
||||||
@@ -139,6 +200,30 @@ tasks.withType(Test).configureEach {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.named('test', Test) {
|
||||||
|
dependsOn('prepareModelConsumerTestRepository')
|
||||||
|
systemProperty('radixor.consumer.repository',
|
||||||
|
layout.buildDirectory.dir('model-consumer-repository').get().asFile.absolutePath)
|
||||||
|
systemProperty('radixor.core.version', version.toString())
|
||||||
|
systemProperty('radixor.catalog.version', project(':models:standard').version.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('modelDependencyResolutionTest', Test) {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Verifies that published model coordinates resolve from the generated consumer repository.'
|
||||||
|
dependsOn(tasks.named('prepareModelConsumerTestRepository'))
|
||||||
|
testClassesDirs = sourceSets.test.output.classesDirs
|
||||||
|
classpath = sourceSets.test.runtimeClasspath
|
||||||
|
useJUnitPlatform()
|
||||||
|
filter {
|
||||||
|
includeTestsMatching('org.egothor.stemmer.ModelDependencyResolutionTest')
|
||||||
|
}
|
||||||
|
systemProperty('radixor.consumer.repository',
|
||||||
|
layout.buildDirectory.dir('model-consumer-repository').get().asFile.absolutePath)
|
||||||
|
systemProperty('radixor.core.version', version.toString())
|
||||||
|
systemProperty('radixor.catalog.version', project(':models:standard').version.toString())
|
||||||
|
}
|
||||||
|
|
||||||
def configureJUnitPlatformTags = { Test task, String includeTagsExpr, String excludeTagsExpr ->
|
def configureJUnitPlatformTags = { Test task, String includeTagsExpr, String excludeTagsExpr ->
|
||||||
task.useJUnitPlatform {
|
task.useJUnitPlatform {
|
||||||
final def includes = splitTagExpression(includeTagsExpr)
|
final def includes = splitTagExpression(includeTagsExpr)
|
||||||
@@ -154,10 +239,49 @@ def configureJUnitPlatformTags = { Test task, String includeTagsExpr, String exc
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('test', Test) {
|
tasks.named('test', Test) {
|
||||||
configureJUnitPlatformTags(it, cliIncludeTags, cliExcludeTags)
|
final def requestedIncludes = splitTagExpression(cliIncludeTags)
|
||||||
|
final boolean slowExplicitlyIncluded = requestedIncludes.contains('slow')
|
||||||
|
final String defaultExcludeTags = cliExcludeTags ?: (slowExplicitlyIncluded ? 'large-model' : 'slow,large-model')
|
||||||
|
configureJUnitPlatformTags(it, cliIncludeTags, defaultExcludeTags)
|
||||||
finalizedBy(tasks.named('jacocoTestReport'))
|
finalizedBy(tasks.named('jacocoTestReport'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def largeModelMaxHeap = providers.gradleProperty('radixorLargeModelMaxHeap').orElse('6g')
|
||||||
|
def runtimeModelId = providers.gradleProperty('modelId').orElse('pl-pl-polimorf')
|
||||||
|
def runtimeModelClasspath = configurations.testRuntimeClasspath.incoming.artifactView {
|
||||||
|
componentFilter { componentIdentifier ->
|
||||||
|
if (!(componentIdentifier instanceof org.gradle.api.artifacts.component.ProjectComponentIdentifier)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
final String projectPath = componentIdentifier.projectPath
|
||||||
|
return !projectPath.startsWith(':models:') || projectPath == ":models:${runtimeModelId.get()}"
|
||||||
|
}
|
||||||
|
}.files
|
||||||
|
|
||||||
|
tasks.register('runtimeModelIntegrationTest', Test) {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Constructs one complete selected runtime model in an isolated, memory-sized JVM.'
|
||||||
|
testClassesDirs = sourceSets.test.output.classesDirs
|
||||||
|
classpath = sourceSets.test.output + sourceSets.main.output + sourceSets.jmh.output + runtimeModelClasspath
|
||||||
|
dependsOn(tasks.named('compileTestJava'))
|
||||||
|
useJUnitPlatform {
|
||||||
|
includeTags('large-model')
|
||||||
|
}
|
||||||
|
systemProperty('radixor.test.modelId', runtimeModelId.get())
|
||||||
|
minHeapSize = '1g'
|
||||||
|
maxHeapSize = largeModelMaxHeap.get()
|
||||||
|
maxParallelForks = 1
|
||||||
|
forkEvery = 1
|
||||||
|
reports {
|
||||||
|
junitXml.required = true
|
||||||
|
html.required = true
|
||||||
|
}
|
||||||
|
doFirst {
|
||||||
|
logger.lifecycle("Runtime model integration uses model '{}' with maximum heap {}.",
|
||||||
|
systemProperties.get('radixor.test.modelId'), maxHeapSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def configureTaggedTestProfile = { String taskName, String includeTagsExpr, String excludeTagsExpr = null,
|
def configureTaggedTestProfile = { String taskName, String includeTagsExpr, String excludeTagsExpr = null,
|
||||||
String taskDescription = null, String testNameExcludePatterns = null ->
|
String taskDescription = null, String testNameExcludePatterns = null ->
|
||||||
tasks.register(taskName, Test) {
|
tasks.register(taskName, Test) {
|
||||||
@@ -169,10 +293,6 @@ def configureTaggedTestProfile = { String taskName, String includeTagsExpr, Stri
|
|||||||
classpath = sourceSets.test.runtimeClasspath
|
classpath = sourceSets.test.runtimeClasspath
|
||||||
dependsOn(tasks.named('compileTestJava'))
|
dependsOn(tasks.named('compileTestJava'))
|
||||||
|
|
||||||
doFirst {
|
|
||||||
jvmArgs "-javaagent:${configurations.mockitoAgent.singleFile}"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testNameExcludePatterns != null && !testNameExcludePatterns.isBlank()) {
|
if (testNameExcludePatterns != null && !testNameExcludePatterns.isBlank()) {
|
||||||
filter {
|
filter {
|
||||||
testNameExcludePatterns.split(',').each { String pattern ->
|
testNameExcludePatterns.split(',').each { String pattern ->
|
||||||
@@ -233,11 +353,19 @@ configureTaggedTestProfile(
|
|||||||
configureTaggedTestProfile(
|
configureTaggedTestProfile(
|
||||||
'ciRelease',
|
'ciRelease',
|
||||||
null,
|
null,
|
||||||
'slow',
|
'slow,large-model',
|
||||||
'Release-profile validation of all non-slow tests.',
|
'Release-profile validation of all non-slow tests.',
|
||||||
'org.egothor.stemmer.CompileIntegrationTest*,org.egothor.stemmer.StemmerPatchTrieLoaderTest$BundledDictionaryTests*'
|
'org.egothor.stemmer.CompileIntegrationTest*,org.egothor.stemmer.StemmerPatchTrieLoaderTest$BundledDictionaryTests*'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tasks.named('ciRelease', Test) {
|
||||||
|
dependsOn('prepareModelConsumerTestRepository')
|
||||||
|
systemProperty('radixor.consumer.repository',
|
||||||
|
layout.buildDirectory.dir('model-consumer-repository').get().asFile.absolutePath)
|
||||||
|
systemProperty('radixor.core.version', version.toString())
|
||||||
|
systemProperty('radixor.catalog.version', project(':models:standard').version.toString())
|
||||||
|
}
|
||||||
|
|
||||||
configureTaggedTestProfile(
|
configureTaggedTestProfile(
|
||||||
'ciNightly',
|
'ciNightly',
|
||||||
'fuzz',
|
'fuzz',
|
||||||
@@ -313,25 +441,387 @@ tasks.named('check') {
|
|||||||
// no-default, only on-demand: dependsOn(tasks.named('dependencyCheckAnalyze'))
|
// no-default, only on-demand: dependsOn(tasks.named('dependencyCheckAnalyze'))
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
tasks.register('verifyCoreJarExcludesModels') {
|
||||||
tasks.matching { it.name == 'cyclonedxDirectBom' }.configureEach {
|
group = 'verification'
|
||||||
|
description = 'Verifies that the root Radixor JAR contains no language dictionary bytes.'
|
||||||
|
dependsOn(tasks.named('jar'))
|
||||||
|
doLast {
|
||||||
|
File archive = tasks.named('jar', Jar).get().archiveFile.get().asFile
|
||||||
|
List<String> dictionaries = []
|
||||||
|
new java.util.zip.ZipFile(archive).withCloseable { zip ->
|
||||||
|
zip.entries().each { entry -> if (entry.name.endsWith('/stemmer.gz')) dictionaries.add(entry.name) }
|
||||||
|
}
|
||||||
|
if (!dictionaries.isEmpty()) {
|
||||||
|
throw new GradleException('The org.egothor:radixor JAR must not contain model data: ' + dictionaries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('verifyJavaLicenseHeaders') {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Verifies deterministic license classification for every maintained Java source file.'
|
||||||
|
inputs.file(layout.projectDirectory.file('gradle/java-license-header.txt'))
|
||||||
|
inputs.files(fileTree('src/main/java') { include '**/*.java' })
|
||||||
|
inputs.files(fileTree('src/test/java') { include '**/*.java' })
|
||||||
|
inputs.files(fileTree('src/jmh/java') { include '**/*.java' })
|
||||||
|
outputs.file(layout.buildDirectory.file('reports/license/java-license-headers.txt'))
|
||||||
|
doLast {
|
||||||
|
String canonicalHeader = layout.projectDirectory.file('gradle/java-license-header.txt')
|
||||||
|
.asFile.getText('UTF-8')
|
||||||
|
File canonicalSource = file('src/main/java/org/egothor/stemmer/CaseProcessingMode.java')
|
||||||
|
if (!canonicalSource.getText('UTF-8').startsWith(canonicalHeader)) {
|
||||||
|
throw new GradleException('CaseProcessingMode.java does not begin with the canonical Radixor license template.')
|
||||||
|
}
|
||||||
|
|
||||||
|
List<File> maintainedSources = files(
|
||||||
|
fileTree('src/main/java') { include '**/*.java' },
|
||||||
|
fileTree('src/test/java') { include '**/*.java' },
|
||||||
|
fileTree('src/jmh/java') { include '**/*.java' })
|
||||||
|
.files.toList().sort { File left, File right ->
|
||||||
|
relativePath(left) <=> relativePath(right)
|
||||||
|
}
|
||||||
|
List<String> classifications = []
|
||||||
|
List<String> failures = []
|
||||||
|
maintainedSources.each { File sourceFile ->
|
||||||
|
String relative = relativePath(sourceFile)
|
||||||
|
String content = sourceFile.getText('UTF-8')
|
||||||
|
if (content.startsWith(canonicalHeader)) {
|
||||||
|
classifications.add("RADIXOR_CANONICAL_HEADER ${relative}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
String leadingNotice = ''
|
||||||
|
if (content.startsWith('/*')) {
|
||||||
|
int closingIndex = content.indexOf('*/')
|
||||||
|
if (closingIndex >= 0) {
|
||||||
|
leadingNotice = content.substring(0, closingIndex + 2)
|
||||||
|
}
|
||||||
|
} else if (content.startsWith('//')) {
|
||||||
|
leadingNotice = content.readLines().takeWhile { String line ->
|
||||||
|
line.startsWith('//') || line.isBlank()
|
||||||
|
}.join('\n')
|
||||||
|
}
|
||||||
|
String remainder = content.substring(leadingNotice.length()).stripLeading()
|
||||||
|
boolean duplicateNotice = !leadingNotice.isEmpty()
|
||||||
|
&& (remainder.startsWith('/*') || remainder.startsWith('//'))
|
||||||
|
boolean historicalRadixor = leadingNotice =~ /(?s)Copyright \(C\) \d{4}(?:-\d{4})?, Leo Galambos/
|
||||||
|
&& leadingNotice.contains('All rights reserved.')
|
||||||
|
&& leadingNotice.contains('Redistribution and use in source and binary forms')
|
||||||
|
&& leadingNotice.contains('THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS')
|
||||||
|
boolean thirdPartyOrProvenance = leadingNotice =~ /(?is)(SPDX-License-Identifier|Licensed under|MIT License|Apache License|Permission is hereby granted|Original source|Adapted from|Ported from|Source:\s*\S)/
|
||||||
|
if (duplicateNotice) {
|
||||||
|
classifications.add("AMBIGUOUS_AUTHORSHIP ${relative}")
|
||||||
|
failures.add("${relative}: duplicate leading comment blocks")
|
||||||
|
} else if (historicalRadixor) {
|
||||||
|
classifications.add("RADIXOR_HISTORICAL_HEADER ${relative}")
|
||||||
|
} else if (thirdPartyOrProvenance) {
|
||||||
|
classifications.add("THIRD_PARTY_OR_PROVENANCE_HEADER ${relative}")
|
||||||
|
} else {
|
||||||
|
classifications.add("AMBIGUOUS_AUTHORSHIP ${relative}")
|
||||||
|
failures.add("${relative}: no recognized governing license or provenance header")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File report = layout.buildDirectory.file('reports/license/java-license-headers.txt').get().asFile
|
||||||
|
report.parentFile.mkdirs()
|
||||||
|
report.setText(classifications.join('\n') + '\n', 'UTF-8')
|
||||||
|
if (!failures.isEmpty()) {
|
||||||
|
throw new GradleException('Maintained Java license verification failed: '
|
||||||
|
+ failures.sort().join(', '))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('verifyAllDefaultModels') {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Verifies that every language default model project is configured.'
|
||||||
|
dependsOn(defaultModelProjects().collect { Project modelProject ->
|
||||||
|
modelProject.path + ':verifyModelDescriptor'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('verifyAllModels') {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Runs complete validation and artifact verification for every independently versioned model module.'
|
||||||
|
dependsOn(modelProjects().collect { Project modelProject -> modelProject.tasks.named('check') })
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('verifyJmhModelClasspath') {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Verifies that JMH receives each individual model JAR exactly once and embeds no dictionary.'
|
||||||
|
dependsOn(tasks.named('jmhJar'))
|
||||||
|
dependsOn(modelProjects().collect { Project modelProject -> modelProject.tasks.named('jar') })
|
||||||
|
outputs.file(layout.buildDirectory.file('reports/models/jmh-model-classpath.txt'))
|
||||||
|
doLast {
|
||||||
|
List<File> modelJars = configurations.jmhRuntimeClasspath.files.findAll { File dependency ->
|
||||||
|
dependency.name.startsWith('radixor-model-') && dependency.name.endsWith('.jar')
|
||||||
|
}.sort { File left, File right -> left.name <=> right.name }
|
||||||
|
List<String> expectedPrefixes = modelProjects().collect { Project modelProject ->
|
||||||
|
"radixor-model-${modelProject.name}-"
|
||||||
|
}
|
||||||
|
expectedPrefixes.each { String prefix ->
|
||||||
|
List<File> matches = modelJars.findAll { File dependency -> dependency.name.startsWith(prefix) }
|
||||||
|
if (matches.size() != 1) {
|
||||||
|
throw new GradleException("JMH must resolve exactly one model JAR with prefix ${prefix}; resolved ${matches}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (modelJars.any { File dependency -> dependency.name.contains('benchmark-pack') }) {
|
||||||
|
throw new GradleException('JMH must not resolve a benchmark-pack artifact.')
|
||||||
|
}
|
||||||
|
File executable = tasks.named('jmhJar', Jar).get().archiveFile.get().asFile
|
||||||
|
if (!zipTree(executable).matching { include '**/stemmer.gz' }.isEmpty()) {
|
||||||
|
throw new GradleException('The JMH executable JAR must not embed model dictionaries.')
|
||||||
|
}
|
||||||
|
File report = layout.buildDirectory.file('reports/models/jmh-model-classpath.txt').get().asFile
|
||||||
|
report.parentFile.mkdirs()
|
||||||
|
report.setText(modelJars.collect { File dependency -> dependency.name }.join('\n') + '\n', 'UTF-8')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('prepareModelConsumerTestRepository') {
|
||||||
|
coreVersion = version.toString()
|
||||||
|
catalogVersion = project(':models:standard').version.toString()
|
||||||
|
modelVersions = modelProjects().collectEntries { Project modelProject ->
|
||||||
|
final String modelVersion = providers.gradleProperty('modelReleaseVersion')
|
||||||
|
.orElse(providers.fileContents(modelProject.layout.projectDirectory.file('model-version.txt'))
|
||||||
|
.asText.map(String::trim))
|
||||||
|
.get()
|
||||||
|
[(modelProject.name): modelVersion]
|
||||||
|
}
|
||||||
|
corePom = layout.file(tasks.named('generatePomFileForMavenJavaPublication').map { it.destination })
|
||||||
|
coreJar = tasks.named('jar', Jar).flatMap { it.archiveFile }
|
||||||
|
modelPoms.from(modelProjects().collect { Project modelProject ->
|
||||||
|
modelProject.tasks.named('generatePomFileForModelPublication').map { it.destination }
|
||||||
|
})
|
||||||
|
modelJars.from(modelProjects().collect { Project modelProject ->
|
||||||
|
modelProject.tasks.named('jar', Jar).flatMap { it.archiveFile }
|
||||||
|
})
|
||||||
|
standardPom = layout.file(project(':models:standard').tasks.named('generatePomFileForStandardPublication')
|
||||||
|
.map { it.destination })
|
||||||
|
bomPom = layout.file(project(':models:bom').tasks.named('generatePomFileForBomPublication')
|
||||||
|
.map { it.destination })
|
||||||
|
repositoryDirectory = layout.buildDirectory.dir('model-consumer-repository')
|
||||||
|
}
|
||||||
|
|
||||||
|
def rawModelCatalogRepository = layout.buildDirectory.dir('model-catalog-staging-repository')
|
||||||
|
def preparedModelCatalogBundleInput = layout.buildDirectory.dir('model-catalog-bundle-input')
|
||||||
|
|
||||||
|
def cleanModelCatalogStaging = tasks.register('cleanModelCatalogStaging', Delete) {
|
||||||
|
group = 'publishing'
|
||||||
|
description = 'Cleans the isolated model catalog Maven staging repository.'
|
||||||
|
delete(rawModelCatalogRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
gradle.projectsEvaluated {
|
||||||
|
project(':models:standard').tasks.named('publishStandardPublicationToCatalogStagingRepository') {
|
||||||
|
dependsOn(cleanModelCatalogStaging)
|
||||||
|
}
|
||||||
|
project(':models:bom').tasks.named('publishBomPublicationToCatalogStagingRepository') {
|
||||||
|
dependsOn(cleanModelCatalogStaging)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def prepareModelCatalogReleaseCandidate = tasks.named('prepareModelCatalogReleaseCandidate') {
|
||||||
|
dependsOn(project(':models:standard').tasks.named('check'))
|
||||||
|
dependsOn(project(':models:bom').tasks.named('check'))
|
||||||
|
dependsOn(':models:standard:publishStandardPublicationToCatalogStagingRepository')
|
||||||
|
dependsOn(':models:bom:publishBomPublicationToCatalogStagingRepository')
|
||||||
|
rawRepositoryDirectory = rawModelCatalogRepository
|
||||||
|
preparedBundleDirectory = preparedModelCatalogBundleInput
|
||||||
|
catalogVersion = project(':models:standard').version.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
def modelCatalogCentralBundle = tasks.register('modelCatalogCentralBundle', Zip) {
|
||||||
|
group = 'publishing'
|
||||||
|
description = 'Builds the local POM-only model catalog bundle without remote publication.'
|
||||||
|
dependsOn(prepareModelCatalogReleaseCandidate)
|
||||||
|
from(preparedModelCatalogBundleInput)
|
||||||
|
destinationDirectory = layout.buildDirectory.dir('model-catalog-release-candidate')
|
||||||
|
archiveFileName = "radixor-models-catalog-${project(':models:standard').version}-central-bundle.zip"
|
||||||
|
doFirst {
|
||||||
|
File preparedInput = preparedModelCatalogBundleInput.get().asFile
|
||||||
|
if (!preparedInput.isDirectory() || preparedInput.listFiles() == null || preparedInput.listFiles().length == 0) {
|
||||||
|
throw new GradleException("The prepared model catalog bundle input is missing or empty: ${preparedInput}.")
|
||||||
|
}
|
||||||
|
if (providers.environmentVariable('GITHUB_REF_TYPE').orNull == 'tag'
|
||||||
|
&& (providers.environmentVariable('SIGNING_KEY').orNull?.isBlank() != false
|
||||||
|
|| providers.environmentVariable('SIGNING_PASSWORD').orNull?.isBlank() != false)) {
|
||||||
|
throw new GradleException('A tagged model catalog release requires SIGNING_KEY and SIGNING_PASSWORD.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def publishedModelVersions = modelProjects().collect { Project modelProject ->
|
||||||
|
modelProject.file('model-version.txt').getText('UTF-8').trim()
|
||||||
|
}.toSet()
|
||||||
|
if (publishedModelVersions.size() != 1) {
|
||||||
|
throw new GradleException("The catalog verifier requires one common model version; found ${publishedModelVersions}.")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('verifyModelCatalogReleaseCandidate') {
|
||||||
|
bundleFile = modelCatalogCentralBundle.flatMap { Zip archive -> archive.archiveFile }
|
||||||
|
reportFile = layout.buildDirectory.file('reports/models/catalog-release-candidate.txt')
|
||||||
|
catalogVersion = project(':models:standard').version.toString()
|
||||||
|
modelVersion = publishedModelVersions.first()
|
||||||
|
defaultModelIds = defaultModelProjects().collect { Project modelProject -> modelProject.name }
|
||||||
|
allModelIds = modelProjects().collect { Project modelProject -> modelProject.name }
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('verifyArtifactSizes') {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Reports artifact sizes and rejects dictionary bytes in core.'
|
||||||
|
dependsOn(tasks.named('verifyCoreJarExcludesModels'))
|
||||||
|
doLast {
|
||||||
|
File archive = tasks.named('jar', Jar).get().archiveFile.get().asFile
|
||||||
|
println('org.egothor:radixor:' + version + ' ' + archive.length() + ' bytes')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('check') {
|
||||||
|
dependsOn(tasks.named('verifyCoreJarExcludesModels'))
|
||||||
|
dependsOn(tasks.named('verifyAllDefaultModels'))
|
||||||
|
dependsOn(tasks.named('verifyJmhModelClasspath'))
|
||||||
|
dependsOn(tasks.named('verifyJavaLicenseHeaders'))
|
||||||
|
}
|
||||||
|
|
||||||
|
def modelCatalogText = providers.provider {
|
||||||
|
StringBuilder output = new StringBuilder()
|
||||||
|
output.append('| Model ID | Language | Default | Coordinates | Version | Source | Repository | Source version | Revision | Revision status | License | Attribution | SHA-256 | Bytes |\n')
|
||||||
|
output.append('|---|---|---:|---|---:|---|---|---|---|---|---|---|---|---:|\n')
|
||||||
|
modelProjects().each { Project modelProject ->
|
||||||
|
String script = modelProject.file('build.gradle').getText('UTF-8')
|
||||||
|
def value = { String key ->
|
||||||
|
def matcher = script =~ /(?m)^\s*${key}\s*=\s*(?:'([^']+)'|([^\s]+))\s*$/
|
||||||
|
if (!matcher.find()) return 'unavailable'
|
||||||
|
return matcher.group(1) != null ? matcher.group(1) : matcher.group(2)
|
||||||
|
}
|
||||||
|
File input = modelProject.file('src/modelInput/stemmer.gz')
|
||||||
|
String checksum = java.security.MessageDigest.getInstance('SHA-256').digest(input.bytes).encodeHex().toString()
|
||||||
|
output.append('| ').append(modelProject.name)
|
||||||
|
.append(' | ').append(value('language'))
|
||||||
|
.append(' | ').append(value('defaultModel'))
|
||||||
|
.append(' | org.egothor:radixor-model-').append(modelProject.name)
|
||||||
|
.append(' | ').append(modelProject.file('model-version.txt').text.trim())
|
||||||
|
.append(' | ').append(value('sourceName'))
|
||||||
|
.append(' | ').append(value('sourceRepository'))
|
||||||
|
.append(' | ').append(value('sourceVersion'))
|
||||||
|
.append(' | ').append(value('sourceRevision'))
|
||||||
|
.append(' | ').append(value('sourceRevisionStatus'))
|
||||||
|
.append(' | ').append(value('sourceLicense'))
|
||||||
|
.append(' | ').append(value('sourceAttribution'))
|
||||||
|
.append(' | ').append(checksum)
|
||||||
|
.append(' | ').append(input.length()).append(' |\n')
|
||||||
|
}
|
||||||
|
return output.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
def modelCatalogDocumentationInputs = files(modelProjects().collectMany { Project modelProject ->
|
||||||
|
[
|
||||||
|
modelProject.file('build.gradle'),
|
||||||
|
modelProject.file('model-version.txt'),
|
||||||
|
modelProject.file('src/modelInput/stemmer.gz')
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
tasks.register('generateModelCatalogDocumentation') {
|
||||||
|
group = 'documentation'
|
||||||
|
description = 'Generates the deterministic model catalog in the build documentation staging tree.'
|
||||||
|
inputs.files(modelCatalogDocumentationInputs)
|
||||||
|
outputs.file(layout.buildDirectory.file('mkdocs-source/stemmer-model-catalog.md'))
|
||||||
|
doLast {
|
||||||
|
File catalog = layout.buildDirectory.file('mkdocs-source/stemmer-model-catalog.md').get().asFile
|
||||||
|
catalog.parentFile.mkdirs()
|
||||||
|
catalog.setText('# Published Stemmer Model Catalog\n\n' + modelCatalogText.get(), 'UTF-8')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('publishModelCatalogDocumentation') {
|
||||||
|
group = 'documentation'
|
||||||
|
description = 'Updates the checked-in model catalog used by a direct local MkDocs invocation.'
|
||||||
|
dependsOn(tasks.named('generateModelCatalogDocumentation'))
|
||||||
|
inputs.file(layout.buildDirectory.file('mkdocs-source/stemmer-model-catalog.md'))
|
||||||
|
outputs.file(layout.projectDirectory.file('docs/stemmer-model-catalog.md'))
|
||||||
|
doLast {
|
||||||
|
File generated = layout.buildDirectory.file('mkdocs-source/stemmer-model-catalog.md').get().asFile
|
||||||
|
File published = layout.projectDirectory.file('docs/stemmer-model-catalog.md').asFile
|
||||||
|
published.setText(generated.getText('UTF-8'), 'UTF-8')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('prepareMkDocsSource', Sync) {
|
||||||
|
group = 'documentation'
|
||||||
|
description = 'Stages maintained documentation, generated catalog, and MkDocs configuration under build/.'
|
||||||
|
dependsOn(modelProjects().collect { Project modelProject -> modelProject.path + ':verifyModelDescriptor' })
|
||||||
|
inputs.files(modelCatalogDocumentationInputs)
|
||||||
|
into(layout.buildDirectory.dir('mkdocs-source'))
|
||||||
|
from(layout.projectDirectory.dir('docs'))
|
||||||
|
doLast {
|
||||||
|
File catalog = layout.buildDirectory.file('mkdocs-source/stemmer-model-catalog.md').get().asFile
|
||||||
|
catalog.setText('# Published Stemmer Model Catalog\n\n' + modelCatalogText.get(), 'UTF-8')
|
||||||
|
File buildsPage = layout.buildDirectory.file('mkdocs-source/builds.md').get().asFile
|
||||||
|
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()
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('verifyModelCatalogDocumentation') {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Validates model metadata and both the checked-in and staged MkDocs catalogs.'
|
||||||
|
dependsOn(tasks.named('prepareMkDocsSource'))
|
||||||
|
dependsOn(tasks.named('verifyAllDefaultModels'))
|
||||||
|
doLast {
|
||||||
|
File catalog = layout.buildDirectory.file('mkdocs-source/stemmer-model-catalog.md').get().asFile
|
||||||
|
String expected = '# Published Stemmer Model Catalog\n\n' + modelCatalogText.get()
|
||||||
|
if (!catalog.isFile() || catalog.getText('UTF-8') != expected) {
|
||||||
|
throw new GradleException('The staged model catalog is missing or nondeterministic.')
|
||||||
|
}
|
||||||
|
File publishedCatalog = layout.projectDirectory.file('docs/stemmer-model-catalog.md').asFile
|
||||||
|
if (!publishedCatalog.isFile() || publishedCatalog.getText('UTF-8') != expected) {
|
||||||
|
throw new GradleException(
|
||||||
|
'The checked-in model catalog is stale; run ./gradlew publishModelCatalogDocumentation.')
|
||||||
|
}
|
||||||
|
List<String> identifiers = modelProjects().collect { Project modelProject -> modelProject.name }
|
||||||
|
if (identifiers != identifiers.sort()) {
|
||||||
|
throw new GradleException('Published model projects are not in deterministic model-ID order.')
|
||||||
|
}
|
||||||
|
identifiers.each { String identifier ->
|
||||||
|
if (!expected.contains('org.egothor:radixor-model-' + identifier)) {
|
||||||
|
throw new GradleException('The staged model catalog omits published model ' + identifier + '.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!layout.buildDirectory.file('mkdocs/mkdocs.yml').get().asFile.isFile()) {
|
||||||
|
throw new GradleException('The staged MkDocs configuration is missing.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('check') {
|
||||||
|
dependsOn(tasks.named('verifyModelCatalogDocumentation'))
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('cyclonedxDirectBom') {
|
||||||
includeConfigs = ['runtimeClasspath', 'compileClasspath']
|
includeConfigs = ['runtimeClasspath', 'compileClasspath']
|
||||||
skipConfigs = ['testRuntimeClasspath', 'testCompileClasspath', 'jmh.*', 'mockitoAgent']
|
skipConfigs = ['testRuntimeClasspath', 'testCompileClasspath', 'jmh.*', 'mockitoAgent']
|
||||||
includeBomSerialNumber = true
|
includeBomSerialNumber = true
|
||||||
includeLicenseText = false
|
includeLicenseText = false
|
||||||
includeMetadataResolution = true
|
includeMetadataResolution = true
|
||||||
includeBuildSystem = true
|
includeBuildSystem = true
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.named('cyclonedxBom') {
|
|
||||||
includeBomSerialNumber = true
|
|
||||||
includeLicenseText = false
|
|
||||||
includeBuildSystem = true
|
|
||||||
jsonOutput.set(sbomReportsDirectory.map { it.file('radixor-sbom.json') })
|
jsonOutput.set(sbomReportsDirectory.map { it.file('radixor-sbom.json') })
|
||||||
xmlOutput.set(sbomReportsDirectory.map { it.file('radixor-sbom.xml') })
|
xmlOutput.set(sbomReportsDirectory.map { it.file('radixor-sbom.xml') })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
tasks.matching { Task candidate -> candidate.name == 'cyclonedxDirectBom' }.configureEach {
|
||||||
|
enabled = false
|
||||||
|
description = 'Disabled because the root project exclusively owns CycloneDX SBOM generation.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pitest {
|
pitest {
|
||||||
pitestVersion = '1.22.1'
|
pitestVersion = '1.22.1'
|
||||||
junit5PluginVersion = '1.2.3'
|
junit5PluginVersion = '1.2.3'
|
||||||
@@ -354,7 +844,10 @@ pitest {
|
|||||||
excludedTestClasses = [
|
excludedTestClasses = [
|
||||||
'org.egothor.stemmer.CompileIntegrationTest',
|
'org.egothor.stemmer.CompileIntegrationTest',
|
||||||
'org.egothor.stemmer.StemmerPatchTrieLoaderTest',
|
'org.egothor.stemmer.StemmerPatchTrieLoaderTest',
|
||||||
'org.egothor.stemmer.StemmerKnowledgeExperimentTest'
|
'org.egothor.stemmer.StemmerKnowledgeExperimentTest',
|
||||||
|
// These integration tests require dedicated Gradle task wiring and must not run in PIT worker JVMs.
|
||||||
|
'org.egothor.stemmer.FullRuntimeModelIntegrationTest',
|
||||||
|
'org.egothor.stemmer.ModelDependencyResolutionTest'
|
||||||
]
|
]
|
||||||
outputFormats = ['XML', 'HTML']
|
outputFormats = ['XML', 'HTML']
|
||||||
timestampedReports = false
|
timestampedReports = false
|
||||||
@@ -421,6 +914,7 @@ tasks.named('distTar') {
|
|||||||
|
|
||||||
jmh {
|
jmh {
|
||||||
jmhVersion = '1.37'
|
jmhVersion = '1.37'
|
||||||
|
includeTests = false
|
||||||
warmupIterations = 3
|
warmupIterations = 3
|
||||||
iterations = 5
|
iterations = 5
|
||||||
fork = 1
|
fork = 1
|
||||||
@@ -430,13 +924,25 @@ jmh {
|
|||||||
resultsFile = benchmarkReportsDirectory.map { it.file('jmh-results.csv').asFile }.get()
|
resultsFile = benchmarkReportsDirectory.map { it.file('jmh-results.csv').asFile }.get()
|
||||||
humanOutputFile = benchmarkReportsDirectory.map { it.file('jmh-results.txt').asFile }.get()
|
humanOutputFile = benchmarkReportsDirectory.map { it.file('jmh-results.txt').asFile }.get()
|
||||||
duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
|
if (jmhIncludesProperty.isPresent()) {
|
||||||
|
includes = [jmhIncludesProperty.get()]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('jmh') {
|
tasks.named('jmh') {
|
||||||
group = 'verification'
|
group = 'verification'
|
||||||
description = 'Runs JMH benchmarks for the Radixor algorithmic core and Snowball comparison suite.'
|
description = 'Runs JMH benchmarks for the Radixor algorithmic core and external stemmer comparison suites.'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.named('jmhJar', Jar) {
|
||||||
|
exclude 'META-INF/radixor/models.index'
|
||||||
|
exclude 'META-INF/radixor/models/**'
|
||||||
|
exclude 'org/egothor/stemmer/models/**'
|
||||||
|
exclude 'META-INF/LICENSES/**'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: 'gradle/lucene-benchmarks.gradle'
|
||||||
|
|
||||||
tasks.register('regressionArtifactGenerator', JavaExec) {
|
tasks.register('regressionArtifactGenerator', JavaExec) {
|
||||||
group = 'verification'
|
group = 'verification'
|
||||||
description = 'Generates deterministic compiled trie regression artifacts.'
|
description = 'Generates deterministic compiled trie regression artifacts.'
|
||||||
@@ -458,6 +964,125 @@ tasks.register('regressionArtifactGenerator', JavaExec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.register('stemmingQuality', JavaExec) {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Evaluates pairwise over-stemming and under-stemming against registered model dictionary groups.'
|
||||||
|
dependsOn(tasks.named('testClasses'))
|
||||||
|
dependsOn(tasks.named('jmhClasses'))
|
||||||
|
dependsOn(tasks.named('prepareBenchmarkModelInputs'))
|
||||||
|
classpath = files(sourceSets.test.runtimeClasspath, configurations.stemmingQualityJmhRuntime)
|
||||||
|
mainClass = 'org.egothor.stemmer.benchmark.quality.StemmingQualityApplication'
|
||||||
|
args layout.buildDirectory.dir('reports/stemming-quality').get().asFile.absolutePath,
|
||||||
|
layout.buildDirectory.dir('generated/benchmark-model-inputs').get().asFile.absolutePath,
|
||||||
|
providers.gradleProperty('stemmingQualityLanguage').getOrElse(''),
|
||||||
|
providers.gradleProperty('stemmingQualityStemmer').getOrElse(''),
|
||||||
|
providers.gradleProperty('stemmingQualityMode').getOrElse(''),
|
||||||
|
providers.gradleProperty('stemmingQualityOutputPolicy').getOrElse(''),
|
||||||
|
providers.gradleProperty('stemmingQualityRankMetric').getOrElse('PAIRWISE_F05'),
|
||||||
|
providers.gradleProperty('stemmingQualityAudit').getOrElse('false'),
|
||||||
|
providers.gradleProperty('stemmingQualityAuditLimit').getOrElse('25')
|
||||||
|
maxHeapSize = '6g'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('benchmarkCorpusReport', JavaExec) {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Reports corpus and preferred patch-command counts for every default model.'
|
||||||
|
dependsOn(tasks.named('jmhClasses'))
|
||||||
|
classpath = files(sourceSets.jmh.runtimeClasspath, configurations.stemmingQualityJmhRuntime)
|
||||||
|
mainClass = 'org.egothor.stemmer.benchmark.BenchmarkCorpusReportApplication'
|
||||||
|
args layout.buildDirectory.file('reports/jmh/benchmark-corpora.csv').get().asFile.absolutePath
|
||||||
|
maxHeapSize = '6g'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('writeJmhRuntimeClasspath') {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Writes the complete modular JMH runtime classpath for isolated direct JMH execution.'
|
||||||
|
dependsOn(tasks.named('jmhJar'))
|
||||||
|
dependsOn(modelProjects().collect { Project modelProject -> modelProject.tasks.named('jar') })
|
||||||
|
outputs.file(layout.buildDirectory.file('reports/jmh/jmh-runtime-classpath.txt'))
|
||||||
|
doLast {
|
||||||
|
File report = layout.buildDirectory.file('reports/jmh/jmh-runtime-classpath.txt').get().asFile
|
||||||
|
report.parentFile.mkdirs()
|
||||||
|
File executable = tasks.named('jmhJar', Jar).get().archiveFile.get().asFile
|
||||||
|
report.setText(executable.absolutePath + File.pathSeparator
|
||||||
|
+ sourceSets.jmh.runtimeClasspath.asPath + System.lineSeparator(), 'UTF-8')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('prepareBenchmarkModelInputs', Sync) {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Prepares default model inputs for JMH and quality evaluation without changing source data.'
|
||||||
|
into(layout.buildDirectory.dir('generated/benchmark-model-inputs'))
|
||||||
|
defaultModelProjects().each { Project modelProject ->
|
||||||
|
String languageDirectory = modelProject.name == 'pl-pl-unimorph'
|
||||||
|
? 'pl_pl'
|
||||||
|
: modelProject.name.replace('-default', '').replace('-', '_')
|
||||||
|
from(modelProject.file('src/modelInput/stemmer.gz')) {
|
||||||
|
into(languageDirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('publishStemmingQualityDocumentation', JavaExec) {
|
||||||
|
group = 'documentation'
|
||||||
|
description = 'Publishes validated complete stemming-quality results on the language benchmark pages.'
|
||||||
|
dependsOn(tasks.named('testClasses'))
|
||||||
|
classpath = sourceSets.test.runtimeClasspath
|
||||||
|
mainClass = 'org.egothor.stemmer.benchmark.quality.StemmingQualityDocumentationPublisher'
|
||||||
|
args layout.buildDirectory.file('reports/stemming-quality/stemming-quality.csv').get().asFile.absolutePath,
|
||||||
|
layout.projectDirectory.dir('docs').asFile.absolutePath,
|
||||||
|
'update'
|
||||||
|
doFirst {
|
||||||
|
if (!file("$buildDir/reports/stemming-quality/stemming-quality.csv").isFile()) {
|
||||||
|
throw new GradleException('A complete stemming-quality CSV is required. Run stemmingQuality only when no validated complete report is available.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('verifyStemmingQualityDocumentation', JavaExec) {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Verifies published language-page quality tables against the checked-in authoritative CSV.'
|
||||||
|
dependsOn(tasks.named('testClasses'))
|
||||||
|
classpath = sourceSets.test.runtimeClasspath
|
||||||
|
mainClass = 'org.egothor.stemmer.benchmark.quality.StemmingQualityDocumentationPublisher'
|
||||||
|
args layout.projectDirectory.file('docs/benchmarks/data/stemming-quality.csv').asFile.absolutePath,
|
||||||
|
layout.projectDirectory.dir('docs').asFile.absolutePath,
|
||||||
|
'verify'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('check') {
|
||||||
|
dependsOn(tasks.named('verifyStemmingQualityDocumentation'))
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('verifyStemmingQualitySourceSets') {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Verifies the production, JMH, and standard-test ownership of stemming-quality infrastructure.'
|
||||||
|
doLast {
|
||||||
|
if (sourceSets.findByName('stemmingQualityTest') != null || file('src/stemmingQualityTest').exists()) {
|
||||||
|
throw new GradleException('The obsolete stemmingQualityTest source set or directory still exists.')
|
||||||
|
}
|
||||||
|
if (!file('src/jmh/java/org/egothor/stemmer/benchmark/QualityStemmerMatrix.java').isFile()) {
|
||||||
|
throw new GradleException('The authoritative JMH stemmer matrix is not in src/jmh.')
|
||||||
|
}
|
||||||
|
if (!file('src/test/java/org/egothor/stemmer/benchmark/quality/StemmingQualityApplication.java').isFile()) {
|
||||||
|
throw new GradleException('The stemming-quality evaluator is not in the standard test source set.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('verifyProductionJarExcludesStemmingQuality') {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Verifies that analytical stemming-quality classes are absent from the production JAR.'
|
||||||
|
dependsOn(tasks.named('jar'))
|
||||||
|
doLast {
|
||||||
|
final File archive = tasks.named('jar').get().archiveFile.get().asFile
|
||||||
|
final def forbidden = zipTree(archive).matching { include '**/benchmark/**' }.files
|
||||||
|
if (!forbidden.isEmpty()) {
|
||||||
|
throw new GradleException("Production JAR contains analytical stemming-quality classes: ${forbidden}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tasks.register('printDependencyCheckNvdConfig') {
|
tasks.register('printDependencyCheckNvdConfig') {
|
||||||
doLast {
|
doLast {
|
||||||
System.out.println("NVD API key present: " + (nvdApiKey != null && !nvdApiKey.isBlank()))
|
System.out.println("NVD API key present: " + (nvdApiKey != null && !nvdApiKey.isBlank()))
|
||||||
@@ -506,6 +1131,11 @@ javadoc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply from: 'gradle/snowball-benchmarks.gradle'
|
apply from: 'gradle/snowball-benchmarks.gradle'
|
||||||
|
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 ->
|
gradle.taskGraph.whenReady { taskGraph ->
|
||||||
def banner = """
|
def banner = """
|
||||||
|
|||||||
@@ -13,10 +13,32 @@ Radixor does not keep a large flat table of final stems. Instead, it converts di
|
|||||||
|
|
||||||
The build-time flow is:
|
The build-time flow is:
|
||||||
|
|
||||||
```text
|
```mermaid
|
||||||
Dictionary -> Mutable trie -> Reduced trie -> Compiled trie
|
flowchart TD
|
||||||
|
dictionary[Training dictionary]
|
||||||
|
mutable[Mutable trie]
|
||||||
|
reduced[Reduced trie]
|
||||||
|
compiled[Compiled trie]
|
||||||
|
|
||||||
|
dictionary --> mutable --> reduced --> compiled
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
At runtime, the compiled trie does not directly return the final stem string. It returns one or more stored patch commands for the addressed key, and those commands are then applied to the original input word.
|
At runtime, the compiled trie does not directly return the final stem string. It returns one or more stored patch commands for the addressed key, and those commands are then applied to the original input word.
|
||||||
|
|
||||||
## Why this matters
|
## Why this matters
|
||||||
@@ -50,3 +72,5 @@ For most readers, the best order is:
|
|||||||
- [Programmatic usage](programmatic-usage.md)
|
- [Programmatic usage](programmatic-usage.md)
|
||||||
- [CLI compilation](cli-compilation.md)
|
- [CLI compilation](cli-compilation.md)
|
||||||
- [Dictionary format](dictionary-format.md)
|
- [Dictionary format](dictionary-format.md)
|
||||||
|
- [Model selection and loading](model-selection-and-loading.md)
|
||||||
|
- [Stemmer models](stemmer-models.md)
|
||||||
|
|||||||
@@ -2,6 +2,109 @@
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
## Java component boundaries
|
||||||
|
|
||||||
|
| Component | Responsibility |
|
||||||
|
|---|---|
|
||||||
|
| Root Radixor core | Patch commands, dictionary parser, trie construction/lookup, descriptor and registry APIs, loaders; no language data |
|
||||||
|
| Individual model module | Immutable source input and license; publishes one independently versioned resource JAR |
|
||||||
|
| `StemmerModelRegistry` | Deterministic index/descriptor discovery and selection by model ID or language default |
|
||||||
|
| `StemmerModelDescriptor` | Immutable public view of validated runtime identity, format, resource, checksum, and source URL |
|
||||||
|
| Model convention plugin | Validates inputs and generates the resource namespace, descriptor, index, license, and publication |
|
||||||
|
| Standard aggregate | POM-only transitive runtime dependencies for one default per language |
|
||||||
|
| Verification classpaths | Direct individual-model dependencies for tests, quality evaluation, and JMH, including optional PoliMorf |
|
||||||
|
| Models BOM | POM-only recommended individual model versions in Maven dependency management |
|
||||||
|
| Documentation staging | Maintained `docs/` plus generated catalog under `build/mkdocs-source/` |
|
||||||
|
| Release workflows | Independent core, one-model, and catalog publication boundaries |
|
||||||
|
|
||||||
|
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("<alias>")` 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:
|
||||||
|
|
||||||
|
1. use the thread context `ClassLoader`, or an explicit non-null loader;
|
||||||
|
2. enumerate every `META-INF/radixor/models.index` with `ClassLoader.getResources(...)`;
|
||||||
|
3. sort index URLs and validate every descriptor path;
|
||||||
|
4. read descriptor resources and required properties;
|
||||||
|
5. validate model ID, language, exact resource namespace, checksum syntax, format name, and format version;
|
||||||
|
6. sort descriptors by model ID and reject duplicate IDs;
|
||||||
|
7. resolve either `Language.defaultModelId()` or an exact explicit model ID;
|
||||||
|
8. open the declared model resource with the descriptor's discovering loader;
|
||||||
|
9. compare SHA-256 over the compressed bytes;
|
||||||
|
10. decompress GZip and parse UTF-8 Radixor dictionary rows;
|
||||||
|
11. build and reduce a `FrequencyTrie`;
|
||||||
|
12. optionally compile stored patch strings into `CompiledPatchCommand` values for the language-oriented compiled API.
|
||||||
|
|
||||||
|
Descriptor discovery verifies resource presence before selection. Byte-level checksum verification happens when the selected model is loaded. The registry never scans arbitrary JAR contents and never selects “the first model for a language.”
|
||||||
|
|
||||||
|
### Default Polish resolution
|
||||||
|
|
||||||
|
`StemmerPatchTrieLoader.Language.PL_PL` declares `pl-pl-unimorph` in the enum constructor. A language-oriented load creates a context-loader registry and calls `requireDefault(PL_PL)`. If that ID is absent, loading stops with `StemmerModelNotFoundException` naming `org.egothor:radixor-model-pl-pl-unimorph:<version>`.
|
||||||
|
|
||||||
|
### Explicit PoliMorf resolution
|
||||||
|
|
||||||
|
`registry.require("pl-pl-polimorf")` addresses the alternative directly. It neither changes nor consults the Polish default. Both descriptors may coexist; duplicate declarations of either same ID are rejected.
|
||||||
|
|
||||||
|
## Version axes
|
||||||
|
|
||||||
|
| Version | Owned by | Compatibility purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| Core version | Root Git-derived release | Java implementation and public API |
|
||||||
|
| Model artifact version | Each `model-version.txt` | One independently published model JAR |
|
||||||
|
| Catalog version | `models/catalog-version.txt` | Standard aggregate and BOM recommendation set |
|
||||||
|
| Source dictionary version | Module provenance | Upstream lexical data lineage |
|
||||||
|
| Model format version | Descriptor and registry | Loader compatibility for packaged dictionary representation |
|
||||||
|
|
||||||
|
No equality relationship is implied between these values.
|
||||||
|
|
||||||
|
## Build topology and generated output
|
||||||
|
|
||||||
|
`models/model-projects.properties` is the single Gradle-readable topology list for the 21 individual model projects and their default or optional aggregate role. Per-model build scripts and generated descriptors remain authoritative for language, resource, provenance, checksum, and model-specific metadata. `settings.gradle`, root verification classpaths, the standard POM, and BOM constraints all derive membership from the topology list.
|
||||||
|
|
||||||
|
Gradle implicitly creates the lifecycle parent `:models` because child paths are nested. It has no build script, applied project plugin, Maven coordinate, publication, or archive. The root CycloneDX plugin exposes direct-task instances to subprojects internally; every subproject instance is disabled, so only root `:cyclonedxDirectBom` can generate an SBOM. The ignored path `models/build/` is generated output, not a module, and the supported build does not write reports there. Root aggregate reports, including `verifyJmhModelClasspath`, belong under `build/reports/models/`; each individual model retains its own outputs under `models/<model-id>/build/`.
|
||||||
|
|
||||||
|
`models/bom` is a Maven dependency BOM: it controls recommended dependency versions and adds no runtime artifacts. The root CycloneDX task produces a software bill of materials (SBOM) under `build/reports/sbom/`. These artifacts have different purposes and output locations.
|
||||||
|
|
||||||
|
## Build-time model packaging
|
||||||
|
|
||||||
|
The `org.egothor.radixor.model` convention plugin treats `src/modelInput` as immutable. `validateModelInput` checks the GZip stream, strict UTF-8, dictionary rows, ID, semantic model version, and license. `prepareModelResources` copies identical compressed bytes under `org/egothor/stemmer/models/<model-id>/stemmer.gz` and generates the descriptor, index, and packaged license under `build/`. `verifyModelDescriptor` checks the digest, while `verifyModelJar` checks the unique resource, packaged-byte digest, metadata, and dictionary-free documentation artifacts. The root `runtimeModelIntegrationTest` accepts `-PmodelId=<id>` and verifies transformation of a packaged resource into `FrequencyTrie<CompiledPatchCommand>`; PoliMorf release validation depends on this complete runtime test.
|
||||||
|
|
||||||
|
For UniMorph models, the convention validates and packages one model-specific attribution,
|
||||||
|
licensing, provenance, and contribution notice. Source and packaged notice bytes must match. The
|
||||||
|
notice identifies CC BY-SA 3.0 through its canonical URI; no project-wide CC license directory or
|
||||||
|
duplicated full legal text is used. Descriptors distinguish exact revisions from the explicit
|
||||||
|
legacy-import sentinel. UniMorph supplies morphological data; runtime patch commands and tries are
|
||||||
|
constructed by Radixor. The Java software remains BSD-3-Clause, while PoliMorf data remains under
|
||||||
|
its separately packaged BSD-2-Clause license.
|
||||||
|
|
||||||
|
## Release and security boundaries
|
||||||
|
|
||||||
|
| Tag | Publication boundary |
|
||||||
|
|---|---|
|
||||||
|
| `release@<core-version>` | Root `org.egothor:radixor` artifacts only; never model JARs |
|
||||||
|
| `model/<model-id>@<model-version>` | Exactly one matching model; never core, catalog, or other models |
|
||||||
|
| `models-catalog@<catalog-version>` | BOM and standard aggregate only; never model bytes |
|
||||||
|
|
||||||
|
License inclusion, strict metadata paths, resource presence, SHA-256 verification, unsupported-format rejection, and duplicate-ID rejection form the model integrity boundary. These checks detect packaging mistakes and corruption; model data remains non-executable dictionary input.
|
||||||
|
|
||||||
## The central idea
|
## The central idea
|
||||||
|
|
||||||
Radixor does not store final stems directly as a large flat lookup table. Instead, it stores **patch commands** that describe how a word form should be transformed into a canonical stem.
|
Radixor does not store final stems directly as a large flat lookup table. Instead, it stores **patch commands** that describe how a word form should be transformed into a canonical stem.
|
||||||
@@ -10,12 +113,18 @@ For example, if a dictionary states that `running` should reduce to `run`, the f
|
|||||||
|
|
||||||
That matters because many words share similar transformation patterns. Once those mappings are organized in a trie and compiled into a canonical structure, the result is much smaller and more reusable than a naive direct-output table.
|
That matters because many words share similar transformation patterns. Once those mappings are organized in a trie and compiled into a canonical structure, the result is much smaller and more reusable than a naive direct-output table.
|
||||||
|
|
||||||
## End-to-end build flow
|
## Trie construction flow
|
||||||
|
|
||||||
The full build-time flow is:
|
The full build-time flow is:
|
||||||
|
|
||||||
```text
|
```mermaid
|
||||||
Dictionary -> Mutable trie -> Reduced trie -> Compiled trie
|
flowchart TD
|
||||||
|
dictionary[Training dictionary]
|
||||||
|
mutable[Mutable trie]
|
||||||
|
reduced[Reduced trie]
|
||||||
|
compiled[Compiled trie]
|
||||||
|
|
||||||
|
dictionary --> mutable --> reduced --> compiled
|
||||||
```
|
```
|
||||||
|
|
||||||
Each stage has a different purpose.
|
Each stage has a different purpose.
|
||||||
@@ -53,6 +162,16 @@ Those patch-command values are inserted into a mutable trie keyed by the source
|
|||||||
|
|
||||||
Equivalent subtrees are merged into canonical reduced nodes.
|
Equivalent subtrees are merged into canonical reduced nodes.
|
||||||
|
|
||||||
|
Before a selected semantic reduction mode is applied, Radixor also performs uniform-subtree
|
||||||
|
contraction. If every reachable entry below a subtree resolves to the same preferred patch
|
||||||
|
command, that subtree can be represented as an accepting leaf for that command. Runtime lookup can
|
||||||
|
then stop at that leaf even when the input word still has remaining characters.
|
||||||
|
|
||||||
|
This is a structural optimization of preferred-result lookup. It reduces trie depth in regions
|
||||||
|
where the remaining suffix cannot change the selected command, while preserving the `get()` result
|
||||||
|
used by the standard stemmer path. The benchmark tables in `docs/benchmarks/` are based on this
|
||||||
|
contracted compiled representation.
|
||||||
|
|
||||||
### Compilation
|
### Compilation
|
||||||
|
|
||||||
The reduced structure is frozen into an immutable compiled trie optimized for runtime lookup.
|
The reduced structure is frozen into an immutable compiled trie optimized for runtime lookup.
|
||||||
@@ -124,7 +243,7 @@ At runtime, lookup is conceptually simple:
|
|||||||
3. retrieve one or more stored patch commands,
|
3. retrieve one or more stored patch commands,
|
||||||
4. apply the chosen patch command to the original word.
|
4. apply the chosen patch command to the original word.
|
||||||
|
|
||||||
The trie itself does not create the final stem string. It selects the stored transformation command. `PatchCommandEncoder.apply(...)` then performs the actual transformation.
|
The trie itself does not create the final stem string. It selects the stored transformation command. Runtime code should use `CompiledPatchCommand.apply(...)` so the serialized command is compiled once and reused.
|
||||||
|
|
||||||
That separation is architecturally important:
|
That separation is architecturally important:
|
||||||
|
|
||||||
@@ -170,6 +289,7 @@ The final compiled trie can be much smaller than the original dictionary for sev
|
|||||||
|
|
||||||
- patch commands are compact,
|
- patch commands are compact,
|
||||||
- trie paths reuse shared structure,
|
- trie paths reuse shared structure,
|
||||||
|
- uniform preferred-command subtrees can be contracted into accepting leaves,
|
||||||
- reduction merges equivalent subtrees,
|
- reduction merges equivalent subtrees,
|
||||||
- binary persistence stores the already reduced form,
|
- binary persistence stores the already reduced form,
|
||||||
- GZip compression is applied on top of the binary format.
|
- GZip compression is applied on top of the binary format.
|
||||||
@@ -180,7 +300,7 @@ This is why a very large dictionary can still produce a manageable deployable ru
|
|||||||
|
|
||||||
The compactness of the final artifact should not be confused with the memory usage of preparation.
|
The compactness of the final artifact should not be confused with the memory usage of preparation.
|
||||||
|
|
||||||
Before reduction has completed, the mutable build-time structure must exist in memory. For large dictionaries, that temporary preparation cost can be noticeably higher than the size of the final persisted artifact or the loaded compiled trie.
|
Before reduction has completed, the mutable build-time structure must exist in memory. For large dictionaries, that temporary preparation cost can be noticeably higher than the size of the final persisted artifact or the loaded compiled trie. PoliMorf is the exceptional current case: two complete test constructions took 23.7 and 23.5 seconds, produced 358,993 canonical nodes, and used a task-specific 6 GiB maximum heap. The process peak does not establish the retained heap of the final trie, which is not currently measured separately.
|
||||||
|
|
||||||
That is why the preferred operational model is usually:
|
That is why the preferred operational model is usually:
|
||||||
|
|
||||||
@@ -207,3 +327,5 @@ Determinism matters not only for tests, but also for operational trust. It makes
|
|||||||
- [Reduction Semantics](reduction-semantics.md)
|
- [Reduction Semantics](reduction-semantics.md)
|
||||||
- [Programmatic usage](programmatic-usage.md)
|
- [Programmatic usage](programmatic-usage.md)
|
||||||
- [CLI compilation](cli-compilation.md)
|
- [CLI compilation](cli-compilation.md)
|
||||||
|
- [Model selection and loading](model-selection-and-loading.md)
|
||||||
|
- [Stemmer models](stemmer-models.md)
|
||||||
|
|||||||
50
docs/assets/data/homepage-performance.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 540 KiB |
1
docs/assets/images/flags/cs.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="32" fill="#fff"/><rect y="32" width="64" height="32" fill="#d7141a"/><path d="M0 0 34 32 0 64Z" fill="#11457e"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 381 B |
1
docs/assets/images/flags/da.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="64" fill="#c8102e"/><rect x="19" width="8" height="64" fill="#fff"/><rect y="27" width="64" height="8" fill="#fff"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 385 B |
1
docs/assets/images/flags/de.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="22" fill="#111"/><rect y="22" width="64" height="21" fill="#dd0000"/><rect y="43" width="64" height="21" fill="#ffce00"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 390 B |
1
docs/assets/images/flags/en.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="64" fill="#234b9b"/><path d="M0 0 64 64M64 0 0 64" stroke="white" stroke-width="14"/><path d="M0 0 64 64M64 0 0 64" stroke="#d4253f" stroke-width="7"/><path d="M32 0v64M0 32h64" stroke="white" stroke-width="18"/><path d="M32 0v64M0 32h64" stroke="#d4253f" stroke-width="10"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 544 B |
1
docs/assets/images/flags/es.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="64" fill="#aa151b"/><rect y="16" width="64" height="32" fill="#f1bf00"/><circle cx="23" cy="32" r="4" fill="#aa151b"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 387 B |
1
docs/assets/images/flags/fa.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="22" fill="#239f40"/><rect y="22" width="64" height="20" fill="#fff"/><rect y="42" width="64" height="22" fill="#da0000"/><circle cx="32" cy="32" r="5" fill="none" stroke="#da0000" stroke-width="2"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 467 B |
1
docs/assets/images/flags/fi.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="64" fill="#fff"/><rect x="19" width="9" height="64" fill="#003580"/><rect y="27" width="64" height="9" fill="#003580"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 388 B |
1
docs/assets/images/flags/fr.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="22" height="64" fill="#0055a4"/><rect x="22" width="20" height="64" fill="#fff"/><rect x="42" width="22" height="64" fill="#ef4135"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 390 B |
1
docs/assets/images/flags/he.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="64" fill="#fff"/><rect y="13" width="64" height="6" fill="#0038b8"/><rect y="45" width="64" height="6" fill="#0038b8"/><path d="M32 22 39 35H25Zm0 20-7-13h14Z" fill="none" stroke="#0038b8" stroke-width="2.3"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 478 B |
1
docs/assets/images/flags/hu.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="22" fill="#ce2939"/><rect y="22" width="64" height="20" fill="#fff"/><rect y="42" width="64" height="22" fill="#477050"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 390 B |
1
docs/assets/images/flags/it.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="22" height="64" fill="#009246"/><rect x="22" width="20" height="64" fill="#fff"/><rect x="42" width="22" height="64" fill="#ce2b37"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 390 B |
1
docs/assets/images/flags/nb.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="64" fill="#ba0c2f"/><rect x="18" width="12" height="64" fill="#fff"/><rect y="25" width="64" height="12" fill="#fff"/><rect x="21" width="6" height="64" fill="#00205b"/><rect y="28" width="64" height="6" fill="#00205b"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 489 B |
1
docs/assets/images/flags/nl.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="22" fill="#ae1c28"/><rect y="22" width="64" height="20" fill="#fff"/><rect y="42" width="64" height="22" fill="#21468b"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 390 B |
1
docs/assets/images/flags/nn.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="64" fill="#ba0c2f"/><rect x="18" width="12" height="64" fill="#fff"/><rect y="25" width="64" height="12" fill="#fff"/><rect x="21" width="6" height="64" fill="#00205b"/><rect y="28" width="64" height="6" fill="#00205b"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 489 B |
1
docs/assets/images/flags/pl.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="32" fill="#fff"/><rect y="32" width="64" height="32" fill="#dc143c"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 338 B |
1
docs/assets/images/flags/pt.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="25" height="64" fill="#046a38"/><rect x="25" width="39" height="64" fill="#da291c"/><circle cx="25" cy="32" r="7" fill="#ffcd00"/><circle cx="25" cy="32" r="4" fill="#fff"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 430 B |
1
docs/assets/images/flags/ru.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="22" fill="#fff"/><rect y="22" width="64" height="20" fill="#0039a6"/><rect y="42" width="64" height="22" fill="#d52b1e"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 390 B |
1
docs/assets/images/flags/sv.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="64" fill="#006aa7"/><rect x="19" width="8" height="64" fill="#fecc00"/><rect y="27" width="64" height="8" fill="#fecc00"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
1
docs/assets/images/flags/uk.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="32" fill="#0057b7"/><rect y="32" width="64" height="32" fill="#ffd700"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 341 B |
1
docs/assets/images/flags/yi.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><clipPath id="c"><circle cx="32" cy="32" r="30"/></clipPath></defs><g clip-path="url(#c)"><rect width="64" height="64" fill="#fff"/><rect y="13" width="64" height="6" fill="#0038b8"/><rect y="45" width="64" height="6" fill="#0038b8"/><path d="M32 22 39 35H25Zm0 20-7-13h14Z" fill="none" stroke="#0038b8" stroke-width="2.3"/></g><circle cx="32" cy="32" r="30" fill="none" stroke="#d8e2ee" stroke-width="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 478 B |
1
docs/assets/images/hero-lines.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 700" fill="none"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#6759ff" stop-opacity=".10"/><stop offset=".5" stop-color="#2d8cff" stop-opacity=".13"/><stop offset="1" stop-color="#12bfa7" stop-opacity=".10"/></linearGradient></defs><g stroke="url(#g)" stroke-width="1.25"><path d="M-40 585C150 328 262 493 408 255S665 12 927 50"/><path d="M-55 620C142 363 274 507 424 273S689 16 940 78"/><path d="M-65 652C151 403 289 526 447 301S704 34 958 112"/><path d="M-71 679C162 442 311 548 471 336S727 63 970 148"/><path d="M-74 704C186 481 338 574 500 371S748 94 985 190"/><path d="M244 8C400 46 516 22 622 93S780 236 940 227"/><path d="M270 -7C428 28 540 8 655 78S811 209 964 197"/></g><g fill="#0ea5e9" opacity=".18"><circle cx="408" cy="255" r="3.8"/><circle cx="500" cy="371" r="3.8"/><circle cx="655" cy="78" r="3.8"/><circle cx="811" cy="209" r="3.8"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 947 B |
1
docs/assets/images/java-award.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 190" fill="none"><defs><linearGradient id="s" x1="35" y1="25" x2="173" y2="172"><stop stop-color="#8b78ff"/><stop offset=".55" stop-color="#5b49e9"/><stop offset="1" stop-color="#3158f5"/></linearGradient><filter id="f"><feDropShadow dx="0" dy="8" stdDeviation="8" flood-color="#4f46e5" flood-opacity=".18"/></filter></defs><g stroke="#7667f2" stroke-width="5" stroke-linecap="round" fill="none" opacity=".8"><path d="M49 143c-23-9-32-25-37-45M42 127c-12-3-21-11-26-21M171 143c23-9 32-25 37-45M178 127c12-3 21-11 26-21"/><path d="M51 144c-17 6-28 7-38 5M169 144c17 6 28 7 38 5"/></g><path filter="url(#f)" d="M110 18 168 42v52c0 41-25 66-58 82-33-16-58-41-58-82V42Z" fill="url(#s)" stroke="#b7afff" stroke-width="3"/><path d="M88 114h44M96 124h28" stroke="white" stroke-width="5" stroke-linecap="round"/><path d="M90 92c0 12 8 20 20 20s20-8 20-20v-6H90v6Z" fill="none" stroke="white" stroke-width="5"/><path d="M130 88h8c9 0 9 13 0 13h-9" stroke="white" stroke-width="5" stroke-linecap="round"/><path d="M102 79c-13-12 9-16-2-30M115 78c-10-11 8-15 0-27M126 79c-8-9 7-13 0-23" stroke="white" stroke-width="4" stroke-linecap="round"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
BIN
docs/assets/images/radixor-logo.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
1
docs/assets/javascripts/landing-v2.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded",()=>{const 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){}}));});
|
||||||
14
docs/assets/javascripts/mermaid.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -67,6 +67,85 @@
|
|||||||
padding: 0.45rem 0.7rem;
|
padding: 0.45rem 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Primary quality rankings fit the content column; raw counts remain in details. */
|
||||||
|
.quality-summary {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0.65rem 0 1rem;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-summary .md-typeset__table,
|
||||||
|
.quality-summary table {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-summary table {
|
||||||
|
display: table;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-summary th,
|
||||||
|
.quality-summary td {
|
||||||
|
padding: 0.4rem 0.45rem !important;
|
||||||
|
line-height: 1.3;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-summary th:nth-child(1),
|
||||||
|
.quality-summary td:nth-child(1) {
|
||||||
|
width: 7%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-summary th:nth-child(2),
|
||||||
|
.quality-summary td:nth-child(2) {
|
||||||
|
width: 39%;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-summary th:nth-child(n + 3),
|
||||||
|
.quality-summary td:nth-child(n + 3) {
|
||||||
|
width: 18%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-summary td:nth-child(n + 3) {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-summary--oracle th:nth-child(1),
|
||||||
|
.quality-summary--oracle td:nth-child(1) {
|
||||||
|
width: 46%;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-summary--oracle th:nth-child(n + 2),
|
||||||
|
.quality-summary--oracle td:nth-child(n + 2) {
|
||||||
|
width: 27%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-details > summary {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 44.99em) {
|
||||||
|
.quality-summary {
|
||||||
|
font-size: 0.62rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-summary th,
|
||||||
|
.quality-summary td {
|
||||||
|
padding: 0.3rem 0.2rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
.quality-details:not([open]) > *:not(summary) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Code blocks */
|
/* Code blocks */
|
||||||
.md-typeset pre > code {
|
.md-typeset pre > code {
|
||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
|
|||||||
136
docs/assets/stylesheets/landing-v2.css
Normal file
146
docs/assets/stylesheets/radixor-docs-safety.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,159 +1,47 @@
|
|||||||
# Benchmarking
|
# Benchmarking
|
||||||
|
|
||||||
Radixor includes a JMH benchmark suite for both the internal algorithmic core and a side-by-side English comparison against the Snowball Porter stemmer family.
|
Radixor contains internal trie microbenchmarks, a separate stemmer comparison suite, and a dictionary coverage benchmark for Radixor itself. The current default-model publication uses the same-language speed and exact-root methods selected by the command recorded on the [environment page](benchmarks/reference/environment.md). Internal `FrequencyTrie*` microbenchmarks, the optional `PolishPolimorfStemmerComparisonBenchmark`, and the separate German CISTEM gold-standard experiment are not part of these language tables.
|
||||||
|
|
||||||
This document explains what is benchmarked, how to run the suite, and how benchmark results should be interpreted.
|
Every current default Radixor benchmark scenario uses the model ID declared by its `Language.defaultModelId()`. The root JMH runtime configuration depends directly on all default model projects plus optional `pl-pl-polimorf`; no benchmark-pack project or artifact exists. These dependencies are benchmark-only and never enter the root published POM. A PoliMorf comparison must be labeled with model ID `pl-pl-polimorf`, while the default Polish row remains `pl-pl-unimorph`.
|
||||||
|
|
||||||
## Scope
|
The optional model now has a verified complete compiled loading path. It is not included in the 2026-07-23 corpus, accuracy, speed, coverage, or stemming-quality measurements. Any future full PoliMorf benchmark must provision its documented startup heap independently and record the exact model artifact version and checksum.
|
||||||
|
|
||||||
The benchmark suite currently covers two categories:
|
This page is the entry point for benchmark interpretation. Detailed tables and long reference material are split into focused subpages so that important points do not get buried.
|
||||||
|
|
||||||
- Radixor core operations,
|
## Key Takeaways
|
||||||
- English stemmer comparison on the same token workload.
|
|
||||||
|
|
||||||
The comparison benchmark processes the same deterministic English token stream through:
|
- Speed and accuracy must be read together. A faster row is not necessarily a better stemmer.
|
||||||
|
- Radixor is the quality-oriented baseline in same-language comparisons. Its exact-root accuracy is often close to 100%, while many faster competitors are light, minimal, possessive, or aggressive rule-based stemmers with much lower root agreement.
|
||||||
|
- The measured Radixor cost buys dictionary-trained stemming precision. That precision improves search quality by mapping inflected forms to intended dictionary roots instead of approximate or over-reduced stems.
|
||||||
|
- Speed benchmarks process changed dictionary tokens where the surface form differs from the expected root. Accuracy benchmarks process the complete dictionary.
|
||||||
|
- Accuracy tables use deterministic auxiliary counters from a single non-timed JMH evaluation, while Radixor counters are independently cross-checked by the default-model corpus report. Runtime scores from accuracy methods are not interpreted.
|
||||||
|
- The historical Porter performance badge is retired. Benchmark reporting now uses speed and quality tables rather than a single Porter ratio.
|
||||||
|
|
||||||
- Radixor with bundled `US_UK` (older benchmark snapshots used the now-retired `US_UK_PROFI` resource),
|
## Benchmark Documentation Map
|
||||||
- Snowball original Porter,
|
|
||||||
- Snowball English, commonly referred to as Porter2.
|
|
||||||
|
|
||||||
The purpose of the comparison is throughput measurement on identical input. It is not intended to demonstrate linguistic equivalence between the compared stemmers.
|
| Page | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [Benchmark methodology](benchmarks/reference/methodology.md) | Workload design, speed pass, quality pass, normalization policy, and exact-root metrics. |
|
||||||
|
| [Linguistic quality methodology](benchmarks/reference/linguistic-quality.md) | Pairwise gold standard, over/under-stemming, candidate policies, metrics, and ranking rules. |
|
||||||
|
| [Tested stemmers](benchmarks/reference/tested-stemmers.md) | Upstream attribution, tested versions, language coverage, adapter behaviour, and limitations. |
|
||||||
|
| [Reproducibility and raw data](benchmarks/reference/reproducibility.md) | Versioned quality snapshot, checksum, commands, reports, and provenance limitations. |
|
||||||
|
| [Benchmark corpora](benchmarks/reference/corpora.md) | Dictionary row counts, complete quality tokens, already-root tokens, changed speed tokens, and timing token counts. |
|
||||||
|
| [Benchmark environment and reports](benchmarks/reference/environment.md) | Hardware, OS, JVM, JMH settings, report files, and current badge/report policy. |
|
||||||
|
| [English dictionary coverage benchmark](benchmarks/reference/english-coverage.md) | The quality/speed operating curve for contracted Radixor tries built from 100% down to 10% of English dictionary rows. |
|
||||||
|
| [Candidate evaluation](benchmarks/reference/candidates.md) | Included benchmark families and evaluated candidates that were skipped. |
|
||||||
|
| [Language benchmark pages](benchmarks/languages/index.md) | Per-language accuracy tables, speed tables, and implementation notes. |
|
||||||
|
|
||||||
## How to read the published numbers
|
## How To Read Results
|
||||||
|
|
||||||
Two kinds of benchmark numbers are relevant in the project.
|
Start with the [language benchmark pages](benchmarks/languages/index.md). Each language page lists accuracy first and speed second because throughput without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
### Reference measurements
|
When Radixor is slower than a narrow competitor, check the accuracy table before drawing a conclusion. Many Lucene light/minimal filters and possessive filters intentionally do less work. They can be fast precisely because they are not trying to match the dictionary root with the same precision.
|
||||||
|
|
||||||
The detailed benchmark snapshot documented on this page comes from a controlled run on a Ryzen 5 system. Those numbers are the best reference point for understanding absolute throughput under a known local benchmark environment.
|
The [English dictionary coverage benchmark](benchmarks/reference/english-coverage.md) shows the central operating curve explicitly: contracted tries preserve high quality even at reduced dictionary coverage, while changed-form exactness still reflects how much language knowledge was available during training. This is why Radixor performance should be discussed as a configurable quality/speed point, not as a single fixed ratio against Porter.
|
||||||
|
|
||||||
### Published badge figures
|
## Current Result Locations
|
||||||
|
|
||||||
The benchmark badge metadata published through GitHub Pages is generated in the GitHub-hosted container environment. That environment is convenient for continuous publication, but it is not the right place to treat absolute throughput values as stable across time. CPU scheduling, shared-host variability, and container-level noise can materially affect raw numbers from run to run.
|
The current measured language results are published in [Language Benchmark Pages](benchmarks/languages/index.md). Generated local report files for this benchmark update are listed in [Benchmark environment and reports](benchmarks/reference/environment.md).
|
||||||
|
|
||||||
For that reason, the published badge values should be treated primarily as a compact status surface. They are useful for observing broad trends and relative positioning, but not as the authoritative source for precise absolute throughput claims.
|
JMH TXT and CSV reports are still published as benchmark artifacts. They are no longer converted into a Shields endpoint benchmark badge.
|
||||||
|
Model IDs, independent artifact versions, and descriptor checksums identify the inputs in the checked corpus snapshot. The optional PoliMorf model must not be attributed to the default Polish results. See [Model Selection and Loading](model-selection-and-loading.md) and [Reproducibility](benchmarks/reference/reproducibility.md).
|
||||||
## Current snapshot
|
|
||||||
|
|
||||||
A recent JMH run on JDK 21.0.10 with JMH 1.37, one thread, three warmup iterations, and five measurement iterations produced the following approximate throughput ranges:
|
|
||||||
|
|
||||||
| Workload | Radixor `US_UK` *(historical runs: `US_UK_PROFI`)* | Snowball Porter | Snowball English |
|
|
||||||
| --- | ---: | ---: | ---: |
|
|
||||||
| About 12,000 generated tokens | 30.99 M tokens/s | 8.21 M tokens/s | 5.46 M tokens/s |
|
|
||||||
| About 60,000 generated tokens | 32.25 M tokens/s | 8.02 M tokens/s | 5.11 M tokens/s |
|
|
||||||
|
|
||||||
On that workload, Radixor measured approximately:
|
|
||||||
|
|
||||||
- 4 times the throughput of Snowball original Porter,
|
|
||||||
- 6 times the throughput of Snowball English.
|
|
||||||
|
|
||||||
These values are workload-dependent and environment-dependent. They should be read as measured results for the documented setup, not as universal constants.
|
|
||||||
|
|
||||||
## Interpreting the relative result
|
|
||||||
|
|
||||||
Although the absolute numbers can move across environments, the throughput relationship between Radixor and the compared Porter-family stemmers has remained broadly stable in practical measurements. In particular, the comparison against Snowball original Porter is consistently in the rough range of about four to one in Radixor’s favor.
|
|
||||||
|
|
||||||
That relative behavior is more informative than any single absolute figure. It reflects a real architectural difference rather than a cosmetic benchmark artifact.
|
|
||||||
|
|
||||||
Radixor is built around a compiled patch-command trie that resolves the result through a direct lookup and patch application path. In contrast, classic rule-based stemmers such as the Porter family follow a different operational model. The result is that Radixor combines two properties that do not often appear together:
|
|
||||||
|
|
||||||
- dictionary-driven compiled lookup performance,
|
|
||||||
- the ability to generalize beyond explicitly listed word forms instead of behaving like a pure closed-form dictionary lookup table.
|
|
||||||
|
|
||||||
Within that design space, the measured throughput profile is strong enough to place Radixor among the fastest known practical implementations of this kind, while still supporting stemming of previously unseen forms. That should still be read as a carefully bounded engineering statement, not as an absolute claim over every possible stemmer architecture or benchmark scenario.
|
|
||||||
|
|
||||||
## Benchmark classes
|
|
||||||
|
|
||||||
The main benchmark classes are under `src/jmh/java/org/egothor/stemmer/benchmark`.
|
|
||||||
|
|
||||||
Relevant classes include:
|
|
||||||
|
|
||||||
- `FrequencyTrieLookupBenchmark`,
|
|
||||||
- `FrequencyTrieCompilationBenchmark`,
|
|
||||||
- `EnglishStemmerComparisonBenchmark`.
|
|
||||||
|
|
||||||
The English comparison benchmark uses the bundled Radixor English resource and the official Snowball Java distribution integrated into the JMH source set.
|
|
||||||
|
|
||||||
## Workload design
|
|
||||||
|
|
||||||
The English comparison benchmark uses a deterministic generated corpus rather than an uncontrolled ad hoc text sample.
|
|
||||||
|
|
||||||
The workload intentionally mixes:
|
|
||||||
|
|
||||||
- simple inflections,
|
|
||||||
- common derivational forms,
|
|
||||||
- US and UK spelling families,
|
|
||||||
- lexical forms appropriate for the current bundled `US_UK` resource (with historical continuity from earlier `US_UK_PROFI` runs).
|
|
||||||
|
|
||||||
This design keeps runs reproducible across environments and avoids accidental drift caused by changing external corpora.
|
|
||||||
|
|
||||||
## Running benchmarks
|
|
||||||
|
|
||||||
Run the full benchmark suite:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew jmh
|
|
||||||
```
|
|
||||||
|
|
||||||
Run only the English comparison benchmark:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew jmh -Pjmh.includes=EnglishStemmerComparisonBenchmark
|
|
||||||
```
|
|
||||||
|
|
||||||
## Generated reports
|
|
||||||
|
|
||||||
JMH reports are written to:
|
|
||||||
|
|
||||||
- `build/reports/jmh/jmh-results.txt`,
|
|
||||||
- `build/reports/jmh/jmh-results.csv`.
|
|
||||||
|
|
||||||
The text report is convenient for human review. The CSV report is more useful for CI archiving, historical tracking, and external processing.
|
|
||||||
|
|
||||||
## Interpreting results responsibly
|
|
||||||
|
|
||||||
Benchmark numbers should always be read with care.
|
|
||||||
|
|
||||||
Important factors include:
|
|
||||||
|
|
||||||
- CPU model and frequency behavior,
|
|
||||||
- thermal throttling,
|
|
||||||
- JVM vendor and version,
|
|
||||||
- system background load,
|
|
||||||
- operating-system scheduling noise,
|
|
||||||
- benchmark parameter changes.
|
|
||||||
|
|
||||||
For meaningful comparison, keep these stable:
|
|
||||||
|
|
||||||
- hardware or VM class,
|
|
||||||
- JDK version,
|
|
||||||
- benchmark parameters,
|
|
||||||
- thread count,
|
|
||||||
- benchmark source revision.
|
|
||||||
|
|
||||||
If a regression is suspected, repeat the run and compare against previous CSV output rather than relying on a single measurement.
|
|
||||||
|
|
||||||
## Regression tracking
|
|
||||||
|
|
||||||
The recommended regression workflow is:
|
|
||||||
|
|
||||||
1. archive `jmh-results.csv`,
|
|
||||||
2. compare the same benchmark names across runs,
|
|
||||||
3. compare only like-for-like environments,
|
|
||||||
4. investigate sustained regressions rather than one-off noise.
|
|
||||||
|
|
||||||
For public reporting, the README should keep only the condensed benchmark summary, while detailed benchmark methodology and interpretation should remain in this document.
|
|
||||||
|
|
||||||
## Notes on comparison fairness
|
|
||||||
|
|
||||||
Radixor, Snowball Porter, and Snowball English are not the same kind of stemmer.
|
|
||||||
|
|
||||||
Radixor uses a compiled patch-command trie driven by dictionary data. Snowball Porter and Snowball English are rule-based English stemmers.
|
|
||||||
|
|
||||||
Because of that, the comparison should be understood as:
|
|
||||||
|
|
||||||
- equal input workload,
|
|
||||||
- different stemming strategies,
|
|
||||||
- measured throughput rather than semantic identity.
|
|
||||||
|
|
||||||
That distinction matters whenever performance claims are discussed in documentation, release notes, or badge summaries.
|
|
||||||
|
|||||||
315
docs/benchmarks/data/stemming-quality.csv
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
Stemmer,Language,Dictionary model ID,Dictionary model version,Dictionary model SHA-256,Dictionary mode,Output policy,Applied dictionary rows,Processed word forms,Singleton dictionary rows,Forms with one candidate,Forms with multiple candidates,Maximum candidates for one form,Total candidate assignments,Distinct output stems,True-positive pairs,False-positive pairs,False-negative pairs,True-negative pairs,Over-stemming error pairs,Over-stemming possible pairs,Over-stemming percentage,Under-stemming error pairs,Under-stemming possible pairs,Under-stemming percentage,Pairwise precision,Pairwise recall,Pairwise specificity,Pairwise accuracy,Balanced accuracy,Pairwise F0.5,Pairwise F1,Pairwise F2,Jaccard index,Fowlkes-Mallows index,Matthews correlation coefficient,Pairwise error rate,Adjusted Rand Index,Homogeneity,Completeness,V-measure,Normalized mutual information
|
||||||
|
"CZECH_LUCENE_CZECH_STEM_FILTER","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","ALL_WORDS","PRIMARY_OUTPUT","5113","51401","2","51401","0","1","51401","9647","176908","12256","123601","1320692935","12256","1320705191","0.000928","123601","300509","41.130549","0.935209659343","0.588694514973","0.999990720109","0.999897156386","0.794342617541","0.836709501355","0.722555664699","0.635810810811","0.565625949195","0.741992450639","0.741949479040","0.000102843614","","","","",""
|
||||||
|
"CZECH_LUCENE_CZECH_STEM_FILTER","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","5038","50697","2","50697","0","1","50697","9558","174046","11790","123441","1284758279","11790","1284770069","0.000918","123441","297487","41.494586","0.936556964205","0.585054136819","0.999990823261","0.999894767400","0.792522480040","0.836091546082","0.720205742330","0.632533886133","0.562751190680","0.740227347695","0.740183575451","0.000105232600","","","","",""
|
||||||
|
"CZECH_RADIXOR","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","ALL_WORDS","PRIMARY_OUTPUT","5113","51401","2","51401","0","1","51401","5162","298476","0","2033","1320705191","0","1320705191","0.000000","2033","300509","0.676519","1.000000000000","0.993234811603","1.000000000000","0.999998461021","0.996617405801","0.998639599629","0.996605925023","0.994580516517","0.993234811603","0.996611665396","0.996610898340","0.000001538979","","","","",""
|
||||||
|
"CZECH_RADIXOR","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","ALL_WORDS","ANY_CANDIDATE","5113","51401","2","51080","321","4","51739","5166","","","","","0","1320705191","0.000000","0","300509","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"CZECH_RADIXOR","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","ALL_WORDS","ALL_CANDIDATES","5113","51401","2","51080","321","4","51739","5166","300509","0","0","1320705191","0","1320705191","0.000000","0","300509","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","","","","",""
|
||||||
|
"CZECH_RADIXOR","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","5038","50697","2","50697","0","1","50697","5037","295818","0","1669","1284770069","0","1284770069","0.000000","1669","297487","0.561033","1.000000000000","0.994389670809","1.000000000000","0.999998701236","0.997194835405","0.998872875329","0.997186944320","0.995506694863","0.994389670809","0.997190889855","0.997190242147","0.000001298764","","","","",""
|
||||||
|
"CZECH_RADIXOR","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","5038","50697","2","50428","269","4","50975","5040","","","","","0","1284770069","0.000000","0","297487","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"CZECH_RADIXOR","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","5038","50697","2","50428","269","4","50975","5040","297487","0","0","1284770069","0","1284770069","0.000000","0","297487","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","","","","",""
|
||||||
|
"DA_DK_RADIXOR","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","ALL_WORDS","PRIMARY_OUTPUT","4179","27921","32","27921","0","1","27921","4184","89021","0","674","389687465","0","389687465","0.000000","674","89695","0.751435","1.000000000000","0.992485645800","1.000000000000","0.999998270807","0.996242822900","0.998488040038","0.996228653282","0.993979468559","0.992485645800","0.996235738066","0.996234876527","0.000001729193","","","","",""
|
||||||
|
"DA_DK_RADIXOR","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","ALL_WORDS","ANY_CANDIDATE","4179","27921","32","27756","165","3","28087","4187","","","","","0","389687465","0.000000","0","89695","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"DA_DK_RADIXOR","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","ALL_WORDS","ALL_CANDIDATES","4179","27921","32","27756","165","3","28087","4187","89695","0","0","389687465","0","389687465","0.000000","0","89695","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","","","","",""
|
||||||
|
"DA_DK_RADIXOR","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4173","27875","32","27875","0","1","27875","4170","88910","0","630","388404335","0","388404335","0.000000","630","89540","0.703596","1.000000000000","0.992964038419","1.000000000000","0.999998378353","0.996482019209","0.998584842086","0.996469599328","0.994363298812","0.992964038419","0.996475809249","0.996475001098","0.000001621647","","","","",""
|
||||||
|
"DA_DK_RADIXOR","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","4173","27875","32","27718","157","3","28033","4173","","","","","0","388404335","0.000000","0","89540","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"DA_DK_RADIXOR","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","4173","27875","32","27718","157","3","28033","4173","89540","0","0","388404335","0","388404335","0.000000","0","89540","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","","","","",""
|
||||||
|
"ENGLISH_LUCENE_KSTEM_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","PRIMARY_OUTPUT","396939","591946","250964","591946","0","1","591946","371125","237301","193361","76054","175199230769","193361","175199424130","0.000110","76054","313355","24.270875","0.551014484677","0.757291251137","0.999998896338","0.999998462241","0.878645073737","0.582761911451","0.637891338504","0.704541109043","0.468311638077","0.645970934714","0.645970209547","0.000001537759","","","","",""
|
||||||
|
"ENGLISH_LUCENE_KSTEM_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","347624","237291","193354","74091","161561796284","193354","161561989638","0.000120","74091","311382","23.794246","0.551013015361","0.762057537045","0.999998803221","0.999998344632","0.881028170133","0.583322107296","0.639575109801","0.707835647036","0.470128938693","0.647999707843","0.647998929360","0.000001655368","","","","",""
|
||||||
|
"ENGLISH_LUCENE_MINIMAL_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","PRIMARY_OUTPUT","396939","591946","250964","591946","0","1","591946","453328","137223","1401","176132","175199422729","1401","175199424130","0.000001","176132","313355","56.208454","0.989893524931","0.437915463292","0.999999992003","0.999998986682","0.718957727648","0.790590781136","0.607209626996","0.492883127257","0.435966272287","0.658399332913","0.658398992571","0.000001013318","","","","",""
|
||||||
|
"ENGLISH_LUCENE_MINIMAL_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","430129","136930","1388","174452","161561988250","1388","161561989638","0.000001","174452","311382","56.025075","0.989965152764","0.439749246906","0.999999991409","0.999998911627","0.719874619157","0.791819618021","0.608983766956","0.494744357392","0.437797742750","0.659800295840","0.659799929564","0.000001088373","","","","",""
|
||||||
|
"ENGLISH_LUCENE_PORTER_COPIED","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","PRIMARY_OUTPUT","396939","591946","250964","591946","0","1","591946","319968","285026","362583","28329","175199061547","362583","175199424130","0.000207","28329","313355","9.040545","0.440120504811","0.909594549313","0.999997930456","0.999997768764","0.954796239884","0.490782566652","0.593208486478","0.749662419669","0.421674768988","0.632717324100","0.632716488082","0.000002231236","","","","",""
|
||||||
|
"ENGLISH_LUCENE_PORTER_COPIED","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","298779","283398","359344","27984","161561630294","359344","161561989638","0.000222","27984","311382","8.987032","0.440920307059","0.910129679943","0.999997775813","0.999997602609","0.955063727878","0.491609277152","0.594048572303","0.750417048409","0.422524249843","0.633478222155","0.633477323677","0.000002397391","","","","",""
|
||||||
|
"ENGLISH_LUCENE_PORTER_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","PRIMARY_OUTPUT","396939","591946","250964","591946","0","1","591946","319968","285026","362583","28329","175199061547","362583","175199424130","0.000207","28329","313355","9.040545","0.440120504811","0.909594549313","0.999997930456","0.999997768764","0.954796239884","0.490782566652","0.593208486478","0.749662419669","0.421674768988","0.632717324100","0.632716488082","0.000002231236","","","","",""
|
||||||
|
"ENGLISH_LUCENE_PORTER_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","298779","283398","359344","27984","161561630294","359344","161561989638","0.000222","27984","311382","8.987032","0.440920307059","0.910129679943","0.999997775813","0.999997602609","0.955063727878","0.491609277152","0.594048572303","0.750417048409","0.422524249843","0.633478222155","0.633477323677","0.000002397391","","","","",""
|
||||||
|
"ENGLISH_LUCENE_POSSESSIVE_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","PRIMARY_OUTPUT","396939","591946","250964","591946","0","1","591946","591899","7","40","313348","175199424090","40","175199424130","0.000000","313348","313355","99.997766","0.148936170213","0.000022338881","0.999999999772","0.999998211253","0.500011169326","0.000111627432","0.000044671061","0.000027922554","0.000022336030","0.001824025043","0.001824004770","0.000001788747","","","","",""
|
||||||
|
"ENGLISH_LUCENE_POSSESSIVE_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","568400","5","36","311377","161561989602","36","161561989638","0.000000","311377","311382","99.998394","0.121951219512","0.000016057447","0.999999999777","0.999998072490","0.500008028612","0.000080244972","0.000032110666","0.000020071148","0.000016055591","0.001399366020","0.001399345253","0.000001927510","","","","",""
|
||||||
|
"ENGLISH_OPENNLP_PORTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","PRIMARY_OUTPUT","396939","591946","250964","591946","0","1","591946","319968","285026","362583","28329","175199061547","362583","175199424130","0.000207","28329","313355","9.040545","0.440120504811","0.909594549313","0.999997930456","0.999997768764","0.954796239884","0.490782566652","0.593208486478","0.749662419669","0.421674768988","0.632717324100","0.632716488082","0.000002231236","","","","",""
|
||||||
|
"ENGLISH_OPENNLP_PORTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","298779","283398","359344","27984","161561630294","359344","161561989638","0.000222","27984","311382","8.987032","0.440920307059","0.910129679943","0.999997775813","0.999997602609","0.955063727878","0.491609277152","0.594048572303","0.750417048409","0.422524249843","0.633478222155","0.633477323677","0.000002397391","","","","",""
|
||||||
|
"ENGLISH_PAICE_HUSK_LANCASTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","PRIMARY_OUTPUT","396939","591946","250964","591946","0","1","591946","268169","283611","1682034","29744","175197742096","1682034","175199424130","0.000960","29744","313355","9.492110","0.144283937334","0.905078904118","0.999990399318","0.999990229563","0.952534651718","0.173442548161","0.248890741553","0.440517665844","0.142133188065","0.361370098215","0.361367968977","0.000009770437","","","","",""
|
||||||
|
"ENGLISH_PAICE_HUSK_LANCASTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","249411","282022","1666990","29360","161560322648","1666990","161561989638","0.001032","29360","311382","9.428933","0.144699981324","0.905710670495","0.999989682041","0.999989500335","0.952850176268","0.173928112855","0.249533488410","0.441412535138","0.142552563421","0.362017012177","0.362014722775","0.000010499665","","","","",""
|
||||||
|
"ENGLISH_RADIXOR","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","PRIMARY_OUTPUT","396939","591946","250964","591946","0","1","591946","390361","291757","3","21598","175199424127","3","175199424130","0.000000","21598","313355","6.892502","0.999989717576","0.931074978858","0.999999999983","0.999999876706","0.965537489420","0.985402544591","0.964302653215","0.944087420236","0.931066065012","0.964917304825","0.964917245339","0.000000123294","","","","",""
|
||||||
|
"ENGLISH_RADIXOR","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","ANY_CANDIDATE","396939","591946","250964","578228","13718","1355","607918","397392","","","","","0","175199424130","0.000000","15","313355","0.004787","","","","","","","","","","","","","","","","",""
|
||||||
|
"ENGLISH_RADIXOR","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","ALL_CANDIDATES","396939","591946","250964","578228","13718","1355","607918","397392","313340","55","15","175199424075","55","175199424130","0.000000","15","313355","0.004787","0.999824502624","0.999952130970","0.999999999686","0.999999999600","0.999976065328","0.999850025687","0.999888312724","0.999926602694","0.999776650394","0.999888314761","0.999888314561","0.000000000400","","","","",""
|
||||||
|
"ENGLISH_RADIXOR","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","367590","290334","3","21048","161561989635","3","161561989638","0.000000","21048","311382","6.759543","0.999989667180","0.932404570592","0.999999999981","0.999999869704","0.966202285287","0.985700026481","0.965015231362","0.945180728775","0.932395587456","0.965605994297","0.965605931388","0.000000130296","","","","",""
|
||||||
|
"ENGLISH_RADIXOR","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","374384","568441","228735","555084","13357","1355","584042","374506","","","","","0","161561989638","0.000000","0","311382","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"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","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","","","","",""
|
||||||
|
"FINNISH_RADIXOR","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","ALL_WORDS","ANY_CANDIDATE","57027","1788784","292","1754389","34395","6","1826768","69769","","","","","0","1599841739337","0.000000","0","31465599","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"FINNISH_RADIXOR","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","ALL_WORDS","ALL_CANDIDATES","57027","1788784","292","1754389","34395","6","1826768","69769","31465599","2327","0","1599841737010","2327","1599841739337","0.000000","0","31465599","0.000000","0.999926051688","1.000000000000","0.999999998545","0.999999998546","0.999999999273","0.999940840476","0.999963024477","0.999985209463","0.999926051688","0.999963025161","0.999963024433","0.000000001454","","","","",""
|
||||||
|
"FINNISH_RADIXOR","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","54762","1734784","274","1734784","0","1","1734784","54633","30037514","804","723369","1504706134249","804","1504706135053","0.000000","723369","30760883","2.351587","0.999973234187","0.976484127585","0.999999999466","0.999999518738","0.988242063525","0.995185441684","0.988089103342","0.981093251747","0.976458605798","0.988158889650","0.988158651850","0.000000481262","","","","",""
|
||||||
|
"FINNISH_RADIXOR","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","54762","1734784","274","1712724","22060","6","1758300","54984","","","","","0","1504706135053","0.000000","0","30760883","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"FINNISH_RADIXOR","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","54762","1734784","274","1712724","22060","6","1758300","54984","30760883","2235","0","1504706132818","2235","1504706135053","0.000000","0","30760883","0.000000","0.999927348067","1.000000000000","0.999999998515","0.999999998515","0.999999999257","0.999941877609","0.999963672714","0.999985468769","0.999927348067","0.999963673373","0.999963672631","0.000000001485","","","","",""
|
||||||
|
"FRENCH_LUCENE_FRENCH_LIGHT_STEM_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","ALL_WORDS","PRIMARY_OUTPUT","59240","404011","2301","404011","0","1","404011","245918","198474","152794","5171725","81606719062","152794","81606871856","0.000187","5171725","5370199","96.304159","0.565021578965","0.036958406942","0.999998127682","0.999934758330","0.518478267312","0.146469417976","0.069378710041","0.045454703687","0.035935949946","0.144507084415","0.144495320409","0.000065241670","","","","",""
|
||||||
|
"FRENCH_LUCENE_FRENCH_LIGHT_STEM_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","57698","400712","2133","400712","0","1","400712","245182","196458","145140","5159793","80279351725","145140","80279496865","0.000181","5159793","5356251","96.332173","0.575114608399","0.036678266198","0.999998192066","0.999933923613","0.518338229132","0.146116638947","0.068958654397","0.045128311714","0.035710604827","0.145238447737","0.145226752971","0.000066076387","","","","",""
|
||||||
|
"FRENCH_LUCENE_FRENCH_MINIMAL_STEM_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","ALL_WORDS","PRIMARY_OUTPUT","59240","404011","2301","404011","0","1","404011","269236","180266","67902","5189933","81606803954","67902","81606871856","0.000083","5189933","5370199","96.643216","0.726386963670","0.033567843575","0.999999167938","0.999935575413","0.516783505756","0.141654608431","0.064170247333","0.041480578641","0.033148703932","0.156151349567","0.156142578924","0.000064424587","","","","",""
|
||||||
|
"FRENCH_LUCENE_FRENCH_MINIMAL_STEM_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","57698","400712","2133","400712","0","1","400712","268411","178414","60724","5177837","80279436141","60724","80279496865","0.000076","5177837","5356251","96.669051","0.746071306108","0.033309492031","0.999999243593","0.999934750320","0.516654367812","0.141311236863","0.063771794955","0.041177259639","0.032936094407","0.157642812159","0.157634208515","0.000065249680","","","","",""
|
||||||
|
"FRENCH_RADIXOR","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","ALL_WORDS","PRIMARY_OUTPUT","59240","404011","2301","404011","0","1","404011","60225","4925833","29","444366","81606871827","29","81606871856","0.000000","444366","5370199","8.274665","0.999994112706","0.917253345733","0.999999999645","0.999994554800","0.958626672689","0.982272941786","0.956838348180","0.932687695656","0.917248392433","0.957730622666","0.957728014957","0.000005445200","","","","",""
|
||||||
|
"FRENCH_RADIXOR","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","ALL_WORDS","ANY_CANDIDATE","59240","404011","2301","382167","21844","56","427440","60383","","","","","0","81606871856","0.000000","232","5370199","0.004320","","","","","","","","","","","","","","","","",""
|
||||||
|
"FRENCH_RADIXOR","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","ALL_WORDS","ALL_CANDIDATES","59240","404011","2301","382167","21844","56","427440","60383","5369967","2303","232","81606869553","2303","81606871856","0.000003","232","5370199","0.004320","0.999571317153","0.999956798621","0.999999971779","0.999999968938","0.999978385200","0.999648389668","0.999764020729","0.999879678544","0.999528152805","0.999764039308","0.999764023779","0.000000031062","","","","",""
|
||||||
|
"FRENCH_RADIXOR","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","57698","400712","2133","400712","0","1","400712","58069","4915501","1","440750","80279496864","1","80279496865","0.000000","440750","5356251","8.228703","0.999999796562","0.917712967521","0.999999999988","0.999994510160","0.958856483755","0.982382706966","0.957090965875","0.933068825633","0.917712796187","0.957973267280","0.957970637555","0.000005489840","","","","",""
|
||||||
|
"FRENCH_RADIXOR","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","57698","400712","2133","380101","20611","56","422336","58208","","","","","0","80279496865","0.000000","0","5356251","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"FRENCH_RADIXOR","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","57698","400712","2133","380101","20611","56","422336","58208","5356251","75","0","80279496790","75","80279496865","0.000000","0","5356251","0.000000","0.999985997865","1.000000000000","0.999999999066","0.999999999066","0.999999999533","0.999988798261","0.999992998883","0.999997199542","0.999985997865","0.999992998908","0.999992998441","0.000000000934","","","","",""
|
||||||
|
"GERMAN_CISTEM","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","PRIMARY_OUTPUT","54092","277266","1474","277266","0","1","277266","59097","1018135","258954","326717","38436474939","258954","38436733893","0.000674","326717","1344852","24.293900","0.797231046544","0.757061000021","0.999993262851","0.999984763260","0.878527131436","0.788859587356","0.776626934016","0.764767865140","0.634824286728","0.776886435294","0.776878836909","0.000015236740","","","","",""
|
||||||
|
"GERMAN_CISTEM","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","16007","145574","228","145574","0","1","145574","23023","712025","86055","146392","10594877479","86055","10594963534","0.000812","146392","858417","17.053716","0.892172463913","0.829462836826","0.999991877745","0.999978062391","0.914727357285","0.878883274821","0.859675568383","0.841289462416","0.753886827773","0.860246419845","0.860235546687","0.000021937609","","","","",""
|
||||||
|
"GERMAN_LUCENE_GERMAN_LIGHT_STEM_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","PRIMARY_OUTPUT","54092","277266","1474","277266","0","1","277266","98357","682737","73514","662115","38436660379","73514","38436733893","0.000191","662115","1344852","49.233299","0.902791533499","0.507667014660","0.999998087403","0.999980861973","0.753832551031","0.781189357269","0.649884370257","0.556368109766","0.481354600999","0.676991493796","0.676983759805","0.000019138027","","","","",""
|
||||||
|
"GERMAN_LUCENE_GERMAN_LIGHT_STEM_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","16007","145574","228","145574","0","1","145574","50335","461774","13791","396643","10594949743","13791","10594963534","0.000130","396643","858417","46.206331","0.971000809563","0.537936690443","0.999998698344","0.999961264544","0.768967694393","0.836341955252","0.692324184284","0.590619694452","0.529431053143","0.722728830139","0.722714023718","0.000038735456","","","","",""
|
||||||
|
"GERMAN_LUCENE_GERMAN_MINIMAL_STEM_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","PRIMARY_OUTPUT","54092","277266","1474","277266","0","1","277266","140505","257534","33845","1087318","38436700048","33845","38436733893","0.000088","1087318","1344852","80.850384","0.883845438415","0.191496164634","0.999999119462","0.999970831971","0.595747642048","0.512940732195","0.314789293199","0.227070775185","0.186795213161","0.411403708765","0.411396179319","0.000029168029","","","","",""
|
||||||
|
"GERMAN_LUCENE_GERMAN_MINIMAL_STEM_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","16007","145574","228","145574","0","1","145574","80363","128762","4742","729655","10594958792","4742","10594963534","0.000045","729655","858417","85.000064","0.964480465005","0.149999359286","0.999999552429","0.999930689945","0.574999455857","0.462363359673","0.259621481953","0.180481905554","0.149175296788","0.380357005712","0.380342859388","0.000069310055","","","","",""
|
||||||
|
"GERMAN_LUCENE_GERMAN_STEM_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","PRIMARY_OUTPUT","54092","277266","1474","277266","0","1","277266","81085","594410","170297","750442","38436563596","170297","38436733893","0.000443","750442","1344852","55.801084","0.777304248555","0.441989155684","0.999995569421","0.999976046175","0.720992362552","0.674901446063","0.563539583392","0.483723042293","0.392311251237","0.586139956434","0.586129657503","0.000023953825","","","","",""
|
||||||
|
"GERMAN_LUCENE_GERMAN_STEM_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","16007","145574","228","145574","0","1","145574","41574","371092","37962","487325","10594925572","37962","10594963534","0.000358","487325","858417","56.770194","0.907195626983","0.432298055607","0.999996416977","0.999950425082","0.716147236292","0.743780748511","0.585562904398","0.482850437789","0.413990064470","0.626241890646","0.626223420324","0.000049574918","","","","",""
|
||||||
|
"GERMAN_RADIXOR","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","PRIMARY_OUTPUT","54092","277266","1474","277266","0","1","277266","68104","1103976","663","240876","38436733230","663","38436733893","0.000002","240876","1344852","17.910967","0.999399803918","0.820890328452","0.999999982751","0.999993716153","0.910445155602","0.957745833715","0.901392166781","0.851301663915","0.820485836278","0.905758043461","0.905755194143","0.000006283847","","","","",""
|
||||||
|
"GERMAN_RADIXOR","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","ANY_CANDIDATE","54092","277266","1474","248231","29035","8","313927","70717","","","","","502","38436733893","0.000001","111107","1344852","8.261653","","","","","","","","","","","","","","","","",""
|
||||||
|
"GERMAN_RADIXOR","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","ALL_CANDIDATES","54092","277266","1474","248231","29035","8","313927","70717","1233745","6862","111107","38436727031","6862","38436733893","0.000018","111107","1344852","8.261653","0.994468836626","0.917383474167","0.999999821473","0.999996930934","0.958691647820","0.978032527492","0.954372125027","0.931829459601","0.912726360754","0.955148824160","0.955147343048","0.000003069066","","","","",""
|
||||||
|
"GERMAN_RADIXOR","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","16007","145574","228","145574","0","1","145574","17264","801691","80","56726","10594963454","80","10594963534","0.000001","56726","858417","6.608210","0.999900220886","0.933917897712","0.999999992449","0.999994638830","0.966958945080","0.985968273037","0.965783393206","0.946408388835","0.933830869531","0.966346062346","0.966343471257","0.000005361170","","","","",""
|
||||||
|
"GERMAN_RADIXOR","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","16007","145574","228","135120","10454","8","157137","18366","","","","","0","10594963534","0.000000","0","858417","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"GERMAN_RADIXOR","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","16007","145574","228","135120","10454","8","157137","18366","858417","1490","0","10594962044","1490","10594963534","0.000014","0","858417","0.000000","0.998267254482","1.000000000000","0.999999859367","0.999999859379","0.999999929684","0.998613323034","0.999132875988","0.999652969844","0.998267254482","0.999133251615","0.999133181359","0.000000140621","","","","",""
|
||||||
|
"HE_IL_RADIXOR","HE_IL","he-il-default","1.0.0","9a47dc69bb7dab21aba0266b73cd74cdaeb17db94363796a0a56111ac8518256","ALL_WORDS","PRIMARY_OUTPUT","2358","57658","0","57658","0","1","57658","2358","685765","0","19645","1661488243","0","1661488243","0.000000","19645","705410","2.784905","1.000000000000","0.972150947676","1.000000000000","0.999988181281","0.986075473838","0.994303270726","0.985878843424","0.977595971951","0.972150947676","0.985977153729","0.985971324814","0.000011818719","","","","",""
|
||||||
|
"HE_IL_RADIXOR","HE_IL","he-il-default","1.0.0","9a47dc69bb7dab21aba0266b73cd74cdaeb17db94363796a0a56111ac8518256","ALL_WORDS","ANY_CANDIDATE","2358","57658","0","56674","984","40","58714","2358","","","","","0","1661488243","0.000000","0","705410","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"HE_IL_RADIXOR","HE_IL","he-il-default","1.0.0","9a47dc69bb7dab21aba0266b73cd74cdaeb17db94363796a0a56111ac8518256","ALL_WORDS","ALL_CANDIDATES","2358","57658","0","56674","984","40","58714","2358","705410","0","0","1661488243","0","1661488243","0.000000","0","705410","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","","","","",""
|
||||||
|
"HE_IL_RADIXOR","HE_IL","he-il-default","1.0.0","9a47dc69bb7dab21aba0266b73cd74cdaeb17db94363796a0a56111ac8518256","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","2358","57658","0","57658","0","1","57658","2358","685765","0","19645","1661488243","0","1661488243","0.000000","19645","705410","2.784905","1.000000000000","0.972150947676","1.000000000000","0.999988181281","0.986075473838","0.994303270726","0.985878843424","0.977595971951","0.972150947676","0.985977153729","0.985971324814","0.000011818719","","","","",""
|
||||||
|
"HE_IL_RADIXOR","HE_IL","he-il-default","1.0.0","9a47dc69bb7dab21aba0266b73cd74cdaeb17db94363796a0a56111ac8518256","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","2358","57658","0","56674","984","40","58714","2358","","","","","0","1661488243","0.000000","0","705410","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"HE_IL_RADIXOR","HE_IL","he-il-default","1.0.0","9a47dc69bb7dab21aba0266b73cd74cdaeb17db94363796a0a56111ac8518256","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","2358","57658","0","56674","984","40","58714","2358","705410","0","0","1661488243","0","1661488243","0.000000","0","705410","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","","","","",""
|
||||||
|
"HUNGARIAN_LUCENE_HUNGARIAN_LIGHT_STEM_FILTER","HU_HU","hu-hu-default","1.0.0","359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e","ALL_WORDS","PRIMARY_OUTPUT","19406","910688","1","910688","0","1","910688","94328","14021483","3795942","8096372","414649947531","3795942","414653743473","0.000915","8096372","22117855","36.605593","0.786953389729","0.633944069169","0.999990845514","0.999971321422","0.816967457342","0.750714749947","0.702210326308","0.659593346790","0.541081764282","0.706317516512","0.706303654339","0.000028678578","","","","",""
|
||||||
|
"HUNGARIAN_LUCENE_HUNGARIAN_LIGHT_STEM_FILTER","HU_HU","hu-hu-default","1.0.0","359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","18360","872878","1","872878","0","1","872878","91516","13478053","3311675","7889264","380932886011","3311675","380936197686","0.000869","7889264","21367317","36.922109","0.802755887409","0.630778913422","0.999991306484","0.999970597935","0.815385109953","0.761246308142","0.706451613326","0.659015515857","0.546134663847","0.711590813883","0.711576731219","0.000029402065","","","","",""
|
||||||
|
"HUNGARIAN_RADIXOR","HU_HU","hu-hu-default","1.0.0","359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e","ALL_WORDS","PRIMARY_OUTPUT","19406","910688","1","910688","0","1","910688","20535","21921219","39","196636","414653743434","39","414653743473","0.000000","196636","22117855","0.889037","0.999998220905","0.991109626137","0.999999999906","0.999999525714","0.995554813021","0.998207770257","0.995534083532","0.992874681417","0.991107878535","0.995544003477","0.995543767377","0.000000474286","","","","",""
|
||||||
|
"HUNGARIAN_RADIXOR","HU_HU","hu-hu-default","1.0.0","359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e","ALL_WORDS","ANY_CANDIDATE","19406","910688","1","904024","6664","5","917595","20567","","","","","0","414653743473","0.000000","0","22117855","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNGARIAN_RADIXOR","HU_HU","hu-hu-default","1.0.0","359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e","ALL_WORDS","ALL_CANDIDATES","19406","910688","1","904024","6664","5","917595","20567","22117855","192","0","414653743281","192","414653743473","0.000000","0","22117855","0.000000","0.999991319306","1.000000000000","0.999999999537","0.999999999537","0.999999999768","0.999993055433","0.999995659634","0.999998263849","0.999991319306","0.999995659644","0.999995659412","0.000000000463","","","","",""
|
||||||
|
"HUNGARIAN_RADIXOR","HU_HU","hu-hu-default","1.0.0","359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","18360","872878","1","872878","0","1","872878","18363","21206087","39","161230","380936197647","39","380936197686","0.000000","161230","21367317","0.754564","0.999998160909","0.992454363831","0.999999999898","0.999999576675","0.996227181864","0.998480240771","0.996211981258","0.993954004051","0.992452552389","0.996219121788","0.996218910913","0.000000423325","","","","",""
|
||||||
|
"HUNGARIAN_RADIXOR","HU_HU","hu-hu-default","1.0.0","359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","18360","872878","1","867360","5518","5","878574","18375","","","","","0","380936197686","0.000000","0","21367317","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNGARIAN_RADIXOR","HU_HU","hu-hu-default","1.0.0","359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","18360","872878","1","867360","5518","5","878574","18375","21367317","192","0","380936197494","192","380936197686","0.000000","0","21367317","0.000000","0.999991014395","1.000000000000","0.999999999496","0.999999999496","0.999999999748","0.999992811503","0.999995507177","0.999998202866","0.999991014395","0.999995507187","0.999995506935","0.000000000504","","","","",""
|
||||||
|
"HUNSPELL_CZECH_LUCENE_FILTER","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","ALL_WORDS","PRIMARY_OUTPUT","5113","51401","2","51401","0","1","51401","10920","212842","9128","87667","1320696063","9128","1320705191","0.000691","87667","300509","29.172837","0.958877325765","0.708271632464","0.999993088541","0.999926726281","0.854132360502","0.895506437707","0.814738965585","0.747335334261","0.687392010645","0.824102911566","0.824070367475","0.000073273719","","","","",""
|
||||||
|
"HUNSPELL_CZECH_LUCENE_FILTER","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","ALL_WORDS","ANY_CANDIDATE","5113","51401","2","48207","3194","5","55179","11359","","","","","8582","1320705191","0.000650","76964","300509","25.611213","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_CZECH_LUCENE_FILTER","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","ALL_WORDS","ALL_CANDIDATES","5113","51401","2","48207","3194","5","55179","11359","223545","10775","76964","1320694416","10775","1320705191","0.000816","76964","300509","25.611213","0.954015875726","0.743887870247","0.999991841480","0.999933581664","0.871939855863","0.903001238499","0.835949434305","0.778167111775","0.718138420221","0.842425568211","0.842395220341","0.000066418336","","","","",""
|
||||||
|
"HUNSPELL_CZECH_LUCENE_FILTER","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","5038","50697","2","50697","0","1","50697","10816","210117","8993","87370","1284761076","8993","1284770069","0.000700","87370","297487","29.369351","0.958956688421","0.706306494065","0.999993000304","0.999925013281","0.853149747185","0.894932138029","0.813465815713","0.745593864837","0.685581440877","0.822992914040","0.822959656132","0.000074986719","","","","",""
|
||||||
|
"HUNSPELL_CZECH_LUCENE_FILTER","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","5038","50697","2","47580","3117","5","54394","11240","","","","","8518","1284770069","0.000663","76872","297487","25.840457","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_CZECH_LUCENE_FILTER","CS_CZ","cs-cz-default","1.0.0","62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","5038","50697","2","47580","3117","5","54394","11240","220615","10523","76872","1284759546","10523","1284770069","0.000819","76872","297487","25.840457","0.954473085343","0.741595431061","0.999991809429","0.999931991902","0.870793620245","0.902651224715","0.834674864034","0.776219736174","0.716259212363","0.841328044915","0.841297029699","0.000068008098","","","","",""
|
||||||
|
"HUNSPELL_DUTCH_LUCENE_FILTER","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","ALL_WORDS","PRIMARY_OUTPUT","4992","26201","85","26201","0","1","26201","15909","18409","356","46028","343168307","356","343168663","0.000104","46028","64437","71.431010","0.981028510525","0.285689898661","0.999998962609","0.999864861518","0.642844430635","0.659834978530","0.442513401120","0.332877658555","0.284120198170","0.529405266082","0.529368118333","0.000135138482","","","","",""
|
||||||
|
"HUNSPELL_DUTCH_LUCENE_FILTER","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","ALL_WORDS","ANY_CANDIDATE","4992","26201","85","25002","1199","3","27429","16027","","","","","330","343168663","0.000096","43157","64437","66.975495","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_DUTCH_LUCENE_FILTER","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","ALL_WORDS","ALL_CANDIDATES","4992","26201","85","25002","1199","3","27429","16027","21280","503","43157","343168160","503","343168663","0.000147","43157","64437","66.975495","0.976908598448","0.330245045548","0.999998534248","0.999872797816","0.665121789898","0.701990512572","0.493620969613","0.380637567926","0.327687095781","0.567995796279","0.567957979352","0.000127202184","","","","",""
|
||||||
|
"HUNSPELL_DUTCH_LUCENE_FILTER","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4796","25402","84","25402","0","1","25402","15258","18260","333","44758","322554750","333","322555083","0.000103","44758","63018","71.024152","0.982090033884","0.289758481704","0.999998967618","0.999860234129","0.644878724661","0.664531625300","0.447488696377","0.337317348013","0.288235386971","0.533450013698","0.533411381379","0.000139765871","","","","",""
|
||||||
|
"HUNSPELL_DUTCH_LUCENE_FILTER","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","4796","25402","84","24271","1131","3","26562","15323","","","","","307","322555083","0.000095","41900","63018","66.488940","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_DUTCH_LUCENE_FILTER","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","4796","25402","84","24271","1131","3","26562","15323","21118","477","41900","322554606","477","322555083","0.000148","41900","63018","66.488940","0.977911553600","0.335110603320","0.999998521183","0.999868646552","0.667554562251","0.706769836276","0.499166794701","0.385833878400","0.332593117568","0.572458322256","0.572419041773","0.000131353448","","","","",""
|
||||||
|
"HUNSPELL_ENGLISH_LUCENE_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","PRIMARY_OUTPUT","396939","591946","250964","591946","0","1","591946","557518","45837","21444","267518","175199402686","21444","175199424130","0.000012","267518","313355","85.372182","0.681277032149","0.146278182892","0.999999877602","0.999998350671","0.573139030247","0.393464828775","0.240844271167","0.173532843543","0.136909011078","0.315683332326","0.315682846485","0.000001649329","","","","",""
|
||||||
|
"HUNSPELL_ENGLISH_LUCENE_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","ANY_CANDIDATE","396939","591946","250964","586210","5736","4","597698","557638","","","","","20367","175199424130","0.000012","262339","313355","83.719424","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_ENGLISH_LUCENE_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","ALL_WORDS","ALL_CANDIDATES","396939","591946","250964","586210","5736","4","597698","557638","51016","38780","262339","175199385350","38780","175199424130","0.000022","262339","313355","83.719424","0.568132210789","0.162805763431","0.999999778652","0.999998281282","0.581402771042","0.379279119873","0.253086312573","0.189902443092","0.144876254845","0.304130232478","0.304129624950","0.000001718718","","","","",""
|
||||||
|
"HUNSPELL_ENGLISH_LUCENE_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","535362","45763","19600","265619","161561970038","19600","161561989638","0.000012","265619","311382","85.303261","0.700136162661","0.146967390536","0.999999878684","0.999998234619","0.573483634610","0.399443817930","0.242938857848","0.174549218814","0.138264316489","0.320775910639","0.320775401621","0.000001765381","","","","",""
|
||||||
|
"HUNSPELL_ENGLISH_LUCENE_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","374384","568441","228735","562756","5685","4","574142","535485","","","","","18564","161561989638","0.000011","260443","311382","83.640994","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_ENGLISH_LUCENE_FILTER","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","374384","568441","228735","562756","5685","4","574142","535485","50939","36611","260443","161561953027","36611","161561989638","0.000023","260443","311382","83.640994","0.581827527127","0.163590059798","0.999999773393","0.999998161366","0.581794916596","0.384978732795","0.255376856206","0.191057837576","0.146379381194","0.308514505258","0.308513864190","0.000001838634","","","","",""
|
||||||
|
"HUNSPELL_FRENCH_LUCENE_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","ALL_WORDS","PRIMARY_OUTPUT","59240","404011","2301","404011","0","1","404011","154336","3402849","440809","1967350","81606431047","440809","81606871856","0.000540","1967350","5370199","36.634583","0.885315238765","0.633654171847","0.999994598384","0.999970492674","0.816824385116","0.820167925205","0.738637250394","0.671850417782","0.585586700276","0.748988447470","0.748975057537","0.000029507326","","","","",""
|
||||||
|
"HUNSPELL_FRENCH_LUCENE_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","ALL_WORDS","ANY_CANDIDATE","59240","404011","2301","395781","8230","4","412364","154718","","","","","439665","81606871856","0.000539","1782362","5370199","33.189869","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_FRENCH_LUCENE_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","ALL_WORDS","ALL_CANDIDATES","59240","404011","2301","395781","8230","4","412364","154718","3587837","500695","1782362","81606371161","500695","81606871856","0.000614","1782362","5370199","33.189869","0.877536729565","0.668101312447","0.999993864549","0.999972025557","0.834047588498","0.825764821161","0.758629672416","0.701590006589","0.611122769377","0.765691478823","0.765678434549","0.000027974443","","","","",""
|
||||||
|
"HUNSPELL_FRENCH_LUCENE_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","57698","400712","2133","400712","0","1","400712","153822","3392703","433354","1963548","80279063511","433354","80279496865","0.000540","1963548","5356251","36.658999","0.886736135923","0.633410010098","0.999994601934","0.999970145029","0.816702306016","0.821061070269","0.738965192629","0.671794147581","0.585999044840","0.749444824393","0.749431295094","0.000029854971","","","","",""
|
||||||
|
"HUNSPELL_FRENCH_LUCENE_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","57698","400712","2133","392518","8194","4","409028","154205","","","","","432307","80279496865","0.000539","1778903","5356251","33.211718","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_FRENCH_LUCENE_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","57698","400712","2133","392518","8194","4","409028","154205","3577348","492522","1778903","80279004343","492522","80279496865","0.000614","1778903","5356251","33.211718","0.878983358191","0.667882815798","0.999993864909","0.999971707926","0.833938340354","0.826722240168","0.759028660888","0.701581816015","0.611640766362","0.766197024471","0.766183847721","0.000028292074","","","","",""
|
||||||
|
"HUNSPELL_GERMAN_LUCENE_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","PRIMARY_OUTPUT","54092","277266","1474","277266","0","1","277266","182774","377391","111635","967461","38436622258","111635","38436733893","0.000290","967461","1344852","71.938102","0.771719704065","0.280618982609","0.999997095617","0.999971926380","0.640308039113","0.571638943385","0.411576996943","0.321543191932","0.259110448634","0.465359214171","0.465349217076","0.000028073620","","","","",""
|
||||||
|
"HUNSPELL_GERMAN_LUCENE_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","ANY_CANDIDATE","54092","277266","1474","270784","6482","3","283881","183111","","","","","83073","38436733893","0.000216","952309","1344852","70.811435","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_GERMAN_LUCENE_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","ALL_CANDIDATES","54092","277266","1474","270784","6482","3","283881","183111","392543","135961","952309","38436597932","135961","38436733893","0.000354","952309","1344852","70.811435","0.742743668922","0.291885649871","0.999996462733","0.999971687711","0.645941056302","0.567444319934","0.419079982662","0.332218049287","0.265086138493","0.465613808312","0.465603221302","0.000028312289","","","","",""
|
||||||
|
"HUNSPELL_GERMAN_LUCENE_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","16007","145574","228","145574","0","1","145574","86983","273967","58904","584450","10594904630","58904","10594963534","0.000556","584450","858417","68.084626","0.823042560031","0.319153744625","0.999994440377","0.999939282294","0.659574092501","0.625523710889","0.459950910275","0.363685335530","0.298659902041","0.512520355713","0.512498716988","0.000060717706","","","","",""
|
||||||
|
"HUNSPELL_GERMAN_LUCENE_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","16007","145574","228","141036","4538","3","150205","87393","","","","","40608","10594963534","0.000383","573996","858417","66.866802","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_GERMAN_LUCENE_FILTER","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","16007","145574","228","141036","4538","3","150205","87393","284421","66639","573996","10594896895","66639","10594963534","0.000629","573996","858417","66.866802","0.810177747394","0.331331975019","0.999993710313","0.999939538905","0.665662842666","0.628511082325","0.470320642724","0.375748270417","0.307463548153","0.518109827315","0.518087587435","0.000060461095","","","","",""
|
||||||
|
"HUNSPELL_POLISH_LUCENE_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","ALL_WORDS","PRIMARY_OUTPUT","9990","120867","1","120867","0","1","120867","18419","968411","27967","148662","7303210371","27967","7303238338","0.000383","148662","1117073","13.308172","0.971931335296","0.866918276603","0.999996170603","0.999975818674","0.933457223603","0.948941565893","0.916426262071","0.886065398277","0.845744253476","0.917924309609","0.917912670119","0.000024181326","","","","",""
|
||||||
|
"HUNSPELL_POLISH_LUCENE_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","ALL_WORDS","ANY_CANDIDATE","9990","120867","1","110382","10485","6","132492","19068","","","","","25967","7303238338","0.000356","80738","1117073","7.227639","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_POLISH_LUCENE_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","ALL_WORDS","ALL_CANDIDATES","9990","120867","1","110382","10485","6","132492","19068","1036335","44498","80738","7303193840","44498","7303238338","0.000609","80738","1117073","7.227639","0.958829902492","0.927723613408","0.999993907086","0.999982854613","0.963858760247","0.952442878793","0.943020311151","0.933782353074","0.892183947430","0.943148525834","0.943139991603","0.000017145387","","","","",""
|
||||||
|
"HUNSPELL_POLISH_LUCENE_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","9846","119451","1","119451","0","1","119451","18149","963133","27267","147624","7133072951","27267","7133100218","0.000382","147624","1110757","13.290396","0.972468699515","0.867096043509","0.999996177398","0.999975485586","0.933546110454","0.949393940529","0.916764430264","0.886303269317","0.846320464243","0.918272161065","0.918260365969","0.000024514414","","","","",""
|
||||||
|
"HUNSPELL_POLISH_LUCENE_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","9846","119451","1","109148","10303","6","130856","18789","","","","","25425","7133100218","0.000356","80363","1110757","7.234976","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_POLISH_LUCENE_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","9846","119451","1","109148","10303","6","130856","18789","1030394","43630","80363","7133056588","43630","7133100218","0.000612","80363","1110757","7.234976","0.959377071648","0.927650242132","0.999993883445","0.999982619942","0.963822062789","0.952859269523","0.943246943286","0.933826616099","0.892589746766","0.943380290663","0.943371641374","0.000017380058","","","","",""
|
||||||
|
"HUNSPELL_SPANISH_LUCENE_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","ALL_WORDS","PRIMARY_OUTPUT","65059","849661","3589","849661","0","1","849661","495840","9648381","244539","32290659","360919299051","244539","360919543590","0.000068","32290659","41939040","76.994273","0.975281413374","0.230057268836","0.999999322456","0.999909865181","0.615028295646","0.591847366825","0.372294661441","0.271557302745","0.228723622526","0.473677715654","0.473655293112","0.000090134819","","","","",""
|
||||||
|
"HUNSPELL_SPANISH_LUCENE_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","ALL_WORDS","ANY_CANDIDATE","65059","849661","3589","838352","11309","5","861853","496361","","","","","223500","360919543590","0.000062","31877837","41939040","76.009935","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_SPANISH_LUCENE_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","ALL_WORDS","ALL_CANDIDATES","65059","849661","3589","838352","11309","5","861853","496361","10061203","263629","31877837","360919279961","263629","360919543590","0.000073","31877837","41939040","76.009935","0.974466509479","0.239900651040","0.999999269563","0.999910955967","0.619949960302","0.604360900012","0.385015599303","0.282489525889","0.238402054619","0.483502998999","0.483480352448","0.000089044033","","","","",""
|
||||||
|
"HUNSPELL_SPANISH_LUCENE_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","64918","847879","3525","847879","0","1","847879","495045","9614637","244260","32215829","359406900655","244260","359407144915","0.000068","32215829","41830466","77.015229","0.975224408978","0.229847714343","0.999999320381","0.999909694863","0.614923517362","0.591553085622","0.372016076112","0.271322827200","0.228513359778","0.473448097868","0.473425641311","0.000090305137","","","","",""
|
||||||
|
"HUNSPELL_SPANISH_LUCENE_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","64918","847879","3525","836592","11287","5","860048","495572","","","","","223274","359407144915","0.000062","31806834","41830466","76.037484","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_SPANISH_LUCENE_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","64918","847879","3525","836592","11287","5","860048","495572","10023632","263289","31806834","359406881626","263289","359407144915","0.000073","31806834","41830466","76.037484","0.974405461070","0.239625157415","0.999999267435","0.999910779762","0.619812212425","0.603992255793","0.384655969034","0.282182888645","0.238126344395","0.483210163382","0.483187483018","0.000089220238","","","","",""
|
||||||
|
"HUNSPELL_UKRAINIAN_LUCENE_FILTER","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","ALL_WORDS","PRIMARY_OUTPUT","1493","14150","4","14150","0","1","14150","3137","50249","6","14876","100039044","6","100039050","0.000006","14876","65125","22.842226","0.999880608895","0.771577735125","0.999999940023","0.999851334872","0.885788837574","0.944015480283","0.871017507367","0.808498656498","0.771506655817","0.878342538880","0.878277198610","0.000148665128","","","","",""
|
||||||
|
"HUNSPELL_UKRAINIAN_LUCENE_FILTER","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","ALL_WORDS","ANY_CANDIDATE","1493","14150","4","12891","1259","6","15577","3311","","","","","0","100039050","0.000000","9465","65125","14.533589","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_UKRAINIAN_LUCENE_FILTER","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","ALL_WORDS","ALL_CANDIDATES","1493","14150","4","12891","1259","6","15577","3311","55660","47","9465","100039003","47","100039050","0.000047","9465","65125","14.533589","0.999156299926","0.854664107486","0.999999530183","0.999904978988","0.927331818835","0.966477168149","0.921279131356","0.880119668445","0.854047750568","0.924090378326","0.924046375011","0.000095021012","","","","",""
|
||||||
|
"HUNSPELL_UKRAINIAN_LUCENE_FILTER","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","1491","14141","4","14141","0","1","14141","3134","50237","6","14872","99911755","6","99911761","0.000006","14872","65109","22.841696","0.999880580379","0.771583037675","0.999999939947","0.999851185579","0.885791488811","0.944017047440","0.871020875234","0.808503310491","0.771511940413","0.878345544488","0.878280138338","0.000148814421","","","","",""
|
||||||
|
"HUNSPELL_UKRAINIAN_LUCENE_FILTER","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","1491","14141","4","12883","1258","6","15567","3308","","","","","0","99911761","0.000000","9465","65109","14.537161","","","","","","","","","","","","","","","","",""
|
||||||
|
"HUNSPELL_UKRAINIAN_LUCENE_FILTER","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","1491","14141","4","12883","1258","6","15567","3308","55644","47","9465","99911714","47","99911761","0.000047","9465","65109","14.537161","0.999156057532","0.854628392388","0.999999529585","0.999904857994","0.927313960987","0.966467852143","0.921258278146","0.880089331187","0.854011909878","0.924070957878","0.924026899410","0.000095142006","","","","",""
|
||||||
|
"ITALIAN_LUCENE_ITALIAN_LIGHT_STEM_FILTER","IT_IT","it-it-default","1.0.0","5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12","ALL_WORDS","PRIMARY_OUTPUT","10009","324366","0","324366","0","1","324366","244870","109427","2752","6024695","52600351921","2752","52600354673","0.000005","6024695","6134122","98.216094","0.975467779174","0.017839064825","0.999999947681","0.999885423887","0.508919506253","0.083115367566","0.035037376521","0.022197346412","0.017831065132","0.131914491042","0.131906553725","0.000114576113","","","","",""
|
||||||
|
"ITALIAN_LUCENE_ITALIAN_LIGHT_STEM_FILTER","IT_IT","it-it-default","1.0.0","5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","10007","324285","0","324285","0","1","324285","244808","109401","2752","6023081","52574083236","2752","52574085988","0.000005","6023081","6132482","98.216040","0.975462091964","0.017839595779","0.999999947655","0.999885397338","0.508919771717","0.083117639712","0.035038396960","0.022198003488","0.017831593709","0.131916069597","0.131908130255","0.000114602662","","","","",""
|
||||||
|
"ITALIAN_RADIXOR","IT_IT","it-it-default","1.0.0","5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12","ALL_WORDS","PRIMARY_OUTPUT","10009","324366","0","324366","0","1","324366","10010","6093034","0","41088","52600354673","0","52600354673","0.000000","41088","6134122","0.669827","1.000000000000","0.993301730875","1.000000000000","0.999999218956","0.996650865438","0.998653128810","0.996639611043","0.994634196381","0.993301730875","0.996645238224","0.996644848967","0.000000781044","","","","",""
|
||||||
|
"ITALIAN_RADIXOR","IT_IT","it-it-default","1.0.0","5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12","ALL_WORDS","ANY_CANDIDATE","10009","324366","0","321297","3069","4","327552","10012","","","","","0","52600354673","0.000000","80","6134122","0.001304","","","","","","","","","","","","","","","","",""
|
||||||
|
"ITALIAN_RADIXOR","IT_IT","it-it-default","1.0.0","5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12","ALL_WORDS","ALL_CANDIDATES","10009","324366","0","321297","3069","4","327552","10012","6134042","0","80","52600354673","0","52600354673","0.000000","80","6134122","0.001304","1.000000000000","0.999986958199","1.000000000000","0.999999998479","0.999993479099","0.999997391613","0.999993479057","0.999989566532","0.999986958199","0.999993479078","0.999993478318","0.000000001521","","","","",""
|
||||||
|
"ITALIAN_RADIXOR","IT_IT","it-it-default","1.0.0","5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","10007","324285","0","324285","0","1","324285","10007","6091474","0","41008","52574085988","0","52574085988","0.000000","41008","6132482","0.668702","1.000000000000","0.993312984857","1.000000000000","0.999999220087","0.996656492428","0.998655403904","0.996645275883","0.994643223717","0.993312984857","0.996650884140","0.996650495444","0.000000779913","","","","",""
|
||||||
|
"ITALIAN_RADIXOR","IT_IT","it-it-default","1.0.0","5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","10007","324285","0","321217","3068","4","327469","10007","","","","","0","52574085988","0.000000","0","6132482","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"ITALIAN_RADIXOR","IT_IT","it-it-default","1.0.0","5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","10007","324285","0","321217","3068","4","327469","10007","6132482","0","0","52574085988","0","52574085988","0.000000","0","6132482","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","","","","",""
|
||||||
|
"NL_NL_RADIXOR","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","ALL_WORDS","PRIMARY_OUTPUT","4992","26201","85","26201","0","1","26201","5015","62985","0","1452","343168663","0","343168663","0.000000","1452","64437","2.253364","1.000000000000","0.977466362494","1.000000000000","0.999995769639","0.988733181247","0.995410538693","0.988604793521","0.981891479829","0.977466362494","0.988668985300","0.988666893700","0.000004230361","","","","",""
|
||||||
|
"NL_NL_RADIXOR","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","ALL_WORDS","ANY_CANDIDATE","4992","26201","85","25905","296","3","26501","5016","","","","","0","343168663","0.000000","0","64437","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"NL_NL_RADIXOR","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","ALL_WORDS","ALL_CANDIDATES","4992","26201","85","25905","296","3","26501","5016","64437","0","0","343168663","0","343168663","0.000000","0","64437","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","","","","",""
|
||||||
|
"NL_NL_RADIXOR","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4796","25402","84","25402","0","1","25402","4797","61646","0","1372","322555083","0","322555083","0.000000","1372","63018","2.177156","1.000000000000","0.978228442667","1.000000000000","0.999995747294","0.989114221334","0.995568504079","0.988994416993","0.982506582345","0.978228442667","0.989054317349","0.989052213866","0.000004252706","","","","",""
|
||||||
|
"NL_NL_RADIXOR","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","4796","25402","84","25129","273","3","25679","4797","","","","","0","322555083","0.000000","0","63018","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"NL_NL_RADIXOR","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","4796","25402","84","25129","273","3","25679","4797","63018","0","0","322555083","0","322555083","0.000000","0","63018","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","","","","",""
|
||||||
|
"NN_NO_RADIXOR","NN_NO","nn-no-default","1.0.0","900cf2005605aea2a3d8d731ec0b0c1f47fb4469b4ba6b9134145d4d026a0398","ALL_WORDS","PRIMARY_OUTPUT","4688","16937","23","16937","0","1","16937","4680","25582","0","2780","143394154","0","143394154","0.000000","2780","28362","9.801848","1.000000000000","0.901981524575","1.000000000000","0.999980616712","0.950990762288","0.978728288316","0.948465074892","0.920017262461","0.901981524575","0.949727078994","0.949717872891","0.000019383288","","","","",""
|
||||||
|
"NN_NO_RADIXOR","NN_NO","nn-no-default","1.0.0","900cf2005605aea2a3d8d731ec0b0c1f47fb4469b4ba6b9134145d4d026a0398","ALL_WORDS","ANY_CANDIDATE","4688","16937","23","15846","1091","5","18255","4693","","","","","0","143394154","0.000000","0","28362","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"NN_NO_RADIXOR","NN_NO","nn-no-default","1.0.0","900cf2005605aea2a3d8d731ec0b0c1f47fb4469b4ba6b9134145d4d026a0398","ALL_WORDS","ALL_CANDIDATES","4688","16937","23","15846","1091","5","18255","4693","28362","0","0","143394154","0","143394154","0.000000","0","28362","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","","","","",""
|
||||||
|
"NN_NO_RADIXOR","NN_NO","nn-no-default","1.0.0","900cf2005605aea2a3d8d731ec0b0c1f47fb4469b4ba6b9134145d4d026a0398","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4681","16906","23","16906","0","1","16906","4668","25537","0","2768","142869660","0","142869660","0.000000","2768","28305","9.779191","1.000000000000","0.902208090443","1.000000000000","0.999980629535","0.951104045222","0.978781630166","0.948590319825","0.920205827454","0.902208090443","0.949846350966","0.949837149794","0.000019370465","","","","",""
|
||||||
|
"NN_NO_RADIXOR","NN_NO","nn-no-default","1.0.0","900cf2005605aea2a3d8d731ec0b0c1f47fb4469b4ba6b9134145d4d026a0398","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","4681","16906","23","15820","1086","5","18219","4681","","","","","0","142869660","0.000000","0","28305","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"NN_NO_RADIXOR","NN_NO","nn-no-default","1.0.0","900cf2005605aea2a3d8d731ec0b0c1f47fb4469b4ba6b9134145d4d026a0398","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","4681","16906","23","15820","1086","5","18219","4681","28305","0","0","142869660","0","142869660","0.000000","0","28305","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","","","","",""
|
||||||
|
"NORWEGIAN_BOKMAL_LUCENE_NORWEGIAN_LIGHT_STEM_FILTER","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","ALL_WORDS","PRIMARY_OUTPUT","17929","73170","252","73170","0","1","73170","25999","98455","11122","42440","2676735848","11122","2676746970","0.000416","42440","140895","30.121722","0.898500597753","0.698782781504","0.999995844957","0.999979990944","0.849389313230","0.849917904431","0.786155737967","0.731292997027","0.647657827743","0.792374120527","0.792364773649","0.000020009056","","","","",""
|
||||||
|
"NORWEGIAN_BOKMAL_LUCENE_NORWEGIAN_LIGHT_STEM_FILTER","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","17914","73111","252","73111","0","1","73111","25985","98376","11069","42430","2672420730","11069","2672431799","0.000414","42430","140806","30.133659","0.898862442323","0.698663409230","0.999995858080","0.999979982209","0.849329633655","0.850141551991","0.786218636489","0.731236313848","0.647743209877","0.792465960393","0.792456612651","0.000020017791","","","","",""
|
||||||
|
"NORWEGIAN_BOKMAL_LUCENE_NORWEGIAN_MINIMAL_STEM_FILTER","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","ALL_WORDS","PRIMARY_OUTPUT","17929","73170","252","73170","0","1","73170","27457","93352","2948","47543","2676744022","2948","2676746970","0.000110","47543","140895","33.743568","0.969387331256","0.662564320948","0.999998898663","0.999981138171","0.831281609806","0.887216187191","0.787132949683","0.707340728617","0.648985352085","0.801424643288","0.801416635301","0.000018861829","","","","",""
|
||||||
|
"NORWEGIAN_BOKMAL_LUCENE_NORWEGIAN_MINIMAL_STEM_FILTER","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","17914","73111","252","73111","0","1","73111","27443","93273","2895","47533","2672428904","2895","2672431799","0.000108","47533","140806","33.757794","0.969896431245","0.662422055878","0.999998916717","0.999981131289","0.831210486298","0.887506232421","0.787200283575","0.707265177618","0.649076902736","0.801548992872","0.801540987973","0.000018868711","","","","",""
|
||||||
|
"NORWEGIAN_BOKMAL_RADIXOR","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","ALL_WORDS","PRIMARY_OUTPUT","17929","73170","252","73170","0","1","73170","17886","134138","0","6757","2676746970","0","2676746970","0.000000","6757","140895","4.795770","1.000000000000","0.952042301004","1.000000000000","0.999997475800","0.976021150502","0.990025787995","0.975432039064","0.961262286483","0.952042301004","0.975726550323","0.975725318796","0.000002524200","","","","",""
|
||||||
|
"NORWEGIAN_BOKMAL_RADIXOR","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","ALL_WORDS","ANY_CANDIDATE","17929","73170","252","71073","2097","9","75343","17962","","","","","0","2676746970","0.000000","0","140895","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"NORWEGIAN_BOKMAL_RADIXOR","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","ALL_WORDS","ALL_CANDIDATES","17929","73170","252","71073","2097","9","75343","17962","140895","0","0","2676746970","0","2676746970","0.000000","0","140895","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","","","","",""
|
||||||
|
"NORWEGIAN_BOKMAL_RADIXOR","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","17914","73111","252","73111","0","1","73111","17838","134115","0","6691","2672431799","0","2672431799","0.000000","6691","140806","4.751928","1.000000000000","0.952480718151","1.000000000000","0.999997496420","0.976240359076","0.990120573010","0.975662099294","0.961619814753","0.952480718151","0.975951186357","0.975949964609","0.000002503580","","","","",""
|
||||||
|
"NORWEGIAN_BOKMAL_RADIXOR","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","17914","73111","252","71047","2064","9","75251","17914","","","","","0","2672431799","0.000000","0","140806","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"NORWEGIAN_BOKMAL_RADIXOR","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","17914","73111","252","71047","2064","9","75251","17914","140806","0","0","2672431799","0","2672431799","0.000000","0","140806","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","","","","",""
|
||||||
|
"PERSIAN_LUCENE_PERSIAN_STEM_FILTER","FA_IR","fa-ir-default","1.0.0","b29a0d168a6a97f980666aa40b74a0edd8b6be4ab3320a7abfbb76b3529f4ea1","ALL_WORDS","PRIMARY_OUTPUT","69","3544","0","3544","0","1","3544","3190","425","3","95619","6182149","3","6182152","0.000049","95619","96044","99.557494","0.992990654206","0.004425055183","0.999999514732","0.984769191660","0.502212284958","0.021737796146","0.008810846671","0.005525163545","0.004424916968","0.066287543635","0.065773583741","0.015230808340","","","","",""
|
||||||
|
"PERSIAN_LUCENE_PERSIAN_STEM_FILTER","FA_IR","fa-ir-default","1.0.0","b29a0d168a6a97f980666aa40b74a0edd8b6be4ab3320a7abfbb76b3529f4ea1","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","69","3544","0","3544","0","1","3544","3190","425","3","95619","6182149","3","6182152","0.000049","95619","96044","99.557494","0.992990654206","0.004425055183","0.999999514732","0.984769191660","0.502212284958","0.021737796146","0.008810846671","0.005525163545","0.004424916968","0.066287543635","0.065773583741","0.015230808340","","","","",""
|
||||||
|
"PERSIAN_RADIXOR","FA_IR","fa-ir-default","1.0.0","b29a0d168a6a97f980666aa40b74a0edd8b6be4ab3320a7abfbb76b3529f4ea1","ALL_WORDS","PRIMARY_OUTPUT","69","3544","0","3544","0","1","3544","69","91503","0","4541","6182152","0","6182152","0.000000","4541","96044","4.728041","1.000000000000","0.952719586856","1.000000000000","0.999276703053","0.976359793428","0.990172186921","0.975787402624","0.961814585046","0.952719586856","0.976073556068","0.975715273893","0.000723296947","","","","",""
|
||||||
|
"PERSIAN_RADIXOR","FA_IR","fa-ir-default","1.0.0","b29a0d168a6a97f980666aa40b74a0edd8b6be4ab3320a7abfbb76b3529f4ea1","ALL_WORDS","ANY_CANDIDATE","69","3544","0","3387","157","2","3701","69","","","","","0","6182152","0.000000","0","96044","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"PERSIAN_RADIXOR","FA_IR","fa-ir-default","1.0.0","b29a0d168a6a97f980666aa40b74a0edd8b6be4ab3320a7abfbb76b3529f4ea1","ALL_WORDS","ALL_CANDIDATES","69","3544","0","3387","157","2","3701","69","96044","0","0","6182152","0","6182152","0.000000","0","96044","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","","","","",""
|
||||||
|
"PERSIAN_RADIXOR","FA_IR","fa-ir-default","1.0.0","b29a0d168a6a97f980666aa40b74a0edd8b6be4ab3320a7abfbb76b3529f4ea1","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","69","3544","0","3544","0","1","3544","69","91503","0","4541","6182152","0","6182152","0.000000","4541","96044","4.728041","1.000000000000","0.952719586856","1.000000000000","0.999276703053","0.976359793428","0.990172186921","0.975787402624","0.961814585046","0.952719586856","0.976073556068","0.975715273893","0.000723296947","","","","",""
|
||||||
|
"PERSIAN_RADIXOR","FA_IR","fa-ir-default","1.0.0","b29a0d168a6a97f980666aa40b74a0edd8b6be4ab3320a7abfbb76b3529f4ea1","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","69","3544","0","3387","157","2","3701","69","","","","","0","6182152","0.000000","0","96044","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"PERSIAN_RADIXOR","FA_IR","fa-ir-default","1.0.0","b29a0d168a6a97f980666aa40b74a0edd8b6be4ab3320a7abfbb76b3529f4ea1","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","69","3544","0","3387","157","2","3701","69","96044","0","0","6182152","0","6182152","0.000000","0","96044","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","","","","",""
|
||||||
|
"POLISH_LUCENE_MORFOLOGIK_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","ALL_WORDS","PRIMARY_OUTPUT","9990","120867","1","120867","0","1","120867","15519","1001785","76101","115288","7303162237","76101","7303238338","0.001042","115288","1117073","10.320543","0.929397914065","0.896794569379","0.999989579828","0.999973797962","0.948392074604","0.922688964795","0.912805205017","0.903130948917","0.839596739453","0.912950711772","0.912937654604","0.000026202038","","","","",""
|
||||||
|
"POLISH_LUCENE_MORFOLOGIK_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","ALL_WORDS","ANY_CANDIDATE","9990","120867","1","109091","11776","5","133810","16295","","","","","73019","7303238338","0.001000","27850","1117073","2.493123","","","","","","","","","","","","","","","","",""
|
||||||
|
"POLISH_LUCENE_MORFOLOGIK_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","ALL_WORDS","ALL_CANDIDATES","9990","120867","1","109091","11776","5","133810","16295","1089223","100514","27850","7303137824","100514","7303238338","0.001376","27850","1117073","2.493123","0.915515782059","0.975068773482","0.999986237064","0.999982426375","0.987527505273","0.926837225395","0.944354324804","0.962546321343","0.894575089911","0.944823184896","0.944814549127","0.000017573625","","","","",""
|
||||||
|
"POLISH_LUCENE_MORFOLOGIK_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","9846","119451","1","119451","0","1","119451","15277","996176","76097","114581","7133024121","76097","7133100218","0.001067","114581","1110757","10.315578","0.929032065528","0.896844224254","0.999989331848","0.999973272728","0.948416778051","0.922410978529","0.912654429852","0.903102115370","0.839341654492","0.912796276349","0.912782956155","0.000026727272","","","","",""
|
||||||
|
"POLISH_LUCENE_MORFOLOGIK_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","9846","119451","1","107785","11666","5","132279","16044","","","","","73019","7133100218","0.001024","27489","1110757","2.474799","","","","","","","","","","","","","","","","",""
|
||||||
|
"POLISH_LUCENE_MORFOLOGIK_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","9846","119451","1","107785","11666","5","132279","16044","1083268","100503","27489","7132999715","100503","7133100218","0.001409","27489","1110757","2.474799","0.915099288629","0.975252012816","0.999985910334","0.999982059404","0.987618961575","0.926528792008","0.944218593105","0.962597028968","0.894331522547","0.944697000716","0.944688187384","0.000017940596","","","","",""
|
||||||
|
"POLISH_LUCENE_STEMPEL_DIRECT","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","ALL_WORDS","PRIMARY_OUTPUT","9990","120867","1","120867","0","1","120867","31432","794690","43990","322383","7303194348","43990","7303238338","0.000602","322383","1117073","28.859618","0.947548528640","0.711403820520","0.999993976645","0.999949841844","0.855698898582","0.888558571472","0.812669084491","0.748722623749","0.684450370049","0.821029623950","0.821007024526","0.000050158156","","","","",""
|
||||||
|
"POLISH_LUCENE_STEMPEL_DIRECT","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","9846","119451","1","119451","0","1","119451","30830","791610","43601","319147","7133056617","43601","7133100218","0.000611","319147","1110757","28.732387","0.947796425095","0.712676129883","0.999993887511","0.999949153732","0.856335008697","0.889129551368","0.813589945981","0.749880784102","0.685757797841","0.821870968068","0.821848045519","0.000050846268","","","","",""
|
||||||
|
"POLISH_LUCENE_STEMPEL_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","ALL_WORDS","PRIMARY_OUTPUT","9990","120867","1","120867","0","1","120867","31432","794690","43990","322383","7303194348","43990","7303238338","0.000602","322383","1117073","28.859618","0.947548528640","0.711403820520","0.999993976645","0.999949841844","0.855698898582","0.888558571472","0.812669084491","0.748722623749","0.684450370049","0.821029623950","0.821007024526","0.000050158156","","","","",""
|
||||||
|
"POLISH_LUCENE_STEMPEL_FILTER","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","9846","119451","1","119451","0","1","119451","30830","791610","43601","319147","7133056617","43601","7133100218","0.000611","319147","1110757","28.732387","0.947796425095","0.712676129883","0.999993887511","0.999949153732","0.856335008697","0.889129551368","0.813589945981","0.749880784102","0.685757797841","0.821870968068","0.821848045519","0.000050846268","","","","",""
|
||||||
|
"POLISH_RADIXOR","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","ALL_WORDS","PRIMARY_OUTPUT","9990","120867","1","120867","0","1","120867","10074","1097200","0","19873","7303238338","0","7303238338","0.000000","19873","1117073","1.779024","1.000000000000","0.982209757106","1.000000000000","0.999997279294","0.991104878553","0.996390581475","0.991025045241","0.985716986027","0.982209757106","0.991064961093","0.991063612692","0.000002720706","","","","",""
|
||||||
|
"POLISH_RADIXOR","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","ALL_WORDS","ANY_CANDIDATE","9990","120867","1","119475","1392","4","122430","10079","","","","","0","7303238338","0.000000","0","1117073","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"POLISH_RADIXOR","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","ALL_WORDS","ALL_CANDIDATES","9990","120867","1","119475","1392","4","122430","10079","1117073","0","0","7303238338","0","7303238338","0.000000","0","1117073","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","","","","",""
|
||||||
|
"POLISH_RADIXOR","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","9846","119451","1","119451","0","1","119451","9844","1091431","0","19326","7133100218","0","7133100218","0.000000","19326","1110757","1.739895","1.000000000000","0.982601054956","1.000000000000","0.999997291081","0.991300527478","0.996471091564","0.991224182495","0.986032239104","0.982601054956","0.991262354251","0.991261011420","0.000002708919","","","","",""
|
||||||
|
"POLISH_RADIXOR","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","9846","119451","1","118145","1306","4","120926","9847","","","","","0","7133100218","0.000000","0","1110757","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"POLISH_RADIXOR","PL_PL","pl-pl-unimorph","1.0.0","8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","9846","119451","1","118145","1306","4","120926","9847","1110757","0","0","7133100218","0","7133100218","0.000000","0","1110757","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","","","","",""
|
||||||
|
"PORTUGUESE_LUCENE_PORTUGUESE_LIGHT_STEM_FILTER","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","ALL_WORDS","PRIMARY_OUTPUT","4001","211091","0","211091","0","1","211091","112814","149580","1249","5336772","22274111994","1249","22274113243","0.000006","5336772","5486352","97.273598","0.991719099112","0.027264018058","0.999999943926","0.999760407678","0.513631980992","0.122814577084","0.053069078321","0.033847392205","0.027257812658","0.164433109277","0.164413080578","0.000239592322","","","","",""
|
||||||
|
"PORTUGUESE_LUCENE_PORTUGUESE_LIGHT_STEM_FILTER","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4001","211091","0","211091","0","1","211091","112814","149580","1249","5336772","22274111994","1249","22274113243","0.000006","5336772","5486352","97.273598","0.991719099112","0.027264018058","0.999999943926","0.999760407678","0.513631980992","0.122814577084","0.053069078321","0.033847392205","0.027257812658","0.164433109277","0.164413080578","0.000239592322","","","","",""
|
||||||
|
"PORTUGUESE_LUCENE_PORTUGUESE_MINIMAL_STEM_FILTER","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","ALL_WORDS","PRIMARY_OUTPUT","4001","211091","0","211091","0","1","211091","167745","43329","17","5443023","22274113226","17","22274113243","0.000000","5443023","5486352","99.210240","0.999607806949","0.007897597529","0.999999999237","0.999755693994","0.503948798383","0.038278287185","0.015671380245","0.009852536437","0.007897573058","0.088850999693","0.088840137075","0.000244306006","","","","",""
|
||||||
|
"PORTUGUESE_LUCENE_PORTUGUESE_MINIMAL_STEM_FILTER","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4001","211091","0","211091","0","1","211091","167745","43329","17","5443023","22274113226","17","22274113243","0.000000","5443023","5486352","99.210240","0.999607806949","0.007897597529","0.999999999237","0.999755693994","0.503948798383","0.038278287185","0.015671380245","0.009852536437","0.007897573058","0.088850999693","0.088840137075","0.000244306006","","","","",""
|
||||||
|
"PORTUGUESE_LUCENE_PORTUGUESE_STEM_FILTER","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","ALL_WORDS","PRIMARY_OUTPUT","4001","211091","0","211091","0","1","211091","27586","3802658","80995","1683694","22274032248","80995","22274113243","0.000364","1683694","5486352","30.688771","0.979144635218","0.693112290280","0.999996363716","0.999920793505","0.846554326998","0.904491820642","0.811666162398","0.736120062592","0.683028738823","0.823806518930","0.823772560883","0.000079206495","","","","",""
|
||||||
|
"PORTUGUESE_LUCENE_PORTUGUESE_STEM_FILTER","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4001","211091","0","211091","0","1","211091","27586","3802658","80995","1683694","22274032248","80995","22274113243","0.000364","1683694","5486352","30.688771","0.979144635218","0.693112290280","0.999996363716","0.999920793505","0.846554326998","0.904491820642","0.811666162398","0.736120062592","0.683028738823","0.823806518930","0.823772560883","0.000079206495","","","","",""
|
||||||
|
"PORTUGUESE_RADIXOR","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","ALL_WORDS","PRIMARY_OUTPUT","4001","211091","0","211091","0","1","211091","4001","5470353","0","15999","22274113243","0","22274113243","0.000000","15999","5486352","0.291615","1.000000000000","0.997083854627","1.000000000000","0.999999281899","0.998541927313","0.999415407119","0.998539798233","0.997665722283","0.997083854627","0.998540862773","0.998540504158","0.000000718101","","","","",""
|
||||||
|
"PORTUGUESE_RADIXOR","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","ALL_WORDS","ANY_CANDIDATE","4001","211091","0","210699","392","3","211489","4001","","","","","0","22274113243","0.000000","0","5486352","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"PORTUGUESE_RADIXOR","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","ALL_WORDS","ALL_CANDIDATES","4001","211091","0","210699","392","3","211489","4001","5486352","0","0","22274113243","0","22274113243","0.000000","0","5486352","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","","","","",""
|
||||||
|
"PORTUGUESE_RADIXOR","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4001","211091","0","211091","0","1","211091","4001","5470353","0","15999","22274113243","0","22274113243","0.000000","15999","5486352","0.291615","1.000000000000","0.997083854627","1.000000000000","0.999999281899","0.998541927313","0.999415407119","0.998539798233","0.997665722283","0.997083854627","0.998540862773","0.998540504158","0.000000718101","","","","",""
|
||||||
|
"PORTUGUESE_RADIXOR","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","4001","211091","0","210699","392","3","211489","4001","","","","","0","22274113243","0.000000","0","5486352","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"PORTUGUESE_RADIXOR","PT_PT","pt-pt-default","1.0.0","7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","4001","211091","0","210699","392","3","211489","4001","5486352","0","0","22274113243","0","22274113243","0.000000","0","5486352","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","","","","",""
|
||||||
|
"RUSSIAN_LUCENE_RUSSIAN_LIGHT_STEM_FILTER","RU_RU","ru-ru-default","1.0.0","df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40","ALL_WORDS","PRIMARY_OUTPUT","37410","759333","10","759333","0","1","759333","232250","3036212","170067","10001394","288279715105","170067","288279885172","0.000059","10001394","13037606","76.711890","0.946958140574","0.232881097956","0.999999410063","0.999964718312","0.616440254010","0.586986164875","0.373828305236","0.274240682289","0.229882432734","0.469604782232","0.469595585287","0.000035281688","","","","",""
|
||||||
|
"RUSSIAN_LUCENE_RUSSIAN_LIGHT_STEM_FILTER","RU_RU","ru-ru-default","1.0.0","df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","37297","758584","10","758584","0","1","758584","232143","3034033","167825","10001194","287711260184","167825","287711428009","0.000058","10001194","13035227","76.724356","0.947585120889","0.232756437613","0.999999416690","0.999964657093","0.616377927152","0.587020283013","0.373716464501","0.274112880444","0.229797852799","0.469634471769","0.469625269488","0.000035342907","","","","",""
|
||||||
|
"RUSSIAN_RADIXOR","RU_RU","ru-ru-default","1.0.0","df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40","ALL_WORDS","PRIMARY_OUTPUT","37410","759333","10","759333","0","1","759333","37561","12781761","0","255845","288279885172","0","288279885172","0.000000","255845","13037606","1.962362","1.000000000000","0.980376381983","1.000000000000","0.999999112552","0.990188190992","0.996012679879","0.990090965437","0.984239248995","0.980376381983","0.990139577021","0.990139137653","0.000000887448","","","","",""
|
||||||
|
"RUSSIAN_RADIXOR","RU_RU","ru-ru-default","1.0.0","df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40","ALL_WORDS","ANY_CANDIDATE","37410","759333","10","749720","9613","4","769106","37593","","","","","0","288279885172","0.000000","13","13037606","0.000100","","","","","","","","","","","","","","","","",""
|
||||||
|
"RUSSIAN_RADIXOR","RU_RU","ru-ru-default","1.0.0","df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40","ALL_WORDS","ALL_CANDIDATES","37410","759333","10","749720","9613","4","769106","37593","13037593","0","13","288279885172","0","288279885172","0.000000","13","13037606","0.000100","1.000000000000","0.999999002884","1.000000000000","0.999999999955","0.999999501442","0.999999800577","0.999999501442","0.999999202307","0.999999002884","0.999999501442","0.999999501420","0.000000000045","","","","",""
|
||||||
|
"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_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","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","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","","","","",""
|
||||||
|
"SNOWBALL_HUNGARIAN_DIRECT","HU_HU","hu-hu-default","1.0.0","359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","18360","872878","1","872878","0","1","872878","111379","13763897","1273370","7603420","380934924316","1273370","380936197686","0.000334","7603420","21367317","35.584346","0.915319053655","0.644156540571","0.999996657262","0.999976698743","0.822076598916","0.844241130173","0.756162850261","0.684726470771","0.607927533294","0.767859853828","0.767849489469","0.000023301257","","","","",""
|
||||||
|
"SNOWBALL_HUNGARIAN_LUCENE_FILTER","HU_HU","hu-hu-default","1.0.0","359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e","ALL_WORDS","PRIMARY_OUTPUT","19406","910688","1","910688","0","1","910688","114867","14286575","1565633","7831280","414652177840","1565633","414653743473","0.000378","7831280","22117855","35.407050","0.901235651210","0.645929499040","0.999996224240","0.999977339137","0.822962861640","0.835211528771","0.752517845440","0.684723838400","0.603229346961","0.762977517823","0.762967293456","0.000022660863","","","","",""
|
||||||
|
"SNOWBALL_HUNGARIAN_LUCENE_FILTER","HU_HU","hu-hu-default","1.0.0","359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","18360","872878","1","872878","0","1","872878","111379","13763897","1273370","7603420","380934924316","1273370","380936197686","0.000334","7603420","21367317","35.584346","0.915319053655","0.644156540571","0.999996657262","0.999976698743","0.822076598916","0.844241130173","0.756162850261","0.684726470771","0.607927533294","0.767859853828","0.767849489469","0.000023301257","","","","",""
|
||||||
|
"SNOWBALL_ITALIAN_DIRECT","IT_IT","it-it-default","1.0.0","5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12","ALL_WORDS","PRIMARY_OUTPUT","10009","324366","0","324366","0","1","324366","46828","4493783","388246","1640339","52599966427","388246","52600354673","0.000738","1640339","6134122","26.741219","0.920474458468","0.732587809633","0.999992618947","0.999961438502","0.866290214290","0.875563347203","0.815853559015","0.763767765724","0.688980290594","0.821174991918","0.821156945015","0.000038561498","","","","",""
|
||||||
|
"SNOWBALL_ITALIAN_DIRECT","IT_IT","it-it-default","1.0.0","5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","10007","324285","0","324285","0","1","324285","46814","4492785","388246","1639697","52573697742","388246","52574085988","0.000738","1639697","6132482","26.737902","0.920458198278","0.732620984456","0.999992615259","0.999961431446","0.866306799858","0.875561054334","0.815867743562","0.763794373383","0.689000522641","0.821186331701","0.821168280921","0.000038568554","","","","",""
|
||||||
|
"SNOWBALL_ITALIAN_LUCENE_FILTER","IT_IT","it-it-default","1.0.0","5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12","ALL_WORDS","PRIMARY_OUTPUT","10009","324366","0","324366","0","1","324366","46828","4493783","388246","1640339","52599966427","388246","52600354673","0.000738","1640339","6134122","26.741219","0.920474458468","0.732587809633","0.999992618947","0.999961438502","0.866290214290","0.875563347203","0.815853559015","0.763767765724","0.688980290594","0.821174991918","0.821156945015","0.000038561498","","","","",""
|
||||||
|
"SNOWBALL_ITALIAN_LUCENE_FILTER","IT_IT","it-it-default","1.0.0","5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","10007","324285","0","324285","0","1","324285","46814","4492785","388246","1639697","52573697742","388246","52574085988","0.000738","1639697","6132482","26.737902","0.920458198278","0.732620984456","0.999992615259","0.999961431446","0.866306799858","0.875561054334","0.815867743562","0.763794373383","0.689000522641","0.821186331701","0.821168280921","0.000038568554","","","","",""
|
||||||
|
"SNOWBALL_NORWEGIAN_BOKMAL_DIRECT","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","ALL_WORDS","PRIMARY_OUTPUT","17929","73170","252","73170","0","1","73170","24394","105463","10337","35432","2676736633","10337","2676746970","0.000386","35432","140895","25.147805","0.910734024180","0.748521948969","0.999996138223","0.999982902160","0.874259043596","0.872900785472","0.821698903368","0.776170920545","0.697359024545","0.825653926758","0.825645796759","0.000017097840","","","","",""
|
||||||
|
"SNOWBALL_NORWEGIAN_BOKMAL_DIRECT","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","17914","73111","252","73111","0","1","73111","24367","105404","10337","35402","2672421462","10337","2672431799","0.000387","35402","140806","25.142395","0.910688520058","0.748576054998","0.999996131987","0.999982885778","0.874286093493","0.872882057737","0.821712980468","0.776210850338","0.697379303044","0.825663139347","0.825655001073","0.000017114222","","","","",""
|
||||||
|
"SNOWBALL_NORWEGIAN_BOKMAL_LUCENE_FILTER","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","ALL_WORDS","PRIMARY_OUTPUT","17929","73170","252","73170","0","1","73170","24396","105429","10403","35466","2676736567","10403","2676746970","0.000389","35466","140895","25.171937","0.910188894261","0.748280634515","0.999996113566","0.999982864803","0.874138374041","0.872434515071","0.821331609063","0.775884146880","0.696830096895","0.825273726303","0.825265576414","0.000017135197","","","","",""
|
||||||
|
"SNOWBALL_NORWEGIAN_BOKMAL_LUCENE_FILTER","NB_NO","nb-no-default","1.0.0","f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","17914","73111","252","73111","0","1","73111","24381","105352","10350","35454","2672421449","10350","2672431799","0.000387","35454","140806","25.179325","0.910546057977","0.748206752553","0.999996127123","0.999982861457","0.874101439838","0.872676909416","0.821432469942","0.775872480948","0.696975310275","0.825394880702","0.825386731577","0.000017138543","","","","",""
|
||||||
|
"SNOWBALL_NORWEGIAN_NYNORSK_DIRECT","NN_NO","nn-no-default","1.0.0","900cf2005605aea2a3d8d731ec0b0c1f47fb4469b4ba6b9134145d4d026a0398","ALL_WORDS","PRIMARY_OUTPUT","4688","16937","23","16937","0","1","16937","6138","20880","1201","7482","143392953","1201","143394154","0.000838","7482","28362","26.380368","0.945609347403","0.736196319018","0.999991624484","0.999939458599","0.868093971751","0.894708876815","0.827865115080","0.770314840366","0.706288265738","0.834358508549","0.834330743646","0.000060541401","","","","",""
|
||||||
|
"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","","","","",""
|
||||||
|
"SNOWBALL_PORTUGUESE_LUCENE_FILTER","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_RUSSIAN_DIRECT","RU_RU","ru-ru-default","1.0.0","df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40","ALL_WORDS","PRIMARY_OUTPUT","37410","759333","10","759333","0","1","759333","64358","8723768","3499880","4313838","288276385292","3499880","288279885172","0.001214","4313838","13037606","33.087654","0.713679582396","0.669123457175","0.999987859437","0.999972896601","0.834555658306","0.704299886143","0.690683684983","0.677583981327","0.527514762522","0.691042509176","0.691028995355","0.000027103399","","","","",""
|
||||||
|
"SNOWBALL_RUSSIAN_DIRECT","RU_RU","ru-ru-default","1.0.0","df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","37297","758584","10","758584","0","1","758584","64159","8721831","3499880","4313396","287707928129","3499880","287711428009","0.001216","4313396","13035227","33.090302","0.713634203918","0.669096978518","0.999987835450","0.999972844589","0.834542406984","0.704258664088","0.690648327996","0.677554078401","0.527473514384","0.691006866542","0.690993326753","0.000027155411","","","","",""
|
||||||
|
"SNOWBALL_RUSSIAN_LUCENE_FILTER","RU_RU","ru-ru-default","1.0.0","df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40","ALL_WORDS","PRIMARY_OUTPUT","37410","759333","10","759333","0","1","759333","64266","8724001","3502741","4313605","288276382431","3502741","288279885172","0.001215","4313605","13037606","33.085867","0.713518041028","0.669141328554","0.999987849513","0.999972887486","0.834564589033","0.704177980141","0.690617545325","0.677569512768","0.527437604544","0.690973523314","0.690960004637","0.000027112514","","","","",""
|
||||||
|
"SNOWBALL_RUSSIAN_LUCENE_FILTER","RU_RU","ru-ru-default","1.0.0","df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","37297","758584","10","758584","0","1","758584","64159","8721831","3499880","4313396","287707928129","3499880","287711428009","0.001216","4313396","13035227","33.090302","0.713634203918","0.669096978518","0.999987835450","0.999972844589","0.834542406984","0.704258664088","0.690648327996","0.677554078401","0.527473514384","0.691006866542","0.690993326753","0.000027155411","","","","",""
|
||||||
|
"SNOWBALL_SPANISH_DIRECT","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","ALL_WORDS","PRIMARY_OUTPUT","65059","849661","3589","849661","0","1","849661","195021","12786403","1491944","29152637","360918051646","1491944","360919543590","0.000413","29152637","41939040","69.511932","0.895510033479","0.304880679195","0.999995866270","0.999915102906","0.652438272733","0.645436121970","0.454891402192","0.351208219000","0.294407398184","0.522516705219","0.522489599380","0.000084897094","","","","",""
|
||||||
|
"SNOWBALL_SPANISH_DIRECT","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","64918","847879","3525","847879","0","1","847879","194444","12762004","1472547","29068462","359405672368","1472547","359407144915","0.000410","29068462","41830466","69.491126","0.896551215419","0.305088736042","0.999995902844","0.999915033813","0.652542319443","0.646055272385","0.455257295293","0.351461114717","0.294713995998","0.522998735285","0.522971634774","0.000084966187","","","","",""
|
||||||
|
"SNOWBALL_SPANISH_LUCENE_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","ALL_WORDS","PRIMARY_OUTPUT","65059","849661","3589","849661","0","1","849661","194971","12786409","1493087","29152631","360918050503","1493087","360919543590","0.000414","29152631","41939040","69.511918","0.895438396425","0.304880822260","0.999995863103","0.999915099756","0.652438342682","0.645406478192","0.454882318529","0.351206166994","0.294399788433","0.522495927817","0.522468818464","0.000084900244","","","","",""
|
||||||
|
"SNOWBALL_SPANISH_LUCENE_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","64918","847879","3525","847879","0","1","847879","194444","12762004","1472547","29068462","359405672368","1472547","359407144915","0.000410","29068462","41830466","69.491126","0.896551215419","0.305088736042","0.999995902844","0.999915033813","0.652542319443","0.646055272385","0.455257295293","0.351461114717","0.294713995998","0.522998735285","0.522971634774","0.000084966187","","","","",""
|
||||||
|
"SNOWBALL_SWEDISH_DIRECT","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","ALL_WORDS","PRIMARY_OUTPUT","12371","95181","68","95181","0","1","95181","25915","234278","37166","145369","4529246977","37166","4529284143","0.000821","145369","379647","38.290570","0.863080414376","0.617094300758","0.999991794288","0.999959702307","0.808543047523","0.799352814853","0.719647483992","0.654396122527","0.562069801086","0.729795865162","0.729777443809","0.000040297693","","","","",""
|
||||||
|
"SNOWBALL_SWEDISH_DIRECT","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","12342","94954","68","94954","0","1","94954","25840","233849","37166","145019","4507667547","37166","4507704713","0.000824","145019","378868","38.276920","0.862863679132","0.617230803340","0.999991755006","0.999959587040","0.808611279173","0.799249860554","0.719664924302","0.654493987362","0.562091079094","0.729784928485","0.729766449710","0.000040412960","","","","",""
|
||||||
|
"SNOWBALL_SWEDISH_LUCENE_FILTER","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","ALL_WORDS","PRIMARY_OUTPUT","12371","95181","68","95181","0","1","95181","26781","227960","35082","151687","4529249061","35082","4529284143","0.000775","151687","379647","39.954747","0.866629663704","0.600452525636","0.999992254405","0.999958767580","0.800222390020","0.796052562656","0.709394434944","0.639751239034","0.549660139513","0.721366737771","0.721348147050","0.000041232420","","","","",""
|
||||||
|
"SNOWBALL_SWEDISH_LUCENE_FILTER","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","12342","94954","68","94954","0","1","94954","26706","227531","35082","151337","4507669631","35082","4507704713","0.000778","151337","378868","39.944519","0.866411792257","0.600554810646","0.999992217325","0.999958647839","0.800273513986","0.795941426692","0.709392795734","0.639820368543","0.549658171277","0.721337486785","0.721318837420","0.000041352161","","","","",""
|
||||||
|
"SNOWBALL_YIDDISH_DIRECT","YI","yi-default","1.0.0","f47de665c27dcd72833a82904e49c68a945bb5aca769a7ec5a0164e2c981a6d3","ALL_WORDS","PRIMARY_OUTPUT","802","3532","0","3532","0","1","3532","1087","4943","823","1375","6228605","823","6229428","0.013211","1375","6318","21.763216","0.857266736039","0.782367837923","0.999867885141","0.999647516111","0.891117861532","0.841161255190","0.818106587223","0.796281976932","0.692199971993","0.818961490425","0.818786919931","0.000352483889","","","","",""
|
||||||
|
"SNOWBALL_YIDDISH_DIRECT","YI","yi-default","1.0.0","f47de665c27dcd72833a82904e49c68a945bb5aca769a7ec5a0164e2c981a6d3","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","802","3532","0","3532","0","1","3532","1087","4943","823","1375","6228605","823","6229428","0.013211","1375","6318","21.763216","0.857266736039","0.782367837923","0.999867885141","0.999647516111","0.891117861532","0.841161255190","0.818106587223","0.796281976932","0.692199971993","0.818961490425","0.818786919931","0.000352483889","","","","",""
|
||||||
|
"SNOWBALL_YIDDISH_LUCENE_FILTER","YI","yi-default","1.0.0","f47de665c27dcd72833a82904e49c68a945bb5aca769a7ec5a0164e2c981a6d3","ALL_WORDS","PRIMARY_OUTPUT","802","3532","0","3532","0","1","3532","1087","4943","823","1375","6228605","823","6229428","0.013211","1375","6318","21.763216","0.857266736039","0.782367837923","0.999867885141","0.999647516111","0.891117861532","0.841161255190","0.818106587223","0.796281976932","0.692199971993","0.818961490425","0.818786919931","0.000352483889","","","","",""
|
||||||
|
"SNOWBALL_YIDDISH_LUCENE_FILTER","YI","yi-default","1.0.0","f47de665c27dcd72833a82904e49c68a945bb5aca769a7ec5a0164e2c981a6d3","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","802","3532","0","3532","0","1","3532","1087","4943","823","1375","6228605","823","6229428","0.013211","1375","6318","21.763216","0.857266736039","0.782367837923","0.999867885141","0.999647516111","0.891117861532","0.841161255190","0.818106587223","0.796281976932","0.692199971993","0.818961490425","0.818786919931","0.000352483889","","","","",""
|
||||||
|
"SPANISH_LUCENE_SPANISH_LIGHT_STEM_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","ALL_WORDS","PRIMARY_OUTPUT","65059","849661","3589","849661","0","1","849661","405552","1221659","32066","40717381","360919511524","32066","360919543590","0.000009","40717381","41939040","97.087060","0.974423418214","0.029129398289","0.999999911155","0.999887108600","0.514564654722","0.130091212793","0.056567760828","0.036141643431","0.029107143376","0.168476609210","0.168466600268","0.000112891400","","","","",""
|
||||||
|
"SPANISH_LUCENE_SPANISH_LIGHT_STEM_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","64918","847879","3525","847879","0","1","847879","404617","1219357","31857","40611109","359407113058","31857","359407144915","0.000009","40611109","41830466","97.085003","0.974539127599","0.029149974088","0.999999911362","0.999886929804","0.514574942725","0.130174935063","0.056606752569","0.036167014759","0.029127791117","0.168546107394","0.168536080782","0.000113070196","","","","",""
|
||||||
|
"SPANISH_LUCENE_SPANISH_MINIMAL_STEM_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","ALL_WORDS","PRIMARY_OUTPUT","65059","849661","3589","849661","0","1","849661","718633","140718","4263","41798322","360919539327","4263","360919543590","0.000001","41798322","41939040","99.664470","0.970596147081","0.003355298548","0.999999988189","0.999884191009","0.501677643368","0.016547675056","0.006687478841","0.004190501600","0.003354957524","0.057066976813","0.057063472077","0.000115808991","","","","",""
|
||||||
|
"SPANISH_LUCENE_SPANISH_MINIMAL_STEM_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","64918","847879","3525","847879","0","1","847879","717093","140505","4162","41689961","359407140753","4162","359407144915","0.000001","41689961","41830466","99.664108","0.971230481036","0.003358915485","0.999999988420","0.999884005448","0.501679451953","0.016565417252","0.006694678013","0.004195017332","0.003358581317","0.057116382085","0.057112873151","0.000115994552","","","","",""
|
||||||
|
"SPANISH_LUCENE_SPANISH_PLURAL_STEM_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","ALL_WORDS","PRIMARY_OUTPUT","65059","849661","3589","849661","0","1","849661","578805","315690","6721","41623350","360919536869","6721","360919543590","0.000002","41623350","41939040","99.247265","0.979153937055","0.007527353988","0.999999981378","0.999884668938","0.503763667683","0.036513949858","0.014939856182","0.009391143622","0.007526147875","0.085851256794","0.085846095212","0.000115331062","","","","",""
|
||||||
|
"SPANISH_LUCENE_SPANISH_PLURAL_STEM_FILTER","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","64918","847879","3525","847879","0","1","847879","577533","315155","6589","41515311","359407138326","6589","359407144915","0.000002","41515311","41830466","99.246590","0.979520985628","0.007534102059","0.999999981667","0.999884484578","0.503767041863","0.036546115143","0.014953189880","0.009399553128","0.007532915498","0.085905826777","0.085900657455","0.000115515422","","","","",""
|
||||||
|
"SPANISH_RADIXOR","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","ALL_WORDS","PRIMARY_OUTPUT","65059","849661","3589","849661","0","1","849661","64995","41053986","0","885054","360919543590","0","360919543590","0.000000","885054","41939040","2.110334","1.000000000000","0.978896655717","1.000000000000","0.999997548065","0.989448327859","0.995706851308","0.989335802746","0.983045766368","0.978896655717","0.989392063702","0.989390850601","0.000002451935","","","","",""
|
||||||
|
"SPANISH_RADIXOR","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","ALL_WORDS","ANY_CANDIDATE","65059","849661","3589","828694","20967","21","871404","65118","","","","","0","360919543590","0.000000","626","41939040","0.001493","","","","","","","","","","","","","","","","",""
|
||||||
|
"SPANISH_RADIXOR","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","ALL_WORDS","ALL_CANDIDATES","65059","849661","3589","828694","20967","21","871404","65118","41938414","1737","626","360919541853","1737","360919543590","0.000000","626","41939040","0.001493","0.999958583840","0.999985073573","0.999999995187","0.999999993454","0.999992534380","0.999963881674","0.999971828531","0.999979775515","0.999943658650","0.999971828619","0.999971825345","0.000000006546","","","","",""
|
||||||
|
"SPANISH_RADIXOR","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","64918","847879","3525","847879","0","1","847879","64814","40958710","34","871756","359407144881","34","359407144915","0.000000","871756","41830466","2.084022","0.999999169896","0.979159782729","0.999999999905","0.999997574649","0.989579891317","0.995760629537","0.989469763028","0.983257884479","0.979158986864","0.989524618150","0.989523418038","0.000002425351","","","","",""
|
||||||
|
"SPANISH_RADIXOR","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","64918","847879","3525","826968","20911","21","869542","64933","","","","","0","359407144915","0.000000","0","41830466","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"SPANISH_RADIXOR","ES_ES","es-es-default","1.0.0","7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","64918","847879","3525","826968","20911","21","869542","64933","41830466","560","0","359407144355","560","359407144915","0.000000","0","41830466","0.000000","0.999986612807","1.000000000000","0.999999998442","0.999999998442","0.999999999221","0.999989290217","0.999993306359","0.999997322533","0.999986612807","0.999993306381","0.999993305602","0.000000001558","","","","",""
|
||||||
|
"SWEDISH_LUCENE_SWEDISH_LIGHT_STEM_FILTER","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","ALL_WORDS","PRIMARY_OUTPUT","12371","95181","68","95181","0","1","95181","22392","216573","24174","163074","4529259969","24174","4529284143","0.000534","163074","379647","42.954113","0.899587533801","0.570458873638","0.999994662733","0.999958661833","0.785226768185","0.806522249159","0.698178899216","0.615496764402","0.536309404414","0.716364216911","0.716346530489","0.000041338167","","","","",""
|
||||||
|
"SWEDISH_LUCENE_SWEDISH_LIGHT_STEM_FILTER","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","12342","94954","68","94954","0","1","94954","22338","216064","24174","162804","4507680539","24174","4507704713","0.000536","162804","378868","42.971167","0.899374786670","0.570288332612","0.999994637182","0.999958523839","0.785141484897","0.806317266498","0.697987097524","0.615318019491","0.536083088115","0.716172428597","0.716154681700","0.000041476161","","","","",""
|
||||||
|
"SWEDISH_LUCENE_SWEDISH_MINIMAL_STEM_FILTER","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","ALL_WORDS","PRIMARY_OUTPUT","12371","95181","68","95181","0","1","95181","23360","226201","19890","153446","4529264253","19890","4529284143","0.000439","153446","379647","40.418073","0.919176239684","0.595819274221","0.999995608578","0.999961733142","0.797907441399","0.829175864418","0.722989494005","0.640912596569","0.566157827686","0.740042512299","0.740026147317","0.000038266858","","","","",""
|
||||||
|
"SWEDISH_LUCENE_SWEDISH_MINIMAL_STEM_FILTER","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","12342","94954","68","94954","0","1","94954","23312","225644","19890","153224","4507684823","19890","4507704713","0.000441","153224","378868","40.442582","0.918992888969","0.595574184149","0.999995587555","0.999961599204","0.797784885852","0.828961560730","0.722752329429","0.640667890967","0.565867017088","0.739816490818","0.739800068479","0.000038400796","","","","",""
|
||||||
|
"SWEDISH_RADIXOR","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","ALL_WORDS","PRIMARY_OUTPUT","12371","95181","68","95181","0","1","95181","12330","362653","0","16994","4529284143","0","4529284143","0.000000","16994","379647","4.476263","1.000000000000","0.955237365237","1.000000000000","0.999996248287","0.977618682618","0.990714975312","0.977106291257","0.963866405208","0.955237365237","0.977362453359","0.977360619819","0.000003751713","","","","",""
|
||||||
|
"SWEDISH_RADIXOR","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","ALL_WORDS","ANY_CANDIDATE","12371","95181","68","92341","2840","5","98108","12371","","","","","0","4529284143","0.000000","0","379647","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"SWEDISH_RADIXOR","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","ALL_WORDS","ALL_CANDIDATES","12371","95181","68","92341","2840","5","98108","12371","379647","0","0","4529284143","0","4529284143","0.000000","0","379647","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","","","","",""
|
||||||
|
"SWEDISH_RADIXOR","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","12342","94954","68","94954","0","1","94954","12301","361874","0","16994","4507704713","0","4507704713","0.000000","16994","378868","4.485467","1.000000000000","0.955145327660","1.000000000000","0.999996230327","0.977572663830","0.990695173580","0.977058139001","0.963791437487","0.955145327660","0.977315367556","0.977313525326","0.000003769673","","","","",""
|
||||||
|
"SWEDISH_RADIXOR","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","12342","94954","68","92114","2840","5","97881","12342","","","","","0","4507704713","0.000000","0","378868","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"SWEDISH_RADIXOR","SV_SE","sv-se-default","1.0.0","d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","12342","94954","68","92114","2840","5","97881","12342","378868","0","0","4507704713","0","4507704713","0.000000","0","378868","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","","","","",""
|
||||||
|
"UKRAINIAN_LUCENE_MORFOLOGIK_FILTER","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","ALL_WORDS","PRIMARY_OUTPUT","1493","14150","4","14150","0","1","14150","2358","55865","28","9260","100039022","28","100039050","0.000028","9260","65125","14.218810","0.999499042814","0.857811900192","0.999999720109","0.999907216657","0.928905810151","0.967536898548","0.923251086615","0.882841908639","0.857443248968","0.925949336171","0.925906311882","0.000092783343","","","","",""
|
||||||
|
"UKRAINIAN_LUCENE_MORFOLOGIK_FILTER","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","ALL_WORDS","ANY_CANDIDATE","1493","14150","4","12020","2130","6","16748","2912","","","","","0","100039050","0.000000","4946","65125","7.594626","","","","","","","","","","","","","","","","",""
|
||||||
|
"UKRAINIAN_LUCENE_MORFOLOGIK_FILTER","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","ALL_WORDS","ALL_CANDIDATES","1493","14150","4","12020","2130","6","16748","2912","60179","59","4946","100038991","59","100039050","0.000059","4946","65125","7.594626","0.999020551811","0.924053742802","0.999999410230","0.999950002085","0.962026576516","0.983069619736","0.960075939472","0.938133305065","0.923217353952","0.960806265611","0.960782185122","0.000049997915","","","","",""
|
||||||
|
"UKRAINIAN_LUCENE_MORFOLOGIK_FILTER","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","1491","14141","4","14141","0","1","14141","2356","55849","28","9260","99911733","28","99911761","0.000028","9260","65109","14.222304","0.999498899368","0.857776958639","0.999999719753","0.999907098512","0.928888339196","0.967527900297","0.923230787033","0.882812277712","0.857408231880","0.925930411026","0.925887332795","0.000092901488","","","","",""
|
||||||
|
"UKRAINIAN_LUCENE_MORFOLOGIK_FILTER","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","1491","14141","4","12011","2130","6","16739","2910","","","","","0","99911761","0.000000","4946","65109","7.596492","","","","","","","","","","","","","","","","",""
|
||||||
|
"UKRAINIAN_LUCENE_MORFOLOGIK_FILTER","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","1491","14141","4","12011","2130","6","16739","2910","60163","59","4946","99911702","59","99911761","0.000059","4946","65109","7.596492","0.999020291588","0.924035079636","0.999999409479","0.999949938421","0.962017244557","0.983065193450","0.960065745905","0.938117870130","0.923198502332","0.960796437699","0.960772326767","0.000050061579","","","","",""
|
||||||
|
"UKRAINIAN_MORFOLOGIK_DIRECT","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","ALL_WORDS","PRIMARY_OUTPUT","1493","14150","4","14150","0","1","14150","2365","55849","28","9276","100039022","28","100039050","0.000028","9276","65125","14.243378","0.999498899368","0.857566218810","0.999999720109","0.999907056824","0.928782969460","0.967474266629","0.923108708947","0.882633693347","0.857197673169","0.925816662108","0.925773569870","0.000092943176","","","","",""
|
||||||
|
"UKRAINIAN_MORFOLOGIK_DIRECT","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","ALL_WORDS","ANY_CANDIDATE","1493","14150","4","12020","2130","6","16748","2919","","","","","0","100039050","0.000000","4962","65125","7.619194","","","","","","","","","","","","","","","","",""
|
||||||
|
"UKRAINIAN_MORFOLOGIK_DIRECT","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","ALL_WORDS","ALL_CANDIDATES","1493","14150","4","12020","2130","6","16748","2919","60163","59","4962","100038991","59","100039050","0.000059","4962","65125","7.619194","0.999020291588","0.923808061420","0.999999410230","0.999949842252","0.961903735825","0.983013793532","0.959943197683","0.937930668928","0.922971894944","0.960678405551","0.960654251285","0.000050157748","","","","",""
|
||||||
|
"UKRAINIAN_MORFOLOGIK_DIRECT","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","1491","14141","4","14141","0","1","14141","2356","55849","28","9260","99911733","28","99911761","0.000028","9260","65109","14.222304","0.999498899368","0.857776958639","0.999999719753","0.999907098512","0.928888339196","0.967527900297","0.923230787033","0.882812277712","0.857408231880","0.925930411026","0.925887332795","0.000092901488","","","","",""
|
||||||
|
"UKRAINIAN_MORFOLOGIK_DIRECT","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","1491","14141","4","12011","2130","6","16739","2910","","","","","0","99911761","0.000000","4946","65109","7.596492","","","","","","","","","","","","","","","","",""
|
||||||
|
"UKRAINIAN_MORFOLOGIK_DIRECT","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","1491","14141","4","12011","2130","6","16739","2910","60163","59","4946","99911702","59","99911761","0.000059","4946","65109","7.596492","0.999020291588","0.924035079636","0.999999409479","0.999949938421","0.962017244557","0.983065193450","0.960065745905","0.938117870130","0.923198502332","0.960796437699","0.960772326767","0.000050061579","","","","",""
|
||||||
|
"UKRAINIAN_RADIXOR","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","ALL_WORDS","PRIMARY_OUTPUT","1493","14150","4","14150","0","1","14150","1493","64580","0","545","100039050","0","100039050","0.000000","545","65125","0.836852","1.000000000000","0.991631477927","1.000000000000","0.999994555672","0.995815738964","0.998315014918","0.995798157357","0.993293958410","0.991631477927","0.995806948122","0.995804235618","0.000005444328","","","","",""
|
||||||
|
"UKRAINIAN_RADIXOR","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","ALL_WORDS","ANY_CANDIDATE","1493","14150","4","14055","95","2","14245","1493","","","","","0","100039050","0.000000","0","65125","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"UKRAINIAN_RADIXOR","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","ALL_WORDS","ALL_CANDIDATES","1493","14150","4","14055","95","2","14245","1493","65125","0","0","100039050","0","100039050","0.000000","0","65125","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","","","","",""
|
||||||
|
"UKRAINIAN_RADIXOR","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","1491","14141","4","14141","0","1","14141","1491","64564","0","545","99911761","0","99911761","0.000000","545","65109","0.837058","1.000000000000","0.991629421432","1.000000000000","0.999994548739","0.995814710716","0.998314598055","0.995797120449","0.993292307692","0.991629421432","0.995805915544","0.995803199587","0.000005451261","","","","",""
|
||||||
|
"UKRAINIAN_RADIXOR","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","1491","14141","4","14046","95","2","14236","1491","","","","","0","99911761","0.000000","0","65109","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"UKRAINIAN_RADIXOR","UK_UA","uk-ua-default","1.0.0","cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","1491","14141","4","14046","95","2","14236","1491","65109","0","0","99911761","0","99911761","0.000000","0","65109","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","","","","",""
|
||||||
|
"YI_RADIXOR","YI","yi-default","1.0.0","f47de665c27dcd72833a82904e49c68a945bb5aca769a7ec5a0164e2c981a6d3","ALL_WORDS","PRIMARY_OUTPUT","802","3532","0","3532","0","1","3532","802","6180","0","138","6229428","0","6229428","0.000000","138","6318","2.184236","1.000000000000","0.978157644824","1.000000000000","0.999977869528","0.989078822412","0.995553837232","0.988958233317","0.982449446776","0.978157644824","0.989018526027","0.989007571386","0.000022130472","","","","",""
|
||||||
|
"YI_RADIXOR","YI","yi-default","1.0.0","f47de665c27dcd72833a82904e49c68a945bb5aca769a7ec5a0164e2c981a6d3","ALL_WORDS","ANY_CANDIDATE","802","3532","0","3489","43","3","3578","802","","","","","0","6229428","0.000000","0","6318","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"YI_RADIXOR","YI","yi-default","1.0.0","f47de665c27dcd72833a82904e49c68a945bb5aca769a7ec5a0164e2c981a6d3","ALL_WORDS","ALL_CANDIDATES","802","3532","0","3489","43","3","3578","802","6318","0","0","6229428","0","6229428","0.000000","0","6318","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","","","","",""
|
||||||
|
"YI_RADIXOR","YI","yi-default","1.0.0","f47de665c27dcd72833a82904e49c68a945bb5aca769a7ec5a0164e2c981a6d3","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","802","3532","0","3532","0","1","3532","802","6180","0","138","6229428","0","6229428","0.000000","138","6318","2.184236","1.000000000000","0.978157644824","1.000000000000","0.999977869528","0.989078822412","0.995553837232","0.988958233317","0.982449446776","0.978157644824","0.989018526027","0.989007571386","0.000022130472","","","","",""
|
||||||
|
"YI_RADIXOR","YI","yi-default","1.0.0","f47de665c27dcd72833a82904e49c68a945bb5aca769a7ec5a0164e2c981a6d3","LOWERCASE_GROUPS_ONLY","ANY_CANDIDATE","802","3532","0","3489","43","3","3578","802","","","","","0","6229428","0.000000","0","6318","0.000000","","","","","","","","","","","","","","","","",""
|
||||||
|
"YI_RADIXOR","YI","yi-default","1.0.0","f47de665c27dcd72833a82904e49c68a945bb5aca769a7ec5a0164e2c981a6d3","LOWERCASE_GROUPS_ONLY","ALL_CANDIDATES","802","3532","0","3489","43","3","3578","802","6318","0","0","6229428","0","6229428","0.000000","0","6318","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","","","","",""
|
||||||
|
1
docs/benchmarks/data/stemming-quality.sha256
Normal file
@@ -0,0 +1 @@
|
|||||||
|
d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5 stemming-quality.csv
|
||||||
330
docs/benchmarks/index.md
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
# Benchmark Results
|
||||||
|
|
||||||
|
This section contains the published Radixor benchmark reference set. It is intentionally split into
|
||||||
|
two layers:
|
||||||
|
|
||||||
|
- **benchmark reference pages**, which explain methodology, corpora, environment, candidate
|
||||||
|
selection, and the English dictionary coverage experiment;
|
||||||
|
- **language result pages**, which contain the actual same-language accuracy and throughput tables.
|
||||||
|
- **pairwise quality pages and generated sections**, which publish over-stemming, under-stemming,
|
||||||
|
candidate-policy, classification, and partition measurements from one checked result snapshot.
|
||||||
|
|
||||||
|
This structure keeps methodology separate from per-language result pages, while preserving all
|
||||||
|
measured data and the command-class analysis for each Radixor default model.
|
||||||
|
|
||||||
|
## Read This First
|
||||||
|
|
||||||
|
Start with [Benchmarking](../benchmarking.md) for the high-level interpretation model. The most
|
||||||
|
important rule is that speed and exact-root quality must be read together. Many competing stemmers
|
||||||
|
are intentionally light, minimal, or aggressive; they can be fast because they are not trying to
|
||||||
|
match dictionary roots with the same precision.
|
||||||
|
|
||||||
|
Radixor rows in the refreshed tables use contracted compiled patch tries. Contraction collapses
|
||||||
|
uniform preferred-command subtrees into accepting leaves, reducing hot lookup depth while preserving
|
||||||
|
the preferred result measured by the accuracy pass.
|
||||||
|
|
||||||
|
## Reference Pages
|
||||||
|
|
||||||
|
| Page | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [Methodology](reference/methodology.md) | Workload design, normalization, speed metrics, quality metrics, and interpretation rules. |
|
||||||
|
| [Linguistic quality methodology](reference/linguistic-quality.md) | Gold-standard groups, output policies, pairwise formulas, ranking rules, aggregation, and limitations. |
|
||||||
|
| [Tested stemmers](reference/tested-stemmers.md) | Versions, upstream attribution, evaluated coverage, adapters, preprocessing, and output capability. |
|
||||||
|
| [Reproducibility and raw data](reference/reproducibility.md) | Commands, versioned CSV snapshot, checksum, generated artifacts, and unavailable provenance. |
|
||||||
|
| [Corpora](reference/corpora.md) | Dictionary row counts, complete quality tokens, already-root tokens, changed speed tokens, and timing token counts. |
|
||||||
|
| [Environment and reports](reference/environment.md) | Hardware, JVM, JMH settings, report files, and badge/report policy. |
|
||||||
|
| [English dictionary coverage](reference/english-coverage.md) | Quality/speed operating curve for contracted Radixor tries built from 100% down to 10% of English dictionary rows. |
|
||||||
|
| [Candidate evaluation](reference/candidates.md) | Included benchmark families and evaluated candidates that were skipped. |
|
||||||
|
|
||||||
|
## Language Results
|
||||||
|
|
||||||
|
Each language page contains:
|
||||||
|
|
||||||
|
- the dictionary corpus size,
|
||||||
|
- the Radixor patch-command distribution,
|
||||||
|
- exact-root quality metrics,
|
||||||
|
- throughput metrics,
|
||||||
|
- interpretation notes for the compared stemmers.
|
||||||
|
|
||||||
|
Open [Language Benchmark Pages](languages/index.md) for the complete language list.
|
||||||
|
|
||||||
|
## Key Published Result
|
||||||
|
|
||||||
|
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 `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,
|
||||||
|
while contracted tries reduce lookup cost in uniform regions of the compiled graph.
|
||||||
|
|
||||||
|
## 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-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.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY-OVERVIEW:START -->
|
||||||
|
|
||||||
|
## Pairwise Quality Findings
|
||||||
|
|
||||||
|
The validated snapshot is a broad multilingual comparison covering the complete 20-language Radixor default-model universe, with one benchmark page per language. The direct ranking below uses only deterministic `PRIMARY_OUTPUT` rows over identical per-language inputs. Candidate-aware rows are intentionally excluded from this claim.
|
||||||
|
|
||||||
|
!!! success "Evidence-based primary-output result"
|
||||||
|
Radixor achieved the highest balanced accuracy among the evaluated deterministic stemmers for every documented language in both `ALL_WORDS` and `LOWERCASE_GROUPS_ONLY`: **40 wins in 40 language-mode comparisons, with no exact first-place ties**. This statement is limited to the evaluated implementations, versions, dictionaries, adapters, and balanced-accuracy metric; it is not a universal claim about every stemming use case.
|
||||||
|
|
||||||
|
### Per-language winner matrix
|
||||||
|
|
||||||
|
| 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|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.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|
|
||||||
|
|German (`DE_DE`)|LOWERCASE_GROUPS_ONLY|Radixor|0.966959|GERMAN CISTEM|0.052231588|no|8|
|
||||||
|
|Hebrew (`HE_IL`)|ALL_WORDS|Radixor|0.986075|n/a|n/a|no|1|
|
||||||
|
|Hebrew (`HE_IL`)|LOWERCASE_GROUPS_ONLY|Radixor|0.986075|n/a|n/a|no|1|
|
||||||
|
|Hungarian (`HU_HU`)|ALL_WORDS|Radixor|0.995555|SNOWBALL HUNGARIAN LUCENE FILTER|0.172591951|no|4|
|
||||||
|
|Hungarian (`HU_HU`)|LOWERCASE_GROUPS_ONLY|Radixor|0.996227|SNOWBALL HUNGARIAN DIRECT|0.174150583|no|4|
|
||||||
|
|Italian (`IT_IT`)|ALL_WORDS|Radixor|0.996651|SNOWBALL ITALIAN DIRECT|0.130360651|no|4|
|
||||||
|
|Italian (`IT_IT`)|LOWERCASE_GROUPS_ONLY|Radixor|0.996656|SNOWBALL ITALIAN DIRECT|0.130349693|no|4|
|
||||||
|
|Norwegian Bokmal (`NB_NO`)|ALL_WORDS|Radixor|0.976021|SNOWBALL NORWEGIAN BOKMAL DIRECT|0.101762107|no|5|
|
||||||
|
|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|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|
|
||||||
|
|Russian (`RU_RU`)|LOWERCASE_GROUPS_ONLY|Radixor|0.990213|SNOWBALL RUSSIAN DIRECT|0.155670422|no|4|
|
||||||
|
|Spanish (`ES_ES`)|ALL_WORDS|Radixor|0.989448|SNOWBALL SPANISH LUCENE FILTER|0.337009985|no|7|
|
||||||
|
|Spanish (`ES_ES`)|LOWERCASE_GROUPS_ONLY|Radixor|0.989580|SNOWBALL SPANISH DIRECT|0.337037572|no|7|
|
||||||
|
|Swedish (`SV_SE`)|ALL_WORDS|Radixor|0.977619|SNOWBALL SWEDISH DIRECT|0.169075635|no|5|
|
||||||
|
|Swedish (`SV_SE`)|LOWERCASE_GROUPS_ONLY|Radixor|0.977573|SNOWBALL SWEDISH DIRECT|0.168961385|no|5|
|
||||||
|
|Ukrainian (`UK_UA`)|ALL_WORDS|Radixor|0.995816|UKRAINIAN LUCENE MORFOLOGIK FILTER|0.066909929|no|4|
|
||||||
|
|Ukrainian (`UK_UA`)|LOWERCASE_GROUPS_ONLY|Radixor|0.995815|UKRAINIAN LUCENE MORFOLOGIK FILTER|0.066926372|no|4|
|
||||||
|
|Yiddish (`YI`)|ALL_WORDS|Radixor|0.989079|SNOWBALL YIDDISH DIRECT|0.097960961|no|3|
|
||||||
|
|Yiddish (`YI`)|LOWERCASE_GROUPS_ONLY|Radixor|0.989079|SNOWBALL YIDDISH DIRECT|0.097960961|no|3|
|
||||||
|
|
||||||
|
### Secondary-metric trade-offs
|
||||||
|
|
||||||
|
Balanced-accuracy leadership does not imply leadership on every error trade-off. The table below lists all **0** deterministic primary-output language-mode-metric cases where a non-Radixor adapter has the best displayed value. Equal values are resolved by the authoritative row ordering and should be read as ties when the unrounded values are equal. Throughput leadership remains in the separate performance tables.
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Non-Radixor secondary-metric leaders</summary>
|
||||||
|
|
||||||
|
| Language | Dictionary mode | Metric | Leader | Value |
|
||||||
|
|---|---|---|---|---:|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### Win, tie, and placement summary
|
||||||
|
|
||||||
|
Counts use `PRIMARY_OUTPUT` only and retain each adapter configuration as a separate stemmer except that language-specific Radixor identifiers are combined as Radixor. Coverage is displayed explicitly; unsupported languages are absent, not losses.
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>ALL_WORDS placements</summary>
|
||||||
|
|
||||||
|
| Stemmer | Evaluated languages | Wins | Exact first-place ties | Top-three placements | Average rank | Median rank |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|20|20|0|20|1.000|1.000|
|
||||||
|
|CZECH LUCENE CZECH STEM FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|ENGLISH LUCENE KSTEM FILTER|1|0|0|0|8.000|8.000|
|
||||||
|
|ENGLISH LUCENE MINIMAL FILTER|1|0|0|0|9.000|9.000|
|
||||||
|
|ENGLISH LUCENE PORTER COPIED|1|0|0|1|2.000|2.000|
|
||||||
|
|ENGLISH LUCENE PORTER FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|ENGLISH LUCENE POSSESSIVE FILTER|1|0|0|0|11.000|11.000|
|
||||||
|
|ENGLISH OPENNLP PORTER|1|0|0|0|4.000|4.000|
|
||||||
|
|ENGLISH PAICE HUSK LANCASTER|1|0|0|0|7.000|7.000|
|
||||||
|
|ENGLISH SNOWBALL ORIGINAL PORTER|1|0|0|0|6.000|6.000|
|
||||||
|
|ENGLISH SNOWBALL PORTER2|1|0|0|0|5.000|5.000|
|
||||||
|
|FINNISH LUCENE FINNISH LIGHT STEM FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|FRENCH LUCENE FRENCH LIGHT STEM FILTER|1|0|0|0|5.000|5.000|
|
||||||
|
|FRENCH LUCENE FRENCH MINIMAL STEM FILTER|1|0|0|0|6.000|6.000|
|
||||||
|
|GERMAN CISTEM|1|0|0|1|2.000|2.000|
|
||||||
|
|GERMAN LUCENE GERMAN LIGHT STEM FILTER|1|0|0|0|5.000|5.000|
|
||||||
|
|GERMAN LUCENE GERMAN MINIMAL STEM FILTER|1|0|0|0|8.000|8.000|
|
||||||
|
|GERMAN LUCENE GERMAN STEM FILTER|1|0|0|0|6.000|6.000|
|
||||||
|
|HUNGARIAN LUCENE HUNGARIAN LIGHT STEM FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|HUNSPELL CZECH LUCENE FILTER|1|0|0|1|2.000|2.000|
|
||||||
|
|HUNSPELL DUTCH LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|HUNSPELL ENGLISH LUCENE FILTER|1|0|0|0|10.000|10.000|
|
||||||
|
|HUNSPELL FRENCH LUCENE FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|HUNSPELL GERMAN LUCENE FILTER|1|0|0|0|7.000|7.000|
|
||||||
|
|HUNSPELL POLISH LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|HUNSPELL SPANISH LUCENE FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|HUNSPELL UKRAINIAN LUCENE FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|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|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|
|
||||||
|
|PORTUGUESE LUCENE PORTUGUESE LIGHT STEM FILTER|1|0|0|0|5.000|5.000|
|
||||||
|
|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|
|
||||||
|
|SNOWBALL DUTCH LUCENE FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|SNOWBALL FINNISH DIRECT|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL FINNISH LUCENE FILTER|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL FRENCH DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL FRENCH LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL GERMAN DIRECT|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL GERMAN LUCENE FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|SNOWBALL HUNGARIAN DIRECT|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL HUNGARIAN LUCENE FILTER|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL ITALIAN DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL ITALIAN LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL NORWEGIAN BOKMAL DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|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|
|
||||||
|
|SNOWBALL RUSSIAN LUCENE FILTER|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL SPANISH DIRECT|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL SPANISH LUCENE FILTER|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL SWEDISH DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL SWEDISH LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL YIDDISH DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL YIDDISH LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|SPANISH LUCENE SPANISH LIGHT STEM FILTER|1|0|0|0|5.000|5.000|
|
||||||
|
|SPANISH LUCENE SPANISH MINIMAL STEM FILTER|1|0|0|0|7.000|7.000|
|
||||||
|
|SPANISH LUCENE SPANISH PLURAL STEM FILTER|1|0|0|0|6.000|6.000|
|
||||||
|
|SWEDISH LUCENE SWEDISH LIGHT STEM FILTER|1|0|0|0|5.000|5.000|
|
||||||
|
|SWEDISH LUCENE SWEDISH MINIMAL STEM FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|UKRAINIAN LUCENE MORFOLOGIK FILTER|1|0|0|1|2.000|2.000|
|
||||||
|
|UKRAINIAN MORFOLOGIK DIRECT|1|0|0|1|3.000|3.000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>LOWERCASE_GROUPS_ONLY placements</summary>
|
||||||
|
|
||||||
|
| Stemmer | Evaluated languages | Wins | Exact first-place ties | Top-three placements | Average rank | Median rank |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|20|20|0|20|1.000|1.000|
|
||||||
|
|CZECH LUCENE CZECH STEM FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|ENGLISH LUCENE KSTEM FILTER|1|0|0|0|8.000|8.000|
|
||||||
|
|ENGLISH LUCENE MINIMAL FILTER|1|0|0|0|9.000|9.000|
|
||||||
|
|ENGLISH LUCENE PORTER COPIED|1|0|0|1|2.000|2.000|
|
||||||
|
|ENGLISH LUCENE PORTER FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|ENGLISH LUCENE POSSESSIVE FILTER|1|0|0|0|11.000|11.000|
|
||||||
|
|ENGLISH OPENNLP PORTER|1|0|0|0|4.000|4.000|
|
||||||
|
|ENGLISH PAICE HUSK LANCASTER|1|0|0|0|7.000|7.000|
|
||||||
|
|ENGLISH SNOWBALL ORIGINAL PORTER|1|0|0|0|6.000|6.000|
|
||||||
|
|ENGLISH SNOWBALL PORTER2|1|0|0|0|5.000|5.000|
|
||||||
|
|FINNISH LUCENE FINNISH LIGHT STEM FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|FRENCH LUCENE FRENCH LIGHT STEM FILTER|1|0|0|0|5.000|5.000|
|
||||||
|
|FRENCH LUCENE FRENCH MINIMAL STEM FILTER|1|0|0|0|6.000|6.000|
|
||||||
|
|GERMAN CISTEM|1|0|0|1|2.000|2.000|
|
||||||
|
|GERMAN LUCENE GERMAN LIGHT STEM FILTER|1|0|0|0|5.000|5.000|
|
||||||
|
|GERMAN LUCENE GERMAN MINIMAL STEM FILTER|1|0|0|0|8.000|8.000|
|
||||||
|
|GERMAN LUCENE GERMAN STEM FILTER|1|0|0|0|6.000|6.000|
|
||||||
|
|HUNGARIAN LUCENE HUNGARIAN LIGHT STEM FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|HUNSPELL CZECH LUCENE FILTER|1|0|0|1|2.000|2.000|
|
||||||
|
|HUNSPELL DUTCH LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|HUNSPELL ENGLISH LUCENE FILTER|1|0|0|0|10.000|10.000|
|
||||||
|
|HUNSPELL FRENCH LUCENE FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|HUNSPELL GERMAN LUCENE FILTER|1|0|0|0|7.000|7.000|
|
||||||
|
|HUNSPELL POLISH LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|HUNSPELL SPANISH LUCENE FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|HUNSPELL UKRAINIAN LUCENE FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|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|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|
|
||||||
|
|PORTUGUESE LUCENE PORTUGUESE LIGHT STEM FILTER|1|0|0|0|5.000|5.000|
|
||||||
|
|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|
|
||||||
|
|SNOWBALL DUTCH LUCENE FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|SNOWBALL FINNISH DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL FINNISH LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL FRENCH DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL FRENCH LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL GERMAN DIRECT|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL GERMAN LUCENE FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|SNOWBALL HUNGARIAN DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL HUNGARIAN LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL ITALIAN DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL ITALIAN LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL NORWEGIAN BOKMAL DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|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|
|
||||||
|
|SNOWBALL RUSSIAN LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL SPANISH DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL SPANISH LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL SWEDISH DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL SWEDISH LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|SNOWBALL YIDDISH DIRECT|1|0|0|1|2.000|2.000|
|
||||||
|
|SNOWBALL YIDDISH LUCENE FILTER|1|0|0|1|3.000|3.000|
|
||||||
|
|SPANISH LUCENE SPANISH LIGHT STEM FILTER|1|0|0|0|5.000|5.000|
|
||||||
|
|SPANISH LUCENE SPANISH MINIMAL STEM FILTER|1|0|0|0|7.000|7.000|
|
||||||
|
|SPANISH LUCENE SPANISH PLURAL STEM FILTER|1|0|0|0|6.000|6.000|
|
||||||
|
|SWEDISH LUCENE SWEDISH LIGHT STEM FILTER|1|0|0|0|5.000|5.000|
|
||||||
|
|SWEDISH LUCENE SWEDISH MINIMAL STEM FILTER|1|0|0|0|4.000|4.000|
|
||||||
|
|UKRAINIAN LUCENE MORFOLOGIK FILTER|1|0|0|1|2.000|2.000|
|
||||||
|
|UKRAINIAN MORFOLOGIK DIRECT|1|0|0|1|3.000|3.000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
### Radixor full-coverage aggregates
|
||||||
|
|
||||||
|
These aggregates cover all 20 documented languages. Macro balanced accuracy gives each language equal weight. Micro metrics first sum raw pair counts across languages. Unsupported third-party languages are never inserted as zero results, so this full-coverage table is not presented as a cross-stemmer common-language ranking.
|
||||||
|
|
||||||
|
| Dictionary mode | Languages | Macro balanced accuracy | Micro balanced accuracy | Micro precision | Micro recall | Micro F1 |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|ALL_WORDS|20|0.980724|0.987976|0.999988|0.975952|0.987824|
|
||||||
|
|LOWERCASE_GROUPS_ONLY|20|0.983891|0.989614|0.999992|0.979228|0.989501|
|
||||||
|
|
||||||
|
### Reproducible data
|
||||||
|
|
||||||
|
- [Machine-readable quality snapshot](data/stemming-quality.csv)
|
||||||
|
- SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- [Linguistic quality methodology](reference/linguistic-quality.md)
|
||||||
|
- [Tested stemmer inventory](reference/tested-stemmers.md)
|
||||||
|
- [Reproducibility and raw data](reference/reproducibility.md)
|
||||||
|
- Pearson and Spearman correlation files are generated under `build/reports/stemming-quality/`; they are separated by dictionary mode and output policy. Correlation does not establish metric equivalence.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY-OVERVIEW:END -->
|
||||||
341
docs/benchmarks/languages/czech.md
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
# Czech Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Czech. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **56,612**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 711 | 1.256% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 22,643 | 39.997% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 15,007 | 26.509% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 10,046 | 17.745% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 8,205 | 14.493% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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
|
||||||
|
|
||||||
|
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 | `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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `CS_CZ` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
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 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 **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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|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%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|HUNSPELL CZECH LUCENE FILTER|0.000650%|25.611213%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 1320705191|0 / 300509|
|
||||||
|
|HUNSPELL CZECH LUCENE FILTER|8582 / 1320705191|76964 / 300509|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|2|HUNSPELL CZECH LUCENE FILTER|0.871940|0.000816%|25.611213%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|2|HUNSPELL CZECH LUCENE FILTER|ALL_CANDIDATES|0.954016|0.743888|0.999992|0.871940|0.999934|0.000066|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|2|HUNSPELL CZECH LUCENE FILTER|ALL_CANDIDATES|0.903001|0.835949|0.778167|0.718138|0.842426|0.842395|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|300509|0|0|1320705191|0 / 1320705191|0 / 300509|
|
||||||
|
|2|HUNSPELL CZECH LUCENE FILTER|ALL_CANDIDATES|223545|10775|76964|1320694416|10775 / 1320705191|76964 / 300509|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|2033|0|0|321|0.624501%|4|51739|
|
||||||
|
|HUNSPELL CZECH LUCENE FILTER|10703|546|1647|3194|6.213887%|5|55179|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|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%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|HUNSPELL CZECH LUCENE FILTER|0.000663%|25.840457%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 1284770069|0 / 297487|
|
||||||
|
|HUNSPELL CZECH LUCENE FILTER|8518 / 1284770069|76872 / 297487|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|2|HUNSPELL CZECH LUCENE FILTER|0.870794|0.000819%|25.840457%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|2|HUNSPELL CZECH LUCENE FILTER|ALL_CANDIDATES|0.954473|0.741595|0.999992|0.870794|0.999932|0.000068|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|2|HUNSPELL CZECH LUCENE FILTER|ALL_CANDIDATES|0.902651|0.834675|0.776220|0.716259|0.841328|0.841297|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|297487|0|0|1284770069|0 / 1284770069|0 / 297487|
|
||||||
|
|2|HUNSPELL CZECH LUCENE FILTER|ALL_CANDIDATES|220615|10523|76872|1284759546|10523 / 1284770069|76872 / 297487|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|1669|0|0|269|0.530603%|4|50975|
|
||||||
|
|HUNSPELL CZECH LUCENE FILTER|10498|475|1530|3117|6.148293%|5|54394|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `CS_CZ`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
317
docs/benchmarks/languages/danish.md
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
# Danish Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Danish. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **32,256**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 179 | 0.555% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 1,127 | 3.494% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 22,680 | 70.312% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 8,269 | 25.636% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 1 | 0.003% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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.971% | 54.791% | 59.347% | 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[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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `DA_DK` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
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 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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.996243|0.000000%|0.751435%|
|
||||||
|
|2|SNOWBALL DANISH DIRECT|0.942482|0.001236%|11.502313%|
|
||||||
|
|3|SNOWBALL DANISH LUCENE FILTER|0.937905|0.001273%|12.417638%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| 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 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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| 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 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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| 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 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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 389687465|0 / 89695|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|89695|0|0|389687465|0 / 389687465|0 / 89695|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|674|0|0|165|0.590953%|3|28087|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.996482|0.000000%|0.703596%|
|
||||||
|
|2|SNOWBALL DANISH DIRECT|0.942383|0.001240%|11.522225%|
|
||||||
|
|3|SNOWBALL DANISH LUCENE FILTER|0.938010|0.001235%|12.396694%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| 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.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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| 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.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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| 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|79223|4816|10317|388399519|4816 / 388404335|10317 / 89540|
|
||||||
|
|3|SNOWBALL DANISH LUCENE FILTER|PRIMARY_OUTPUT|78440|4795|11100|388399540|4795 / 388404335|11100 / 89540|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 388404335|0 / 89540|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|89540|0|0|388404335|0 / 388404335|0 / 89540|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|630|0|0|157|0.563229%|3|28033|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `DA_DK`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
341
docs/benchmarks/languages/dutch.md
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
# Dutch Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Dutch. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **31,466**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 2,107 | 6.696% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 11,484 | 36.497% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 7,732 | 24.573% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 10,127 | 32.184% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 16 | 0.051% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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.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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `NL_NL` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `nl-nl-default`, loaded from classpath resource `org/egothor/stemmer/models/nl-nl-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.988733** among 4 deterministic stemmers. The runner-up is `SNOWBALL DUTCH DIRECT` at 0.727093, a difference of 0.261640. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.989114** among 4 deterministic stemmers. The runner-up is `SNOWBALL DUTCH DIRECT` at 0.730509, a difference of 0.258605. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
### `ALL_WORDS`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.988733|0.000000%|2.253364%|
|
||||||
|
|2|SNOWBALL DUTCH DIRECT|0.727093|0.000870%|54.580443%|
|
||||||
|
|3|HUNSPELL DUTCH LUCENE FILTER|0.642844|0.000104%|71.431010%|
|
||||||
|
|4|SNOWBALL DUTCH LUCENE FILTER|0.617975|0.000221%|76.404861%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.977466|1.000000|0.988733|0.999996|0.000004|
|
||||||
|
|2|SNOWBALL DUTCH DIRECT|PRIMARY_OUTPUT|0.907391|0.454196|0.999991|0.727093|0.999889|0.000111|
|
||||||
|
|3|HUNSPELL DUTCH LUCENE FILTER|PRIMARY_OUTPUT|0.981029|0.285690|0.999999|0.642844|0.999865|0.000135|
|
||||||
|
|4|SNOWBALL DUTCH LUCENE FILTER|PRIMARY_OUTPUT|0.952453|0.235951|0.999998|0.617975|0.999854|0.000146|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.995411|0.988605|0.981891|0.977466|0.988669|0.988667|
|
||||||
|
|2|SNOWBALL DUTCH DIRECT|PRIMARY_OUTPUT|0.756437|0.605372|0.504600|0.434074|0.641976|0.641934|
|
||||||
|
|3|HUNSPELL DUTCH LUCENE FILTER|PRIMARY_OUTPUT|0.659835|0.442513|0.332878|0.284120|0.529405|0.529368|
|
||||||
|
|4|SNOWBALL DUTCH LUCENE FILTER|PRIMARY_OUTPUT|0.592568|0.378209|0.277738|0.233204|0.474060|0.474022|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|62985|0|1452|343168663|0 / 343168663|1452 / 64437|
|
||||||
|
|2|SNOWBALL DUTCH DIRECT|PRIMARY_OUTPUT|29267|2987|35170|343165676|2987 / 343168663|35170 / 64437|
|
||||||
|
|3|HUNSPELL DUTCH LUCENE FILTER|PRIMARY_OUTPUT|18409|356|46028|343168307|356 / 343168663|46028 / 64437|
|
||||||
|
|4|SNOWBALL DUTCH LUCENE FILTER|PRIMARY_OUTPUT|15204|759|49233|343167904|759 / 343168663|49233 / 64437|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL DUTCH LUCENE FILTER|0.000096%|66.975495%|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL DUTCH LUCENE FILTER|330 / 343168663|43157 / 64437|
|
||||||
|
|Radixor|0 / 343168663|0 / 64437|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|2|HUNSPELL DUTCH LUCENE FILTER|0.665122|0.000147%|66.975495%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|2|HUNSPELL DUTCH LUCENE FILTER|ALL_CANDIDATES|0.976909|0.330245|0.999999|0.665122|0.999873|0.000127|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|2|HUNSPELL DUTCH LUCENE FILTER|ALL_CANDIDATES|0.701991|0.493621|0.380638|0.327687|0.567996|0.567958|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|64437|0|0|343168663|0 / 343168663|0 / 64437|
|
||||||
|
|2|HUNSPELL DUTCH LUCENE FILTER|ALL_CANDIDATES|21280|503|43157|343168160|503 / 343168663|43157 / 64437|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|HUNSPELL DUTCH LUCENE FILTER|2871|26|147|1199|4.576161%|3|27429|
|
||||||
|
|Radixor|1452|0|0|296|1.129728%|3|26501|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.989114|0.000000%|2.177156%|
|
||||||
|
|2|SNOWBALL DUTCH DIRECT|0.730509|0.000926%|53.897299%|
|
||||||
|
|3|HUNSPELL DUTCH LUCENE FILTER|0.644879|0.000103%|71.024152%|
|
||||||
|
|4|SNOWBALL DUTCH LUCENE FILTER|0.618013|0.000222%|76.397220%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.978228|1.000000|0.989114|0.999996|0.000004|
|
||||||
|
|2|SNOWBALL DUTCH DIRECT|PRIMARY_OUTPUT|0.906773|0.461027|0.999991|0.730509|0.999885|0.000115|
|
||||||
|
|3|HUNSPELL DUTCH LUCENE FILTER|PRIMARY_OUTPUT|0.982090|0.289758|0.999999|0.644879|0.999860|0.000140|
|
||||||
|
|4|SNOWBALL DUTCH LUCENE FILTER|PRIMARY_OUTPUT|0.954134|0.236028|0.999998|0.618013|0.999849|0.000151|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.995569|0.988994|0.982507|0.978228|0.989054|0.989052|
|
||||||
|
|2|SNOWBALL DUTCH DIRECT|PRIMARY_OUTPUT|0.759842|0.611269|0.511295|0.440164|0.646565|0.646521|
|
||||||
|
|3|HUNSPELL DUTCH LUCENE FILTER|PRIMARY_OUTPUT|0.664532|0.447489|0.337317|0.288235|0.533450|0.533411|
|
||||||
|
|4|SNOWBALL DUTCH LUCENE FILTER|PRIMARY_OUTPUT|0.593185|0.378440|0.277851|0.233380|0.474555|0.474515|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|61646|0|1372|322555083|0 / 322555083|1372 / 63018|
|
||||||
|
|2|SNOWBALL DUTCH DIRECT|PRIMARY_OUTPUT|29053|2987|33965|322552096|2987 / 322555083|33965 / 63018|
|
||||||
|
|3|HUNSPELL DUTCH LUCENE FILTER|PRIMARY_OUTPUT|18260|333|44758|322554750|333 / 322555083|44758 / 63018|
|
||||||
|
|4|SNOWBALL DUTCH LUCENE FILTER|PRIMARY_OUTPUT|14874|715|48144|322554368|715 / 322555083|48144 / 63018|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL DUTCH LUCENE FILTER|0.000095%|66.488940%|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL DUTCH LUCENE FILTER|307 / 322555083|41900 / 63018|
|
||||||
|
|Radixor|0 / 322555083|0 / 63018|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|2|HUNSPELL DUTCH LUCENE FILTER|0.667555|0.000148%|66.488940%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|2|HUNSPELL DUTCH LUCENE FILTER|ALL_CANDIDATES|0.977912|0.335111|0.999999|0.667555|0.999869|0.000131|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|2|HUNSPELL DUTCH LUCENE FILTER|ALL_CANDIDATES|0.706770|0.499167|0.385834|0.332593|0.572458|0.572419|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|63018|0|0|322555083|0 / 322555083|0 / 63018|
|
||||||
|
|2|HUNSPELL DUTCH LUCENE FILTER|ALL_CANDIDATES|21118|477|41900|322554606|477 / 322555083|41900 / 63018|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|HUNSPELL DUTCH LUCENE FILTER|2858|26|144|1131|4.452405%|3|26562|
|
||||||
|
|Radixor|1372|0|0|273|1.074719%|3|25679|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `NL_NL`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
411
docs/benchmarks/languages/english.md
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
# English Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for English. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **1,004,374**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 73 | 0.007% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 22,481 | 2.238% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 202,637 | 20.175% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 779,106 | 77.571% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 77 | 0.008% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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.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` | 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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `US_UK` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `us-uk-default`, loaded from classpath resource `org/egothor/stemmer/models/us-uk-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.965537** among 11 deterministic stemmers. The runner-up is `ENGLISH LUCENE PORTER COPIED` at 0.954796, a difference of 0.010741. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.966202** among 11 deterministic stemmers. The runner-up is `ENGLISH LUCENE PORTER COPIED` at 0.955064, a difference of 0.011139. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
### `ALL_WORDS`
|
||||||
|
|
||||||
|
This mode contains **15 result rows**, **11 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.965537|<0.000001%|6.892502%|
|
||||||
|
|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.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%|
|
||||||
|
|9|ENGLISH LUCENE MINIMAL FILTER|0.718958|0.000001%|56.208454%|
|
||||||
|
|10|HUNSPELL ENGLISH LUCENE FILTER|0.573139|0.000012%|85.372182%|
|
||||||
|
|11|ENGLISH LUCENE POSSESSIVE FILTER|0.500011|<0.000001%|99.997766%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.999990|0.931075|1.000000|0.965537|1.000000|0.000000|
|
||||||
|
|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.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|
|
||||||
|
|9|ENGLISH LUCENE MINIMAL FILTER|PRIMARY_OUTPUT|0.989894|0.437915|1.000000|0.718958|0.999999|0.000001|
|
||||||
|
|10|HUNSPELL ENGLISH LUCENE FILTER|PRIMARY_OUTPUT|0.681277|0.146278|1.000000|0.573139|0.999998|0.000002|
|
||||||
|
|11|ENGLISH LUCENE POSSESSIVE FILTER|PRIMARY_OUTPUT|0.148936|0.000022|1.000000|0.500011|0.999998|0.000002|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.985403|0.964303|0.944087|0.931066|0.964917|0.964917|
|
||||||
|
|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.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|
|
||||||
|
|9|ENGLISH LUCENE MINIMAL FILTER|PRIMARY_OUTPUT|0.790591|0.607210|0.492883|0.435966|0.658399|0.658399|
|
||||||
|
|10|HUNSPELL ENGLISH LUCENE FILTER|PRIMARY_OUTPUT|0.393465|0.240844|0.173533|0.136909|0.315683|0.315683|
|
||||||
|
|11|ENGLISH LUCENE POSSESSIVE FILTER|PRIMARY_OUTPUT|0.000112|0.000045|0.000028|0.000022|0.001824|0.001824|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|291757|3|21598|175199424127|3 / 175199424130|21598 / 313355|
|
||||||
|
|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|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|
|
||||||
|
|9|ENGLISH LUCENE MINIMAL FILTER|PRIMARY_OUTPUT|137223|1401|176132|175199422729|1401 / 175199424130|176132 / 313355|
|
||||||
|
|10|HUNSPELL ENGLISH LUCENE FILTER|PRIMARY_OUTPUT|45837|21444|267518|175199402686|21444 / 175199424130|267518 / 313355|
|
||||||
|
|11|ENGLISH LUCENE POSSESSIVE FILTER|PRIMARY_OUTPUT|7|40|313348|175199424090|40 / 175199424130|313348 / 313355|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.004787%|
|
||||||
|
|HUNSPELL ENGLISH LUCENE FILTER|0.000012%|83.719424%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 175199424130|15 / 313355|
|
||||||
|
|HUNSPELL ENGLISH LUCENE FILTER|20367 / 175199424130|262339 / 313355|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.999976|<0.000001%|0.004787%|
|
||||||
|
|2|HUNSPELL ENGLISH LUCENE FILTER|0.581403|0.000022%|83.719424%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999825|0.999952|1.000000|0.999976|1.000000|0.000000|
|
||||||
|
|2|HUNSPELL ENGLISH LUCENE FILTER|ALL_CANDIDATES|0.568132|0.162806|1.000000|0.581403|0.999998|0.000002|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999850|0.999888|0.999927|0.999777|0.999888|0.999888|
|
||||||
|
|2|HUNSPELL ENGLISH LUCENE FILTER|ALL_CANDIDATES|0.379279|0.253086|0.189902|0.144876|0.304130|0.304130|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|313340|55|15|175199424075|55 / 175199424130|15 / 313355|
|
||||||
|
|2|HUNSPELL ENGLISH LUCENE FILTER|ALL_CANDIDATES|51016|38780|262339|175199385350|38780 / 175199424130|262339 / 313355|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|21583|3|52|13718|2.317441%|1355|607918|
|
||||||
|
|HUNSPELL ENGLISH LUCENE FILTER|5179|1077|17336|5736|0.969007%|4|597698|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
This mode contains **15 result rows**, **11 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.966202|<0.000001%|6.759543%|
|
||||||
|
|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.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%|
|
||||||
|
|9|ENGLISH LUCENE MINIMAL FILTER|0.719875|0.000001%|56.025075%|
|
||||||
|
|10|HUNSPELL ENGLISH LUCENE FILTER|0.573484|0.000012%|85.303261%|
|
||||||
|
|11|ENGLISH LUCENE POSSESSIVE FILTER|0.500008|<0.000001%|99.998394%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.999990|0.932405|1.000000|0.966202|1.000000|0.000000|
|
||||||
|
|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.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|
|
||||||
|
|9|ENGLISH LUCENE MINIMAL FILTER|PRIMARY_OUTPUT|0.989965|0.439749|1.000000|0.719875|0.999999|0.000001|
|
||||||
|
|10|HUNSPELL ENGLISH LUCENE FILTER|PRIMARY_OUTPUT|0.700136|0.146967|1.000000|0.573484|0.999998|0.000002|
|
||||||
|
|11|ENGLISH LUCENE POSSESSIVE FILTER|PRIMARY_OUTPUT|0.121951|0.000016|1.000000|0.500008|0.999998|0.000002|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.985700|0.965015|0.945181|0.932396|0.965606|0.965606|
|
||||||
|
|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.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|
|
||||||
|
|9|ENGLISH LUCENE MINIMAL FILTER|PRIMARY_OUTPUT|0.791820|0.608984|0.494744|0.437798|0.659800|0.659800|
|
||||||
|
|10|HUNSPELL ENGLISH LUCENE FILTER|PRIMARY_OUTPUT|0.399444|0.242939|0.174549|0.138264|0.320776|0.320775|
|
||||||
|
|11|ENGLISH LUCENE POSSESSIVE FILTER|PRIMARY_OUTPUT|0.000080|0.000032|0.000020|0.000016|0.001399|0.001399|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|290334|3|21048|161561989635|3 / 161561989638|21048 / 311382|
|
||||||
|
|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|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|
|
||||||
|
|9|ENGLISH LUCENE MINIMAL FILTER|PRIMARY_OUTPUT|136930|1388|174452|161561988250|1388 / 161561989638|174452 / 311382|
|
||||||
|
|10|HUNSPELL ENGLISH LUCENE FILTER|PRIMARY_OUTPUT|45763|19600|265619|161561970038|19600 / 161561989638|265619 / 311382|
|
||||||
|
|11|ENGLISH LUCENE POSSESSIVE FILTER|PRIMARY_OUTPUT|5|36|311377|161561989602|36 / 161561989638|311377 / 311382|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|HUNSPELL ENGLISH LUCENE FILTER|0.000011%|83.640994%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 161561989638|0 / 311382|
|
||||||
|
|HUNSPELL ENGLISH LUCENE FILTER|18564 / 161561989638|260443 / 311382|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|<0.000001%|0.000000%|
|
||||||
|
|2|HUNSPELL ENGLISH LUCENE FILTER|0.581795|0.000023%|83.640994%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999952|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|2|HUNSPELL ENGLISH LUCENE FILTER|ALL_CANDIDATES|0.581828|0.163590|1.000000|0.581795|0.999998|0.000002|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999961|0.999976|0.999990|0.999952|0.999976|0.999976|
|
||||||
|
|2|HUNSPELL ENGLISH LUCENE FILTER|ALL_CANDIDATES|0.384979|0.255377|0.191058|0.146379|0.308515|0.308514|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|311382|15|0|161561989623|15 / 161561989638|0 / 311382|
|
||||||
|
|2|HUNSPELL ENGLISH LUCENE FILTER|ALL_CANDIDATES|50939|36611|260443|161561953027|36611 / 161561989638|260443 / 311382|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|21048|3|12|13357|2.349760%|1355|584042|
|
||||||
|
|HUNSPELL ENGLISH LUCENE FILTER|5176|1036|17011|5685|1.000104%|4|574142|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `US_UK`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
327
docs/benchmarks/languages/finnish.md
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
# Finnish Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Finnish. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **1,865,215**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 1,117 | 0.060% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 1,175,880 | 63.043% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 570,130 | 30.566% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 112,029 | 6.006% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 6,059 | 0.325% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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.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
|
||||||
|
|
||||||
|
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 | `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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `FI_FI` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
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.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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.984838|<0.000001%|3.032474%|
|
||||||
|
|2|SNOWBALL FINNISH LUCENE FILTER|0.740279|0.000081%|51.944179%|
|
||||||
|
|3|SNOWBALL FINNISH DIRECT|0.739870|0.000060%|52.025976%|
|
||||||
|
|4|FINNISH LUCENE FINNISH LIGHT STEM FILTER|0.695725|0.000094%|60.854936%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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.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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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.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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 1599841739337|0 / 31465599|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|<0.000001%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999926|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999941|0.999963|0.999985|0.999926|0.999963|0.999963|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|31465599|2327|0|1599841737010|2327 / 1599841739337|0 / 31465599|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|954186|804|1523|34395|1.922815%|6|1826768|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.988242|<0.000001%|2.351587%|
|
||||||
|
|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%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| 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.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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| 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.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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| 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|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 1504706135053|0 / 30760883|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|<0.000001%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999927|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999942|0.999964|0.999985|0.999927|0.999964|0.999964|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|30760883|2235|0|1504706132818|2235 / 1504706135053|0 / 30760883|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|723369|804|1431|22060|1.271628%|6|1758300|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `FI_FI`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
361
docs/benchmarks/languages/french.md
Normal file
@@ -0,0 +1,361 @@
|
|||||||
|
# French Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for French. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **474,110**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 10,082 | 2.127% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 184,521 | 38.919% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 154,760 | 32.642% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 110,933 | 23.398% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 13,814 | 2.914% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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 Radixor's dictionary-trained transformation model. |
|
||||||
|
|
||||||
|
## 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 | `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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `FR_FR` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `fr-fr-default`, loaded from classpath resource `org/egothor/stemmer/models/fr-fr-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.958627** among 6 deterministic stemmers. The runner-up is `SNOWBALL FRENCH DIRECT` at 0.848662, a difference of 0.109965. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.958856** among 6 deterministic stemmers. The runner-up is `SNOWBALL FRENCH DIRECT` at 0.848826, a difference of 0.110031. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
### `ALL_WORDS`
|
||||||
|
|
||||||
|
This mode contains **10 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.958627|<0.000001%|8.274665%|
|
||||||
|
|2|SNOWBALL FRENCH DIRECT|0.848662|0.001338%|30.266309%|
|
||||||
|
|3|SNOWBALL FRENCH LUCENE FILTER|0.848404|0.001345%|30.317815%|
|
||||||
|
|4|HUNSPELL FRENCH LUCENE FILTER|0.816824|0.000540%|36.634583%|
|
||||||
|
|5|FRENCH LUCENE FRENCH LIGHT STEM FILTER|0.518478|0.000187%|96.304159%|
|
||||||
|
|6|FRENCH LUCENE FRENCH MINIMAL STEM FILTER|0.516784|0.000083%|96.643216%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.999994|0.917253|1.000000|0.958627|0.999995|0.000005|
|
||||||
|
|2|SNOWBALL FRENCH DIRECT|PRIMARY_OUTPUT|0.774195|0.697337|0.999987|0.848662|0.999967|0.000033|
|
||||||
|
|3|SNOWBALL FRENCH LUCENE FILTER|PRIMARY_OUTPUT|0.773169|0.696822|0.999987|0.848404|0.999967|0.000033|
|
||||||
|
|4|HUNSPELL FRENCH LUCENE FILTER|PRIMARY_OUTPUT|0.885315|0.633654|0.999995|0.816824|0.999970|0.000030|
|
||||||
|
|5|FRENCH LUCENE FRENCH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.565022|0.036958|0.999998|0.518478|0.999935|0.000065|
|
||||||
|
|6|FRENCH LUCENE FRENCH MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.726387|0.033568|0.999999|0.516784|0.999936|0.000064|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.982273|0.956838|0.932688|0.917248|0.957731|0.957728|
|
||||||
|
|2|SNOWBALL FRENCH DIRECT|PRIMARY_OUTPUT|0.757497|0.733759|0.711463|0.579478|0.734761|0.734745|
|
||||||
|
|3|SNOWBALL FRENCH LUCENE FILTER|PRIMARY_OUTPUT|0.756590|0.733013|0.710861|0.578548|0.734003|0.733987|
|
||||||
|
|4|HUNSPELL FRENCH LUCENE FILTER|PRIMARY_OUTPUT|0.820168|0.738637|0.671850|0.585587|0.748988|0.748975|
|
||||||
|
|5|FRENCH LUCENE FRENCH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.146469|0.069379|0.045455|0.035936|0.144507|0.144495|
|
||||||
|
|6|FRENCH LUCENE FRENCH MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.141655|0.064170|0.041481|0.033149|0.156151|0.156143|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|4925833|29|444366|81606871827|29 / 81606871856|444366 / 5370199|
|
||||||
|
|2|SNOWBALL FRENCH DIRECT|PRIMARY_OUTPUT|3744838|1092238|1625361|81605779618|1092238 / 81606871856|1625361 / 5370199|
|
||||||
|
|3|SNOWBALL FRENCH LUCENE FILTER|PRIMARY_OUTPUT|3742072|1097843|1628127|81605774013|1097843 / 81606871856|1628127 / 5370199|
|
||||||
|
|4|HUNSPELL FRENCH LUCENE FILTER|PRIMARY_OUTPUT|3402849|440809|1967350|81606431047|440809 / 81606871856|1967350 / 5370199|
|
||||||
|
|5|FRENCH LUCENE FRENCH LIGHT STEM FILTER|PRIMARY_OUTPUT|198474|152794|5171725|81606719062|152794 / 81606871856|5171725 / 5370199|
|
||||||
|
|6|FRENCH LUCENE FRENCH MINIMAL STEM FILTER|PRIMARY_OUTPUT|180266|67902|5189933|81606803954|67902 / 81606871856|5189933 / 5370199|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.004320%|
|
||||||
|
|HUNSPELL FRENCH LUCENE FILTER|0.000539%|33.189869%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 81606871856|232 / 5370199|
|
||||||
|
|HUNSPELL FRENCH LUCENE FILTER|439665 / 81606871856|1782362 / 5370199|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.999978|0.000003%|0.004320%|
|
||||||
|
|2|HUNSPELL FRENCH LUCENE FILTER|0.834048|0.000614%|33.189869%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999571|0.999957|1.000000|0.999978|1.000000|0.000000|
|
||||||
|
|2|HUNSPELL FRENCH LUCENE FILTER|ALL_CANDIDATES|0.877537|0.668101|0.999994|0.834048|0.999972|0.000028|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999648|0.999764|0.999880|0.999528|0.999764|0.999764|
|
||||||
|
|2|HUNSPELL FRENCH LUCENE FILTER|ALL_CANDIDATES|0.825765|0.758630|0.701590|0.611123|0.765691|0.765678|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|5369967|2303|232|81606869553|2303 / 81606871856|232 / 5370199|
|
||||||
|
|2|HUNSPELL FRENCH LUCENE FILTER|ALL_CANDIDATES|3587837|500695|1782362|81606371161|500695 / 81606871856|1782362 / 5370199|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|444134|29|2274|21844|5.406783%|56|427440|
|
||||||
|
|HUNSPELL FRENCH LUCENE FILTER|184988|1144|59886|8230|2.037073%|4|412364|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
This mode contains **10 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.958856|<0.000001%|8.228703%|
|
||||||
|
|2|SNOWBALL FRENCH DIRECT|0.848826|0.001356%|30.233460%|
|
||||||
|
|3|SNOWBALL FRENCH LUCENE FILTER|0.848580|0.001353%|30.282729%|
|
||||||
|
|4|HUNSPELL FRENCH LUCENE FILTER|0.816702|0.000540%|36.658999%|
|
||||||
|
|5|FRENCH LUCENE FRENCH LIGHT STEM FILTER|0.518338|0.000181%|96.332173%|
|
||||||
|
|6|FRENCH LUCENE FRENCH MINIMAL STEM FILTER|0.516654|0.000076%|96.669051%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.917713|1.000000|0.958856|0.999995|0.000005|
|
||||||
|
|2|SNOWBALL FRENCH DIRECT|PRIMARY_OUTPUT|0.774357|0.697665|0.999986|0.848826|0.999966|0.000034|
|
||||||
|
|3|SNOWBALL FRENCH LUCENE FILTER|PRIMARY_OUTPUT|0.774620|0.697173|0.999986|0.848580|0.999966|0.000034|
|
||||||
|
|4|HUNSPELL FRENCH LUCENE FILTER|PRIMARY_OUTPUT|0.886736|0.633410|0.999995|0.816702|0.999970|0.000030|
|
||||||
|
|5|FRENCH LUCENE FRENCH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.575115|0.036678|0.999998|0.518338|0.999934|0.000066|
|
||||||
|
|6|FRENCH LUCENE FRENCH MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.746071|0.033309|0.999999|0.516654|0.999935|0.000065|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.982383|0.957091|0.933069|0.917713|0.957973|0.957971|
|
||||||
|
|2|SNOWBALL FRENCH DIRECT|PRIMARY_OUTPUT|0.757699|0.734013|0.711764|0.579795|0.735012|0.734995|
|
||||||
|
|3|SNOWBALL FRENCH LUCENE FILTER|PRIMARY_OUTPUT|0.757784|0.733859|0.711398|0.579603|0.734877|0.734860|
|
||||||
|
|4|HUNSPELL FRENCH LUCENE FILTER|PRIMARY_OUTPUT|0.821061|0.738965|0.671794|0.585999|0.749445|0.749431|
|
||||||
|
|5|FRENCH LUCENE FRENCH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.146117|0.068959|0.045128|0.035711|0.145238|0.145227|
|
||||||
|
|6|FRENCH LUCENE FRENCH MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.141311|0.063772|0.041177|0.032936|0.157643|0.157634|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|4915501|1|440750|80279496864|1 / 80279496865|440750 / 5356251|
|
||||||
|
|2|SNOWBALL FRENCH DIRECT|PRIMARY_OUTPUT|3736871|1088903|1619380|80278407962|1088903 / 80279496865|1619380 / 5356251|
|
||||||
|
|3|SNOWBALL FRENCH LUCENE FILTER|PRIMARY_OUTPUT|3734232|1086494|1622019|80278410371|1086494 / 80279496865|1622019 / 5356251|
|
||||||
|
|4|HUNSPELL FRENCH LUCENE FILTER|PRIMARY_OUTPUT|3392703|433354|1963548|80279063511|433354 / 80279496865|1963548 / 5356251|
|
||||||
|
|5|FRENCH LUCENE FRENCH LIGHT STEM FILTER|PRIMARY_OUTPUT|196458|145140|5159793|80279351725|145140 / 80279496865|5159793 / 5356251|
|
||||||
|
|6|FRENCH LUCENE FRENCH MINIMAL STEM FILTER|PRIMARY_OUTPUT|178414|60724|5177837|80279436141|60724 / 80279496865|5177837 / 5356251|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|HUNSPELL FRENCH LUCENE FILTER|0.000539%|33.211718%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 80279496865|0 / 5356251|
|
||||||
|
|HUNSPELL FRENCH LUCENE FILTER|432307 / 80279496865|1778903 / 5356251|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|<0.000001%|0.000000%|
|
||||||
|
|2|HUNSPELL FRENCH LUCENE FILTER|0.833938|0.000614%|33.211718%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999986|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|2|HUNSPELL FRENCH LUCENE FILTER|ALL_CANDIDATES|0.878983|0.667883|0.999994|0.833938|0.999972|0.000028|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999989|0.999993|0.999997|0.999986|0.999993|0.999993|
|
||||||
|
|2|HUNSPELL FRENCH LUCENE FILTER|ALL_CANDIDATES|0.826722|0.759029|0.701582|0.611641|0.766197|0.766184|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|5356251|75|0|80279496790|75 / 80279496865|0 / 5356251|
|
||||||
|
|2|HUNSPELL FRENCH LUCENE FILTER|ALL_CANDIDATES|3577348|492522|1778903|80279004343|492522 / 80279496865|1778903 / 5356251|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|440750|1|74|20611|5.143594%|56|422336|
|
||||||
|
|HUNSPELL FRENCH LUCENE FILTER|184645|1047|59168|8194|2.044860%|4|409028|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `FR_FR`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
381
docs/benchmarks/languages/german.md
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
# German Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for German. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **333,036**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 12,107 | 3.635% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 81,805 | 24.563% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 142,376 | 42.751% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 88,820 | 26.670% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 7,928 | 2.381% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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 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.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` | 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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `DE_DE` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `de-de-default`, loaded from classpath resource `org/egothor/stemmer/models/de-de-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.910445** among 8 deterministic stemmers. The runner-up is `GERMAN CISTEM` at 0.878527, a difference of 0.031918. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.966959** among 8 deterministic stemmers. The runner-up is `GERMAN CISTEM` at 0.914727, a difference of 0.052232. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
### `ALL_WORDS`
|
||||||
|
|
||||||
|
This mode contains **12 result rows**, **8 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.910445|0.000002%|17.910967%|
|
||||||
|
|2|GERMAN CISTEM|0.878527|0.000674%|24.293900%|
|
||||||
|
|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%|
|
||||||
|
|7|HUNSPELL GERMAN LUCENE FILTER|0.640308|0.000290%|71.938102%|
|
||||||
|
|8|GERMAN LUCENE GERMAN MINIMAL STEM FILTER|0.595748|0.000088%|80.850384%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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.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|
|
||||||
|
|7|HUNSPELL GERMAN LUCENE FILTER|PRIMARY_OUTPUT|0.771720|0.280619|0.999997|0.640308|0.999972|0.000028|
|
||||||
|
|8|GERMAN LUCENE GERMAN MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.883845|0.191496|0.999999|0.595748|0.999971|0.000029|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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.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|
|
||||||
|
|7|HUNSPELL GERMAN LUCENE FILTER|PRIMARY_OUTPUT|0.571639|0.411577|0.321543|0.259110|0.465359|0.465349|
|
||||||
|
|8|GERMAN LUCENE GERMAN MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.512941|0.314789|0.227071|0.186795|0.411404|0.411396|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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|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|
|
||||||
|
|7|HUNSPELL GERMAN LUCENE FILTER|PRIMARY_OUTPUT|377391|111635|967461|38436622258|111635 / 38436733893|967461 / 1344852|
|
||||||
|
|8|GERMAN LUCENE GERMAN MINIMAL STEM FILTER|PRIMARY_OUTPUT|257534|33845|1087318|38436700048|33845 / 38436733893|1087318 / 1344852|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000001%|8.261653%|
|
||||||
|
|HUNSPELL GERMAN LUCENE FILTER|0.000216%|70.811435%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|502 / 38436733893|111107 / 1344852|
|
||||||
|
|HUNSPELL GERMAN LUCENE FILTER|83073 / 38436733893|952309 / 1344852|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.958692|0.000018%|8.261653%|
|
||||||
|
|2|HUNSPELL GERMAN LUCENE FILTER|0.645941|0.000354%|70.811435%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.994469|0.917383|1.000000|0.958692|0.999997|0.000003|
|
||||||
|
|2|HUNSPELL GERMAN LUCENE FILTER|ALL_CANDIDATES|0.742744|0.291886|0.999996|0.645941|0.999972|0.000028|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.978033|0.954372|0.931829|0.912726|0.955149|0.955147|
|
||||||
|
|2|HUNSPELL GERMAN LUCENE FILTER|ALL_CANDIDATES|0.567444|0.419080|0.332218|0.265086|0.465614|0.465603|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1233745|6862|111107|38436727031|6862 / 38436733893|111107 / 1344852|
|
||||||
|
|2|HUNSPELL GERMAN LUCENE FILTER|ALL_CANDIDATES|392543|135961|952309|38436597932|135961 / 38436733893|952309 / 1344852|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|129769|161|6199|29035|10.471893%|8|313927|
|
||||||
|
|HUNSPELL GERMAN LUCENE FILTER|15152|28562|24326|6482|2.337827%|3|283881|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
This mode contains **12 result rows**, **8 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.966959|0.000001%|6.608210%|
|
||||||
|
|2|GERMAN CISTEM|0.914727|0.000812%|17.053716%|
|
||||||
|
|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%|
|
||||||
|
|7|HUNSPELL GERMAN LUCENE FILTER|0.659574|0.000556%|68.084626%|
|
||||||
|
|8|GERMAN LUCENE GERMAN MINIMAL STEM FILTER|0.574999|0.000045%|85.000064%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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.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|
|
||||||
|
|7|HUNSPELL GERMAN LUCENE FILTER|PRIMARY_OUTPUT|0.823043|0.319154|0.999994|0.659574|0.999939|0.000061|
|
||||||
|
|8|GERMAN LUCENE GERMAN MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.964480|0.149999|1.000000|0.574999|0.999931|0.000069|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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.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|
|
||||||
|
|7|HUNSPELL GERMAN LUCENE FILTER|PRIMARY_OUTPUT|0.625524|0.459951|0.363685|0.298660|0.512520|0.512499|
|
||||||
|
|8|GERMAN LUCENE GERMAN MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.462363|0.259621|0.180482|0.149175|0.380357|0.380343|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|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|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|
|
||||||
|
|7|HUNSPELL GERMAN LUCENE FILTER|PRIMARY_OUTPUT|273967|58904|584450|10594904630|58904 / 10594963534|584450 / 858417|
|
||||||
|
|8|GERMAN LUCENE GERMAN MINIMAL STEM FILTER|PRIMARY_OUTPUT|128762|4742|729655|10594958792|4742 / 10594963534|729655 / 858417|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|HUNSPELL GERMAN LUCENE FILTER|0.000383%|66.866802%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 10594963534|0 / 858417|
|
||||||
|
|HUNSPELL GERMAN LUCENE FILTER|40608 / 10594963534|573996 / 858417|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000014%|0.000000%|
|
||||||
|
|2|HUNSPELL GERMAN LUCENE FILTER|0.665663|0.000629%|66.866802%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.998267|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|2|HUNSPELL GERMAN LUCENE FILTER|ALL_CANDIDATES|0.810178|0.331332|0.999994|0.665663|0.999940|0.000060|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.998613|0.999133|0.999653|0.998267|0.999133|0.999133|
|
||||||
|
|2|HUNSPELL GERMAN LUCENE FILTER|ALL_CANDIDATES|0.628511|0.470321|0.375748|0.307464|0.518110|0.518088|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|858417|1490|0|10594962044|1490 / 10594963534|0 / 858417|
|
||||||
|
|2|HUNSPELL GERMAN LUCENE FILTER|ALL_CANDIDATES|284421|66639|573996|10594896895|66639 / 10594963534|573996 / 858417|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|56726|80|1410|10454|7.181227%|8|157137|
|
||||||
|
|HUNSPELL GERMAN LUCENE FILTER|10454|18296|7735|4538|3.117315%|3|150205|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `DE_DE`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
295
docs/benchmarks/languages/hebrew.md
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
# Hebrew Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Hebrew. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
The default Hebrew model currently has no same-language third-party adapter in the benchmark matrix. Its Radixor measurements are still published so the complete default-model language universe has identical corpus, command-distribution, exact-root, runtime, and pairwise-quality coverage.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **61,071**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `DeletePrefixCommand` | Deletes one or more leading characters from the word form in forward traversal. | 2,188 | 3.583% |
|
||||||
|
| `ForwardCompoundCommand` | Applies a multi-step forward patch made from skip, delete, insert, and replace operations. | 51,692 | 84.642% |
|
||||||
|
| `PrependCharacterCommand` | Prepends one character to the beginning of the word form. | 11 | 0.018% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 4,828 | 7.906% |
|
||||||
|
| `ReplaceFirstCharacterCommand` | Replaces the first character of the word form in forward traversal. | 2,352 | 3.851% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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.570 | 0.074 | 63.3 | 1.000 | Full default-model Radixor dictionary patch-command stemmer. |
|
||||||
|
|
||||||
|
## Interpretation Notes
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `HE_IL` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `he-il-default`, loaded from classpath resource `org/egothor/stemmer/models/he-il-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.986075** among 1 deterministic stemmers; no same-language competitor was available. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.986075** among 1 deterministic stemmers; no same-language competitor was available. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
### `ALL_WORDS`
|
||||||
|
|
||||||
|
This mode contains **3 result rows**, **1 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.986075|0.000000%|2.784905%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.972151|1.000000|0.986075|0.999988|0.000012|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.994303|0.985879|0.977596|0.972151|0.985977|0.985971|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|685765|0|19645|1661488243|0 / 1661488243|19645 / 705410|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 1661488243|0 / 705410|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|705410|0|0|1661488243|0 / 1661488243|0 / 705410|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|19645|0|0|984|1.706615%|40|58714|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
This mode contains **3 result rows**, **1 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.986075|0.000000%|2.784905%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.972151|1.000000|0.986075|0.999988|0.000012|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.994303|0.985879|0.977596|0.972151|0.985977|0.985971|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|685765|0|19645|1661488243|0 / 1661488243|19645 / 705410|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 1661488243|0 / 705410|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|705410|0|0|1661488243|0 / 1661488243|0 / 705410|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|19645|0|0|984|1.706615%|40|58714|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `HE_IL`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
327
docs/benchmarks/languages/hungarian.md
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
# Hungarian Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Hungarian. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **935,713**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 15 | 0.002% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 149,173 | 15.942% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 750,282 | 80.183% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 36,139 | 3.862% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 104 | 0.011% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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 Radixor's dictionary-trained transformation model. |
|
||||||
|
|
||||||
|
## 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 | `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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `HU_HU` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `hu-hu-default`, loaded from classpath resource `org/egothor/stemmer/models/hu-hu-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.995555** among 4 deterministic stemmers. The runner-up is `SNOWBALL HUNGARIAN LUCENE FILTER` at 0.822963, a difference of 0.172592. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.996227** among 4 deterministic stemmers. The runner-up is `SNOWBALL HUNGARIAN DIRECT` at 0.822077, a difference of 0.174151. 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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.995555|<0.000001%|0.889037%|
|
||||||
|
|2|SNOWBALL HUNGARIAN LUCENE FILTER|0.822963|0.000378%|35.407050%|
|
||||||
|
|3|SNOWBALL HUNGARIAN DIRECT|0.822704|0.000309%|35.458800%|
|
||||||
|
|4|HUNGARIAN LUCENE HUNGARIAN LIGHT STEM FILTER|0.816967|0.000915%|36.605593%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.999998|0.991110|1.000000|0.995555|1.000000|0.000000|
|
||||||
|
|2|SNOWBALL HUNGARIAN LUCENE FILTER|PRIMARY_OUTPUT|0.901236|0.645929|0.999996|0.822963|0.999977|0.000023|
|
||||||
|
|3|SNOWBALL HUNGARIAN DIRECT|PRIMARY_OUTPUT|0.917622|0.645412|0.999997|0.822704|0.999978|0.000022|
|
||||||
|
|4|HUNGARIAN LUCENE HUNGARIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.786953|0.633944|0.999991|0.816967|0.999971|0.000029|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.998208|0.995534|0.992875|0.991108|0.995544|0.995544|
|
||||||
|
|2|SNOWBALL HUNGARIAN LUCENE FILTER|PRIMARY_OUTPUT|0.835212|0.752518|0.684724|0.603229|0.762978|0.762967|
|
||||||
|
|3|SNOWBALL HUNGARIAN DIRECT|PRIMARY_OUTPUT|0.846240|0.757814|0.686119|0.610064|0.769574|0.769564|
|
||||||
|
|4|HUNGARIAN LUCENE HUNGARIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.750715|0.702210|0.659593|0.541082|0.706318|0.706304|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|21921219|39|196636|414653743434|39 / 414653743473|196636 / 22117855|
|
||||||
|
|2|SNOWBALL HUNGARIAN LUCENE FILTER|PRIMARY_OUTPUT|14286575|1565633|7831280|414652177840|1565633 / 414653743473|7831280 / 22117855|
|
||||||
|
|3|SNOWBALL HUNGARIAN DIRECT|PRIMARY_OUTPUT|14275129|1281527|7842726|414652461946|1281527 / 414653743473|7842726 / 22117855|
|
||||||
|
|4|HUNGARIAN LUCENE HUNGARIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|14021483|3795942|8096372|414649947531|3795942 / 414653743473|8096372 / 22117855|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 414653743473|0 / 22117855|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|<0.000001%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999991|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999993|0.999996|0.999998|0.999991|0.999996|0.999996|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|22117855|192|0|414653743281|192 / 414653743473|0 / 22117855|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|196636|39|153|6664|0.731754%|5|917595|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.996227|<0.000001%|0.754564%|
|
||||||
|
|2|SNOWBALL HUNGARIAN DIRECT|0.822077|0.000334%|35.584346%|
|
||||||
|
|3|SNOWBALL HUNGARIAN LUCENE FILTER|0.822077|0.000334%|35.584346%|
|
||||||
|
|4|HUNGARIAN LUCENE HUNGARIAN LIGHT STEM FILTER|0.815385|0.000869%|36.922109%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.999998|0.992454|1.000000|0.996227|1.000000|0.000000|
|
||||||
|
|2|SNOWBALL HUNGARIAN DIRECT|PRIMARY_OUTPUT|0.915319|0.644157|0.999997|0.822077|0.999977|0.000023|
|
||||||
|
|3|SNOWBALL HUNGARIAN LUCENE FILTER|PRIMARY_OUTPUT|0.915319|0.644157|0.999997|0.822077|0.999977|0.000023|
|
||||||
|
|4|HUNGARIAN LUCENE HUNGARIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.802756|0.630779|0.999991|0.815385|0.999971|0.000029|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.998480|0.996212|0.993954|0.992453|0.996219|0.996219|
|
||||||
|
|2|SNOWBALL HUNGARIAN DIRECT|PRIMARY_OUTPUT|0.844241|0.756163|0.684726|0.607928|0.767860|0.767849|
|
||||||
|
|3|SNOWBALL HUNGARIAN LUCENE FILTER|PRIMARY_OUTPUT|0.844241|0.756163|0.684726|0.607928|0.767860|0.767849|
|
||||||
|
|4|HUNGARIAN LUCENE HUNGARIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.761246|0.706452|0.659016|0.546135|0.711591|0.711577|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|21206087|39|161230|380936197647|39 / 380936197686|161230 / 21367317|
|
||||||
|
|2|SNOWBALL HUNGARIAN DIRECT|PRIMARY_OUTPUT|13763897|1273370|7603420|380934924316|1273370 / 380936197686|7603420 / 21367317|
|
||||||
|
|3|SNOWBALL HUNGARIAN LUCENE FILTER|PRIMARY_OUTPUT|13763897|1273370|7603420|380934924316|1273370 / 380936197686|7603420 / 21367317|
|
||||||
|
|4|HUNGARIAN LUCENE HUNGARIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|13478053|3311675|7889264|380932886011|3311675 / 380936197686|7889264 / 21367317|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 380936197686|0 / 21367317|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|<0.000001%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999991|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999993|0.999996|0.999998|0.999991|0.999996|0.999996|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|21367317|192|0|380936197494|192 / 380936197686|0 / 21367317|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|161230|39|153|5518|0.632162%|5|878574|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `HU_HU`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
46
docs/benchmarks/languages/index.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Language Benchmark Pages
|
||||||
|
|
||||||
|
This section splits Radixor stemmer benchmark results by language. Each of the 20 registered default models has one language page containing the refreshed corpus, patch-command distribution, exact-root accuracy, runtime performance, and pairwise stemming-quality tables for both dictionary-processing modes.
|
||||||
|
|
||||||
|
## Reference Pages
|
||||||
|
|
||||||
|
| Page | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [Methodology](../reference/methodology.md) | Workload design, normalization, speed metrics, and exact-root quality metrics. Pairwise quality definitions are also reproduced on every language page. |
|
||||||
|
| [Corpora](../reference/corpora.md) | Dictionary sizes and changed-token timing workloads. |
|
||||||
|
| [Environment and reports](../reference/environment.md) | Hardware, JVM, JMH settings, report files, and badge policy. |
|
||||||
|
| [English dictionary coverage](../reference/english-coverage.md) | Quality/speed operating curve for contracted Radixor tries built from 100% down to 10% of English dictionary rows. |
|
||||||
|
| [Candidate evaluation](../reference/candidates.md) | Included and skipped stemmer candidates. |
|
||||||
|
|
||||||
|
## Languages
|
||||||
|
|
||||||
|
| Language | Resource | Benchmark page |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Czech | `CS_CZ` | [Czech](czech.md) |
|
||||||
|
| Danish | `DA_DK` | [Danish](danish.md) |
|
||||||
|
| Dutch | `NL_NL` | [Dutch](dutch.md) |
|
||||||
|
| English | `US_UK` | [English](english.md) |
|
||||||
|
| Finnish | `FI_FI` | [Finnish](finnish.md) |
|
||||||
|
| French | `FR_FR` | [French](french.md) |
|
||||||
|
| German | `DE_DE` | [German](german.md) |
|
||||||
|
| Hebrew | `HE_IL` | [Hebrew](hebrew.md) |
|
||||||
|
| Hungarian | `HU_HU` | [Hungarian](hungarian.md) |
|
||||||
|
| Italian | `IT_IT` | [Italian](italian.md) |
|
||||||
|
| Norwegian Bokmal | `NB_NO` | [Norwegian Bokmal](norwegian-bokmal.md) |
|
||||||
|
| Norwegian Nynorsk | `NN_NO` | [Norwegian Nynorsk](norwegian-nynorsk.md) |
|
||||||
|
| Persian | `FA_IR` | [Persian](persian.md) |
|
||||||
|
| Polish | `PL_PL` | [Polish](polish.md) |
|
||||||
|
| Portuguese | `PT_PT` | [Portuguese](portuguese.md) |
|
||||||
|
| Russian | `RU_RU` | [Russian](russian.md) |
|
||||||
|
| Spanish | `ES_ES` | [Spanish](spanish.md) |
|
||||||
|
| Swedish | `SV_SE` | [Swedish](swedish.md) |
|
||||||
|
| Ukrainian | `UK_UA` | [Ukrainian](ukrainian.md) |
|
||||||
|
| Yiddish | `YI` | [Yiddish](yiddish.md) |
|
||||||
|
|
||||||
|
## Methodology Notes
|
||||||
|
|
||||||
|
- Speed benchmarks process only changed dictionary tokens where the surface form differs from the expected root.
|
||||||
|
- Accuracy benchmarks process the complete dictionary and report `All exact`, `Changed exact`, and `Root preserved`.
|
||||||
|
- Radixor speed must be interpreted together with exact-root quality. A slower Radixor row must not be read as a simple performance weakness when Radixor is also the row with accuracy close to 100% and competing stemmers are much lower. Many fast light, minimal, possessive, or aggressive rule-based stemmers are fast because they do much less linguistic work. The measured Radixor cost buys dictionary-trained precision, and that precision is what improves search quality when queries and indexed text are reduced to the same intended roots. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows this contracted-trie operating curve explicitly.
|
||||||
|
- Results are comparable only within the same language and benchmark family.
|
||||||
|
- The historical Porter badge is retired; no JMH badge JSON is generated.
|
||||||
326
docs/benchmarks/languages/italian.md
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
# Italian Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Italian. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **337,546**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 302,089 | 89.496% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 12,348 | 3.658% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 20,013 | 5.929% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 3,096 | 0.917% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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` | 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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `IT_IT` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `it-it-default`, loaded from classpath resource `org/egothor/stemmer/models/it-it-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.996651** among 4 deterministic stemmers. The runner-up is `SNOWBALL ITALIAN DIRECT` at 0.866290, a difference of 0.130361. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.996656** among 4 deterministic stemmers. The runner-up is `SNOWBALL ITALIAN DIRECT` at 0.866307, a difference of 0.130350. 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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.996651|0.000000%|0.669827%|
|
||||||
|
|2|SNOWBALL ITALIAN DIRECT|0.866290|0.000738%|26.741219%|
|
||||||
|
|3|SNOWBALL ITALIAN LUCENE FILTER|0.866290|0.000738%|26.741219%|
|
||||||
|
|4|ITALIAN LUCENE ITALIAN LIGHT STEM FILTER|0.508920|0.000005%|98.216094%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.993302|1.000000|0.996651|0.999999|0.000001|
|
||||||
|
|2|SNOWBALL ITALIAN DIRECT|PRIMARY_OUTPUT|0.920474|0.732588|0.999993|0.866290|0.999961|0.000039|
|
||||||
|
|3|SNOWBALL ITALIAN LUCENE FILTER|PRIMARY_OUTPUT|0.920474|0.732588|0.999993|0.866290|0.999961|0.000039|
|
||||||
|
|4|ITALIAN LUCENE ITALIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.975468|0.017839|1.000000|0.508920|0.999885|0.000115|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.998653|0.996640|0.994634|0.993302|0.996645|0.996645|
|
||||||
|
|2|SNOWBALL ITALIAN DIRECT|PRIMARY_OUTPUT|0.875563|0.815854|0.763768|0.688980|0.821175|0.821157|
|
||||||
|
|3|SNOWBALL ITALIAN LUCENE FILTER|PRIMARY_OUTPUT|0.875563|0.815854|0.763768|0.688980|0.821175|0.821157|
|
||||||
|
|4|ITALIAN LUCENE ITALIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.083115|0.035037|0.022197|0.017831|0.131914|0.131907|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|6093034|0|41088|52600354673|0 / 52600354673|41088 / 6134122|
|
||||||
|
|2|SNOWBALL ITALIAN DIRECT|PRIMARY_OUTPUT|4493783|388246|1640339|52599966427|388246 / 52600354673|1640339 / 6134122|
|
||||||
|
|3|SNOWBALL ITALIAN LUCENE FILTER|PRIMARY_OUTPUT|4493783|388246|1640339|52599966427|388246 / 52600354673|1640339 / 6134122|
|
||||||
|
|4|ITALIAN LUCENE ITALIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|109427|2752|6024695|52600351921|2752 / 52600354673|6024695 / 6134122|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.001304%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 52600354673|80 / 6134122|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.999993|0.000000%|0.001304%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|0.999987|1.000000|0.999993|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999997|0.999993|0.999990|0.999987|0.999993|0.999993|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|6134042|0|80|52600354673|0 / 52600354673|80 / 6134122|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|41008|0|0|3069|0.946153%|4|327552|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.996656|0.000000%|0.668702%|
|
||||||
|
|2|SNOWBALL ITALIAN DIRECT|0.866307|0.000738%|26.737902%|
|
||||||
|
|3|SNOWBALL ITALIAN LUCENE FILTER|0.866307|0.000738%|26.737902%|
|
||||||
|
|4|ITALIAN LUCENE ITALIAN LIGHT STEM FILTER|0.508920|0.000005%|98.216040%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.993313|1.000000|0.996656|0.999999|0.000001|
|
||||||
|
|2|SNOWBALL ITALIAN DIRECT|PRIMARY_OUTPUT|0.920458|0.732621|0.999993|0.866307|0.999961|0.000039|
|
||||||
|
|3|SNOWBALL ITALIAN LUCENE FILTER|PRIMARY_OUTPUT|0.920458|0.732621|0.999993|0.866307|0.999961|0.000039|
|
||||||
|
|4|ITALIAN LUCENE ITALIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.975462|0.017840|1.000000|0.508920|0.999885|0.000115|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.998655|0.996645|0.994643|0.993313|0.996651|0.996650|
|
||||||
|
|2|SNOWBALL ITALIAN DIRECT|PRIMARY_OUTPUT|0.875561|0.815868|0.763794|0.689001|0.821186|0.821168|
|
||||||
|
|3|SNOWBALL ITALIAN LUCENE FILTER|PRIMARY_OUTPUT|0.875561|0.815868|0.763794|0.689001|0.821186|0.821168|
|
||||||
|
|4|ITALIAN LUCENE ITALIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.083118|0.035038|0.022198|0.017832|0.131916|0.131908|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|6091474|0|41008|52574085988|0 / 52574085988|41008 / 6132482|
|
||||||
|
|2|SNOWBALL ITALIAN DIRECT|PRIMARY_OUTPUT|4492785|388246|1639697|52573697742|388246 / 52574085988|1639697 / 6132482|
|
||||||
|
|3|SNOWBALL ITALIAN LUCENE FILTER|PRIMARY_OUTPUT|4492785|388246|1639697|52573697742|388246 / 52574085988|1639697 / 6132482|
|
||||||
|
|4|ITALIAN LUCENE ITALIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|109401|2752|6023081|52574083236|2752 / 52574085988|6023081 / 6132482|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 52574085988|0 / 6132482|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|6132482|0|0|52574085988|0 / 52574085988|0 / 6132482|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|41008|0|0|3068|0.946081%|4|327469|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `IT_IT`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
337
docs/benchmarks/languages/norwegian-bokmal.md
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
# Norwegian Bokmal Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Norwegian Bokmal. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **90,757**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 2,528 | 2.785% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 4,258 | 4.692% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 48,925 | 53.908% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 32,086 | 35.354% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 2,960 | 3.261% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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 Radixor's dictionary-trained transformation model. |
|
||||||
|
|
||||||
|
## 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 | `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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `NB_NO` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `nb-no-default`, loaded from classpath resource `org/egothor/stemmer/models/nb-no-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.976021** among 5 deterministic stemmers. The runner-up is `SNOWBALL NORWEGIAN BOKMAL DIRECT` at 0.874259, a difference of 0.101762. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.976240** among 5 deterministic stemmers. The runner-up is `SNOWBALL NORWEGIAN BOKMAL DIRECT` at 0.874286, a difference of 0.101954. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
### `ALL_WORDS`
|
||||||
|
|
||||||
|
This mode contains **7 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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.976021|0.000000%|4.795770%|
|
||||||
|
|2|SNOWBALL NORWEGIAN BOKMAL DIRECT|0.874259|0.000386%|25.147805%|
|
||||||
|
|3|SNOWBALL NORWEGIAN BOKMAL LUCENE FILTER|0.874138|0.000389%|25.171937%|
|
||||||
|
|4|NORWEGIAN BOKMAL LUCENE NORWEGIAN LIGHT STEM FILTER|0.849389|0.000416%|30.121722%|
|
||||||
|
|5|NORWEGIAN BOKMAL LUCENE NORWEGIAN MINIMAL STEM FILTER|0.831282|0.000110%|33.743568%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.952042|1.000000|0.976021|0.999997|0.000003|
|
||||||
|
|2|SNOWBALL NORWEGIAN BOKMAL DIRECT|PRIMARY_OUTPUT|0.910734|0.748522|0.999996|0.874259|0.999983|0.000017|
|
||||||
|
|3|SNOWBALL NORWEGIAN BOKMAL LUCENE FILTER|PRIMARY_OUTPUT|0.910189|0.748281|0.999996|0.874138|0.999983|0.000017|
|
||||||
|
|4|NORWEGIAN BOKMAL LUCENE NORWEGIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.898501|0.698783|0.999996|0.849389|0.999980|0.000020|
|
||||||
|
|5|NORWEGIAN BOKMAL LUCENE NORWEGIAN MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.969387|0.662564|0.999999|0.831282|0.999981|0.000019|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.990026|0.975432|0.961262|0.952042|0.975727|0.975725|
|
||||||
|
|2|SNOWBALL NORWEGIAN BOKMAL DIRECT|PRIMARY_OUTPUT|0.872901|0.821699|0.776171|0.697359|0.825654|0.825646|
|
||||||
|
|3|SNOWBALL NORWEGIAN BOKMAL LUCENE FILTER|PRIMARY_OUTPUT|0.872435|0.821332|0.775884|0.696830|0.825274|0.825266|
|
||||||
|
|4|NORWEGIAN BOKMAL LUCENE NORWEGIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.849918|0.786156|0.731293|0.647658|0.792374|0.792365|
|
||||||
|
|5|NORWEGIAN BOKMAL LUCENE NORWEGIAN MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.887216|0.787133|0.707341|0.648985|0.801425|0.801417|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|134138|0|6757|2676746970|0 / 2676746970|6757 / 140895|
|
||||||
|
|2|SNOWBALL NORWEGIAN BOKMAL DIRECT|PRIMARY_OUTPUT|105463|10337|35432|2676736633|10337 / 2676746970|35432 / 140895|
|
||||||
|
|3|SNOWBALL NORWEGIAN BOKMAL LUCENE FILTER|PRIMARY_OUTPUT|105429|10403|35466|2676736567|10403 / 2676746970|35466 / 140895|
|
||||||
|
|4|NORWEGIAN BOKMAL LUCENE NORWEGIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|98455|11122|42440|2676735848|11122 / 2676746970|42440 / 140895|
|
||||||
|
|5|NORWEGIAN BOKMAL LUCENE NORWEGIAN MINIMAL STEM FILTER|PRIMARY_OUTPUT|93352|2948|47543|2676744022|2948 / 2676746970|47543 / 140895|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 2676746970|0 / 140895|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|140895|0|0|2676746970|0 / 2676746970|0 / 140895|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|6757|0|0|2097|2.865929%|9|75343|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
This mode contains **7 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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.976240|0.000000%|4.751928%|
|
||||||
|
|2|SNOWBALL NORWEGIAN BOKMAL DIRECT|0.874286|0.000387%|25.142395%|
|
||||||
|
|3|SNOWBALL NORWEGIAN BOKMAL LUCENE FILTER|0.874101|0.000387%|25.179325%|
|
||||||
|
|4|NORWEGIAN BOKMAL LUCENE NORWEGIAN LIGHT STEM FILTER|0.849330|0.000414%|30.133659%|
|
||||||
|
|5|NORWEGIAN BOKMAL LUCENE NORWEGIAN MINIMAL STEM FILTER|0.831210|0.000108%|33.757794%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.952481|1.000000|0.976240|0.999997|0.000003|
|
||||||
|
|2|SNOWBALL NORWEGIAN BOKMAL DIRECT|PRIMARY_OUTPUT|0.910689|0.748576|0.999996|0.874286|0.999983|0.000017|
|
||||||
|
|3|SNOWBALL NORWEGIAN BOKMAL LUCENE FILTER|PRIMARY_OUTPUT|0.910546|0.748207|0.999996|0.874101|0.999983|0.000017|
|
||||||
|
|4|NORWEGIAN BOKMAL LUCENE NORWEGIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.898862|0.698663|0.999996|0.849330|0.999980|0.000020|
|
||||||
|
|5|NORWEGIAN BOKMAL LUCENE NORWEGIAN MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.969896|0.662422|0.999999|0.831210|0.999981|0.000019|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.990121|0.975662|0.961620|0.952481|0.975951|0.975950|
|
||||||
|
|2|SNOWBALL NORWEGIAN BOKMAL DIRECT|PRIMARY_OUTPUT|0.872882|0.821713|0.776211|0.697379|0.825663|0.825655|
|
||||||
|
|3|SNOWBALL NORWEGIAN BOKMAL LUCENE FILTER|PRIMARY_OUTPUT|0.872677|0.821432|0.775872|0.696975|0.825395|0.825387|
|
||||||
|
|4|NORWEGIAN BOKMAL LUCENE NORWEGIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.850142|0.786219|0.731236|0.647743|0.792466|0.792457|
|
||||||
|
|5|NORWEGIAN BOKMAL LUCENE NORWEGIAN MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.887506|0.787200|0.707265|0.649077|0.801549|0.801541|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|134115|0|6691|2672431799|0 / 2672431799|6691 / 140806|
|
||||||
|
|2|SNOWBALL NORWEGIAN BOKMAL DIRECT|PRIMARY_OUTPUT|105404|10337|35402|2672421462|10337 / 2672431799|35402 / 140806|
|
||||||
|
|3|SNOWBALL NORWEGIAN BOKMAL LUCENE FILTER|PRIMARY_OUTPUT|105352|10350|35454|2672421449|10350 / 2672431799|35454 / 140806|
|
||||||
|
|4|NORWEGIAN BOKMAL LUCENE NORWEGIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|98376|11069|42430|2672420730|11069 / 2672431799|42430 / 140806|
|
||||||
|
|5|NORWEGIAN BOKMAL LUCENE NORWEGIAN MINIMAL STEM FILTER|PRIMARY_OUTPUT|93273|2895|47533|2672428904|2895 / 2672431799|47533 / 140806|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 2672431799|0 / 140806|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|140806|0|0|2672431799|0 / 2672431799|0 / 140806|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|6691|0|0|2064|2.823105%|9|75251|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `NB_NO`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
317
docs/benchmarks/languages/norwegian-nynorsk.md
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
# Norwegian Nynorsk Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Norwegian Nynorsk. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **19,651**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 312 | 1.588% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 1,456 | 7.409% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 11,325 | 57.631% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 6,031 | 30.691% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 527 | 2.682% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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.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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `NN_NO` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `nn-no-default`, loaded from classpath resource `org/egothor/stemmer/models/nn-no-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.950991** among 3 deterministic stemmers. The runner-up is `SNOWBALL NORWEGIAN NYNORSK DIRECT` at 0.868094, a difference of 0.082897. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.951104** among 3 deterministic stemmers. The runner-up is `SNOWBALL NORWEGIAN NYNORSK DIRECT` at 0.868252, a difference of 0.082852. 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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.950991|0.000000%|9.801848%|
|
||||||
|
|2|SNOWBALL NORWEGIAN NYNORSK DIRECT|0.868094|0.000838%|26.380368%|
|
||||||
|
|3|SNOWBALL NORWEGIAN NYNORSK LUCENE FILTER|0.867636|0.000852%|26.472040%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.901982|1.000000|0.950991|0.999981|0.000019|
|
||||||
|
|2|SNOWBALL NORWEGIAN NYNORSK DIRECT|PRIMARY_OUTPUT|0.945609|0.736196|0.999992|0.868094|0.999939|0.000061|
|
||||||
|
|3|SNOWBALL NORWEGIAN NYNORSK LUCENE FILTER|PRIMARY_OUTPUT|0.944646|0.735280|0.999991|0.867636|0.999939|0.000061|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.978728|0.948465|0.920017|0.901982|0.949727|0.949718|
|
||||||
|
|2|SNOWBALL NORWEGIAN NYNORSK DIRECT|PRIMARY_OUTPUT|0.894709|0.827865|0.770315|0.706288|0.834359|0.834331|
|
||||||
|
|3|SNOWBALL NORWEGIAN NYNORSK LUCENE FILTER|PRIMARY_OUTPUT|0.893748|0.826916|0.769384|0.704908|0.833414|0.833386|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|25582|0|2780|143394154|0 / 143394154|2780 / 28362|
|
||||||
|
|2|SNOWBALL NORWEGIAN NYNORSK DIRECT|PRIMARY_OUTPUT|20880|1201|7482|143392953|1201 / 143394154|7482 / 28362|
|
||||||
|
|3|SNOWBALL NORWEGIAN NYNORSK LUCENE FILTER|PRIMARY_OUTPUT|20854|1222|7508|143392932|1222 / 143394154|7508 / 28362|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 143394154|0 / 28362|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|28362|0|0|143394154|0 / 143394154|0 / 28362|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|2780|0|0|1091|6.441519%|5|18255|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.951104|0.000000%|9.779191%|
|
||||||
|
|2|SNOWBALL NORWEGIAN NYNORSK DIRECT|0.868252|0.000841%|26.348702%|
|
||||||
|
|3|SNOWBALL NORWEGIAN NYNORSK LUCENE FILTER|0.867846|0.000841%|26.429959%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.902208|1.000000|0.951104|0.999981|0.000019|
|
||||||
|
|2|SNOWBALL NORWEGIAN NYNORSK DIRECT|PRIMARY_OUTPUT|0.945528|0.736513|0.999992|0.868252|0.999939|0.000061|
|
||||||
|
|3|SNOWBALL NORWEGIAN NYNORSK LUCENE FILTER|PRIMARY_OUTPUT|0.945471|0.735700|0.999992|0.867846|0.999939|0.000061|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.978782|0.948590|0.920206|0.902208|0.949846|0.949837|
|
||||||
|
|2|SNOWBALL NORWEGIAN NYNORSK DIRECT|PRIMARY_OUTPUT|0.894744|0.828034|0.770581|0.706534|0.834502|0.834474|
|
||||||
|
|3|SNOWBALL NORWEGIAN NYNORSK LUCENE FILTER|PRIMARY_OUTPUT|0.894463|0.827499|0.769862|0.705755|0.834016|0.833989|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|25537|0|2768|142869660|0 / 142869660|2768 / 28305|
|
||||||
|
|2|SNOWBALL NORWEGIAN NYNORSK DIRECT|PRIMARY_OUTPUT|20847|1201|7458|142868459|1201 / 142869660|7458 / 28305|
|
||||||
|
|3|SNOWBALL NORWEGIAN NYNORSK LUCENE FILTER|PRIMARY_OUTPUT|20824|1201|7481|142868459|1201 / 142869660|7481 / 28305|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 142869660|0 / 28305|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|28305|0|0|142869660|0 / 142869660|0 / 28305|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|2768|0|0|1086|6.423755%|5|18219|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `NN_NO`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
315
docs/benchmarks/languages/persian.md
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
# Persian Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Persian. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **3,770**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `DeletePrefixCommand` | Deletes one or more leading characters from the word form in forward traversal. | 65 | 1.724% |
|
||||||
|
| `ForwardCompoundCommand` | Applies a multi-step forward patch made from skip, delete, insert, and replace operations. | 3,567 | 94.615% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 138 | 3.660% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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
|
||||||
|
|
||||||
|
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 | `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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `FA_IR` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
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 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 **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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.976360|0.000000%|4.728041%|
|
||||||
|
|2|SNOWBALL PERSIAN DIRECT|0.535123|0.001278%|92.974054%|
|
||||||
|
|3|PERSIAN LUCENE PERSIAN STEM FILTER|0.502212|0.000049%|99.557494%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| 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|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| 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|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| 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|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 6182152|0 / 96044|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|96044|0|0|6182152|0 / 6182152|0 / 96044|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|4541|0|0|157|4.430023%|2|3701|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.976360|0.000000%|4.728041%|
|
||||||
|
|2|SNOWBALL PERSIAN DIRECT|0.535123|0.001278%|92.974054%|
|
||||||
|
|3|PERSIAN LUCENE PERSIAN STEM FILTER|0.502212|0.000049%|99.557494%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| 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|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| 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|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| 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|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 6182152|0 / 96044|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|96044|0|0|6182152|0 / 6182152|0 / 96044|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|4541|0|0|157|4.430023%|2|3701|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `FA_IR`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
375
docs/benchmarks/languages/polish.md
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
# Polish Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Polish. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **132,308**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 1,836 | 1.388% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 52,996 | 40.055% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 37,137 | 28.069% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 20,219 | 15.282% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 20,120 | 15.207% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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
|
||||||
|
|
||||||
|
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 | `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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `PL_PL` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
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 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 **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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.991105|0.000000%|1.779024%|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|0.948392|0.001042%|10.320543%|
|
||||||
|
|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%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.982210|1.000000|0.991105|0.999997|0.000003|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|PRIMARY_OUTPUT|0.929398|0.896795|0.999990|0.948392|0.999974|0.000026|
|
||||||
|
|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.996391|0.991025|0.985717|0.982210|0.991065|0.991064|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|PRIMARY_OUTPUT|0.922689|0.912805|0.903131|0.839597|0.912951|0.912938|
|
||||||
|
|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1097200|0|19873|7303238338|0 / 7303238338|19873 / 1117073|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|PRIMARY_OUTPUT|1001785|76101|115288|7303162237|76101 / 7303238338|115288 / 1117073|
|
||||||
|
|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL POLISH LUCENE FILTER|0.000356%|7.227639%|
|
||||||
|
|POLISH LUCENE MORFOLOGIK FILTER|0.001000%|2.493123%|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL POLISH LUCENE FILTER|25967 / 7303238338|80738 / 1117073|
|
||||||
|
|POLISH LUCENE MORFOLOGIK FILTER|73019 / 7303238338|27850 / 1117073|
|
||||||
|
|Radixor|0 / 7303238338|0 / 1117073|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|0.987528|0.001376%|2.493123%|
|
||||||
|
|3|HUNSPELL POLISH LUCENE FILTER|0.963859|0.000609%|7.227639%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|ALL_CANDIDATES|0.915516|0.975069|0.999986|0.987528|0.999982|0.000018|
|
||||||
|
|3|HUNSPELL POLISH LUCENE FILTER|ALL_CANDIDATES|0.958830|0.927724|0.999994|0.963859|0.999983|0.000017|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|ALL_CANDIDATES|0.926837|0.944354|0.962546|0.894575|0.944823|0.944815|
|
||||||
|
|3|HUNSPELL POLISH LUCENE FILTER|ALL_CANDIDATES|0.952443|0.943020|0.933782|0.892184|0.943149|0.943140|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1117073|0|0|7303238338|0 / 7303238338|0 / 1117073|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|ALL_CANDIDATES|1089223|100514|27850|7303137824|100514 / 7303238338|27850 / 1117073|
|
||||||
|
|3|HUNSPELL POLISH LUCENE FILTER|ALL_CANDIDATES|1036335|44498|80738|7303193840|44498 / 7303238338|80738 / 1117073|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|HUNSPELL POLISH LUCENE FILTER|67924|2000|16531|10485|8.674824%|6|132492|
|
||||||
|
|POLISH LUCENE MORFOLOGIK FILTER|87438|3082|24413|11776|9.742941%|5|133810|
|
||||||
|
|Radixor|19873|0|0|1392|1.151679%|4|122430|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.991301|0.000000%|1.739895%|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|0.948417|0.001067%|10.315578%|
|
||||||
|
|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%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.982601|1.000000|0.991301|0.999997|0.000003|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|PRIMARY_OUTPUT|0.929032|0.896844|0.999989|0.948417|0.999973|0.000027|
|
||||||
|
|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.996471|0.991224|0.986032|0.982601|0.991262|0.991261|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|PRIMARY_OUTPUT|0.922411|0.912654|0.903102|0.839342|0.912796|0.912783|
|
||||||
|
|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1091431|0|19326|7133100218|0 / 7133100218|19326 / 1110757|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|PRIMARY_OUTPUT|996176|76097|114581|7133024121|76097 / 7133100218|114581 / 1110757|
|
||||||
|
|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|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL POLISH LUCENE FILTER|0.000356%|7.234976%|
|
||||||
|
|POLISH LUCENE MORFOLOGIK FILTER|0.001024%|2.474799%|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL POLISH LUCENE FILTER|25425 / 7133100218|80363 / 1110757|
|
||||||
|
|POLISH LUCENE MORFOLOGIK FILTER|73019 / 7133100218|27489 / 1110757|
|
||||||
|
|Radixor|0 / 7133100218|0 / 1110757|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|0.987619|0.001409%|2.474799%|
|
||||||
|
|3|HUNSPELL POLISH LUCENE FILTER|0.963822|0.000612%|7.234976%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|ALL_CANDIDATES|0.915099|0.975252|0.999986|0.987619|0.999982|0.000018|
|
||||||
|
|3|HUNSPELL POLISH LUCENE FILTER|ALL_CANDIDATES|0.959377|0.927650|0.999994|0.963822|0.999983|0.000017|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|ALL_CANDIDATES|0.926529|0.944219|0.962597|0.894332|0.944697|0.944688|
|
||||||
|
|3|HUNSPELL POLISH LUCENE FILTER|ALL_CANDIDATES|0.952859|0.943247|0.933827|0.892590|0.943380|0.943372|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1110757|0|0|7133100218|0 / 7133100218|0 / 1110757|
|
||||||
|
|2|POLISH LUCENE MORFOLOGIK FILTER|ALL_CANDIDATES|1083268|100503|27489|7132999715|100503 / 7133100218|27489 / 1110757|
|
||||||
|
|3|HUNSPELL POLISH LUCENE FILTER|ALL_CANDIDATES|1030394|43630|80363|7133056588|43630 / 7133100218|80363 / 1110757|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|HUNSPELL POLISH LUCENE FILTER|67261|1842|16363|10303|8.625294%|6|130856|
|
||||||
|
|POLISH LUCENE MORFOLOGIK FILTER|87092|3078|24406|11666|9.766348%|5|132279|
|
||||||
|
|Radixor|19326|0|0|1306|1.093335%|4|120926|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `PL_PL`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
347
docs/benchmarks/languages/portuguese.md
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
# Portuguese Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Portuguese. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **215,490**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 3,806 | 1.766% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 120,535 | 55.935% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 71,284 | 33.080% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 8,003 | 3.714% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 11,862 | 5.505% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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` | 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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `PT_PT` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `pt-pt-default`, loaded from classpath resource `org/egothor/stemmer/models/pt-pt-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.998542** among 6 deterministic stemmers. The runner-up is `SNOWBALL PORTUGUESE DIRECT` at 0.938922, a difference of 0.059620. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.998542** among 6 deterministic stemmers. The runner-up is `SNOWBALL PORTUGUESE DIRECT` at 0.938922, a difference of 0.059620. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
### `ALL_WORDS`
|
||||||
|
|
||||||
|
This mode contains **8 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.998542|0.000000%|0.291615%|
|
||||||
|
|2|SNOWBALL PORTUGUESE DIRECT|0.938922|0.000656%|12.214929%|
|
||||||
|
|3|SNOWBALL PORTUGUESE LUCENE FILTER|0.938922|0.000656%|12.214929%|
|
||||||
|
|4|PORTUGUESE LUCENE PORTUGUESE STEM FILTER|0.846554|0.000364%|30.688771%|
|
||||||
|
|5|PORTUGUESE LUCENE PORTUGUESE LIGHT STEM FILTER|0.513632|0.000006%|97.273598%|
|
||||||
|
|6|PORTUGUESE LUCENE PORTUGUESE MINIMAL STEM FILTER|0.503949|<0.000001%|99.210240%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.997084|1.000000|0.998542|0.999999|0.000001|
|
||||||
|
|2|SNOWBALL PORTUGUESE DIRECT|PRIMARY_OUTPUT|0.970538|0.877851|0.999993|0.938922|0.999963|0.000037|
|
||||||
|
|3|SNOWBALL PORTUGUESE LUCENE FILTER|PRIMARY_OUTPUT|0.970538|0.877851|0.999993|0.938922|0.999963|0.000037|
|
||||||
|
|4|PORTUGUESE LUCENE PORTUGUESE STEM FILTER|PRIMARY_OUTPUT|0.979145|0.693112|0.999996|0.846554|0.999921|0.000079|
|
||||||
|
|5|PORTUGUESE LUCENE PORTUGUESE LIGHT STEM FILTER|PRIMARY_OUTPUT|0.991719|0.027264|1.000000|0.513632|0.999760|0.000240|
|
||||||
|
|6|PORTUGUESE LUCENE PORTUGUESE MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.999608|0.007898|1.000000|0.503949|0.999756|0.000244|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.999415|0.998540|0.997666|0.997084|0.998541|0.998541|
|
||||||
|
|2|SNOWBALL PORTUGUESE DIRECT|PRIMARY_OUTPUT|0.950467|0.921871|0.894944|0.855065|0.923032|0.923014|
|
||||||
|
|3|SNOWBALL PORTUGUESE LUCENE FILTER|PRIMARY_OUTPUT|0.950467|0.921871|0.894944|0.855065|0.923032|0.923014|
|
||||||
|
|4|PORTUGUESE LUCENE PORTUGUESE STEM FILTER|PRIMARY_OUTPUT|0.904492|0.811666|0.736120|0.683029|0.823807|0.823773|
|
||||||
|
|5|PORTUGUESE LUCENE PORTUGUESE LIGHT STEM FILTER|PRIMARY_OUTPUT|0.122815|0.053069|0.033847|0.027258|0.164433|0.164413|
|
||||||
|
|6|PORTUGUESE LUCENE PORTUGUESE MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.038278|0.015671|0.009853|0.007898|0.088851|0.088840|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|5470353|0|15999|22274113243|0 / 22274113243|15999 / 5486352|
|
||||||
|
|2|SNOWBALL PORTUGUESE DIRECT|PRIMARY_OUTPUT|4816198|146201|670154|22273967042|146201 / 22274113243|670154 / 5486352|
|
||||||
|
|3|SNOWBALL PORTUGUESE LUCENE FILTER|PRIMARY_OUTPUT|4816198|146201|670154|22273967042|146201 / 22274113243|670154 / 5486352|
|
||||||
|
|4|PORTUGUESE LUCENE PORTUGUESE STEM FILTER|PRIMARY_OUTPUT|3802658|80995|1683694|22274032248|80995 / 22274113243|1683694 / 5486352|
|
||||||
|
|5|PORTUGUESE LUCENE PORTUGUESE LIGHT STEM FILTER|PRIMARY_OUTPUT|149580|1249|5336772|22274111994|1249 / 22274113243|5336772 / 5486352|
|
||||||
|
|6|PORTUGUESE LUCENE PORTUGUESE MINIMAL STEM FILTER|PRIMARY_OUTPUT|43329|17|5443023|22274113226|17 / 22274113243|5443023 / 5486352|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 22274113243|0 / 5486352|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|5486352|0|0|22274113243|0 / 22274113243|0 / 5486352|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|15999|0|0|392|0.185702%|3|211489|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
This mode contains **8 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.998542|0.000000%|0.291615%|
|
||||||
|
|2|SNOWBALL PORTUGUESE DIRECT|0.938922|0.000656%|12.214929%|
|
||||||
|
|3|SNOWBALL PORTUGUESE LUCENE FILTER|0.938922|0.000656%|12.214929%|
|
||||||
|
|4|PORTUGUESE LUCENE PORTUGUESE STEM FILTER|0.846554|0.000364%|30.688771%|
|
||||||
|
|5|PORTUGUESE LUCENE PORTUGUESE LIGHT STEM FILTER|0.513632|0.000006%|97.273598%|
|
||||||
|
|6|PORTUGUESE LUCENE PORTUGUESE MINIMAL STEM FILTER|0.503949|<0.000001%|99.210240%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.997084|1.000000|0.998542|0.999999|0.000001|
|
||||||
|
|2|SNOWBALL PORTUGUESE DIRECT|PRIMARY_OUTPUT|0.970538|0.877851|0.999993|0.938922|0.999963|0.000037|
|
||||||
|
|3|SNOWBALL PORTUGUESE LUCENE FILTER|PRIMARY_OUTPUT|0.970538|0.877851|0.999993|0.938922|0.999963|0.000037|
|
||||||
|
|4|PORTUGUESE LUCENE PORTUGUESE STEM FILTER|PRIMARY_OUTPUT|0.979145|0.693112|0.999996|0.846554|0.999921|0.000079|
|
||||||
|
|5|PORTUGUESE LUCENE PORTUGUESE LIGHT STEM FILTER|PRIMARY_OUTPUT|0.991719|0.027264|1.000000|0.513632|0.999760|0.000240|
|
||||||
|
|6|PORTUGUESE LUCENE PORTUGUESE MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.999608|0.007898|1.000000|0.503949|0.999756|0.000244|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.999415|0.998540|0.997666|0.997084|0.998541|0.998541|
|
||||||
|
|2|SNOWBALL PORTUGUESE DIRECT|PRIMARY_OUTPUT|0.950467|0.921871|0.894944|0.855065|0.923032|0.923014|
|
||||||
|
|3|SNOWBALL PORTUGUESE LUCENE FILTER|PRIMARY_OUTPUT|0.950467|0.921871|0.894944|0.855065|0.923032|0.923014|
|
||||||
|
|4|PORTUGUESE LUCENE PORTUGUESE STEM FILTER|PRIMARY_OUTPUT|0.904492|0.811666|0.736120|0.683029|0.823807|0.823773|
|
||||||
|
|5|PORTUGUESE LUCENE PORTUGUESE LIGHT STEM FILTER|PRIMARY_OUTPUT|0.122815|0.053069|0.033847|0.027258|0.164433|0.164413|
|
||||||
|
|6|PORTUGUESE LUCENE PORTUGUESE MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.038278|0.015671|0.009853|0.007898|0.088851|0.088840|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|5470353|0|15999|22274113243|0 / 22274113243|15999 / 5486352|
|
||||||
|
|2|SNOWBALL PORTUGUESE DIRECT|PRIMARY_OUTPUT|4816198|146201|670154|22273967042|146201 / 22274113243|670154 / 5486352|
|
||||||
|
|3|SNOWBALL PORTUGUESE LUCENE FILTER|PRIMARY_OUTPUT|4816198|146201|670154|22273967042|146201 / 22274113243|670154 / 5486352|
|
||||||
|
|4|PORTUGUESE LUCENE PORTUGUESE STEM FILTER|PRIMARY_OUTPUT|3802658|80995|1683694|22274032248|80995 / 22274113243|1683694 / 5486352|
|
||||||
|
|5|PORTUGUESE LUCENE PORTUGUESE LIGHT STEM FILTER|PRIMARY_OUTPUT|149580|1249|5336772|22274111994|1249 / 22274113243|5336772 / 5486352|
|
||||||
|
|6|PORTUGUESE LUCENE PORTUGUESE MINIMAL STEM FILTER|PRIMARY_OUTPUT|43329|17|5443023|22274113226|17 / 22274113243|5443023 / 5486352|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 22274113243|0 / 5486352|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|5486352|0|0|22274113243|0 / 22274113243|0 / 5486352|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|15999|0|0|392|0.185702%|3|211489|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `PT_PT`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
327
docs/benchmarks/languages/russian.md
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
# Russian Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Russian. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **806,279**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 9,287 | 1.152% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 580,915 | 72.049% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 82,956 | 10.289% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 75,527 | 9.367% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 57,594 | 7.143% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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` | 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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `RU_RU` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `ru-ru-default`, loaded from classpath resource `org/egothor/stemmer/models/ru-ru-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.990188** among 4 deterministic stemmers. The runner-up is `SNOWBALL RUSSIAN LUCENE FILTER` at 0.834565, a difference of 0.155624. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.990213** among 4 deterministic stemmers. The runner-up is `SNOWBALL RUSSIAN DIRECT` at 0.834542, a difference of 0.155670. 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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.990188|0.000000%|1.962362%|
|
||||||
|
|2|SNOWBALL RUSSIAN LUCENE FILTER|0.834565|0.001215%|33.085867%|
|
||||||
|
|3|SNOWBALL RUSSIAN DIRECT|0.834556|0.001214%|33.087654%|
|
||||||
|
|4|RUSSIAN LUCENE RUSSIAN LIGHT STEM FILTER|0.616440|0.000059%|76.711890%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.980376|1.000000|0.990188|0.999999|0.000001|
|
||||||
|
|2|SNOWBALL RUSSIAN LUCENE FILTER|PRIMARY_OUTPUT|0.713518|0.669141|0.999988|0.834565|0.999973|0.000027|
|
||||||
|
|3|SNOWBALL RUSSIAN DIRECT|PRIMARY_OUTPUT|0.713680|0.669123|0.999988|0.834556|0.999973|0.000027|
|
||||||
|
|4|RUSSIAN LUCENE RUSSIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.946958|0.232881|0.999999|0.616440|0.999965|0.000035|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.996013|0.990091|0.984239|0.980376|0.990140|0.990139|
|
||||||
|
|2|SNOWBALL RUSSIAN LUCENE FILTER|PRIMARY_OUTPUT|0.704178|0.690618|0.677570|0.527438|0.690974|0.690960|
|
||||||
|
|3|SNOWBALL RUSSIAN DIRECT|PRIMARY_OUTPUT|0.704300|0.690684|0.677584|0.527515|0.691043|0.691029|
|
||||||
|
|4|RUSSIAN LUCENE RUSSIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.586986|0.373828|0.274241|0.229882|0.469605|0.469596|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|12781761|0|255845|288279885172|0 / 288279885172|255845 / 13037606|
|
||||||
|
|2|SNOWBALL RUSSIAN LUCENE FILTER|PRIMARY_OUTPUT|8724001|3502741|4313605|288276382431|3502741 / 288279885172|4313605 / 13037606|
|
||||||
|
|3|SNOWBALL RUSSIAN DIRECT|PRIMARY_OUTPUT|8723768|3499880|4313838|288276385292|3499880 / 288279885172|4313838 / 13037606|
|
||||||
|
|4|RUSSIAN LUCENE RUSSIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|3036212|170067|10001394|288279715105|170067 / 288279885172|10001394 / 13037606|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000100%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 288279885172|13 / 13037606|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000100%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|0.999999|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|0.999999|0.999999|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|13037593|0|13|288279885172|0 / 288279885172|13 / 13037606|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|255832|0|0|9613|1.265979%|4|769106|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.990213|0.000000%|1.957434%|
|
||||||
|
|2|SNOWBALL RUSSIAN DIRECT|0.834542|0.001216%|33.090302%|
|
||||||
|
|3|SNOWBALL RUSSIAN LUCENE FILTER|0.834542|0.001216%|33.090302%|
|
||||||
|
|4|RUSSIAN LUCENE RUSSIAN LIGHT STEM FILTER|0.616378|0.000058%|76.724356%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.980426|1.000000|0.990213|0.999999|0.000001|
|
||||||
|
|2|SNOWBALL RUSSIAN DIRECT|PRIMARY_OUTPUT|0.713634|0.669097|0.999988|0.834542|0.999973|0.000027|
|
||||||
|
|3|SNOWBALL RUSSIAN LUCENE FILTER|PRIMARY_OUTPUT|0.713634|0.669097|0.999988|0.834542|0.999973|0.000027|
|
||||||
|
|4|RUSSIAN LUCENE RUSSIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.947585|0.232756|0.999999|0.616378|0.999965|0.000035|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.996023|0.990116|0.984279|0.980426|0.990164|0.990164|
|
||||||
|
|2|SNOWBALL RUSSIAN DIRECT|PRIMARY_OUTPUT|0.704259|0.690648|0.677554|0.527474|0.691007|0.690993|
|
||||||
|
|3|SNOWBALL RUSSIAN LUCENE FILTER|PRIMARY_OUTPUT|0.704259|0.690648|0.677554|0.527474|0.691007|0.690993|
|
||||||
|
|4|RUSSIAN LUCENE RUSSIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|0.587020|0.373716|0.274113|0.229798|0.469634|0.469625|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|12780071|0|255156|287711428009|0 / 287711428009|255156 / 13035227|
|
||||||
|
|2|SNOWBALL RUSSIAN DIRECT|PRIMARY_OUTPUT|8721831|3499880|4313396|287707928129|3499880 / 287711428009|4313396 / 13035227|
|
||||||
|
|3|SNOWBALL RUSSIAN LUCENE FILTER|PRIMARY_OUTPUT|8721831|3499880|4313396|287707928129|3499880 / 287711428009|4313396 / 13035227|
|
||||||
|
|4|RUSSIAN LUCENE RUSSIAN LIGHT STEM FILTER|PRIMARY_OUTPUT|3034033|167825|10001194|287711260184|167825 / 287711428009|10001194 / 13035227|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 287711428009|0 / 13035227|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|13035227|0|0|287711428009|0 / 287711428009|0 / 13035227|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|255156|0|0|9442|1.244687%|4|768163|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `RU_RU`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
371
docs/benchmarks/languages/spanish.md
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
# Spanish Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Spanish. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **926,393**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 8,534 | 0.921% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 522,685 | 56.422% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 243,410 | 26.275% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 124,386 | 13.427% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 27,378 | 2.955% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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 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` | 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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `ES_ES` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `es-es-default`, loaded from classpath resource `org/egothor/stemmer/models/es-es-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.989448** among 7 deterministic stemmers. The runner-up is `SNOWBALL SPANISH LUCENE FILTER` at 0.652438, a difference of 0.337010. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.989580** among 7 deterministic stemmers. The runner-up is `SNOWBALL SPANISH DIRECT` at 0.652542, a difference of 0.337038. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
### `ALL_WORDS`
|
||||||
|
|
||||||
|
This mode contains **11 result rows**, **7 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.989448|0.000000%|2.110334%|
|
||||||
|
|2|SNOWBALL SPANISH LUCENE FILTER|0.652438|0.000414%|69.511918%|
|
||||||
|
|3|SNOWBALL SPANISH DIRECT|0.652438|0.000413%|69.511932%|
|
||||||
|
|4|HUNSPELL SPANISH LUCENE FILTER|0.615028|0.000068%|76.994273%|
|
||||||
|
|5|SPANISH LUCENE SPANISH LIGHT STEM FILTER|0.514565|0.000009%|97.087060%|
|
||||||
|
|6|SPANISH LUCENE SPANISH PLURAL STEM FILTER|0.503764|0.000002%|99.247265%|
|
||||||
|
|7|SPANISH LUCENE SPANISH MINIMAL STEM FILTER|0.501678|0.000001%|99.664470%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.978897|1.000000|0.989448|0.999998|0.000002|
|
||||||
|
|2|SNOWBALL SPANISH LUCENE FILTER|PRIMARY_OUTPUT|0.895438|0.304881|0.999996|0.652438|0.999915|0.000085|
|
||||||
|
|3|SNOWBALL SPANISH DIRECT|PRIMARY_OUTPUT|0.895510|0.304881|0.999996|0.652438|0.999915|0.000085|
|
||||||
|
|4|HUNSPELL SPANISH LUCENE FILTER|PRIMARY_OUTPUT|0.975281|0.230057|0.999999|0.615028|0.999910|0.000090|
|
||||||
|
|5|SPANISH LUCENE SPANISH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.974423|0.029129|1.000000|0.514565|0.999887|0.000113|
|
||||||
|
|6|SPANISH LUCENE SPANISH PLURAL STEM FILTER|PRIMARY_OUTPUT|0.979154|0.007527|1.000000|0.503764|0.999885|0.000115|
|
||||||
|
|7|SPANISH LUCENE SPANISH MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.970596|0.003355|1.000000|0.501678|0.999884|0.000116|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.995707|0.989336|0.983046|0.978897|0.989392|0.989391|
|
||||||
|
|2|SNOWBALL SPANISH LUCENE FILTER|PRIMARY_OUTPUT|0.645406|0.454882|0.351206|0.294400|0.522496|0.522469|
|
||||||
|
|3|SNOWBALL SPANISH DIRECT|PRIMARY_OUTPUT|0.645436|0.454891|0.351208|0.294407|0.522517|0.522490|
|
||||||
|
|4|HUNSPELL SPANISH LUCENE FILTER|PRIMARY_OUTPUT|0.591847|0.372295|0.271557|0.228724|0.473678|0.473655|
|
||||||
|
|5|SPANISH LUCENE SPANISH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.130091|0.056568|0.036142|0.029107|0.168477|0.168467|
|
||||||
|
|6|SPANISH LUCENE SPANISH PLURAL STEM FILTER|PRIMARY_OUTPUT|0.036514|0.014940|0.009391|0.007526|0.085851|0.085846|
|
||||||
|
|7|SPANISH LUCENE SPANISH MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.016548|0.006687|0.004191|0.003355|0.057067|0.057063|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|41053986|0|885054|360919543590|0 / 360919543590|885054 / 41939040|
|
||||||
|
|2|SNOWBALL SPANISH LUCENE FILTER|PRIMARY_OUTPUT|12786409|1493087|29152631|360918050503|1493087 / 360919543590|29152631 / 41939040|
|
||||||
|
|3|SNOWBALL SPANISH DIRECT|PRIMARY_OUTPUT|12786403|1491944|29152637|360918051646|1491944 / 360919543590|29152637 / 41939040|
|
||||||
|
|4|HUNSPELL SPANISH LUCENE FILTER|PRIMARY_OUTPUT|9648381|244539|32290659|360919299051|244539 / 360919543590|32290659 / 41939040|
|
||||||
|
|5|SPANISH LUCENE SPANISH LIGHT STEM FILTER|PRIMARY_OUTPUT|1221659|32066|40717381|360919511524|32066 / 360919543590|40717381 / 41939040|
|
||||||
|
|6|SPANISH LUCENE SPANISH PLURAL STEM FILTER|PRIMARY_OUTPUT|315690|6721|41623350|360919536869|6721 / 360919543590|41623350 / 41939040|
|
||||||
|
|7|SPANISH LUCENE SPANISH MINIMAL STEM FILTER|PRIMARY_OUTPUT|140718|4263|41798322|360919539327|4263 / 360919543590|41798322 / 41939040|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL SPANISH LUCENE FILTER|0.000062%|76.009935%|
|
||||||
|
|Radixor|0.000000%|0.001493%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL SPANISH LUCENE FILTER|223500 / 360919543590|31877837 / 41939040|
|
||||||
|
|Radixor|0 / 360919543590|626 / 41939040|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.999993|<0.000001%|0.001493%|
|
||||||
|
|2|HUNSPELL SPANISH LUCENE FILTER|0.619950|0.000073%|76.009935%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999959|0.999985|1.000000|0.999993|1.000000|0.000000|
|
||||||
|
|2|HUNSPELL SPANISH LUCENE FILTER|ALL_CANDIDATES|0.974467|0.239901|0.999999|0.619950|0.999911|0.000089|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999964|0.999972|0.999980|0.999944|0.999972|0.999972|
|
||||||
|
|2|HUNSPELL SPANISH LUCENE FILTER|ALL_CANDIDATES|0.604361|0.385016|0.282490|0.238402|0.483503|0.483480|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|41938414|1737|626|360919541853|1737 / 360919543590|626 / 41939040|
|
||||||
|
|2|HUNSPELL SPANISH LUCENE FILTER|ALL_CANDIDATES|10061203|263629|31877837|360919279961|263629 / 360919543590|31877837 / 41939040|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|HUNSPELL SPANISH LUCENE FILTER|412822|21039|19090|11309|1.331001%|5|861853|
|
||||||
|
|Radixor|884428|0|1737|20967|2.467690%|21|871404|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
This mode contains **11 result rows**, **7 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.989580|<0.000001%|2.084022%|
|
||||||
|
|2|SNOWBALL SPANISH DIRECT|0.652542|0.000410%|69.491126%|
|
||||||
|
|3|SNOWBALL SPANISH LUCENE FILTER|0.652542|0.000410%|69.491126%|
|
||||||
|
|4|HUNSPELL SPANISH LUCENE FILTER|0.614924|0.000068%|77.015229%|
|
||||||
|
|5|SPANISH LUCENE SPANISH LIGHT STEM FILTER|0.514575|0.000009%|97.085003%|
|
||||||
|
|6|SPANISH LUCENE SPANISH PLURAL STEM FILTER|0.503767|0.000002%|99.246590%|
|
||||||
|
|7|SPANISH LUCENE SPANISH MINIMAL STEM FILTER|0.501679|0.000001%|99.664108%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.999999|0.979160|1.000000|0.989580|0.999998|0.000002|
|
||||||
|
|2|SNOWBALL SPANISH DIRECT|PRIMARY_OUTPUT|0.896551|0.305089|0.999996|0.652542|0.999915|0.000085|
|
||||||
|
|3|SNOWBALL SPANISH LUCENE FILTER|PRIMARY_OUTPUT|0.896551|0.305089|0.999996|0.652542|0.999915|0.000085|
|
||||||
|
|4|HUNSPELL SPANISH LUCENE FILTER|PRIMARY_OUTPUT|0.975224|0.229848|0.999999|0.614924|0.999910|0.000090|
|
||||||
|
|5|SPANISH LUCENE SPANISH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.974539|0.029150|1.000000|0.514575|0.999887|0.000113|
|
||||||
|
|6|SPANISH LUCENE SPANISH PLURAL STEM FILTER|PRIMARY_OUTPUT|0.979521|0.007534|1.000000|0.503767|0.999884|0.000116|
|
||||||
|
|7|SPANISH LUCENE SPANISH MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.971230|0.003359|1.000000|0.501679|0.999884|0.000116|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.995761|0.989470|0.983258|0.979159|0.989525|0.989523|
|
||||||
|
|2|SNOWBALL SPANISH DIRECT|PRIMARY_OUTPUT|0.646055|0.455257|0.351461|0.294714|0.522999|0.522972|
|
||||||
|
|3|SNOWBALL SPANISH LUCENE FILTER|PRIMARY_OUTPUT|0.646055|0.455257|0.351461|0.294714|0.522999|0.522972|
|
||||||
|
|4|HUNSPELL SPANISH LUCENE FILTER|PRIMARY_OUTPUT|0.591553|0.372016|0.271323|0.228513|0.473448|0.473426|
|
||||||
|
|5|SPANISH LUCENE SPANISH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.130175|0.056607|0.036167|0.029128|0.168546|0.168536|
|
||||||
|
|6|SPANISH LUCENE SPANISH PLURAL STEM FILTER|PRIMARY_OUTPUT|0.036546|0.014953|0.009400|0.007533|0.085906|0.085901|
|
||||||
|
|7|SPANISH LUCENE SPANISH MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.016565|0.006695|0.004195|0.003359|0.057116|0.057113|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|40958710|34|871756|359407144881|34 / 359407144915|871756 / 41830466|
|
||||||
|
|2|SNOWBALL SPANISH DIRECT|PRIMARY_OUTPUT|12762004|1472547|29068462|359405672368|1472547 / 359407144915|29068462 / 41830466|
|
||||||
|
|3|SNOWBALL SPANISH LUCENE FILTER|PRIMARY_OUTPUT|12762004|1472547|29068462|359405672368|1472547 / 359407144915|29068462 / 41830466|
|
||||||
|
|4|HUNSPELL SPANISH LUCENE FILTER|PRIMARY_OUTPUT|9614637|244260|32215829|359406900655|244260 / 359407144915|32215829 / 41830466|
|
||||||
|
|5|SPANISH LUCENE SPANISH LIGHT STEM FILTER|PRIMARY_OUTPUT|1219357|31857|40611109|359407113058|31857 / 359407144915|40611109 / 41830466|
|
||||||
|
|6|SPANISH LUCENE SPANISH PLURAL STEM FILTER|PRIMARY_OUTPUT|315155|6589|41515311|359407138326|6589 / 359407144915|41515311 / 41830466|
|
||||||
|
|7|SPANISH LUCENE SPANISH MINIMAL STEM FILTER|PRIMARY_OUTPUT|140505|4162|41689961|359407140753|4162 / 359407144915|41689961 / 41830466|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL SPANISH LUCENE FILTER|0.000062%|76.037484%|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL SPANISH LUCENE FILTER|223274 / 359407144915|31806834 / 41830466|
|
||||||
|
|Radixor|0 / 359407144915|0 / 41830466|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|<0.000001%|0.000000%|
|
||||||
|
|2|HUNSPELL SPANISH LUCENE FILTER|0.619812|0.000073%|76.037484%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999987|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|2|HUNSPELL SPANISH LUCENE FILTER|ALL_CANDIDATES|0.974405|0.239625|0.999999|0.619812|0.999911|0.000089|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|0.999989|0.999993|0.999997|0.999987|0.999993|0.999993|
|
||||||
|
|2|HUNSPELL SPANISH LUCENE FILTER|ALL_CANDIDATES|0.603992|0.384656|0.282183|0.238126|0.483210|0.483187|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|41830466|560|0|359407144355|560 / 359407144915|0 / 41830466|
|
||||||
|
|2|HUNSPELL SPANISH LUCENE FILTER|ALL_CANDIDATES|10023632|263289|31806834|359406881626|263289 / 359407144915|31806834 / 41830466|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|HUNSPELL SPANISH LUCENE FILTER|408995|20986|19029|11287|1.331204%|5|860048|
|
||||||
|
|Radixor|871756|34|526|20911|2.466272%|21|869542|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `ES_ES`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
337
docs/benchmarks/languages/swedish.md
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
# Swedish Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Swedish. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **110,468**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 711 | 0.644% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 14,126 | 12.787% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 68,749 | 62.234% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 23,583 | 21.348% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 3,299 | 2.986% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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 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.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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `SV_SE` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `sv-se-default`, loaded from classpath resource `org/egothor/stemmer/models/sv-se-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.977619** among 5 deterministic stemmers. The runner-up is `SNOWBALL SWEDISH DIRECT` at 0.808543, a difference of 0.169076. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.977573** among 5 deterministic stemmers. The runner-up is `SNOWBALL SWEDISH DIRECT` at 0.808611, a difference of 0.168961. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
### `ALL_WORDS`
|
||||||
|
|
||||||
|
This mode contains **7 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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.977619|0.000000%|4.476263%|
|
||||||
|
|2|SNOWBALL SWEDISH DIRECT|0.808543|0.000821%|38.290570%|
|
||||||
|
|3|SNOWBALL SWEDISH LUCENE FILTER|0.800222|0.000775%|39.954747%|
|
||||||
|
|4|SWEDISH LUCENE SWEDISH MINIMAL STEM FILTER|0.797907|0.000439%|40.418073%|
|
||||||
|
|5|SWEDISH LUCENE SWEDISH LIGHT STEM FILTER|0.785227|0.000534%|42.954113%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.955237|1.000000|0.977619|0.999996|0.000004|
|
||||||
|
|2|SNOWBALL SWEDISH DIRECT|PRIMARY_OUTPUT|0.863080|0.617094|0.999992|0.808543|0.999960|0.000040|
|
||||||
|
|3|SNOWBALL SWEDISH LUCENE FILTER|PRIMARY_OUTPUT|0.866630|0.600453|0.999992|0.800222|0.999959|0.000041|
|
||||||
|
|4|SWEDISH LUCENE SWEDISH MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.919176|0.595819|0.999996|0.797907|0.999962|0.000038|
|
||||||
|
|5|SWEDISH LUCENE SWEDISH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.899588|0.570459|0.999995|0.785227|0.999959|0.000041|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.990715|0.977106|0.963866|0.955237|0.977362|0.977361|
|
||||||
|
|2|SNOWBALL SWEDISH DIRECT|PRIMARY_OUTPUT|0.799353|0.719647|0.654396|0.562070|0.729796|0.729777|
|
||||||
|
|3|SNOWBALL SWEDISH LUCENE FILTER|PRIMARY_OUTPUT|0.796053|0.709394|0.639751|0.549660|0.721367|0.721348|
|
||||||
|
|4|SWEDISH LUCENE SWEDISH MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.829176|0.722989|0.640913|0.566158|0.740043|0.740026|
|
||||||
|
|5|SWEDISH LUCENE SWEDISH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.806522|0.698179|0.615497|0.536309|0.716364|0.716347|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|362653|0|16994|4529284143|0 / 4529284143|16994 / 379647|
|
||||||
|
|2|SNOWBALL SWEDISH DIRECT|PRIMARY_OUTPUT|234278|37166|145369|4529246977|37166 / 4529284143|145369 / 379647|
|
||||||
|
|3|SNOWBALL SWEDISH LUCENE FILTER|PRIMARY_OUTPUT|227960|35082|151687|4529249061|35082 / 4529284143|151687 / 379647|
|
||||||
|
|4|SWEDISH LUCENE SWEDISH MINIMAL STEM FILTER|PRIMARY_OUTPUT|226201|19890|153446|4529264253|19890 / 4529284143|153446 / 379647|
|
||||||
|
|5|SWEDISH LUCENE SWEDISH LIGHT STEM FILTER|PRIMARY_OUTPUT|216573|24174|163074|4529259969|24174 / 4529284143|163074 / 379647|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 4529284143|0 / 379647|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|379647|0|0|4529284143|0 / 4529284143|0 / 379647|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|16994|0|0|2840|2.983789%|5|98108|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
This mode contains **7 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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.977573|0.000000%|4.485467%|
|
||||||
|
|2|SNOWBALL SWEDISH DIRECT|0.808611|0.000824%|38.276920%|
|
||||||
|
|3|SNOWBALL SWEDISH LUCENE FILTER|0.800274|0.000778%|39.944519%|
|
||||||
|
|4|SWEDISH LUCENE SWEDISH MINIMAL STEM FILTER|0.797785|0.000441%|40.442582%|
|
||||||
|
|5|SWEDISH LUCENE SWEDISH LIGHT STEM FILTER|0.785141|0.000536%|42.971167%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.955145|1.000000|0.977573|0.999996|0.000004|
|
||||||
|
|2|SNOWBALL SWEDISH DIRECT|PRIMARY_OUTPUT|0.862864|0.617231|0.999992|0.808611|0.999960|0.000040|
|
||||||
|
|3|SNOWBALL SWEDISH LUCENE FILTER|PRIMARY_OUTPUT|0.866412|0.600555|0.999992|0.800274|0.999959|0.000041|
|
||||||
|
|4|SWEDISH LUCENE SWEDISH MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.918993|0.595574|0.999996|0.797785|0.999962|0.000038|
|
||||||
|
|5|SWEDISH LUCENE SWEDISH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.899375|0.570288|0.999995|0.785141|0.999959|0.000041|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.990695|0.977058|0.963791|0.955145|0.977315|0.977314|
|
||||||
|
|2|SNOWBALL SWEDISH DIRECT|PRIMARY_OUTPUT|0.799250|0.719665|0.654494|0.562091|0.729785|0.729766|
|
||||||
|
|3|SNOWBALL SWEDISH LUCENE FILTER|PRIMARY_OUTPUT|0.795941|0.709393|0.639820|0.549658|0.721337|0.721319|
|
||||||
|
|4|SWEDISH LUCENE SWEDISH MINIMAL STEM FILTER|PRIMARY_OUTPUT|0.828962|0.722752|0.640668|0.565867|0.739816|0.739800|
|
||||||
|
|5|SWEDISH LUCENE SWEDISH LIGHT STEM FILTER|PRIMARY_OUTPUT|0.806317|0.697987|0.615318|0.536083|0.716172|0.716155|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|361874|0|16994|4507704713|0 / 4507704713|16994 / 378868|
|
||||||
|
|2|SNOWBALL SWEDISH DIRECT|PRIMARY_OUTPUT|233849|37166|145019|4507667547|37166 / 4507704713|145019 / 378868|
|
||||||
|
|3|SNOWBALL SWEDISH LUCENE FILTER|PRIMARY_OUTPUT|227531|35082|151337|4507669631|35082 / 4507704713|151337 / 378868|
|
||||||
|
|4|SWEDISH LUCENE SWEDISH MINIMAL STEM FILTER|PRIMARY_OUTPUT|225644|19890|153224|4507684823|19890 / 4507704713|153224 / 378868|
|
||||||
|
|5|SWEDISH LUCENE SWEDISH LIGHT STEM FILTER|PRIMARY_OUTPUT|216064|24174|162804|4507680539|24174 / 4507704713|162804 / 378868|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 4507704713|0 / 378868|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|378868|0|0|4507704713|0 / 4507704713|0 / 378868|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|16994|0|0|2840|2.990922%|5|97881|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `SV_SE`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
369
docs/benchmarks/languages/ukrainian.md
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
# Ukrainian Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Ukrainian. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **15,737**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `AppendCharacterCommand` | Appends one character to the end of the word form. | 267 | 1.697% |
|
||||||
|
| `BackwardCompoundCommand` | Applies a multi-step backward patch made from skip, delete, insert, and replace operations. | 4,156 | 26.409% |
|
||||||
|
| `DeleteSuffixCommand` | Deletes one or more trailing characters from the word form. | 5,883 | 37.383% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 2,962 | 18.822% |
|
||||||
|
| `ReplaceLastCharacterCommand` | Replaces the final character of the word form. | 2,469 | 15.689% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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.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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `UK_UA` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `uk-ua-default`, loaded from classpath resource `org/egothor/stemmer/models/uk-ua-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.995816** among 4 deterministic stemmers. The runner-up is `UKRAINIAN LUCENE MORFOLOGIK FILTER` at 0.928906, a difference of 0.066910. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.995815** among 4 deterministic stemmers. The runner-up is `UKRAINIAN LUCENE MORFOLOGIK FILTER` at 0.928888, a difference of 0.066926. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
### `ALL_WORDS`
|
||||||
|
|
||||||
|
This mode contains **12 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.995816|0.000000%|0.836852%|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|0.928906|0.000028%|14.218810%|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|0.928783|0.000028%|14.243378%|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|0.885789|0.000006%|22.842226%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.991631|1.000000|0.995816|0.999995|0.000005|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|PRIMARY_OUTPUT|0.999499|0.857812|1.000000|0.928906|0.999907|0.000093|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|PRIMARY_OUTPUT|0.999499|0.857566|1.000000|0.928783|0.999907|0.000093|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|PRIMARY_OUTPUT|0.999881|0.771578|1.000000|0.885789|0.999851|0.000149|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.998315|0.995798|0.993294|0.991631|0.995807|0.995804|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|PRIMARY_OUTPUT|0.967537|0.923251|0.882842|0.857443|0.925949|0.925906|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|PRIMARY_OUTPUT|0.967474|0.923109|0.882634|0.857198|0.925817|0.925774|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|PRIMARY_OUTPUT|0.944015|0.871018|0.808499|0.771507|0.878343|0.878277|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|64580|0|545|100039050|0 / 100039050|545 / 65125|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|PRIMARY_OUTPUT|55865|28|9260|100039022|28 / 100039050|9260 / 65125|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|PRIMARY_OUTPUT|55849|28|9276|100039022|28 / 100039050|9276 / 65125|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|PRIMARY_OUTPUT|50249|6|14876|100039044|6 / 100039050|14876 / 65125|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL UKRAINIAN LUCENE FILTER|0.000000%|14.533589%|
|
||||||
|
|UKRAINIAN LUCENE MORFOLOGIK FILTER|0.000000%|7.594626%|
|
||||||
|
|UKRAINIAN MORFOLOGIK DIRECT|0.000000%|7.619194%|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL UKRAINIAN LUCENE FILTER|0 / 100039050|9465 / 65125|
|
||||||
|
|UKRAINIAN LUCENE MORFOLOGIK FILTER|0 / 100039050|4946 / 65125|
|
||||||
|
|UKRAINIAN MORFOLOGIK DIRECT|0 / 100039050|4962 / 65125|
|
||||||
|
|Radixor|0 / 100039050|0 / 65125|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|0.962027|0.000059%|7.594626%|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|0.961904|0.000059%|7.619194%|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|0.927332|0.000047%|14.533589%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|ALL_CANDIDATES|0.999021|0.924054|0.999999|0.962027|0.999950|0.000050|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|ALL_CANDIDATES|0.999020|0.923808|0.999999|0.961904|0.999950|0.000050|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|ALL_CANDIDATES|0.999156|0.854664|1.000000|0.927332|0.999905|0.000095|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|ALL_CANDIDATES|0.983070|0.960076|0.938133|0.923217|0.960806|0.960782|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|ALL_CANDIDATES|0.983014|0.959943|0.937931|0.922972|0.960678|0.960654|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|ALL_CANDIDATES|0.966477|0.921279|0.880120|0.854048|0.924090|0.924046|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|65125|0|0|100039050|0 / 100039050|0 / 65125|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|ALL_CANDIDATES|60179|59|4946|100038991|59 / 100039050|4946 / 65125|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|ALL_CANDIDATES|60163|59|4962|100038991|59 / 100039050|4962 / 65125|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|ALL_CANDIDATES|55660|47|9465|100039003|47 / 100039050|9465 / 65125|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|HUNSPELL UKRAINIAN LUCENE FILTER|5411|6|41|1259|8.897527%|6|15577|
|
||||||
|
|UKRAINIAN LUCENE MORFOLOGIK FILTER|4314|28|31|2130|15.053004%|6|16748|
|
||||||
|
|UKRAINIAN MORFOLOGIK DIRECT|4314|28|31|2130|15.053004%|6|16748|
|
||||||
|
|Radixor|545|0|0|95|0.671378%|2|14245|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
This mode contains **12 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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.995815|0.000000%|0.837058%|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|0.928888|0.000028%|14.222304%|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|0.928888|0.000028%|14.222304%|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|0.885791|0.000006%|22.841696%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.991629|1.000000|0.995815|0.999995|0.000005|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|PRIMARY_OUTPUT|0.999499|0.857777|1.000000|0.928888|0.999907|0.000093|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|PRIMARY_OUTPUT|0.999499|0.857777|1.000000|0.928888|0.999907|0.000093|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|PRIMARY_OUTPUT|0.999881|0.771583|1.000000|0.885791|0.999851|0.000149|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.998315|0.995797|0.993292|0.991629|0.995806|0.995803|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|PRIMARY_OUTPUT|0.967528|0.923231|0.882812|0.857408|0.925930|0.925887|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|PRIMARY_OUTPUT|0.967528|0.923231|0.882812|0.857408|0.925930|0.925887|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|PRIMARY_OUTPUT|0.944017|0.871021|0.808503|0.771512|0.878346|0.878280|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|64564|0|545|99911761|0 / 99911761|545 / 65109|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|PRIMARY_OUTPUT|55849|28|9260|99911733|28 / 99911761|9260 / 65109|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|PRIMARY_OUTPUT|55849|28|9260|99911733|28 / 99911761|9260 / 65109|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|PRIMARY_OUTPUT|50237|6|14872|99911755|6 / 99911761|14872 / 65109|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL UKRAINIAN LUCENE FILTER|0.000000%|14.537161%|
|
||||||
|
|UKRAINIAN LUCENE MORFOLOGIK FILTER|0.000000%|7.596492%|
|
||||||
|
|UKRAINIAN MORFOLOGIK DIRECT|0.000000%|7.596492%|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|HUNSPELL UKRAINIAN LUCENE FILTER|0 / 99911761|9465 / 65109|
|
||||||
|
|UKRAINIAN LUCENE MORFOLOGIK FILTER|0 / 99911761|4946 / 65109|
|
||||||
|
|UKRAINIAN MORFOLOGIK DIRECT|0 / 99911761|4946 / 65109|
|
||||||
|
|Radixor|0 / 99911761|0 / 65109|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|0.962017|0.000059%|7.596492%|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|0.962017|0.000059%|7.596492%|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|0.927314|0.000047%|14.537161%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|ALL_CANDIDATES|0.999020|0.924035|0.999999|0.962017|0.999950|0.000050|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|ALL_CANDIDATES|0.999020|0.924035|0.999999|0.962017|0.999950|0.000050|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|ALL_CANDIDATES|0.999156|0.854628|1.000000|0.927314|0.999905|0.000095|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|ALL_CANDIDATES|0.983065|0.960066|0.938118|0.923199|0.960796|0.960772|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|ALL_CANDIDATES|0.983065|0.960066|0.938118|0.923199|0.960796|0.960772|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|ALL_CANDIDATES|0.966468|0.921258|0.880089|0.854012|0.924071|0.924027|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|65109|0|0|99911761|0 / 99911761|0 / 65109|
|
||||||
|
|2|UKRAINIAN LUCENE MORFOLOGIK FILTER|ALL_CANDIDATES|60163|59|4946|99911702|59 / 99911761|4946 / 65109|
|
||||||
|
|3|UKRAINIAN MORFOLOGIK DIRECT|ALL_CANDIDATES|60163|59|4946|99911702|59 / 99911761|4946 / 65109|
|
||||||
|
|4|HUNSPELL UKRAINIAN LUCENE FILTER|ALL_CANDIDATES|55644|47|9465|99911714|47 / 99911761|9465 / 65109|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|HUNSPELL UKRAINIAN LUCENE FILTER|5407|6|41|1258|8.896118%|6|15567|
|
||||||
|
|UKRAINIAN LUCENE MORFOLOGIK FILTER|4314|28|31|2130|15.062584%|6|16739|
|
||||||
|
|UKRAINIAN MORFOLOGIK DIRECT|4314|28|31|2130|15.062584%|6|16739|
|
||||||
|
|Radixor|545|0|0|95|0.671805%|2|14236|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `UK_UA`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
316
docs/benchmarks/languages/yiddish.md
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
# Yiddish Stemmer Benchmarks
|
||||||
|
|
||||||
|
This page reports same-language stemming benchmarks for Yiddish. Accuracy is listed first because speed without root agreement is not enough to interpret stemmer quality.
|
||||||
|
|
||||||
|
All speed values are environment-specific and were measured on the hardware and JVM listed in the [benchmark overview](../index.md). Speed benchmark operations process changed dictionary tokens only. Accuracy uses the complete Radixor dictionary for the language.
|
||||||
|
|
||||||
|
Radixor must not be read as simply "slower" when a narrow competitor has a lower timing row. In these tables Radixor is the quality-oriented baseline: its exact-root accuracy is typically close to 100%, while many faster rule-based, light, minimal, or possessive filters reach that speed by doing much less linguistic work and often score far lower in `All exact` and `Changed exact`. The Radixor rows in this benchmark refresh use the contracted compiled patch trie: compilation collapses uniform patch-command subtrees into accepting leaves, reducing hot lookup depth while preserving the preferred stemming result measured by the accuracy pass. The [EnglishRadixorDictionaryCoverageBenchmark](../reference/english-coverage.md) table shows the resulting quality/speed envelope explicitly. The same interpretation applies to this language page: speed rows must be read together with the accuracy table above them.
|
||||||
|
|
||||||
|
## Dictionary Corpus
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Radixor stores the preferred transformation for each normalized dictionary word form as a compiled patch command. This distribution shows which runtime command class is selected by the trained trie for the complete default-model dictionary. The total number of preferred patch commands analyzed for this language is **4,300**.
|
||||||
|
|
||||||
|
| Command class | Meaning | Word forms | Share |
|
||||||
|
| --- | --- | ---: | ---: |
|
||||||
|
| `DeletePrefixCommand` | Deletes one or more leading characters from the word form in forward traversal. | 25 | 0.581% |
|
||||||
|
| `ForwardCompoundCommand` | Applies a multi-step forward patch made from skip, delete, insert, and replace operations. | 2,721 | 63.279% |
|
||||||
|
| `PreserveCommand` | Returns the word form unchanged because it already matches the preferred root. | 1,551 | 36.070% |
|
||||||
|
| `ReplaceFirstCharacterCommand` | Replaces the first character of the word form in forward traversal. | 3 | 0.070% |
|
||||||
|
|
||||||
|
## Accuracy
|
||||||
|
|
||||||
|
Accuracy is computed from JMH auxiliary counters in the current report. The counters are deterministic for a fixed corpus and stemmer; percentages divide matching counters by evaluated counters from the same report and are not timing metrics.
|
||||||
|
|
||||||
|
| Stemmer | All exact | Changed exact | Root preserved | Note |
|
||||||
|
| --- | ---: | ---: | ---: | --- |
|
||||||
|
| 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.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-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.
|
||||||
|
- Snowball rows are rule-based generated suffix stemmers; they are useful algorithmic baselines, not dictionary-root equivalence guarantees.
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:START -->
|
||||||
|
|
||||||
|
## Stemming Quality
|
||||||
|
|
||||||
|
Runtime performance and linguistic grouping quality are independent dimensions. This section evaluates language `YI` using the complete validated stemming-quality result matrix. Every distinct surface form is one evaluated item and can belong to several dictionary groups. Two forms are a positive pair when their group-membership sets intersect and a negative pair when those sets are disjoint. A pair shared through several groups is counted once. Exact equality with a predetermined lemma is not required.
|
||||||
|
|
||||||
|
`ALL_WORDS` includes every valid group and its original forms. `LOWERCASE_GROUPS_ONLY` excludes an entire group when any Unicode code point is uppercase or titlecase; retained words are not lowercased or otherwise rewritten. This isolates case-handling effects without changing retained inputs. [Download the complete machine-readable result snapshot](../data/stemming-quality.csv).
|
||||||
|
|
||||||
|
### Evaluation Scope and Key Findings
|
||||||
|
|
||||||
|
The default model is `yi-default`, loaded from classpath resource `org/egothor/stemmer/models/yi-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.989079** among 3 deterministic stemmers. The runner-up is `SNOWBALL YIDDISH DIRECT` at 0.891118, a difference of 0.097961. This rank does not imply leadership in throughput or every secondary metric.
|
||||||
|
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.989079** among 3 deterministic stemmers. The runner-up is `SNOWBALL YIDDISH DIRECT` at 0.891118, a difference of 0.097961. 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.
|
||||||
|
|
||||||
|
#### `PRIMARY_OUTPUT` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.989079|0.000000%|2.184236%|
|
||||||
|
|2|SNOWBALL YIDDISH DIRECT|0.891118|0.013211%|21.763216%|
|
||||||
|
|3|SNOWBALL YIDDISH LUCENE FILTER|0.891118|0.013211%|21.763216%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.978158|1.000000|0.989079|0.999978|0.000022|
|
||||||
|
|2|SNOWBALL YIDDISH DIRECT|PRIMARY_OUTPUT|0.857267|0.782368|0.999868|0.891118|0.999648|0.000352|
|
||||||
|
|3|SNOWBALL YIDDISH LUCENE FILTER|PRIMARY_OUTPUT|0.857267|0.782368|0.999868|0.891118|0.999648|0.000352|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.995554|0.988958|0.982449|0.978158|0.989019|0.989008|
|
||||||
|
|2|SNOWBALL YIDDISH DIRECT|PRIMARY_OUTPUT|0.841161|0.818107|0.796282|0.692200|0.818961|0.818787|
|
||||||
|
|3|SNOWBALL YIDDISH LUCENE FILTER|PRIMARY_OUTPUT|0.841161|0.818107|0.796282|0.692200|0.818961|0.818787|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|6180|0|138|6229428|0 / 6229428|138 / 6318|
|
||||||
|
|2|SNOWBALL YIDDISH DIRECT|PRIMARY_OUTPUT|4943|823|1375|6228605|823 / 6229428|1375 / 6318|
|
||||||
|
|3|SNOWBALL YIDDISH LUCENE FILTER|PRIMARY_OUTPUT|4943|823|1375|6228605|823 / 6229428|1375 / 6318|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 6229428|0 / 6318|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|6318|0|0|6229428|0 / 6229428|0 / 6318|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|138|0|0|43|1.217441%|3|3578|
|
||||||
|
|
||||||
|
### `LOWERCASE_GROUPS_ONLY`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|0.989079|0.000000%|2.184236%|
|
||||||
|
|2|SNOWBALL YIDDISH DIRECT|0.891118|0.013211%|21.763216%|
|
||||||
|
|3|SNOWBALL YIDDISH LUCENE FILTER|0.891118|0.013211%|21.763216%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|1.000000|0.978158|1.000000|0.989079|0.999978|0.000022|
|
||||||
|
|2|SNOWBALL YIDDISH DIRECT|PRIMARY_OUTPUT|0.857267|0.782368|0.999868|0.891118|0.999648|0.000352|
|
||||||
|
|3|SNOWBALL YIDDISH LUCENE FILTER|PRIMARY_OUTPUT|0.857267|0.782368|0.999868|0.891118|0.999648|0.000352|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|0.995554|0.988958|0.982449|0.978158|0.989019|0.989008|
|
||||||
|
|2|SNOWBALL YIDDISH DIRECT|PRIMARY_OUTPUT|0.841161|0.818107|0.796282|0.692200|0.818961|0.818787|
|
||||||
|
|3|SNOWBALL YIDDISH LUCENE FILTER|PRIMARY_OUTPUT|0.841161|0.818107|0.796282|0.692200|0.818961|0.818787|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|PRIMARY_OUTPUT|6180|0|138|6229428|0 / 6229428|138 / 6318|
|
||||||
|
|2|SNOWBALL YIDDISH DIRECT|PRIMARY_OUTPUT|4943|823|1375|6228605|823 / 6229428|1375 / 6318|
|
||||||
|
|3|SNOWBALL YIDDISH LUCENE FILTER|PRIMARY_OUTPUT|4943|823|1375|6228605|823 / 6229428|1375 / 6318|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ANY_CANDIDATE` oracle bounds
|
||||||
|
|
||||||
|
These results are measured, not missing. `ANY_CANDIDATE` answers two separate optimistic questions for each pair: a gold-related pair avoids under-stemming when the candidate sets intersect, while a gold-negative pair avoids over-stemming when some non-colliding candidate selection exists. The oracle may choose a different candidate for the same word in different pairs. Consequently, these decisions do not form one globally realizable predicted relation or one TP/FP/FN/TN confusion matrix. Balanced accuracy, F-scores, Jaccard, Fowlkes–Mallows, and MCC are therefore mathematically **not applicable**, rather than unknown.
|
||||||
|
|
||||||
|
<div class="quality-summary quality-summary--oracle" markdown="1">
|
||||||
|
|
||||||
|
| Stemmer | Optimistic over-stemming (OI) | Optimistic under-stemming (UI) |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Oracle-bound pair counts</summary>
|
||||||
|
|
||||||
|
| Stemmer | Unavoidable over errors / gold-negative pairs | Unrepairable under errors / gold-related pairs |
|
||||||
|
|---|---:|---:|
|
||||||
|
|Radixor|0 / 6229428|0 / 6318|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `ALL_CANDIDATES` ranking
|
||||||
|
|
||||||
|
<div class="quality-summary" markdown="1">
|
||||||
|
|
||||||
|
| Rank | Stemmer | Balanced accuracy | Over-stemming (OI) | Under-stemming (UI) |
|
||||||
|
|---:|---|---:|---:|---:|
|
||||||
|
|1|Radixor|1.000000|0.000000%|0.000000%|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Classification metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | Precision | Recall | Specificity | Balanced accuracy | Pairwise accuracy | Error rate |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|0.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | Fowlkes–Mallows | MCC |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|1.000000|1.000000|1.000000|1.000000|1.000000|1.000000|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="quality-details" markdown="1"><summary>Raw pair counts</summary>
|
||||||
|
|
||||||
|
| Rank | Stemmer | Output policy | TP | FP | FN | TN | Over error / possible | Under error / possible |
|
||||||
|
|---:|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
|1|Radixor|ALL_CANDIDATES|6318|0|0|6229428|0 / 6229428|0 / 6318|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Multi-output analysis
|
||||||
|
|
||||||
|
Alternative candidates are capability analyses, not replacements for the deterministic comparison.
|
||||||
|
|
||||||
|
| Stemmer | Under pairs repaired | Best-case over pairs avoided | All-candidate collisions added | Multi-candidate forms | Multi-candidate share | Maximum candidates | Total candidate assignments |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
|Radixor|138|0|0|43|1.217441%|3|3578|
|
||||||
|
|
||||||
|
### Output Policies and Metric Definitions
|
||||||
|
|
||||||
|
Each distinct surface form is one item and may belong to several gold groups. Two forms are gold-related when their membership sets intersect; a relation shared by several groups is counted once. `PRIMARY_OUTPUT` uses one deterministic stem per form. `ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound: a gold-related pair succeeds when candidates intersect, while a gold-negative pair succeeds when a non-colliding selection exists. Candidate choices may differ between pairs, so this is not deterministic runtime behaviour and does not define one confusion matrix. `ALL_CANDIDATES` activates every returned candidate; forms are related when candidate sets intersect.
|
||||||
|
|
||||||
|
For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, `TP = underPossiblePairs - underErrorPairs`, `FN = underErrorPairs`, `FP = overErrorPairs`, and `TN = overPossiblePairs - overErrorPairs`. `ANY_CANDIDATE` publishes only its separate oracle-assisted under/over bounds; confusion-derived metrics are mathematically inapplicable and are not presented in its language-page section. Their machine-readable CSV fields remain empty. Undefined metric denominators in otherwise applicable policies are rendered as `n/a`.
|
||||||
|
|
||||||
|
- Under-stemming rate (Paice UI): `FN / (TP + FN)`, the false-negative rate over gold-related pairs.
|
||||||
|
- Over-stemming rate (Paice OI): `FP / (TN + FP)`, the false-positive rate over gold-negative pairs.
|
||||||
|
- Pairwise precision: `TP / (TP + FP)`, the fraction of predicted conflations that are gold-standard positive pairs.
|
||||||
|
- Pairwise recall: `TP / (TP + FN)`, the fraction of gold-standard positive pairs successfully connected.
|
||||||
|
- Pairwise specificity: `TN / (TN + FP)`, the fraction of gold-negative pairs correctly separated.
|
||||||
|
- Balanced accuracy: `(recall + specificity) / 2`. It gives equal weight to positive and negative pair classes and is less dominated by the large true-negative class than ordinary accuracy. It does not replace the raw errors or other metrics.
|
||||||
|
- Pairwise F-beta: `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`. F0.5 emphasizes precision and penalizes over-stemming more; F1 weights precision and recall equally; F2 emphasizes recall and penalizes under-stemming more.
|
||||||
|
- MCC: `(TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))`. It uses all confusion counts and remains useful under class imbalance, except when its denominator is degenerate.
|
||||||
|
- Jaccard index: `TP / (TP + FP + FN)`.
|
||||||
|
- Fowlkes–Mallows index: `sqrt(precision * recall)`.
|
||||||
|
- Pairwise accuracy: `(TP + TN) / (TP + TN + FP + FN)`. It can be dominated by true-negative cross-group pairs.
|
||||||
|
- Pairwise error rate: `(FP + FN) / (TP + TN + FP + FN)`.
|
||||||
|
|
||||||
|
Standard ARI, homogeneity, completeness, V-measure, and NMI are not calculated: their usual contingency-table definitions require an exclusive gold partition, while this gold standard is an overlapping cover.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
- Authoritative source: `docs/benchmarks/data/stemming-quality.csv`
|
||||||
|
- Source SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Evaluation command: `./gradlew stemmingQuality --no-daemon`
|
||||||
|
- Dictionary language: `YI`
|
||||||
|
- Processing modes: `ALL_WORDS`, `LOWERCASE_GROUPS_ONLY`
|
||||||
|
- Stemmer versions and transitive artifacts: resolved by the repository's JMH Gradle configuration and `gradle.lockfile`
|
||||||
|
- Model ID, version, and SHA-256: recorded in every CSV row
|
||||||
|
- Run date, core source state, JDK, operating system, and hardware: recorded on the [benchmark environment page](../reference/environment.md)
|
||||||
|
|
||||||
|
<!-- STEMMING-QUALITY:END -->
|
||||||
31
docs/benchmarks/reference/candidates.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Benchmark Candidate Evaluation
|
||||||
|
|
||||||
|
Implemented benchmark methods are documented on the per-language pages under [Language Benchmark Pages](../languages/index.md). This keeps the exact method names, implementation descriptions, accuracy tables, and speed tables close to the language where they are valid.
|
||||||
|
|
||||||
|
## Included Candidate Families
|
||||||
|
|
||||||
|
The current benchmark pages include Radixor baselines, Lucene language filters where the language matches a bundled Radixor resource, Lucene Stempel and Morfologik paths where applicable, official Snowball Java stemmers where same-language comparison is available, benchmark-only CISTEM German stemmer evaluation, benchmark-only Hunspell comparisons, and selected English-specific non-Lucene baselines such as OpenNLP Porter and Paice/Husk Lancaster.
|
||||||
|
|
||||||
|
Benchmark-only Hunspell comparisons use bundled benchmark dictionaries and the Lucene HunspellStemFilter adapter over the selected language token streams.
|
||||||
|
The CISTEM candidate is implemented in `src/jmh/java/org/egothor/stemmer/benchmark/Cistem.java` and follows the original MIT-licensed upstream implementation from Leonie Weissweiler's CISTEM project.
|
||||||
|
CISTEM German gold-standard files are not vendored in this repository. The Gradle JMH resource preparation tasks download `goldstandard1.txt` and `goldstandard2.txt` from the upstream CISTEM repository into generated build resources.
|
||||||
|
|
||||||
|
Direct stemmer APIs and Lucene TokenFilter paths are documented separately on language pages. TokenFilter rows include TokenStream, attribute, and required normalization overhead. Direct rows measure exposed direct APIs.
|
||||||
|
|
||||||
|
For the benchmark refresh used in this documentation build:
|
||||||
|
|
||||||
|
- Hunspell families are included in `HunspellStemmerComparisonBenchmark` (speed) and `HunspellStemmerComparisonBenchmarkQuality` (quality for all benchmark languages in this corpus). The legacy
|
||||||
|
`EnglishHunspellStemmerComparisonBenchmarkQuality` result is retained for continuity.
|
||||||
|
- CISTEM quality is present in the published per-language results under `GERMAN_CISTEM`. CISTEM speed is present as `germanCistem` in `MultiLanguageStemmerComparisonBenchmark`.
|
||||||
|
|
||||||
|
## Evaluated But Skipped Candidates
|
||||||
|
|
||||||
|
| Candidate | Language | Link/source | Reason skipped |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Lucene Arabic, Bulgarian, Bengali, Sorani, Greek, Galician, Hindi, Indonesian, Latvian, Telugu filters | Various | `lucene-analysis-common` | No bundled same-language Radixor resource in this repository snapshot. |
|
||||||
|
| Lucene analyzer-only paths | Multiple | Lucene analyzers | Full analyzers mix tokenization, stop-word handling, and other behavior; direct filters are used where available. |
|
||||||
|
| Lucene StemmerOverrideFilter | Multiple | `lucene-analysis-common` | Override map facility, not a stemmer algorithm. |
|
||||||
|
| Additional Snowball Lovins | English | Official Snowball Java distribution | No Lovins Java stemmer was present in the selected Snowball Java distribution. |
|
||||||
|
| Lemur Project Krovetz Stemmer | English | Lemur Project | Lucene KStem represents the Krovetz-style path without adding separate dependency and license risk. |
|
||||||
|
| Smile Lancaster / Paice-Husk | English | Smile NLP | Smile is large for one stemmer; Paice/Husk is included through a smaller benchmark-only generated path. |
|
||||||
|
| `stemmerEval` reference repository | Multiple | `https://github.com/endredy/stemmerEval` | Used only as a candidate reference; no code or data copied. |
|
||||||
28
docs/benchmarks/reference/corpora.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Benchmark Corpora
|
||||||
|
|
||||||
|
The table below describes the Radixor resources used to build speed and quality corpora. `Total tokens` is the complete dictionary token count used by quality benchmarks. `Already-root tokens` counts fields where the token is already equal to the line root. `Changed tokens` is the speed workload before the minimum-size repeat rule.
|
||||||
|
|
||||||
|
| Default model ID | Version | SHA-256 | Language | Dictionary rows | Total tokens | Already-root tokens | Changed tokens | Speed timing tokens |
|
||||||
|
| --- | --- | --- | --- | ---: | ---: | ---: | ---: | ---: |
|
||||||
|
| `cs-cz-default` | `1.0.0` | `62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4` | `CS_CZ` | 5,113 | 56,612 | 10,049 | 46,563 | 46,563 |
|
||||||
|
| `da-dk-default` | `1.0.0` | `3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90` | `DA_DK` | 4,179 | 32,256 | 8,356 | 23,900 | 23,900 |
|
||||||
|
| `nl-nl-default` | `1.0.0` | `c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e` | `NL_NL` | 4,992 | 31,466 | 9,981 | 21,485 | 21,485 |
|
||||||
|
| `us-uk-default` | `1.0.0` | `8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460` | `US_UK` | 396,939 | 1,004,374 | 793,874 | 210,500 | 210,500 |
|
||||||
|
| `fi-fi-default` | `1.0.0` | `ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f` | `FI_FI` | 57,027 | 1,865,215 | 110,525 | 1,754,690 | 1,754,690 |
|
||||||
|
| `fr-fr-default` | `1.0.0` | `a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9` | `FR_FR` | 59,240 | 474,110 | 108,141 | 365,969 | 365,969 |
|
||||||
|
| `de-de-default` | `1.0.0` | `cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5` | `DE_DE` | 54,092 | 333,036 | 90,535 | 242,501 | 242,501 |
|
||||||
|
| `he-il-default` | `1.0.0` | `9a47dc69bb7dab21aba0266b73cd74cdaeb17db94363796a0a56111ac8518256` | `HE_IL` | 2,358 | 61,071 | 4,715 | 56,356 | 56,356 |
|
||||||
|
| `hu-hu-default` | `1.0.0` | `359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e` | `HU_HU` | 19,406 | 935,713 | 38,775 | 896,938 | 896,938 |
|
||||||
|
| `it-it-default` | `1.0.0` | `5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12` | `IT_IT` | 10,009 | 337,546 | 20,004 | 317,542 | 317,542 |
|
||||||
|
| `nb-no-default` | `1.0.0` | `f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9` | `NB_NO` | 17,929 | 90,757 | 33,376 | 57,381 | 57,381 |
|
||||||
|
| `nn-no-default` | `1.0.0` | `900cf2005605aea2a3d8d731ec0b0c1f47fb4469b4ba6b9134145d4d026a0398` | `NN_NO` | 4,688 | 19,651 | 6,089 | 13,562 | 13,562 |
|
||||||
|
| `fa-ir-default` | `1.0.0` | `b29a0d168a6a97f980666aa40b74a0edd8b6be4ab3320a7abfbb76b3529f4ea1` | `FA_IR` | 69 | 3,770 | 138 | 3,632 | 5,000 |
|
||||||
|
| `pl-pl-unimorph` | `1.0.0` | `8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721` | `PL_PL` | 9,990 | 132,308 | 19,957 | 112,351 | 112,351 |
|
||||||
|
| `pt-pt-default` | `1.0.0` | `7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d` | `PT_PT` | 4,001 | 215,490 | 8,002 | 207,488 | 207,488 |
|
||||||
|
| `ru-ru-default` | `1.0.0` | `df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40` | `RU_RU` | 37,410 | 806,279 | 74,808 | 731,471 | 731,471 |
|
||||||
|
| `es-es-default` | `1.0.0` | `7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721` | `ES_ES` | 65,059 | 926,393 | 120,121 | 806,272 | 806,272 |
|
||||||
|
| `sv-se-default` | `1.0.0` | `d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c` | `SV_SE` | 12,371 | 110,468 | 24,731 | 85,737 | 85,737 |
|
||||||
|
| `uk-ua-default` | `1.0.0` | `cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae` | `UK_UA` | 1,493 | 15,737 | 2,985 | 12,752 | 12,752 |
|
||||||
|
| `yi-default` | `1.0.0` | `f47de665c27dcd72833a82904e49c68a945bb5aca769a7ec5a0164e2c981a6d3` | `YI` | 802 | 4,300 | 1,524 | 2,776 | 5,000 |
|
||||||
|
|
||||||
|
Speed benchmarks process the complete changed-token dictionary sequence for the language. Only resources with fewer than 5,000 changed tokens are repeated to reach the minimum timing size; larger resources are not sampled or truncated.
|
||||||
43
docs/benchmarks/reference/english-coverage.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# English Dictionary Coverage Benchmark
|
||||||
|
|
||||||
|
`EnglishRadixorDictionaryCoverageBenchmark` builds Radixor from deterministic slices of the English dictionary rows and evaluates accuracy against the complete dictionary. The speed method then stems the full changed-token English timing corpus.
|
||||||
|
|
||||||
|
This benchmark is the clearest demonstration of the Radixor quality/speed envelope after contracted-trie compilation. More dictionary knowledge still gives the strongest changed-form precision, but uniform-subtree contraction removes much of the historical lookup-depth penalty. The table should therefore be read as a measured operating curve rather than as a strictly monotonic function of dictionary size.
|
||||||
|
|
||||||
|
| 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% | 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
|
||||||
|
|
||||||
|
- `Used rows`: requested deterministic percentage of English dictionary rows used to build the trie.
|
||||||
|
- `Actual row ratio`: selected rows divided by all parsed English dictionary rows.
|
||||||
|
- `All exact`: exact agreement over the complete dictionary.
|
||||||
|
- `Changed exact`: exact agreement over dictionary tokens where `token != expectedRoot`.
|
||||||
|
- `Root preserved`: percentage of already-root dictionary tokens that are left unchanged.
|
||||||
|
- `Speed ms/op`: JMH average time for one full changed-token English operation.
|
||||||
|
- `Error ms`: JMH score error converted to milliseconds.
|
||||||
|
- `ns/token`: `Speed ms/op` divided by 210,500 changed English tokens.
|
||||||
|
|
||||||
|
For non-English languages, the same principle applies: dictionary-driven Radixor quality depends on the amount and consistency of the language resource, while contracted tries reduce the cost of uniform regions in the compiled lookup graph. The English table is the clearest because the English resource is large and the benchmark can show gradual deterministic reductions from 100% to 10%.
|
||||||
|
|
||||||
|
## Why The Historical Porter Ratio Changed
|
||||||
|
|
||||||
|
The historical English benchmark in `HEAD` used synthetic lexical families. Its `familyCount=5000` parameter generated roughly 70,000 artificial tokens rather than measuring the complete real English dictionary resource. That older workload was useful as a low-level stress test, but it was not a dictionary-quality comparison. Many synthetic tokens were not present in the Radixor dictionary, so Radixor often executed a fast miss path where lookup returned `null` and no patch command was applied.
|
||||||
|
|
||||||
|
The current benchmark is intentionally based on real Radixor dictionary data. For English, the speed workload processes 210,500 changed token/root pairs where the dictionary token differs from the expected root, and the quality workload evaluates the complete 1,004,374-token dictionary. This is a hit-heavy workload that measures real lookup plus compiled patch-command application against known expected roots. It is therefore a different and more linguistically meaningful workload than the historical synthetic benchmark.
|
||||||
|
|
||||||
|
The result must be interpreted in Radixor's favor through both speed and exact-root quality. Non-Radixor stemmers can look faster because many of them perform narrower or more aggressive transformations and do not attempt to match the dictionary root with the same precision. The English result table shows that this speed often comes with substantially lower `All exact` and `Changed exact` accuracy.
|
||||||
|
|
||||||
|
Radixor uses the dictionary as training data for transformation rules. With the full English dictionary, it reaches much higher exact-root agreement than the Porter-family and other narrow baselines. Higher speed is still possible by reducing the amount or complexity of the input dictionary used to build the stemmer, but that is an explicit quality/speed trade-off rather than an accidental benchmark artifact.
|
||||||
|
|
||||||
|
The coverage table shows that contracted tries substantially improve the operating point. Reducing dictionary knowledge still primarily damages changed-form exactness, while root preservation remains high. Even when Radixor is trained from only 10% of the English dictionary rows, the complete-dictionary `All exact` score remains above 92%. This is why Radixor performance should be discussed as a configurable quality/speed point, not as a single fixed ratio against Porter.
|
||||||
88
docs/benchmarks/reference/environment.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Benchmark Environment And Reports
|
||||||
|
|
||||||
|
The values below are environment-specific and must not be read as universal performance claims.
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Benchmark date | 2026-08-10 (Europe/Prague) |
|
||||||
|
| Corpus command | `./gradlew benchmarkCorpusReport --no-daemon` |
|
||||||
|
| 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-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-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-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 | `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` |
|
||||||
|
| Speed warmup | 5 iterations, 1 s each, independently in every fork |
|
||||||
|
| Speed measurement | 10 iterations, 1 s each, independently in every fork |
|
||||||
|
| Speed forks | 3 independent JVM forks |
|
||||||
|
| 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 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.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.7-200.fc44.x86_64 |
|
||||||
|
| Architecture | x86_64 |
|
||||||
|
| 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 | 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 |
|
||||||
|
|
||||||
|
The workstation is not a hard real-time system. Normal kernel and desktop background activity was not removed, so the three independent forks and the published 99.9% error interval remain essential parts of result interpretation. Initial/final load and temperature sensor readings are stored in the environment report.
|
||||||
|
|
||||||
|
## Contracted Trie Baseline
|
||||||
|
|
||||||
|
All Radixor rows use contracted compiled patch tries. During compilation, a subtree whose reachable entries all resolve to the same preferred patch command is represented as an accepting leaf. Runtime lookup can therefore stop as soon as that leaf is reached while preserving the preferred result used by `get()`.
|
||||||
|
|
||||||
|
## Model And Source Identity
|
||||||
|
|
||||||
|
`benchmark-corpora.csv` records the model ID, independent artifact version, and descriptor SHA-256 for every language. Every stemming-quality CSV row repeats the same three fields. The performance environment report additionally records checksums of the executable JMH JAR, runtime classpath manifest, corpus report, quality report, measured source patch, and untracked-source manifest.
|
||||||
|
|
||||||
|
The JMH runtime classpath contains the optional model artifact because it is a separately testable project dependency. It is not selected by any published benchmark. The selected-method manifest rejects `PolishPolimorf`, and the corpus/quality publication validators reject any non-default Polish model.
|
||||||
|
|
||||||
|
## Report Files
|
||||||
|
|
||||||
|
Generated local report files for this benchmark update:
|
||||||
|
|
||||||
|
- `build/reports/jmh/benchmark-corpora.csv`
|
||||||
|
- `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`
|
||||||
|
- `build/reports/stemming-quality/metric-correlations-spearman.csv`
|
||||||
|
|
||||||
|
The versioned documentation snapshot under `docs/benchmarks/data/` preserves the complete stemming-quality matrix. Machine-specific JMH reports remain build artifacts.
|
||||||
|
|
||||||
|
## Published Metrics
|
||||||
|
|
||||||
|
The historical English Radixor versus Porter performance badge is retired. `tools/generate-pages-badges.py` produces only coverage and mutation badge endpoint JSON files. Benchmark interpretation uses both speed and quality because a narrow or aggressive stemmer can be fast while disagreeing with the dictionary root much more often than Radixor.
|
||||||
82
docs/benchmarks/reference/linguistic-quality.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Linguistic Quality Methodology
|
||||||
|
|
||||||
|
This evaluation measures agreement between the relation predicted by a stemmer and the gold-standard relation represented by Radixor dictionary groups. It does not require a generated stem to equal one predetermined lemma string. Runtime performance and linguistic quality are separate measurements.
|
||||||
|
|
||||||
|
## Scope and fair-comparison rules
|
||||||
|
|
||||||
|
The authoritative Radixor language universe is the reconciliation of registered default model descriptors and `StemmerPatchTrieLoader.Language`. Radixor is evaluated for every reconciled language. Optional models are separate comparison rows. A third-party adapter is evaluated only for languages supported by its tested implementation and having a compatible Radixor dictionary; unsupported combinations are absent rather than assigned zero quality.
|
||||||
|
|
||||||
|
Model identity is part of the candidate identity. Default Polish means `pl-pl-unimorph`; optional PoliMorf means `pl-pl-polimorf`. Results for those inputs must not be combined or relabeled, and historical snapshots cannot acquire a newer model identity retroactively.
|
||||||
|
|
||||||
|
Within one language and dictionary mode, every adapter receives the same original included forms. A distinct surface string is one evaluated item even when it occurs in several rows; those occurrences become multiple gold-group memberships. Candidate strings use exact `String.equals`, with no evaluation-only lowercasing, normalization, accent removal, or gold-label-aware selection. Adapter preprocessing and lifecycle match the JMH comparison path.
|
||||||
|
|
||||||
|
## Gold-standard pairs
|
||||||
|
|
||||||
|
Every usable dictionary row contributes one gold-standard group. The groups form an overlapping cover rather than an exclusive partition: one surface form may belong to several groups. For two distinct forms `u` and `v` with membership sets `G(u)` and `G(v)`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
goldRelated(u, v) = (G(u) intersection G(v) is not empty)
|
||||||
|
```
|
||||||
|
|
||||||
|
A pair is counted once even if it shares several groups. Gold-negative pairs have disjoint membership sets. Thus:
|
||||||
|
|
||||||
|
- `TP = underPossiblePairs - underErrorPairs`: gold-related pairs correctly related.
|
||||||
|
- `FN = underErrorPairs`: gold-related pairs incorrectly separated.
|
||||||
|
- `FP = overErrorPairs`: gold-negative pairs incorrectly related.
|
||||||
|
- `TN = overPossiblePairs - overErrorPairs`: gold-negative pairs correctly separated.
|
||||||
|
|
||||||
|
Under-stemming is Paice's Understemming Index (UI), the false-negative rate among gold-related pairs. Over-stemming is Paice's Overstemming Index (OI), the false-positive rate among gold-negative pairs. The original Paice formulation assumes disjoint lemma groups; this evaluator explicitly generalizes the pair relation to overlapping membership. Their percentages use different denominators and must not be added or averaged without an explicitly defined composite.
|
||||||
|
|
||||||
|
## Dictionary-processing modes
|
||||||
|
|
||||||
|
- `ALL_WORDS` includes every valid group and preserves every original form.
|
||||||
|
- `LOWERCASE_GROUPS_ONLY` excludes an entire group if any Unicode code point is uppercase or titlecase. Retained forms are not converted to lowercase. Digits, punctuation, combining marks, and characters without case distinctions do not exclude a group by themselves.
|
||||||
|
|
||||||
|
## Output policies
|
||||||
|
|
||||||
|
`PRIMARY_OUTPUT` uses the adapter's deterministic primary stem. It defines a strict predicted partition and is the principal direct comparison between implementations.
|
||||||
|
|
||||||
|
`ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound. Gold-related pairs succeed when candidate sets intersect. Gold-negative pairs avoid an error whenever a non-colliding candidate selection exists. Selection may differ between pairs, so this policy is not deterministic runtime behaviour and may not correspond to one globally realizable assignment. Because its positive and negative decisions use different oracle conditions, it does not define one confusion matrix; TP/FP/FN/TN and all confusion-derived scores are therefore `n/a`. Its separate under/over error counts and denominators remain defined.
|
||||||
|
|
||||||
|
`ALL_CANDIDATES` treats every returned candidate as active. Two forms are related when their candidate sets intersect. Alternatives can recover gold-positive relationships while introducing gold-negative collisions. This overlapping relation need not be transitive or form a partition.
|
||||||
|
|
||||||
|
Candidate-aware policies are reported as capability analyses. They are not mixed into the principal `PRIMARY_OUTPUT` ranking.
|
||||||
|
|
||||||
|
## Relation metrics
|
||||||
|
|
||||||
|
Undefined denominators produce `n/a`, never zero, `NaN`, or infinity. Metrics are calculated from unrounded raw counts and displayed with six decimals.
|
||||||
|
|
||||||
|
| Metric | Formula | Range and interpretation | Sensitivity and applicability |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Under-stemming rate | `FN / (TP + FN)` | `[0, 1]`; lower is better. False-negative rate over gold-related pairs. | Sensitive to splitting large gold groups. All policies. |
|
||||||
|
| Over-stemming rate | `FP / (TN + FP)` | `[0, 1]`; lower is better. False-positive rate over gold-negative pairs. | The denominator is usually very large. All policies. |
|
||||||
|
| Precision | `TP / (TP + FP)` | `[0, 1]`; higher is better. Fraction of predicted relations that are gold-positive. | Penalizes over-stemming. `PRIMARY_OUTPUT` and `ALL_CANDIDATES`. |
|
||||||
|
| Recall | `TP / (TP + FN)` | `[0, 1]`; higher is better. Fraction of gold-positive pairs recovered. | Equivalent to one minus the under-stemming rate. `PRIMARY_OUTPUT` and `ALL_CANDIDATES`. |
|
||||||
|
| Specificity | `TN / (TN + FP)` | `[0, 1]`; higher is better. Fraction of negative pairs separated. | Sensitive to false conflations. `PRIMARY_OUTPUT` and `ALL_CANDIDATES`. |
|
||||||
|
| Balanced accuracy | `(recall + specificity) / 2` | `[0, 1]`; higher is better. Equal weight for positive and negative classes. | Primary navigation metric; less dominated by TN than ordinary accuracy, but not uniquely authoritative. |
|
||||||
|
| Pairwise accuracy | `(TP + TN) / (TP + TN + FP + FN)` | `[0, 1]`; higher is better. | Can be dominated by the very large TN class and is not the default ranking metric. |
|
||||||
|
| Pairwise error rate | `(FP + FN) / (TP + TN + FP + FN)` | `[0, 1]`; lower is better. | Also sensitive to the number of negative pairs. |
|
||||||
|
| F0.5 | `1.25 TP / (1.25 TP + 0.25 FN + FP)` | `[0, 1]`; higher is better. | Gives greater weight to precision and over-stemming avoidance. |
|
||||||
|
| F1 | `2 TP / (2 TP + FN + FP)` | `[0, 1]`; higher is better. | Equal precision/recall emphasis. |
|
||||||
|
| F2 | `5 TP / (5 TP + 4 FN + FP)` | `[0, 1]`; higher is better. | Gives greater weight to recall and under-stemming avoidance. |
|
||||||
|
| Jaccard | `TP / (TP + FP + FN)` | `[0, 1]`; higher is better. | Excludes TN. All policies. |
|
||||||
|
| Fowlkes–Mallows | `sqrt(precision * recall)` | `[0, 1]`; higher is better. | Geometric balance of precision and recall. All policies. |
|
||||||
|
| MCC | `(TP TN - FP FN) / sqrt((TP+FP)(TP+FN)(TN+FP)(TN+FN))` | `[-1, 1]`; higher is better. Uses all four counts. | Informative under imbalance; undefined for a zero product denominator. All policies with policy-specific interpretation. |
|
||||||
|
|
||||||
|
The general F-beta formula is `((1 + betaSquared) * TP) / (((1 + betaSquared) * TP) + (betaSquared * FN) + FP)`.
|
||||||
|
|
||||||
|
## Inapplicable partition metrics
|
||||||
|
|
||||||
|
Standard Adjusted Rand Index, homogeneity, completeness, V-measure, and normalized mutual information are not calculated. Their ordinary contingency-table definitions require every item to have one exclusive gold label. Assigning an arbitrary single label or duplicating a multi-membership form would change the scientific question and reintroduce the counting defect this methodology avoids. A future overlapping-clustering index would require a separately specified random model and interpretation; it must not be labelled as ordinary ARI or NMI.
|
||||||
|
|
||||||
|
## Aggregation and ranking
|
||||||
|
|
||||||
|
Macro metrics average defined per-language values, giving each language equal weight. Micro metrics sum TP, FP, FN, and TN before calculating a metric. Cross-stemmer aggregate comparisons require the exact common supported-language intersection; unsupported languages are not zero-filled.
|
||||||
|
|
||||||
|
Language tables sort by unrounded balanced accuracy, then MCC, F1, over-stemming rate, over-stemming error count, under-stemming rate, stemmer name, and stable policy order. Display rounding never controls rank.
|
||||||
|
|
||||||
|
Multiple metrics and Pearson/Spearman correlation datasets are published because metric suitability and correlation remain analytical questions. Strong correlation does not establish equivalence.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
Dictionary groups encode the available annotation, not every linguistic distinction. Homographs and polyfunctional forms may have several memberships, singleton rows contribute no relation by themselves, and group size affects pair counts. `ANY_CANDIDATE` is optimistic; `ALL_CANDIDATES` measures an overlapping graph; neither is a deterministic global assignment. Results characterize the tested versions, adapters, dictionaries, and preprocessing, not every deployment or domain.
|
||||||
62
docs/benchmarks/reference/methodology.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Benchmark Methodology
|
||||||
|
|
||||||
|
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-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
|
||||||
|
|
||||||
|
There are two distinct benchmark passes:
|
||||||
|
|
||||||
|
- Speed benchmarks process only changed dictionary pairs where `token != expectedRoot`. This removes already-root tokens from timing so a stemmer is measured on words that actually require a transformation. If a language has fewer than 5,000 changed pairs, the complete changed-pair sequence is repeated in stable order until the timing corpus has at least 5,000 tokens. Larger changed-pair corpora are not sampled or truncated.
|
||||||
|
- Quality benchmarks process the complete dictionary for the language. They report exact agreement over all tokens, exact agreement over changed tokens only, and preservation of tokens that are already roots.
|
||||||
|
|
||||||
|
Timing corpora are generated once per JMH JVM and kept in memory as shared `{token, expectedRoot}` arrays. Corpus construction, dictionary loading, trie loading, table loading, and analyzer construction are setup work and are not included in measured benchmark methods.
|
||||||
|
|
||||||
|
The deterministic and timed workloads are executed separately. Corpus statistics, patch-command counts, exact-root counters, coverage accuracy, and pairwise quality do not use or interpret warmup or runtime scores. Published speed and coverage-speed methods use three independent forks, five one-second warmup iterations and ten one-second measurement iterations per fork, one benchmark thread, and a fixed 6 GiB heap.
|
||||||
|
|
||||||
|
Performance is interpreted as average time per input token:
|
||||||
|
|
||||||
|
```text
|
||||||
|
timePerChangedTokenNs = JMH score ns/op / changedTimingTokenCount
|
||||||
|
```
|
||||||
|
|
||||||
|
This is necessary because Radixor dictionaries have different token counts by language.
|
||||||
|
|
||||||
|
## Exact-root quality and interpretation
|
||||||
|
|
||||||
|
Runtime and exact-root agreement must be interpreted separately. Light, minimal, possessive, and aggressive rule-based implementations deliberately address different scopes and may achieve lower latency by performing fewer transformations. A throughput advantage does not establish higher linguistic quality, and higher dictionary agreement does not establish lower operational cost.
|
||||||
|
|
||||||
|
The [English dictionary coverage benchmark](english-coverage.md) shows this operating curve explicitly: contracted tries reduce lookup cost in uniform regions, while reduced dictionary coverage still lowers changed-form precision.
|
||||||
|
|
||||||
|
## Normalization Policy
|
||||||
|
|
||||||
|
Radixor is measured over dictionary tokens from its own resources: lower-case with `Locale.ROOT`, diacritics preserved. The corpus is normalized during setup, so the Radixor benchmark path uses `FrequencyTrie.getNormalized(CharSequence)` and does not measure redundant lookup-time lowercasing or diacritic normalization.
|
||||||
|
|
||||||
|
Lucene TokenFilter paths include required normalization in the measured pipeline. Examples include lower-case normalization for filters requiring lower-case input, German normalization before German light/minimal stemming, and Persian decimal, Arabic, and Persian normalization before Persian stemming. No ASCII folding is applied to Czech or Polish paths, because those Lucene stemmers are diacritic-aware or dictionary/table-backed for those languages. TokenFilter throughput methods materialize each emitted `CharTermAttribute` as a `String` before passing it to the JMH `Blackhole`, so output consumption is easier to inspect and closer to the direct stemmer methods.
|
||||||
|
|
||||||
|
For right-to-left Radixor languages, patch application uses the traversal direction stored in trie metadata. This is required because static backward patch application is not correct for all registered language models.
|
||||||
|
|
||||||
|
## Quality Metric
|
||||||
|
|
||||||
|
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-08-10.csv`
|
||||||
|
- `build/reports/jmh/stemmer-accuracy-2026-08-10.txt`
|
||||||
|
|
||||||
|
Accuracy is computed from standard JMH secondary rows:
|
||||||
|
|
||||||
|
```text
|
||||||
|
allExactPercent = correctMatches / evaluatedTokens * 100
|
||||||
|
changedExactPercent = changedCorrectMatches / changedEvaluatedTokens * 100
|
||||||
|
rootPreservedPercent = rootPreservedMatches / rootEvaluatedTokens * 100
|
||||||
|
```
|
||||||
|
|
||||||
|
`allExactPercent` uses the complete dictionary. `changedExactPercent` uses only tokens where `token != expectedRoot`. `rootPreservedPercent` measures whether a stemmer leaves already-root dictionary entries unchanged.
|
||||||
|
|
||||||
|
Morfologik can emit multiple terms for one input token. The quality benchmark uses the first emitted term for exact-root accounting when no ranking weight is exposed. Throughput benchmarks for Morfologik TokenFilter paths consume all emitted terms.
|
||||||
|
|
||||||
|
External-stemmer quality reports use JMH auxiliary counter rows from one deterministic evaluation. Radixor exact-root counts are computed directly while the default-model corpus and preferred patch commands are audited, so all 20 default models have the same coverage even where no older JMH quality adapter existed. Documentation uses counter ratios and does not interpret quality benchmark timing scores.
|
||||||
|
|
||||||
|
Pairwise over-stemming, under-stemming, candidate-aware policies, and relation metrics are a separate analytical evaluation. See [Linguistic Quality Methodology](linguistic-quality.md); exact-root accuracy must not be interpreted as the complement of pairwise under-stemming.
|
||||||
|
Default rows use `Language.defaultModelId()`. Optional variants require a separate model field; `pl-pl-unimorph` and `pl-pl-polimorf` must never share an ambiguous Polish label. The benchmark runtime receives each resource exactly once from its individual model JAR through direct JMH runtime dependencies. See [Model Selection and Loading](../../model-selection-and-loading.md).
|
||||||
96
docs/benchmarks/reference/reproducibility.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# Reproducibility and Raw Data
|
||||||
|
|
||||||
|
## Published quality snapshot
|
||||||
|
|
||||||
|
- Machine-readable CSV: [stemming-quality.csv](../data/stemming-quality.csv)
|
||||||
|
- SHA-256 record: [stemming-quality.sha256](../data/stemming-quality.sha256)
|
||||||
|
- SHA-256: `d34f325da320a2e040b54d8d8b5c216d70448f08cfb8659a423e99882aa1afb5`
|
||||||
|
- Complete scenarios: 314
|
||||||
|
- Authoritative language universe: 20 languages
|
||||||
|
- 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 --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
|
||||||
|
```
|
||||||
|
|
||||||
|
For an immediate local preview, `mkdocs serve` works directly from the repository root. The checked-in
|
||||||
|
model catalog makes that source tree complete. After changing model metadata or model bytes, refresh it
|
||||||
|
with `./gradlew publishModelCatalogDocumentation`; verification rejects a stale checked-in catalog.
|
||||||
|
|
||||||
|
`stemmingQuality` performs the expensive complete evaluation and is intentionally not attached to `test` or `check`. It prepares JMH third-party dependencies automatically and writes:
|
||||||
|
|
||||||
|
- `build/reports/stemming-quality/stemming-quality.csv`
|
||||||
|
- `build/reports/stemming-quality/stemming-quality.md`
|
||||||
|
- `build/reports/stemming-quality/metric-correlations-pearson.csv`
|
||||||
|
- `build/reports/stemming-quality/metric-correlations-spearman.csv`
|
||||||
|
|
||||||
|
Audit mode is enabled with `-PstemmingQualityAudit=true`. Language, stemmer, dictionary-mode, output-policy, and ranking filters are documented on the central [stemming-quality page](../../stemming-quality.md). Filtered reports use separate filenames and cannot be accepted as publication sources.
|
||||||
|
|
||||||
|
`publishStemmingQualityDocumentation` validates the complete build CSV, copies a versioned documentation snapshot, and replaces only marked generated sections. `verifyStemmingQualityDocumentation` re-renders from the checked-in snapshot and fails on changed values, ordering, missing pages, duplicate keys, arithmetic inconsistencies, policy violations, or stale sections.
|
||||||
|
|
||||||
|
The model catalog and rendered site are build outputs under `build/`. They are generated for publication and are never maintained in Git.
|
||||||
|
|
||||||
|
For new measurements, record language, stable model ID, model artifact version, descriptor checksum, source dictionary identity/version, core revision, and benchmark configuration. JMH resolves the required default models and optional PoliMorf directly from their individual model JARs; these benchmark-only dependencies are not transitive to ordinary users.
|
||||||
|
|
||||||
|
Current model descriptors also record the official repository, dataset, license, attribution,
|
||||||
|
verification date, transformations, and source-revision status. Exact historical revisions were
|
||||||
|
not recorded for the legacy UniMorph imports; that limitation is disclosed with
|
||||||
|
`not-recorded-in-legacy-import` rather than reconstructed. Future imports must record the exact
|
||||||
|
upstream revision and source-archive checksum. This reproducibility limitation does not replace or
|
||||||
|
weaken the packaged license and attribution requirements.
|
||||||
|
|
||||||
|
Each UniMorph-derived model artifact carries its own notice with the canonical CC BY-SA 3.0 URI,
|
||||||
|
upstream attribution, transformations, ShareAlike statement, and Leo Galambos contribution notice.
|
||||||
|
The full CC legal text is not duplicated or presented as a root-project license. PoliMorf retains
|
||||||
|
its separately packaged BSD-2-Clause license.
|
||||||
|
|
||||||
|
For a future full PoliMorf measurement, also record the startup heap separately from benchmark parameters. Complete runtime construction is currently verified with a dedicated 6 GiB maximum heap; this limit is neither a retained-trie measurement nor a setting applied to ordinary JMH runs.
|
||||||
|
|
||||||
|
The Pages workflow publishes that staged documentation together with Javadoc, JUnit, PMD, JaCoCo, PIT, representative JMH, SBOM, optional dependency-check output, badge metadata, and retained build history. Its filesystem merge explicitly preserves the `builds/` tree in the separate `gh-pages` publication branch, so documentation regeneration cannot erase durable report URLs.
|
||||||
|
|
||||||
|
## Performance benchmark reproduction
|
||||||
|
|
||||||
|
The current accuracy, speed, and coverage commands are:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew --no-daemon benchmarkCorpusReport writeJmhRuntimeClasspath
|
||||||
|
tools/run-published-accuracy-benchmarks.sh 2026-08-10
|
||||||
|
tools/run-published-speed-benchmarks.sh 2026-08-10
|
||||||
|
```
|
||||||
|
|
||||||
|
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-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.
|
||||||
|
|
||||||
|
## Correlation and audit data
|
||||||
|
|
||||||
|
Pearson and Spearman files are generated from unrounded metric values in cohorts separated by dictionary mode and output policy. A missing coefficient means too few observations, undefined input, or zero variance. Correlation is descriptive and does not demonstrate that two metrics are scientifically interchangeable.
|
||||||
|
|
||||||
|
Audit reports preserve original multilingual forms and identify high-contributing dictionary groups. They are build artifacts rather than checked-in publication data because of their size. No documentation value is manually altered after generation.
|
||||||
|
|
||||||
|
## JMH badge compatibility
|
||||||
|
|
||||||
|
The quality documentation generator does not invoke JMH, change JMH result formats, or modify badge tooling. Existing JMH result paths and historical badge-compatible inputs remain independent. The repository currently publishes coverage and mutation badge metadata and retains JMH TXT/CSV artifacts as documented in [Environment and reports](environment.md).
|
||||||
|
See [Model Selection and Loading](../../model-selection-and-loading.md), [Stemmer Models](../../stemmer-models.md), and the generated [model catalog](../../stemmer-model-catalog.md) for current model identities.
|
||||||
30
docs/benchmarks/reference/tested-stemmers.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Tested Stemmer Inventory
|
||||||
|
|
||||||
|
The JMH adapter registry is authoritative for evaluated implementations and language mappings. Names below describe the implementation actually invoked, not an abstract algorithm in every possible implementation. Unsupported language combinations are omitted rather than scored as failures.
|
||||||
|
|
||||||
|
| 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 | 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 | 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. |
|
||||||
|
| CISTEM | Leonie Weissweiler, CISTEM project | Upstream `master` source path used by preparation; immutable commit not recorded | German | Single output | German stemming algorithm; benchmark-only implementation and gold-standard preparation remain under JMH infrastructure. |
|
||||||
|
| OpenNLP Porter | Apache OpenNLP project | Version resolved by `gradle/opennlp-benchmarks.gradle` and `gradle.lockfile` | English | Direct single output | Porter-family English baseline. |
|
||||||
|
| Lucene Porter source copy | Apache Lucene project | 10.5.0 source artifact | English | Package-isolated benchmark-only generated source; single output | Generated into the JMH build tree, never production code. |
|
||||||
|
| Paice/Husk Lancaster | Upstream Java implementation from `Hopper262/paice-husk-stemmer` | Configured upstream branch/revision in `gradle/paicehusk-benchmarks.gradle`; immutable commit not recorded | English | Direct single output | Aggressive rule-based English baseline; benchmark-only generated source. |
|
||||||
|
|
||||||
|
## Preprocessing and lifecycle
|
||||||
|
|
||||||
|
The quality evaluator calls the same adapter matrix used by JMH. Each language mapping is explicit. Retained dictionary forms are not evaluation-lowercased or normalized. Where an implementation requires preprocessing, such as Lucene German or Persian normalization, that operation is part of its documented adapter path. Stateful TokenFilters are reset through the same sequential lifecycle used by the benchmark and are not invoked concurrently.
|
||||||
|
|
||||||
|
Candidate sets are non-null, non-empty, contain the deterministic primary output, contain no null strings, and are deduplicated using exact Java string equality. Gold-standard group identity never selects, removes, or ranks a candidate.
|
||||||
|
|
||||||
|
## Coverage fairness
|
||||||
|
|
||||||
|
Radixor coverage is derived from registered default descriptors reconciled with language enumeration. Third-party coverage is the intersection of that universe with actual adapter support. Absence therefore means “not supported or not configured for this language,” not “zero quality.” Optional `pl-pl-polimorf` is a separate model row and does not replace default `pl-pl-unimorph`. Consult each language page for the exact evaluated rows.
|
||||||
|
|
||||||
|
Project authors and organizations are named only where repository configuration or source notices establish attribution. No broader authorship or license claim is inferred when metadata was not captured.
|
||||||
|
The JMH runtime configuration directly includes optional models needed for controlled comparisons; ordinary users do not receive these benchmark-only dependencies transitively. Historical rows retain their original model inputs. See [Model Selection and Loading](../../model-selection-and-loading.md).
|
||||||
3
docs/builds.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Historical Builds
|
||||||
|
|
||||||
|
The Pages publication workflow replaces this local placeholder with the retained build index.
|
||||||
@@ -1,256 +1,118 @@
|
|||||||
# Built-in Languages
|
# Built-in Languages and Default Models
|
||||||
|
|
||||||
Radixor ships with a curated set of bundled stemmer dictionaries that can be loaded directly from the library distribution. These resources are intended to provide an immediately usable baseline for evaluation, prototyping, integration, and general-purpose stemming workloads, while still fitting naturally into workflows where the bundled baseline is later refined, extended, or replaced with custom lexical data.
|
“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.
|
||||||
|
|
||||||
## Overview
|
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.
|
||||||
|
|
||||||
Bundled dictionaries are exposed through:
|
## Defaults and variants
|
||||||
|
|
||||||
```java
|
| Language | Java enum | Python alias | Default model ID | Java default artifact | Optional variants |
|
||||||
org.egothor.stemmer.StemmerPatchTrieLoader.Language
|
|---|---|---|---|---|---|
|
||||||
```
|
| 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` | — |
|
||||||
|
|
||||||
Each bundled dictionary is packaged with the library as a compressed UTF-8 text resource. When loaded, the resource is parsed by `StemmerDictionaryParser`, transformed into patch-command mappings, and compiled into a read-only `FrequencyTrie<String>` by `StemmerPatchTrieLoader`.
|
The maintained table deliberately avoids duplicating mutable provenance and checksum fields. Those values come from module metadata and are generated into the model catalog.
|
||||||
|
|
||||||
The bundled language definition also carries a language-level right-to-left flag. That flag is used by the loader to derive the `WordTraversalDirection` used for both trie-key construction and patch-command generation. In practice, left-to-right bundled languages use historical backward Egothor traversal, while right-to-left bundled languages use forward traversal over the stored form.
|
## The Polish dual-model case
|
||||||
|
|
||||||
## Supported bundled languages
|
`PL_PL` represents Polish. It is not an alias for either source dictionary.
|
||||||
|
|
||||||
The following bundled language identifiers are currently available:
|
- `loadCompiled(Language.PL_PL, ...)` resolves `pl-pl-unimorph`.
|
||||||
|
- `registry.require("pl-pl-polimorf")` resolves the optional PoliMorf model.
|
||||||
|
- `StemmerPatchTrieLoader.loadCompiled("pl-pl-polimorf", true, reductionMode)` constructs its compiled trie explicitly; complete construction is verified with a dedicated 6 GiB test heap.
|
||||||
|
- Both artifacts may be present and loaded independently.
|
||||||
|
- Adding PoliMorf does not change the language default.
|
||||||
|
- Radixor does not merge their dictionaries or outputs automatically.
|
||||||
|
|
||||||
| Language | Enum constant | Writing direction | Notes |
|
UniMorph and PoliMorf have different lexical sources and provenance. Applications should compare outputs with application-specific regression tests before changing an explicit model choice.
|
||||||
|---|---|---:|---|
|
|
||||||
| Czech | `CS_CZ` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Danish | `DA_DK` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| German | `DE_DE` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Spanish | `ES_ES` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Persian | `FA_IR` | RTL | Bundled dictionary uses forward traversal over the stored form |
|
|
||||||
| Finnish | `FI_FI` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| French | `FR_FR` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Hebrew | `HE_IL` | RTL | Bundled dictionary uses forward traversal over the stored form |
|
|
||||||
| Hungarian | `HU_HU` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Italian | `IT_IT` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Norwegian Bokmål | `NB_NO` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Dutch | `NL_NL` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Norwegian Nynorsk | `NN_NO` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Polish | `PL_PL` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Portuguese | `PT_PT` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Russian | `RU_RU` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Swedish | `SV_SE` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Ukrainian | `UK_UA` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| English | `US_UK` | LTR | Bundled general-purpose dictionary |
|
|
||||||
| Yiddish | `YI` | RTL | Bundled dictionary uses forward traversal over the stored form |
|
|
||||||
|
|
||||||
## Basic usage
|
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.
|
||||||
|
|
||||||
Load a bundled dictionary like this:
|
## Dependency patterns
|
||||||
|
|
||||||
```java
|
Minimal English:
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.egothor.stemmer.FrequencyTrie;
|
```groovy
|
||||||
import org.egothor.stemmer.ReductionMode;
|
dependencies {
|
||||||
import org.egothor.stemmer.StemmerPatchTrieLoader;
|
implementation 'org.egothor:radixor:<radixor-version>'
|
||||||
|
runtimeOnly 'org.egothor:radixor-model-us-uk-default:1.0.0'
|
||||||
public final class BuiltInExample {
|
|
||||||
|
|
||||||
private BuiltInExample() {
|
|
||||||
throw new AssertionError("No instances.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(final String[] arguments) throws IOException {
|
|
||||||
final FrequencyTrie<String> trie = StemmerPatchTrieLoader.load(
|
|
||||||
StemmerPatchTrieLoader.Language.US_UK,
|
|
||||||
true,
|
|
||||||
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
|
|
||||||
|
|
||||||
System.out.println(trie.traversalDirection());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This call loads the bundled dictionary resource for the selected language, parses its lexical entries, derives patch-command mappings, and compiles the result into a read-only trie.
|
All documented defaults:
|
||||||
|
|
||||||
## Example: stemming with a bundled dictionary
|
```groovy
|
||||||
|
dependencies {
|
||||||
```java
|
implementation 'org.egothor:radixor:<radixor-version>'
|
||||||
import java.io.IOException;
|
runtimeOnly 'org.egothor:radixor-models-standard:<catalog-version>'
|
||||||
|
|
||||||
import org.egothor.stemmer.FrequencyTrie;
|
|
||||||
import org.egothor.stemmer.PatchCommandEncoder;
|
|
||||||
import org.egothor.stemmer.ReductionMode;
|
|
||||||
import org.egothor.stemmer.StemmerPatchTrieLoader;
|
|
||||||
|
|
||||||
public final class EnglishExample {
|
|
||||||
|
|
||||||
private EnglishExample() {
|
|
||||||
throw new AssertionError("No instances.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(final String[] arguments) throws IOException {
|
|
||||||
final FrequencyTrie<String> trie = StemmerPatchTrieLoader.load(
|
|
||||||
StemmerPatchTrieLoader.Language.US_UK,
|
|
||||||
true,
|
|
||||||
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
|
|
||||||
|
|
||||||
final String word = "running";
|
|
||||||
final String patch = trie.get(word);
|
|
||||||
final String stem = PatchCommandEncoder.apply(word, patch, trie.traversalDirection());
|
|
||||||
|
|
||||||
System.out.println(word + " -> " + stem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Passing `trie.traversalDirection()` to `PatchCommandEncoder.apply(...)` is the correct general contract. It ensures that the patch is applied using the same logical traversal model that was used when the trie and its patch commands were produced.
|
The standard pack is metadata-only and excludes optional PoliMorf.
|
||||||
|
|
||||||
## Traversal behavior and right-to-left languages
|
Every individual model artifact carries its own provenance and licensing material. UniMorph
|
||||||
|
models carry different model-specific CC BY-SA 3.0 notices because their official language
|
||||||
|
repositories identify different lexical sources and contributors. Each notice preserves upstream
|
||||||
|
attribution and records the Radixor transformations and Leo Galambos contribution statement.
|
||||||
|
Legacy imports disclose when an exact historical revision was not recorded; this is a
|
||||||
|
reproducibility limitation, not a claim that the source or license is unknown.
|
||||||
|
|
||||||
Bundled dictionaries are not all processed identically.
|
## Loading a language default
|
||||||
|
|
||||||
For traditional left-to-right suffix-oriented resources, Radixor preserves historical Egothor behavior and traverses logical word characters backward. That means trie paths are constructed from the logical end of the stored word toward its beginning, and patch commands are interpreted with the same backward traversal model.
|
|
||||||
|
|
||||||
For bundled right-to-left languages such as Persian, Hebrew, and Yiddish, Radixor uses forward traversal over the stored form. In those cases:
|
|
||||||
|
|
||||||
- trie keys are traversed from the logical beginning of the stored form,
|
|
||||||
- patch commands are generated in that same forward direction,
|
|
||||||
- patch application must use `WordTraversalDirection.FORWARD`, which is naturally obtained from `trie.traversalDirection()`.
|
|
||||||
|
|
||||||
This design keeps the traversal policy explicit and consistent across dictionary loading, trie lookup, binary persistence, builder reconstruction, and patch application.
|
|
||||||
|
|
||||||
## Reduction behavior
|
|
||||||
|
|
||||||
Bundled dictionaries can be compiled using any supported `ReductionMode`. The reduction configuration controls how semantically equivalent subtrees are merged during trie compilation, while preserving the contract of the selected mode.
|
|
||||||
|
|
||||||
Typical entry points are:
|
|
||||||
|
|
||||||
- `StemmerPatchTrieLoader.load(language, storeOriginal, reductionMode)`
|
|
||||||
- `StemmerPatchTrieLoader.load(language, storeOriginal, reductionSettings)`
|
|
||||||
|
|
||||||
For most users, `ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS` is the most conservative general-purpose choice because it preserves ranked `getAll(...)` behavior.
|
|
||||||
|
|
||||||
## Intended role of bundled dictionaries
|
|
||||||
|
|
||||||
Bundled dictionaries should be understood as practical default resources.
|
|
||||||
|
|
||||||
They are a good fit when:
|
|
||||||
|
|
||||||
- a supported language is already available,
|
|
||||||
- immediate usability matters,
|
|
||||||
- a reasonable baseline is sufficient,
|
|
||||||
- the goal is evaluation, prototyping, or straightforward integration.
|
|
||||||
|
|
||||||
They are also well suited to staged refinement workflows in which a bundled base is loaded first, then extended with domain-specific vocabulary, and finally persisted as a custom binary artifact.
|
|
||||||
|
|
||||||
## Character representation
|
|
||||||
|
|
||||||
Bundled dictionaries are ordinary UTF-8 lexical resources. The parser reads them as text, the trie stores standard Java strings, and the patch-command model operates on general character sequences.
|
|
||||||
|
|
||||||
This is important for two reasons:
|
|
||||||
|
|
||||||
1. the built-in resources are not limited to ASCII-only processing,
|
|
||||||
2. the traversal model is orthogonal to character encoding and script choice.
|
|
||||||
|
|
||||||
In other words, right-to-left handling in the loader is about logical traversal strategy, not about introducing a separate character model.
|
|
||||||
|
|
||||||
## When to prefer custom dictionaries
|
|
||||||
|
|
||||||
A custom dictionary is usually the better choice when:
|
|
||||||
|
|
||||||
- domain-specific vocabulary materially affects stemming quality,
|
|
||||||
- lexical coverage must be controlled more precisely,
|
|
||||||
- a stronger lexical resource is available than the bundled baseline,
|
|
||||||
- operational requirements demand an explicitly curated, versioned artifact.
|
|
||||||
|
|
||||||
Typical examples include:
|
|
||||||
|
|
||||||
- technical terminology,
|
|
||||||
- biomedical language,
|
|
||||||
- legal or financial vocabulary,
|
|
||||||
- organization-specific product and process names,
|
|
||||||
- dictionaries maintained with project-specific validation rules.
|
|
||||||
|
|
||||||
## Production recommendation
|
|
||||||
|
|
||||||
For production systems, the most robust workflow is usually:
|
|
||||||
|
|
||||||
1. start from a bundled dictionary when it is suitable,
|
|
||||||
2. extend it with domain-specific forms if needed,
|
|
||||||
3. rebuild it into a binary artifact,
|
|
||||||
4. deploy that compiled binary artifact,
|
|
||||||
5. load it at runtime through `loadBinary(...)`.
|
|
||||||
|
|
||||||
This avoids repeated startup parsing and makes the deployed stemming behavior explicit, reproducible, and versionable.
|
|
||||||
|
|
||||||
## Example refinement workflow
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import java.io.IOException;
|
final FrequencyTrie<CompiledPatchCommand> trie =
|
||||||
import java.nio.file.Path;
|
StemmerPatchTrieLoader.loadCompiled(
|
||||||
|
|
||||||
import org.egothor.stemmer.FrequencyTrie;
|
|
||||||
import org.egothor.stemmer.FrequencyTrieBuilders;
|
|
||||||
import org.egothor.stemmer.ReductionMode;
|
|
||||||
import org.egothor.stemmer.ReductionSettings;
|
|
||||||
import org.egothor.stemmer.StemmerPatchTrieBinaryIO;
|
|
||||||
import org.egothor.stemmer.StemmerPatchTrieLoader;
|
|
||||||
|
|
||||||
public final class BundledRefinementExample {
|
|
||||||
|
|
||||||
private BundledRefinementExample() {
|
|
||||||
throw new AssertionError("No instances.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(final String[] arguments) throws IOException {
|
|
||||||
final FrequencyTrie<String> base = StemmerPatchTrieLoader.load(
|
|
||||||
StemmerPatchTrieLoader.Language.US_UK,
|
StemmerPatchTrieLoader.Language.US_UK,
|
||||||
true,
|
true,
|
||||||
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
|
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
|
||||||
|
|
||||||
final FrequencyTrie.Builder<String> builder = FrequencyTrieBuilders.copyOf(
|
|
||||||
base,
|
|
||||||
String[]::new,
|
|
||||||
ReductionSettings.withDefaults(
|
|
||||||
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS));
|
|
||||||
|
|
||||||
builder.put("microservices", "Na");
|
|
||||||
|
|
||||||
final FrequencyTrie<String> compiled = builder.build();
|
|
||||||
|
|
||||||
StemmerPatchTrieBinaryIO.write(compiled, Path.of("english-custom.radixor.gz"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The reconstructed builder preserves the traversal direction of the source trie, so refinements remain semantically aligned with the original bundled dictionary.
|
The call discovers the default descriptor from the runtime classpath, verifies its compressed resource, parses the GZip UTF-8 dictionary, and constructs a read-only trie. A missing default throws `StemmerModelNotFoundException`; there is no arbitrary fallback.
|
||||||
|
|
||||||
## Extending language support
|
## Writing direction
|
||||||
|
|
||||||
The built-in set is intentionally a practical baseline rather than a closed catalog. Additional languages, stronger lexical coverage, and improved dictionaries for currently supported languages are all natural extension paths.
|
Persian, Hebrew, and Yiddish declare right-to-left language metadata and use forward traversal over stored forms. Other defaults use historical backward Egothor traversal. This setting must remain aligned across dictionary parsing, trie lookup, patch generation, persistence, and application. Model identity remains separate from writing direction.
|
||||||
|
|
||||||
What matters most is not only the number of entries, but the quality, consistency, maintainability, and operational usefulness of the lexical resource being added.
|
## Custom and persisted alternatives
|
||||||
|
|
||||||
## Related API surface
|
Registered model artifacts are a convenient reproducible baseline. Applications may instead load caller-owned textual dictionaries or persist compiled `.radixor.gz` tries. Those paths are distinct from model artifact discovery:
|
||||||
|
|
||||||
The following types are typically involved when working with bundled dictionaries:
|
- a model `stemmer.gz` is a compressed textual dictionary plus descriptor/index metadata;
|
||||||
|
- a `.radixor.gz` created by the binary writer is a persisted compiled trie;
|
||||||
|
- a source dictionary is upstream input, not automatically a valid model artifact.
|
||||||
|
|
||||||
- `StemmerPatchTrieLoader`
|
See [Dictionary Format](dictionary-format.md), [CLI Compilation](cli-compilation.md), and [Stemmer Models](stemmer-models.md).
|
||||||
- `StemmerPatchTrieLoader.Language`
|
|
||||||
- `FrequencyTrie`
|
|
||||||
- `PatchCommandEncoder`
|
|
||||||
- `WordTraversalDirection`
|
|
||||||
- `ReductionMode`
|
|
||||||
- `ReductionSettings`
|
|
||||||
- `StemmerPatchTrieBinaryIO`
|
|
||||||
- `FrequencyTrieBuilders`
|
|
||||||
|
|
||||||
## Next steps
|
## Benchmark interpretation
|
||||||
|
|
||||||
- [Quick start](quick-start.md)
|
Benchmark rows must identify the Radixor model ID used. Default rows use the default IDs above. Optional Polish PoliMorf comparisons must be labeled `pl-pl-polimorf`; they are not interchangeable with the historical default Polish row. Continue with [Benchmarking](benchmarking.md) and [Reproducibility](benchmarks/reference/reproducibility.md).
|
||||||
- [Dictionary format](dictionary-format.md)
|
|
||||||
- [CLI compilation](cli-compilation.md)
|
|
||||||
- [Programmatic usage](programmatic-usage.md)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Radixor’s built-in language support provides immediate usability, a professionally defined baseline API, and a practical starting point for custom refinement. The bundled set now includes both left-to-right and right-to-left languages, and the library models that distinction explicitly through `WordTraversalDirection` so that trie construction, lookup, and patch application remain consistent.
|
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
# CLI Compilation
|
# Java CLI Compilation
|
||||||
|
|
||||||
Radixor provides a command-line compiler for turning line-oriented dictionary files into compact binary stemmer artifacts.
|
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.
|
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.
|
||||||
|
|
||||||
## What the CLI does
|
## What the CLI does
|
||||||
@@ -17,6 +23,10 @@ The `Compile` tool performs the following steps:
|
|||||||
|
|
||||||
This workflow is intentionally aligned with the same dictionary semantics used elsewhere in the library. Remarks introduced by `#` or `//` are supported through the shared dictionary parser.
|
This workflow is intentionally aligned with the same dictionary semantics used elsewhere in the library. Remarks introduced by `#` or `//` are supported through the shared dictionary parser.
|
||||||
|
|
||||||
|
## Create a registered custom model instead
|
||||||
|
|
||||||
|
To publish or deploy a custom dictionary through `StemmerModelRegistry`, do not merely rename CLI output to `stemmer.gz`. Create `models/<model-id>`, preserve the textual dictionary as a GZip module input, provide source metadata and a license, apply `org.egothor.radixor.model`, and run the model validation tasks. The resulting JAR has an index, descriptor, namespaced textual dictionary, checksum, and license. Detailed packaging is documented in [Stemmer Models](stemmer-models.md); selection is documented in [Model Selection and Loading](model-selection-and-loading.md).
|
||||||
|
|
||||||
## Basic usage
|
## Basic usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -171,6 +181,11 @@ The short form `-h` is also supported.
|
|||||||
|
|
||||||
Reduction mode is not only a storage decision. It also influences what semantics are preserved when the mutable trie is compiled into its canonical read-only form.
|
Reduction mode is not only a storage decision. It also influences what semantics are preserved when the mutable trie is compiled into its canonical read-only form.
|
||||||
|
|
||||||
|
Before the selected public reduction mode is applied, compilation performs uniform-subtree
|
||||||
|
contraction. If all reachable entries below a subtree select the same preferred patch command, the
|
||||||
|
compiler stores that subtree as an accepting leaf and removes the deeper branches. This reduces
|
||||||
|
runtime lookup depth without changing the preferred result returned by the standard stemming path.
|
||||||
|
|
||||||
### Ranked `getAll()` equivalence
|
### Ranked `getAll()` equivalence
|
||||||
|
|
||||||
`MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS` merges subtrees whose `getAll()` results remain equivalent for every reachable key suffix and whose local result ordering is the same.
|
`MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS` merges subtrees whose `getAll()` results remain equivalent for every reachable key suffix and whose local result ordering is the same.
|
||||||
@@ -203,9 +218,9 @@ The CLI is best used as a preparation step during packaging, deployment, or cont
|
|||||||
|
|
||||||
### Treat compiled files as versioned assets
|
### Treat compiled files as versioned assets
|
||||||
|
|
||||||
A `.radixor.gz` file should be handled as a versioned output artifact. It represents a specific dictionary state, a specific reduction mode, and, where relevant, specific dominant-result thresholds.
|
A `.radixor.gz` file should be handled as a versioned output artifact. It represents a specific dictionary state, a specific reduction mode, whether uniform-subtree contraction was used, and, where relevant, specific dominant-result thresholds.
|
||||||
|
|
||||||
Compiled tries also persist a human-readable metadata block (`key=value` lines) that includes format version, traversal direction, RTL indicator, reduction mode, dominant thresholds, diacritic-processing mode, and case-processing mode. After decompression, you can inspect this block directly to identify what dictionary/trie configuration the artifact contains. The current CLI uses `DiacriticProcessingMode.AS_IS`; custom diacritic stripping is available through the programmatic builder and loader APIs rather than through a CLI flag.
|
Compiled tries also persist a human-readable metadata block (`key=value` lines) that includes format version, traversal direction, RTL indicator, reduction mode, contraction flag, dominant thresholds, diacritic-processing mode, and case-processing mode. After decompression, you can inspect this block directly to identify what dictionary/trie configuration the artifact contains. The current CLI uses `DiacriticProcessingMode.AS_IS`; custom diacritic stripping is available through the programmatic builder and loader APIs rather than through a CLI flag.
|
||||||
|
|
||||||
### Choose reduction mode deliberately
|
### Choose reduction mode deliberately
|
||||||
|
|
||||||
@@ -215,6 +230,8 @@ The ranked `getAll()` mode is the safest default. The unordered and dominant mod
|
|||||||
|
|
||||||
Compilation is usually a one-time step and is generally fast. The more important operational consideration is memory usage during preparation, because the dictionary-derived mutable structure exists before reduction compacts it into the final read-only trie. This is especially relevant for very large source dictionaries.
|
Compilation is usually a one-time step and is generally fast. The more important operational consideration is memory usage during preparation, because the dictionary-derived mutable structure exists before reduction compacts it into the final read-only trie. This is especially relevant for very large source dictionaries.
|
||||||
|
|
||||||
|
The complete PoliMorf model is the current exceptional case: registered-model verification uses `runtimeModelIntegrationTest` with a 6 GiB maximum heap, configurable through `-PradixorLargeModelMaxHeap=<size>`. This setting applies only to that isolated test process, not the Gradle daemon or ordinary tests.
|
||||||
|
|
||||||
## Example workflow
|
## Example workflow
|
||||||
|
|
||||||
### 1. Prepare a dictionary
|
### 1. Prepare a dictionary
|
||||||
@@ -237,11 +254,12 @@ java org.egothor.stemmer.Compile \
|
|||||||
### 3. Load it in an application
|
### 3. Load it in an application
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
import org.egothor.stemmer.CompiledPatchCommand;
|
||||||
import org.egothor.stemmer.FrequencyTrie;
|
import org.egothor.stemmer.FrequencyTrie;
|
||||||
import org.egothor.stemmer.StemmerPatchTrieLoader;
|
import org.egothor.stemmer.StemmerPatchTrieLoader;
|
||||||
|
|
||||||
final FrequencyTrie<String> trie =
|
final FrequencyTrie<CompiledPatchCommand> trie =
|
||||||
StemmerPatchTrieLoader.loadBinary("english.radixor.gz");
|
StemmerPatchTrieLoader.loadBinaryCompiled("english.radixor.gz");
|
||||||
```
|
```
|
||||||
|
|
||||||
## Exit codes and error handling
|
## Exit codes and error handling
|
||||||
@@ -276,3 +294,5 @@ The CLI and the programmatic API implement the same conceptual preparation step.
|
|||||||
- [Quick start](quick-start.md)
|
- [Quick start](quick-start.md)
|
||||||
- [Programmatic usage](programmatic-usage.md)
|
- [Programmatic usage](programmatic-usage.md)
|
||||||
- [Architecture and reduction](architecture-and-reduction.md)
|
- [Architecture and reduction](architecture-and-reduction.md)
|
||||||
|
!!! note "Radixor 4 model artifacts"
|
||||||
|
Language dictionaries are independently versioned runtime model artifacts, not resources embedded in `radixor`. Language-based APIs resolve deterministic defaults through `StemmerModelRegistry`; see [Stemmer Models](stemmer-models.md).
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ This API is expected to remain supportable across future versions. The preferred
|
|||||||
|
|
||||||
Examples of likely additive evolution include:
|
Examples of likely additive evolution include:
|
||||||
|
|
||||||
- additional bundled language resources,
|
- additional independently versioned language models,
|
||||||
- fuller support for diacritics or native-script language resources,
|
- fuller support for diacritics or native-script language resources,
|
||||||
- expanded documentation and operational tooling,
|
- expanded documentation and operational tooling,
|
||||||
- new convenience methods that do not break existing code.
|
- new convenience methods that do not break existing code.
|
||||||
@@ -81,7 +81,13 @@ Compiled `FrequencyTrie` instances are immutable and thread-safe for concurrent
|
|||||||
|
|
||||||
### Stable patch application behavior
|
### Stable patch application behavior
|
||||||
|
|
||||||
`PatchCommandEncoder.apply(...)` remains the compatibility API for string-returning patch application. Buffer-oriented `applyTo(...)` overloads are additive APIs for caller-owned output storage. They do not retain output arrays, report insufficient capacity with `APPLY_INSUFFICIENT_CAPACITY`, and preserve the existing malformed-patch compatibility behavior where `apply(...)` preserves the source.
|
Serialized patch-command strings remain the stable stored representation used by textual dictionaries and binary artifacts. Runtime stemming should use `CompiledPatchCommand` values produced by `StemmerPatchTrieLoader.loadCompiled(...)`, `StemmerPatchTrieLoader.loadBinaryCompiled(...)`, or `PatchCommandEncoder.compile(...)`.
|
||||||
|
|
||||||
|
Language-default, descriptor, and stable model-ID `loadCompiled` entry points share the same compiled-value conversion. Explicit model IDs never fall back to a language default. Model loading is not cached, and construction-memory requirements are model-dependent; the unusually large PoliMorf input is verified separately with a 6 GiB maximum heap.
|
||||||
|
|
||||||
|
The historical `PatchCommandEncoder.apply(...)` and String-based `applyTo(...)` overloads remain compatibility APIs during the 2.x transition, but they are deprecated because they reparse the patch-command string on each application. See [Migration and Backward Compatibility](migration-and-backward-compatibility.md) for old and new code examples.
|
||||||
|
|
||||||
|
Compiled buffer-oriented `CompiledPatchCommand.applyTo(...)` overloads use caller-owned output storage. They do not retain output arrays and report insufficient capacity with `CompiledPatchCommand.APPLY_INSUFFICIENT_CAPACITY`.
|
||||||
|
|
||||||
### Stable reduction-mode intent
|
### Stable reduction-mode intent
|
||||||
|
|
||||||
@@ -89,6 +95,11 @@ Each public `ReductionMode` constant carries a semantic contract that should rem
|
|||||||
|
|
||||||
In other words, the implementation may evolve, but the intended meaning of modes such as ranked `getAll()` equivalence, unordered `getAll()` equivalence, and dominant `get()` equivalence should not drift casually.
|
In other words, the implementation may evolve, but the intended meaning of modes such as ranked `getAll()` equivalence, unordered `getAll()` equivalence, and dominant `get()` equivalence should not drift casually.
|
||||||
|
|
||||||
|
Internal pre-reduction optimizations may still change the physical compiled trie shape when they
|
||||||
|
preserve the documented lookup contract. Uniform-subtree contraction is one such optimization: it
|
||||||
|
can replace a subtree with an accepting leaf when all reachable entries choose the same preferred
|
||||||
|
patch command.
|
||||||
|
|
||||||
### Stable binary artifact purpose
|
### Stable binary artifact purpose
|
||||||
|
|
||||||
Compiled `.radixor.gz` artifacts are a first-class project output. Loading and persisting compiled stemmer artifacts is part of the intended usage model, not an incidental implementation side effect.
|
Compiled `.radixor.gz` artifacts are a first-class project output. Loading and persisting compiled stemmer artifacts is part of the intended usage model, not an incidental implementation side effect.
|
||||||
@@ -101,7 +112,7 @@ The following kinds of change are generally compatible with the project’s dire
|
|||||||
|
|
||||||
- improved internal data structures,
|
- improved internal data structures,
|
||||||
- changes inside `org.egothor.stemmer.trie`,
|
- changes inside `org.egothor.stemmer.trie`,
|
||||||
- expanded bundled dictionaries,
|
- expanded model dictionaries,
|
||||||
- additional supported languages,
|
- additional supported languages,
|
||||||
- improved native-script handling,
|
- improved native-script handling,
|
||||||
- better benchmarks, tests, and reports,
|
- better benchmarks, tests, and reports,
|
||||||
@@ -113,11 +124,11 @@ The project should be able to improve substantially while keeping the main user-
|
|||||||
|
|
||||||
Some areas should be treated as stable in intent but still approached carefully when changed.
|
Some areas should be treated as stable in intent but still approached carefully when changed.
|
||||||
|
|
||||||
### Bundled dictionary contents
|
### Independently versioned model contents
|
||||||
|
|
||||||
Bundled resources are versioned project data, not immutable language standards. Their contents may improve over time.
|
Model resources are independently versioned project data, not immutable language standards. Their contents may improve over time.
|
||||||
|
|
||||||
That means stemming outcomes can legitimately change when bundled dictionaries are refined or expanded. Such changes are compatible with the project’s direction, but they should still be understood as behavior changes at the lexical-resource level.
|
That means stemming outcomes can legitimately change when a model artifact is updated. Such changes are separate from core compatibility and should be reviewed as lexical-resource behavior changes.
|
||||||
|
|
||||||
### Binary format evolution
|
### Binary format evolution
|
||||||
|
|
||||||
@@ -150,7 +161,7 @@ Users should avoid depending on:
|
|||||||
- internal trie package details,
|
- internal trie package details,
|
||||||
- undocumented internal classes or intermediate representations,
|
- undocumented internal classes or intermediate representations,
|
||||||
- incidental internal ordering outside documented lookup semantics,
|
- incidental internal ordering outside documented lookup semantics,
|
||||||
- assumptions that bundled dictionary contents will never evolve,
|
- assumptions that a model's dictionary contents will never evolve across model versions,
|
||||||
- assumptions that internal binary-format details are frozen forever.
|
- assumptions that internal binary-format details are frozen forever.
|
||||||
|
|
||||||
If a behavior is important to your integration, it should ideally be documented at the public API or project-documentation level rather than inferred from internal implementation details.
|
If a behavior is important to your integration, it should ideally be documented at the public API or project-documentation level rather than inferred from internal implementation details.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
High-quality dictionaries are one of the most valuable ways to improve **Radixor**.
|
High-quality dictionaries are one of the most valuable ways to improve **Radixor**.
|
||||||
|
|
||||||
The project already includes practical bundled dictionaries for common use, but the long-term quality and language reach of the stemmer depend heavily on the quality of its lexical resources. Contributions are therefore welcome not only in the form of code changes, but also in the form of well-prepared dictionary data for existing or additional languages.
|
The project already publishes practical model dictionaries for common use, but long-term quality and language reach depend heavily on lexical-resource quality. Contributions may provide well-prepared model inputs for existing or additional languages.
|
||||||
|
|
||||||
This document explains what makes a dictionary contribution useful, how to structure it, and how to prepare it so that it integrates cleanly with the project.
|
This document explains what makes a dictionary contribution useful, how to structure it, and how to prepare it so that it integrates cleanly with the project.
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ For full format details, see [Dictionary format](dictionary-format.md).
|
|||||||
|
|
||||||
The most useful dictionary contributions generally fall into one of four categories.
|
The most useful dictionary contributions generally fall into one of four categories.
|
||||||
|
|
||||||
### 1. Stronger dictionaries for already bundled languages
|
### 1. Stronger models for already supported languages
|
||||||
|
|
||||||
Improving lexical quality for already supported languages is often more valuable than merely expanding the language list. Better coverage, cleaner canonicalization, and improved consistency directly improve practical stemming outcomes.
|
Improving lexical quality for already supported languages is often more valuable than merely expanding the language list. Better coverage, cleaner canonicalization, and improved consistency directly improve practical stemming outcomes.
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ That convention belongs to the supplied dictionaries, not to the underlying algo
|
|||||||
|
|
||||||
### 4. Domain-quality refinements
|
### 4. Domain-quality refinements
|
||||||
|
|
||||||
Some contributions may be more appropriate as curated domain extensions than as replacements for a general-purpose bundled dictionary. These are still useful when they are clearly scoped and operationally coherent.
|
Some contributions may be more appropriate as curated domain extensions than as replacements for a general-purpose default model. These are still useful when clearly scoped and operationally coherent.
|
||||||
|
|
||||||
## Normalization guidance
|
## Normalization guidance
|
||||||
|
|
||||||
@@ -139,6 +139,14 @@ A dictionary should read like a curated lexical resource, not like an unfiltered
|
|||||||
|
|
||||||
## Practical preparation workflow
|
## Practical preparation workflow
|
||||||
|
|
||||||
|
Before conversion, record the official source project and repository, exact revision or release,
|
||||||
|
source-archive checksum, retrieval date, dataset license and URI, supplied attribution, and any
|
||||||
|
required upstream notice. Add a model-specific notice describing every material transformation and
|
||||||
|
the license applied to the derived data, including its canonical URI. Record any protectable
|
||||||
|
Radixor-specific contribution without claiming ownership over the upstream data. A legacy model
|
||||||
|
may disclose that its historical revision was not recorded; new imports must record an exact
|
||||||
|
revision and source-archive checksum rather than using that sentinel.
|
||||||
|
|
||||||
A disciplined dictionary contribution should typically follow this path:
|
A disciplined dictionary contribution should typically follow this path:
|
||||||
|
|
||||||
1. prepare or normalize the lexical source,
|
1. prepare or normalize the lexical source,
|
||||||
@@ -183,7 +191,7 @@ This note does not need to be long. It simply needs to make the resource intelli
|
|||||||
|
|
||||||
## Bundled-resource expectations
|
## Bundled-resource expectations
|
||||||
|
|
||||||
Not every useful dictionary must automatically become a bundled language resource.
|
Not every useful dictionary must automatically become a published default model.
|
||||||
|
|
||||||
To be suitable for bundling, a dictionary should generally be:
|
To be suitable for bundling, a dictionary should generally be:
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,50 @@
|
|||||||
|
|
||||||
Radixor uses a simple line-oriented dictionary format designed for practical stemming workflows. The textual source format is tab-separated values, meaning that columns are separated by the tab character.
|
Radixor uses a simple line-oriented dictionary format designed for practical stemming workflows. The textual source format is tab-separated values, meaning that columns are separated by the tab character.
|
||||||
|
|
||||||
|
## Source text, model resource, and compiled trie
|
||||||
|
|
||||||
|
Three artifacts must not be confused:
|
||||||
|
|
||||||
|
| Artifact | Representation | Consumer |
|
||||||
|
|---|---|---|
|
||||||
|
| 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("<alias>")` |
|
||||||
|
| 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 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.
|
||||||
|
|
||||||
|
Comment headers in maintained model inputs summarize provenance but do not replace packaged legal
|
||||||
|
material. Each UniMorph-derived artifact includes a language-specific notice describing its
|
||||||
|
official repository, lexical source, upstream attribution, CC BY-SA 3.0 canonical URI, ShareAlike
|
||||||
|
status, Radixor transformations, and Leo Galambos's protectable model-data contributions. The
|
||||||
|
notice does not claim ownership over the underlying data. GZip packaging and descriptor/checksum
|
||||||
|
generation are disclosed transformations; the in-memory trie is a Radixor runtime structure.
|
||||||
|
|
||||||
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.
|
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
|
## Core structure
|
||||||
|
|
||||||
Each non-empty logical line has the following shape:
|
Each non-empty logical line has the following shape:
|
||||||
@@ -129,7 +171,11 @@ run running runs ran
|
|||||||
|
|
||||||
## Character set, compression, and normalization
|
## Character set, compression, and normalization
|
||||||
|
|
||||||
Dictionary files are read as UTF-8 text. Files loaded through `StemmerPatchTrieLoader.load(Path, ...)` may be either plain UTF-8 text or GZip-compressed UTF-8 text; the loader detects GZip input from the stream header instead of relying on the file extension. Bundled dictionaries are stored as GZip resources and are decoded as UTF-8 after decompression.
|
Dictionary files are read as UTF-8 text. Files loaded through `StemmerPatchTrieLoader.load(Path, ...)` may be either plain UTF-8 text or GZip-compressed UTF-8 text; the loader detects GZip input from the stream header instead of relying on the file extension. Registered model dictionaries are stored as GZip resources and are decoded as UTF-8 after decompression.
|
||||||
|
|
||||||
|
## Turn a dictionary into a model artifact
|
||||||
|
|
||||||
|
An arbitrary classpath copy is not a discoverable model. A model module places immutable input and its license under `models/<model-id>/src/modelInput/`, declares metadata and an independent version, and applies the model convention plugin. The build validates the input, copies identical bytes into a generated namespaced resource, generates `META-INF/radixor/models.index` and a descriptor, records SHA-256, and packages licensing material. See [Stemmer Models](stemmer-models.md#create-or-update-a-model-module) for the complete procedure and [Model Selection and Loading](model-selection-and-loading.md) for runtime use.
|
||||||
|
|
||||||
The parser and trie are not restricted to ASCII. Dictionary items are ordinary Java `String` values, and trie traversal works over Java `char` sequences. This supports Latin-script data with diacritics, Cyrillic data, Hebrew, Persian, Yiddish, and other scripts represented in UTF-8, subject to the normal Java `String` model and the project’s traversal configuration.
|
The parser and trie are not restricted to ASCII. Dictionary items are ordinary Java `String` values, and trie traversal works over Java `char` sequences. This supports Latin-script data with diacritics, Cyrillic data, Hebrew, Persian, Yiddish, and other scripts represented in UTF-8, subject to the normal Java `String` model and the project’s traversal configuration.
|
||||||
|
|
||||||
@@ -235,3 +281,5 @@ To understand how those dictionary lines are transformed into compiled runtime a
|
|||||||
- [CLI compilation](cli-compilation.md)
|
- [CLI compilation](cli-compilation.md)
|
||||||
- [Programmatic usage](programmatic-usage.md)
|
- [Programmatic usage](programmatic-usage.md)
|
||||||
- [Architecture and reduction](architecture-and-reduction.md)
|
- [Architecture and reduction](architecture-and-reduction.md)
|
||||||
|
!!! note "Radixor 4 model artifacts"
|
||||||
|
Language dictionaries are independently versioned runtime model artifacts, not resources embedded in `radixor`. Language-based APIs resolve deterministic defaults through `StemmerModelRegistry`; see [Stemmer Models](stemmer-models.md).
|
||||||
|
|||||||