Compare commits

...

16 Commits

Author SHA1 Message Date
b45e143c84 perf(loader): materialize compiled patch tries directly 2026-07-27 22:41:00 +02:00
d2b974e1b8 perf(serialization): add value dictionary to trie stream v7 2026-07-27 20:16:48 +02:00
75d31aeda5 perf(trie): compute compiled trie fingerprints lazily 2026-07-27 19:47:52 +02:00
a83f7c8b57 fix: updated gradle/verification-metadata.xml 2026-07-26 23:10:49 +02:00
b29699b763 Refresh multilingual benchmarks and fix overlapping gold evaluation
Recompute published benchmark results for all default language models,
exclude Polish Polimorf, add Hebrew documentation, and record the current
benchmark environment. Evaluate repeated surface forms as an overlapping
gold cover and publish only applicable metrics for candidate policies.
2026-07-23 17:06:41 +02:00
1f1b03c6a8 fix(build): isolate model catalog bundle preparation 2026-07-23 02:11:50 +02:00
62be4c9127 fix(build): exclude task-scoped integration tests from PIT 2026-07-23 00:15:35 +02:00
e7800b29c9 feat!: modularize stemmer models and release infrastructure
Move bundled stemmer dictionaries from the core artifact into independently
versioned model modules. Add model discovery and explicit model-loading APIs,
a standard model aggregate, a model BOM, and dedicated model and catalog
release workflows.

Add full PoliMorf integration, model provenance and licensing validation,
streaming model-input verification, strict dependency verification, consumer
resolution tests, Configuration Cache compatibility, and expanded JMH,
quality, documentation, and release checks.

Upgrade the CycloneDX and JMH Gradle plugins and remove Gradle 10 and Java
compiler deprecations.

BREAKING CHANGE: The core Radixor artifact no longer contains bundled stemmer
dictionaries. Applications must add the required model artifacts, the standard
model aggregate, or model dependencies managed through the Radixor model BOM.
2026-07-22 23:33:28 +02:00
9c5b9e331b fix(build): refresh dependency locks after JMH classpath isolation 2026-07-20 23:50:55 +02:00
05f3855b99 feat(benchmarks): expand multilingual stemming quality evaluation
* cover all Radixor dictionary languages
* add PRIMARY_OUTPUT, ANY_CANDIDATE, and ALL_CANDIDATES policies
* measure pairwise over-stemming and under-stemming
* add balanced accuracy and complementary quality metrics
* compare single-output and multi-output stemmers fairly
* improve result validation, reporting, and documentation
* move stemming quality tests into the standard test source set
* preserve the existing JMH benchmark structure and badge output
2026-07-20 23:20:17 +02:00
6d35f01303 fix: retain only the 10 latest GitHub Pages builds 2026-07-20 01:37:17 +02:00
049f44e697 Add CISTEM and Hunspell benchmarks and refresh results 2026-07-06 01:51:33 +02:00
a52e82933f feat: hunspell benchmarks 2026-07-04 22:38:58 +02:00
5a65de21d9 fix: workflow/benchmark/jmh exceeded the maximum execution time of 30m 2026-07-03 20:03:58 +02:00
3ce9cbc84f chore: update Gradle dependency verification metadata 2026-07-03 18:55:36 +02:00
38620d7e71 feat: prepare Radixor 3.0.0 with contracted tries and compiled patch commands
Introduce contracted compiled patch tries for faster lookup, make compiled
patch commands the primary runtime path, refresh stemmer benchmarks and
documentation, and restructure the documentation for 3.0.0 onboarding.

BREAKING CHANGE: Radixor 3.0.0 promotes compiled patch-command APIs and
new compiled trie artifacts as the primary runtime integration model.
2026-07-03 18:44:39 +02:00
317 changed files with 35094 additions and 1425 deletions

2
.gitattributes vendored
View File

@@ -9,4 +9,4 @@
# Binary files should be left untouched
*.jar binary
*.gz binary

View File

@@ -10,6 +10,8 @@ on:
paths:
- 'src/main/**'
- 'src/jmh/**'
- 'models/**'
- 'build-logic/**'
- 'build.gradle'
- 'gradle.properties'
- 'gradle.lockfile'
@@ -56,7 +58,7 @@ jobs:
test -f gradle/verification-metadata.xml
- name: Run JMH benchmarks
run: ./gradlew clean jmh --no-daemon
run: ./gradlew clean jmh -Pjmh.includes='.*EnglishStemmerComparisonBenchmark.*' --no-daemon
- name: Upload JMH reports
uses: actions/upload-artifact@v4
@@ -65,4 +67,4 @@ jobs:
path: |
build/reports/jmh/**
build/results/jmh/**
if-no-files-found: warn
if-no-files-found: warn

View File

@@ -51,7 +51,7 @@ jobs:
test -f gradle/verification-metadata.xml
- 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
if: always()
@@ -156,11 +156,14 @@ jobs:
test -f gradle.properties
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
env:
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
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
shell: bash
@@ -177,24 +180,7 @@ jobs:
shell: bash
env:
CENTRAL_BEARER_TOKEN: ${{ secrets.CENTRAL_BEARER_TOKEN }}
run: |
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"
run: ./tools/publish-central-bundle.sh "$(ls build/central-bundle/*.zip)" "org.egothor:radixor:${GITHUB_REF_NAME#release@}"
- name: Publish GitHub release assets
uses: softprops/action-gh-release@v2

37
.github/workflows/catalog-release.yml vendored Normal file
View 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
View 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}"

View File

@@ -10,6 +10,8 @@ on:
- 'src/main/**'
- 'src/test/**'
- 'src/jmh/**'
- 'models/**'
- 'build-logic/**'
- 'build.gradle'
- 'gradle.properties'
- 'gradle.lockfile'
@@ -70,7 +72,7 @@ jobs:
test -f gradle/verification-metadata.xml
- 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
shell: bash
@@ -88,6 +90,9 @@ jobs:
cd ..
fi
- name: Prepare staged MkDocs source
run: ./gradlew --no-daemon prepareMkDocsSource verifyModelCatalogDocumentation
- name: Stage published reports
shell: bash
run: |
@@ -191,11 +196,8 @@ jobs:
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_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)'
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
COVERAGE_BADGE_LINK='<li>Coverage Badge Metadata: not available</li>'
@@ -207,10 +209,6 @@ jobs:
MUTATION_BADGE_REPORT_MD='- Mutation badge metadata: not currently available'
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
<!doctype html>
<html lang="en">
@@ -239,7 +237,6 @@ jobs:
${SBOM_XML_LINK:-<li>SBOM (XML): not available</li>}
${COVERAGE_BADGE_LINK}
${MUTATION_BADGE_LINK}
${JMH_BADGE_LINK}
<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>}"; } \
@@ -254,7 +251,7 @@ jobs:
cp "${RUN_DIR}/index.html" "${LATEST_DIR}/index.html"
cat > docs/reports.md <<EOF
cat > build/mkdocs-source/reports.md <<EOF
# CI Reports
Radixor publishes durable CI artifacts to GitHub Pages on every qualifying run of \`.github/workflows/pages.yml\`.
@@ -271,24 +268,38 @@ jobs:
${SBOM_JSON_REPORT_MD}
${SBOM_XML_REPORT_MD}
## Benchmark and badge metadata
## Benchmark reports and badge metadata
${JMH_TXT_REPORT_MD}
${JMH_CSV_REPORT_MD}
${COVERAGE_BADGE_REPORT_MD}
${MUTATION_BADGE_REPORT_MD}
${JMH_BADGE_REPORT_MD}
## Historical runs
- [Browse historical build reports](https://leogalambos.github.io/Radixor/builds/)
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
echo "The following build report sets are currently published on GitHub Pages."
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 "|---:|---|---|"
@@ -308,19 +319,18 @@ jobs:
| while IFS=$'\t' read -r _ts build published; do
echo "| ${build} | ${published} | [Open](../builds/${build}/) |"
done
} > docs/builds.md
} > build/mkdocs-source/builds.md
- name: Build documentation site (MkDocs Material)
shell: bash
run: |
set -euo pipefail
mkdocs build --strict --site-dir .mkdocs-site
rsync -a --delete --exclude '.git' --exclude '.git/' --exclude 'builds/' .mkdocs-site/ .gh-pages/
mkdocs build --strict --config-file build/mkdocs/mkdocs.yml
rsync -a --delete --exclude '.git' --exclude '.git/' --exclude 'builds/' build/mkdocs-site/ .gh-pages/
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
EOF
rm -rf .mkdocs-site
- name: Commit and push gh-pages
shell: bash

15
.gitignore vendored
View File

@@ -37,6 +37,7 @@ local.properties
.settings/
.loadpath
.recommenders
.classpath
# External tool builders
.externalToolBuilders/
@@ -94,19 +95,17 @@ local.properties
.jqwik-database
##---------------------------------------------------------------------------------------- Gradle
.gradle
.gradle/
**/build/
!src/**/build/
# MkDocs generated site
/site/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
# Avoid ignoring the Gradle Wrapper JAR
!gradle-wrapper.jar
# Cache of project
# Gradle task-name cache
.gradletasknamecache
# Ignore Gradle build output directory
build

110
README.md
View File

@@ -7,9 +7,8 @@
[![Quality gates](https://github.com/leogalambos/Radixor/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/leogalambos/Radixor/actions/workflows/build.yml)
[![Coverage](https://img.shields.io/endpoint?url=https://leogalambos.github.io/Radixor/builds/latest/metrics/coverage-badge.json)](https://leogalambos.github.io/Radixor/builds/latest/coverage/)
[![Mutation score](https://img.shields.io/endpoint?url=https://leogalambos.github.io/Radixor/builds/latest/metrics/pitest-badge.json)](https://leogalambos.github.io/Radixor/builds/latest/pitest/)
[![English benchmark](https://img.shields.io/endpoint?url=https://leogalambos.github.io/Radixor/builds/latest/metrics/jmh-badge.json)](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, built around compact dictionary-derived 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.
@@ -23,6 +22,40 @@ It is particularly well suited to systems that need stemming which is:
It also retains the operational advantages of a compiled artifact model: predictable runtime behavior, direct binary loading, and clear separation between preparation-time compilation and live request processing.
## Add Radixor and a model
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
- [Why Radixor](#why-radixor)
@@ -50,18 +83,33 @@ Radixor is especially attractive when you want something more adaptable than sim
## 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% | 20.627 | 2.117 | 98.0 |
| 90% | 90.000% | 97.047% | 94.913% | 97.613% | 21.713 | 2.104 | 103.2 |
| 80% | 80.000% | 96.635% | 92.768% | 97.661% | 17.408 | 1.438 | 82.7 |
| 70% | 70.000% | 96.209% | 90.565% | 97.705% | 16.946 | 1.531 | 80.5 |
| 60% | 60.000% | 95.750% | 88.384% | 97.703% | 15.735 | 1.278 | 74.8 |
| 50% | 50.000% | 95.262% | 86.107% | 97.690% | 14.714 | 1.089 | 69.9 |
| 40% | 40.000% | 94.753% | 83.855% | 97.643% | 15.090 | 1.254 | 71.7 |
| 30% | 30.000% | 94.208% | 81.651% | 97.537% | 13.773 | 1.071 | 65.4 |
| 20% | 20.000% | 93.633% | 79.366% | 97.416% | 15.396 | 2.497 | 73.1 |
| 10% | 10.000% | 92.868% | 76.516% | 97.204% | 16.970 | 2.847 | 80.6 |
That places Radixor at approximately:
Column meanings:
- **4× the throughput of Snowball original Porter**
- **6× the throughput of Snowball English (Porter2)**
- `Used rows` is the requested deterministic percentage of English dictionary rows used to build the stemmer.
- `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.
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.
The contracted trie result is materially stronger than the older uncontracted profile: full English coverage reaches 97.478% all-token exactness and 97.197% changed-token exactness at 98.0 ns/token, while even a 10% deterministic dictionary slice remains at 92.868% all-token exactness and 76.516% changed-token exactness at 80.6 ns/token. This is why Radixor benchmark results are documented with both speed and quality instead of a single Porter speed badge.
For benchmark scope, workload design, environment, commands, report locations, and interpretation guidance, see [Benchmarking](docs/benchmarking.md).
@@ -101,6 +149,9 @@ Compared with the historical baseline, Radixor emphasizes:
- **frequency-aware deterministic ordering**
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**
Reduction can be tuned toward stronger compression or more conservative semantic preservation.
@@ -121,7 +172,7 @@ Compared with the historical baseline, Radixor emphasizes:
- Compressed binary persistence
- Programmatic compilation and loading
- CLI compilation tool
- Bundled language resources
- Independently versioned language-model resources
- Support for extending compiled stemmer tables
- Reproducible and auditable engineering posture
@@ -131,11 +182,17 @@ The repository keeps the front page concise and places detailed documentation un
### Getting Started
- [Fast Track](docs/fast-track.md)
The shortest path from adding core plus a model artifact to getting a first stem.
- [Quick Start](docs/quick-start.md)
A practical first guide to loading, compiling, and using Radixor.
A broader developer walkthrough covering loading options, querying, extension, persistence, and metadata.
- [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)
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)
How to write and normalize stemming dictionaries.
@@ -148,6 +205,9 @@ The repository keeps the front page concise and places detailed documentation un
- [Programmatic Usage Overview](docs/programmatic-usage.md)
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 bundled resources, textual dictionaries, binary artifacts, and direct builder usage.
@@ -157,6 +217,9 @@ The repository keeps the front page concise and places detailed documentation un
- [Extending and Persisting Compiled Tries](docs/programmatic-extending-and-persistence.md)
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
- [Architecture and Reduction Overview](docs/architecture-and-reduction.md)
@@ -185,7 +248,10 @@ The repository keeps the front page concise and places detailed documentation un
Engineering standards, validation posture, auditability, and operational model.
- [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)
Entry points to CI-published reports and GitHub Pages artifacts.
@@ -216,3 +282,19 @@ The goal is to keep the Egothor/Stempel lineage useful as a serious contemporary
## 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.
# 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'
}
```

25
build-logic/build.gradle Normal file
View 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'
}
}
}

View File

@@ -0,0 +1,8 @@
rootProject.name = 'radixor-build-logic'
dependencyResolutionManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}

View File

@@ -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}"]
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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.'
}
}
}

View File

@@ -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')
}
}

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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()
}
}

View File

@@ -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.
'''
}
}

View File

@@ -1,4 +1,5 @@
plugins {
id 'org.egothor.radixor.build-support'
id 'java'
id 'eclipse'
id 'application'
@@ -7,9 +8,9 @@ plugins {
id 'pmd'
id 'jacoco'
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.cyclonedx.bom' version '3.2.4'
id 'org.cyclonedx.bom' version '3.3.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 sbomReportsDirectory = layout.buildDirectory.dir('reports/sbom')
def jmhIncludesProperty = providers.gradleProperty('jmh.includes')
.orElse(providers.systemProperty('jmh.includes'))
def nvdApiKey = providers.gradleProperty('nvdApiKey')
.orElse(providers.environmentVariable('NVD_API_KEY'))
@@ -28,6 +31,11 @@ apply from: 'gradle/maven-pom.gradle'
configurations {
mockitoAgent
stemmingQualityJmhRuntime {
canBeConsumed = false
canBeResolved = true
extendsFrom(jmhImplementation, jmhRuntimeOnly)
}
}
java {
@@ -38,6 +46,10 @@ java {
targetCompatibility = JavaVersion.VERSION_21
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs.addAll(['-Xlint:deprecation', '-Xlint:unchecked'])
}
tasks.withType(AbstractArchiveTask).configureEach {
preserveFileTimestamps = false
reproducibleFileOrder = true
@@ -63,6 +75,11 @@ dependencyLocking {
dependencies {
jmhImplementation sourceSets.main.output
modelProjects().each { Project modelProject ->
testRuntimeOnly project(modelProject.path)
jmhRuntimeOnly project(modelProject.path)
}
testImplementation platform(libs.junit.bom)
testImplementation libs.junit.jupiter
testRuntimeOnly libs.junit.platform.launcher
@@ -70,12 +87,55 @@ dependencies {
testImplementation libs.mockito.core
testImplementation libs.mockito.junit.jupiter
testImplementation libs.jqwik
testImplementation gradleTestKit()
mockitoAgent(libs.mockito.core) {
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 {
failBuildOnCVSS = 7.0
failOnError = true
@@ -121,9 +181,10 @@ def splitTagExpression = { String tagsExpr ->
}
tasks.withType(Test).configureEach {
doFirst {
jvmArgs "-javaagent:${configurations.mockitoAgent.singleFile}"
}
final def mockitoAgentArguments = objects.newInstance(
org.egothor.radixor.MockitoAgentArgumentProvider)
mockitoAgentArguments.agentClasspath.from(configurations.mockitoAgent)
jvmArgumentProviders.add(mockitoAgentArguments)
/*
* 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 ->
task.useJUnitPlatform {
final def includes = splitTagExpression(includeTagsExpr)
@@ -154,10 +239,49 @@ def configureJUnitPlatformTags = { Test task, String includeTagsExpr, String exc
}
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'))
}
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,
String taskDescription = null, String testNameExcludePatterns = null ->
tasks.register(taskName, Test) {
@@ -169,10 +293,6 @@ def configureTaggedTestProfile = { String taskName, String includeTagsExpr, Stri
classpath = sourceSets.test.runtimeClasspath
dependsOn(tasks.named('compileTestJava'))
doFirst {
jvmArgs "-javaagent:${configurations.mockitoAgent.singleFile}"
}
if (testNameExcludePatterns != null && !testNameExcludePatterns.isBlank()) {
filter {
testNameExcludePatterns.split(',').each { String pattern ->
@@ -233,11 +353,19 @@ configureTaggedTestProfile(
configureTaggedTestProfile(
'ciRelease',
null,
'slow',
'slow,large-model',
'Release-profile validation of all non-slow tests.',
'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(
'ciNightly',
'fuzz',
@@ -313,25 +441,385 @@ tasks.named('check') {
// no-default, only on-demand: dependsOn(tasks.named('dependencyCheckAnalyze'))
}
allprojects {
tasks.matching { it.name == 'cyclonedxDirectBom' }.configureEach {
includeConfigs = ['runtimeClasspath', 'compileClasspath']
skipConfigs = ['testRuntimeClasspath', 'testCompileClasspath', 'jmh.*', 'mockitoAgent']
includeBomSerialNumber = true
includeLicenseText = false
includeMetadataResolution = true
includeBuildSystem = true
tasks.register('verifyCoreJarExcludesModels') {
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.named('cyclonedxBom') {
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()
configuration.setText(layout.projectDirectory.file('mkdocs.yml').asFile.getText('UTF-8')
+ '\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']
skipConfigs = ['testRuntimeClasspath', 'testCompileClasspath', 'jmh.*', 'mockitoAgent']
includeBomSerialNumber = true
includeLicenseText = false
includeMetadataResolution = true
includeBuildSystem = true
jsonOutput.set(sbomReportsDirectory.map { it.file('radixor-sbom.json') })
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 {
pitestVersion = '1.22.1'
junit5PluginVersion = '1.2.3'
@@ -354,7 +842,10 @@ pitest {
excludedTestClasses = [
'org.egothor.stemmer.CompileIntegrationTest',
'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']
timestampedReports = false
@@ -421,6 +912,7 @@ tasks.named('distTar') {
jmh {
jmhVersion = '1.37'
includeTests = false
warmupIterations = 3
iterations = 5
fork = 1
@@ -430,13 +922,25 @@ jmh {
resultsFile = benchmarkReportsDirectory.map { it.file('jmh-results.csv').asFile }.get()
humanOutputFile = benchmarkReportsDirectory.map { it.file('jmh-results.txt').asFile }.get()
duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE
if (jmhIncludesProperty.isPresent()) {
includes = [jmhIncludesProperty.get()]
}
}
tasks.named('jmh') {
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) {
group = 'verification'
description = 'Generates deterministic compiled trie regression artifacts.'
@@ -458,6 +962,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') {
doLast {
System.out.println("NVD API key present: " + (nvdApiKey != null && !nvdApiKey.isBlank()))
@@ -506,6 +1129,10 @@ javadoc {
}
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'
gradle.taskGraph.whenReady { taskGraph ->
def banner = """

View File

@@ -17,6 +17,10 @@ The build-time flow is:
Dictionary -> Mutable trie -> Reduced trie -> Compiled trie
```
For registered models, the dictionary is an independently versioned GZip resource discovered through a descriptor and verified before this flow begins. The model resource is input to trie construction, not a precompiled trie. See [Model Selection and Loading](model-selection-and-loading.md) for discovery and [Architecture](architecture.md) for component and release boundaries.
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.
## Why this matters
@@ -50,3 +54,5 @@ For most readers, the best order is:
- [Programmatic usage](programmatic-usage.md)
- [CLI compilation](cli-compilation.md)
- [Dictionary format](dictionary-format.md)
- [Model selection and loading](model-selection-and-loading.md)
- [Stemmer models](stemmer-models.md)

View File

@@ -2,6 +2,92 @@
This document explains the structural architecture of **Radixor**: what data is stored, how it flows through the build pipeline, and how runtime lookup works once a compiled trie has been produced.
## Component boundaries
| 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.
## 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
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,7 +96,7 @@ 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.
## End-to-end build flow
## Trie construction flow
The full build-time flow is:
@@ -53,6 +139,16 @@ Those patch-command values are inserted into a mutable trie keyed by the source
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
The reduced structure is frozen into an immutable compiled trie optimized for runtime lookup.
@@ -124,7 +220,7 @@ At runtime, lookup is conceptually simple:
3. retrieve one or more stored patch commands,
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:
@@ -170,6 +266,7 @@ The final compiled trie can be much smaller than the original dictionary for sev
- patch commands are compact,
- trie paths reuse shared structure,
- uniform preferred-command subtrees can be contracted into accepting leaves,
- reduction merges equivalent subtrees,
- binary persistence stores the already reduced form,
- GZip compression is applied on top of the binary format.
@@ -180,7 +277,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.
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:
@@ -207,3 +304,5 @@ Determinism matters not only for tests, but also for operational trust. It makes
- [Reduction Semantics](reduction-semantics.md)
- [Programmatic usage](programmatic-usage.md)
- [CLI compilation](cli-compilation.md)
- [Model selection and loading](model-selection-and-loading.md)
- [Stemmer models](stemmer-models.md)

View File

@@ -67,6 +67,85 @@
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 */
.md-typeset pre > code {
font-size: 0.72rem;

View File

@@ -1,159 +1,47 @@
# 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,
- English stemmer comparison on the same token workload.
## Key Takeaways
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),
- Snowball original Porter,
- Snowball English, commonly referred to as Porter2.
## Benchmark Documentation Map
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.
## 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 Radixors 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.
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).

View File

@@ -0,0 +1,309 @@
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","318385","284971","371381","28384","175199052749","371381","175199424130","0.000212","28384","313355","9.058097","0.434174040759","0.909419029535","0.999997880238","0.999997718233","0.954708454887","0.484848557029","0.587746607996","0.746086443827","0.416176453407","0.628367833992","0.628366984426","0.000002281767","","","","",""
"ENGLISH_SNOWBALL_PORTER2","US_UK","us-uk-default","1.0.0","8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","374384","568441","228735","568441","0","1","568441","297220","283368","368027","28014","161561621611","368027","161561989638","0.000228","28014","311382","8.996666","0.435017155489","0.910033335260","0.999997722069","0.999997548679","0.955015528665","0.485724531207","0.588647215295","0.746914872138","0.417080138768","0.629190045142","0.629189132274","0.000002451321","","","","",""
"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_DANISH_DIRECT","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","ALL_WORDS","PRIMARY_OUTPUT","4179","27921","32","27921","0","1","27921","5553","78545","4795","11150","389682670","4795","389687465","0.001230","11150","89695","12.431016","0.942464602832","0.875689837784","0.999987695268","0.999959092010","0.937838766526","0.928307194100","0.907851012801","0.888276938388","0.831251984337","0.908463909669","0.908443737019","0.000040907990","","","","",""
"SNOWBALL_DANISH_DIRECT","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4173","27875","32","27875","0","1","27875","5539","78440","4795","11100","388399540","4795","388404335","0.001235","11100","89540","12.396694","0.942392022587","0.876033057851","0.999987654618","0.999959085584","0.938010356234","0.928327968188","0.908001736362","0.888546539947","0.831504743732","0.908606936602","0.908586757624","0.000040914416","","","","",""
"SNOWBALL_DANISH_LUCENE_FILTER","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","ALL_WORDS","PRIMARY_OUTPUT","4179","27921","32","27921","0","1","27921","5546","78557","4961","11138","389682504","4961","389687465","0.001273","11138","89695","12.417638","0.940599631217","0.875823624505","0.999987269285","0.999958696913","0.937905446895","0.926889068757","0.907056629699","0.888055112164","0.829920977011","0.907633945058","0.907613558620","0.000041303087","","","","",""
"SNOWBALL_DANISH_LUCENE_FILTER","DA_DK","da-dk-default","1.0.0","3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4173","27875","32","27875","0","1","27875","5539","78440","4795","11100","388399540","4795","388404335","0.001235","11100","89540","12.396694","0.942392022587","0.876033057851","0.999987654618","0.999959085584","0.938010356234","0.928327968188","0.908001736362","0.888546539947","0.831504743732","0.908606936602","0.908586757624","0.000040914416","","","","",""
"SNOWBALL_DUTCH_DIRECT","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","ALL_WORDS","PRIMARY_OUTPUT","4992","26201","85","26201","0","1","26201","12051","29267","2987","35170","343165676","2987","343168663","0.000870","35170","64437","54.580443","0.907391331308","0.454195570868","0.999991295825","0.999888830652","0.727093433346","0.756436964017","0.605371751249","0.504599968276","0.434073920266","0.641975952605","0.641933549660","0.000111169348","","","","",""
"SNOWBALL_DUTCH_DIRECT","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4796","25402","84","25402","0","1","25402","11466","29053","2987","33965","322552096","2987","322555083","0.000926","33965","63018","53.897299","0.906772784020","0.461027008156","0.999990739566","0.999885462099","0.730508873861","0.759841613575","0.611268909508","0.511294841471","0.440163623968","0.646565343716","0.646521311443","0.000114537901","","","","",""
"SNOWBALL_DUTCH_LUCENE_FILTER","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","ALL_WORDS","PRIMARY_OUTPUT","4992","26201","85","26201","0","1","26201","14573","15204","759","49233","343167904","759","343168663","0.000221","49233","64437","76.404861","0.952452546514","0.235951394385","0.999997788260","0.999854349712","0.617974591322","0.592568341791","0.378208955224","0.277738198319","0.233204491073","0.474059602198","0.474021680915","0.000145650288","","","","",""
"SNOWBALL_DUTCH_LUCENE_FILTER","NL_NL","nl-nl-default","1.0.0","c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","4796","25402","84","25402","0","1","25402","14116","14874","715","48144","322554368","715","322555083","0.000222","48144","63018","76.397220","0.954134325486","0.236027801581","0.999997783324","0.999848554685","0.618012792452","0.593185189912","0.378439579172","0.277851461363","0.233379881694","0.474554767395","0.474515425112","0.000151445315","","","","",""
"SNOWBALL_FINNISH_DIRECT","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","ALL_WORDS","PRIMARY_OUTPUT","57027","1788784","292","1788784","0","1","1788784","381483","15082807","952306","16382792","1599840787031","952306","1599841739337","0.000060","16382792","31465599","52.065724","0.940611207417","0.479342757784","0.999999404750","0.999989164705","0.739671081267","0.788799811426","0.635056038739","0.531468350160","0.465261620083","0.671472389727","0.671468317739","0.000010835295","","","","",""
"SNOWBALL_FINNISH_DIRECT","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","54762","1734784","274","1734784","0","1","1734784","372232","14663371","936765","16097512","1504705198288","936765","1504706135053","0.000062","16097512","30760883","52.331112","0.939951485038","0.476688884386","0.999999377443","0.999988679564","0.738344130915","0.786987247415","0.632573283171","0.528815026735","0.462601231486","0.669376145960","0.669371899615","0.000011320436","","","","",""
"SNOWBALL_FINNISH_LUCENE_FILTER","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","ALL_WORDS","PRIMARY_OUTPUT","57027","1788784","292","1788784","0","1","1788784","377778","15121052","1288634","16344547","1599840450703","1288634","1599841739337","0.000081","16344547","31465599","51.944179","0.921471136011","0.480558212161","0.999999194524","0.999988978388","0.740278703342","0.778598131291","0.631685095974","0.531413183368","0.461651842069","0.665447610018","0.665443363449","0.000011021612","","","","",""
"SNOWBALL_FINNISH_LUCENE_FILTER","FI_FI","fi-fi-default","1.0.0","ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","54762","1734784","274","1734784","0","1","1734784","372232","14663371","936765","16097512","1504705198288","936765","1504706135053","0.000062","16097512","30760883","52.331112","0.939951485038","0.476688884386","0.999999377443","0.999988679564","0.738344130915","0.786987247415","0.632573283171","0.528815026735","0.462601231486","0.669376145960","0.669371899615","0.000011320436","","","","",""
"SNOWBALL_FRENCH_DIRECT","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","ALL_WORDS","PRIMARY_OUTPUT","59240","404011","2301","404011","0","1","404011","85627","3744838","1092238","1625361","81605779618","1092238","81606871856","0.001338","1625361","5370199","30.266309","0.774194575401","0.697336914330","0.999986615858","0.999966701086","0.848661765094","0.757496924470","0.733758618240","0.711462917671","0.579477680015","0.734761496202","0.734744993787","0.000033298914","","","","",""
"SNOWBALL_FRENCH_DIRECT","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","57698","400712","2133","400712","0","1","400712","84526","3736871","1088903","1619380","80278407962","1088903","80279496865","0.001356","1619380","5356251","30.233460","0.774356818202","0.697665400669","0.999986436101","0.999966266576","0.848825918385","0.757698693319","0.734013322497","0.711763857056","0.579795455624","0.735011537210","0.734994818860","0.000033733424","","","","",""
"SNOWBALL_FRENCH_LUCENE_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","ALL_WORDS","PRIMARY_OUTPUT","59240","404011","2301","404011","0","1","404011","85202","3742072","1097843","1628127","81605774013","1097843","81606871856","0.001345","1628127","5370199","30.317815","0.773168950281","0.696821849619","0.999986547175","0.999966598516","0.848404198397","0.756589837411","0.733012775372","0.710860736247","0.578547882033","0.734003418250","0.733986862867","0.000033401484","","","","",""
"SNOWBALL_FRENCH_LUCENE_FILTER","FR_FR","fr-fr-default","1.0.0","a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","57698","400712","2133","400712","0","1","400712","84810","3734232","1086494","1622019","80278410371","1086494","80279496865","0.001353","1622019","5356251","30.282729","0.774620254294","0.697172705312","0.999986466109","0.999966263711","0.848579585710","0.757784104203","0.733858787339","0.711398006457","0.579602638316","0.734876927298","0.734860210439","0.000033736289","","","","",""
"SNOWBALL_GERMAN_DIRECT","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","ALL_WORDS","PRIMARY_OUTPUT","54092","277266","1474","277266","0","1","277266","81649","742376","65811","602476","38436668082","65811","38436733893","0.000171","602476","1344852","44.798684","0.918569588474","0.552013158325","0.999998287810","0.999982613933","0.776005723068","0.810879063265","0.689607573295","0.599890587538","0.526260347085","0.712083211201","0.712076031428","0.000017386067","","","","",""
"SNOWBALL_GERMAN_DIRECT","DE_DE","de-de-default","1.0.0","cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5","LOWERCASE_GROUPS_ONLY","PRIMARY_OUTPUT","16007","145574","228","145574","0","1","145574","37843","506459","41477","351958","10594922057","41477","10594963534","0.000391","351958","858417","41.000819","0.924303203294","0.589991810507","0.999996085215","0.999962868855","0.794993947861","0.830216831177","0.720244490537","0.635998708058","0.562798507380","0.738465517386","0.738449797528","0.000037131145","","","","",""
"SNOWBALL_GERMAN_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_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 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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
19 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
20 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
21 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
22 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
23 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
24 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
25 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
26 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
27 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
28 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
29 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
30 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
31 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
32 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
33 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
34 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
35 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
36 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
37 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
38 ENGLISH_SNOWBALL_PORTER2 US_UK us-uk-default 1.0.0 8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460 ALL_WORDS PRIMARY_OUTPUT 396939 591946 250964 591946 0 1 591946 318385 284971 371381 28384 175199052749 371381 175199424130 0.000212 28384 313355 9.058097 0.434174040759 0.909419029535 0.999997880238 0.999997718233 0.954708454887 0.484848557029 0.587746607996 0.746086443827 0.416176453407 0.628367833992 0.628366984426 0.000002281767
39 ENGLISH_SNOWBALL_PORTER2 US_UK us-uk-default 1.0.0 8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460 LOWERCASE_GROUPS_ONLY PRIMARY_OUTPUT 374384 568441 228735 568441 0 1 568441 297220 283368 368027 28014 161561621611 368027 161561989638 0.000228 28014 311382 8.996666 0.435017155489 0.910033335260 0.999997722069 0.999997548679 0.955015528665 0.485724531207 0.588647215295 0.746914872138 0.417080138768 0.629190045142 0.629189132274 0.000002451321
40 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
41 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
42 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
43 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
44 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
45 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
46 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
47 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
48 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
49 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
50 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
51 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
52 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
53 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
54 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
55 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
56 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
57 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
58 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
59 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
60 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
61 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
62 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
63 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
64 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
65 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
66 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
67 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
68 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
69 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
70 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
71 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
72 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
73 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
74 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
75 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
76 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
77 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
78 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
79 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
80 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
81 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
82 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
83 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
84 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
85 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
86 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
87 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
88 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
89 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
90 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
91 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
92 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
93 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
94 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
95 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
96 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
97 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
98 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
99 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
100 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
101 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
102 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
103 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
104 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
105 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
106 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
107 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
108 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
109 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
110 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
111 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
112 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
113 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
114 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
115 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
116 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
117 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
118 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
119 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
120 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
121 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
122 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
123 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
124 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
125 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
126 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
127 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
128 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
129 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
130 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
131 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
132 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
133 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
134 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
135 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
136 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
137 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
138 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
139 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
140 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
141 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
142 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
143 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
144 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
145 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
146 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
147 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
148 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
149 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
150 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
151 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
152 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
153 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
154 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
155 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
156 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
157 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
158 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
159 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
160 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
161 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
162 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
163 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
164 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
165 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
166 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
167 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
168 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
169 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
170 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
171 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
172 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
173 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
174 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
175 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
176 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
177 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
178 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
179 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
180 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
181 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
182 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
183 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
184 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
185 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
186 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
187 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
188 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
189 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
190 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
191 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
192 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
193 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
194 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
195 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
196 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
197 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
198 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
199 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
200 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
201 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
202 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
203 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
204 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
205 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
206 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
207 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
208 SNOWBALL_DANISH_DIRECT DA_DK da-dk-default 1.0.0 3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90 ALL_WORDS PRIMARY_OUTPUT 4179 27921 32 27921 0 1 27921 5553 78545 4795 11150 389682670 4795 389687465 0.001230 11150 89695 12.431016 0.942464602832 0.875689837784 0.999987695268 0.999959092010 0.937838766526 0.928307194100 0.907851012801 0.888276938388 0.831251984337 0.908463909669 0.908443737019 0.000040907990
209 SNOWBALL_DANISH_DIRECT DA_DK da-dk-default 1.0.0 3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90 LOWERCASE_GROUPS_ONLY PRIMARY_OUTPUT 4173 27875 32 27875 0 1 27875 5539 78440 4795 11100 388399540 4795 388404335 0.001235 11100 89540 12.396694 0.942392022587 0.876033057851 0.999987654618 0.999959085584 0.938010356234 0.928327968188 0.908001736362 0.888546539947 0.831504743732 0.908606936602 0.908586757624 0.000040914416
210 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
211 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
212 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
213 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
214 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
215 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
216 SNOWBALL_FINNISH_DIRECT FI_FI fi-fi-default 1.0.0 ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f ALL_WORDS PRIMARY_OUTPUT 57027 1788784 292 1788784 0 1 1788784 381483 15082807 952306 16382792 1599840787031 952306 1599841739337 0.000060 16382792 31465599 52.065724 0.940611207417 0.479342757784 0.999999404750 0.999989164705 0.739671081267 0.788799811426 0.635056038739 0.531468350160 0.465261620083 0.671472389727 0.671468317739 0.000010835295
217 SNOWBALL_FINNISH_DIRECT FI_FI fi-fi-default 1.0.0 ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f LOWERCASE_GROUPS_ONLY PRIMARY_OUTPUT 54762 1734784 274 1734784 0 1 1734784 372232 14663371 936765 16097512 1504705198288 936765 1504706135053 0.000062 16097512 30760883 52.331112 0.939951485038 0.476688884386 0.999999377443 0.999988679564 0.738344130915 0.786987247415 0.632573283171 0.528815026735 0.462601231486 0.669376145960 0.669371899615 0.000011320436
218 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
219 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
220 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
221 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
222 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
223 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
224 SNOWBALL_GERMAN_DIRECT DE_DE de-de-default 1.0.0 cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5 ALL_WORDS PRIMARY_OUTPUT 54092 277266 1474 277266 0 1 277266 81649 742376 65811 602476 38436668082 65811 38436733893 0.000171 602476 1344852 44.798684 0.918569588474 0.552013158325 0.999998287810 0.999982613933 0.776005723068 0.810879063265 0.689607573295 0.599890587538 0.526260347085 0.712083211201 0.712076031428 0.000017386067
225 SNOWBALL_GERMAN_DIRECT DE_DE de-de-default 1.0.0 cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5 LOWERCASE_GROUPS_ONLY PRIMARY_OUTPUT 16007 145574 228 145574 0 1 145574 37843 506459 41477 351958 10594922057 41477 10594963534 0.000391 351958 858417 41.000819 0.924303203294 0.589991810507 0.999996085215 0.999962868855 0.794993947861 0.830216831177 0.720244490537 0.635998708058 0.562798507380 0.738465517386 0.738449797528 0.000037131145
226 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
227 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
228 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
229 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
230 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
231 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
232 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
233 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
234 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
235 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
236 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
237 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
238 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
239 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
240 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
241 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
242 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
243 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
244 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
245 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
246 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
247 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
248 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
249 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
250 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
251 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
252 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
253 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
254 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
255 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
256 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
257 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
258 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
259 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
260 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
261 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
262 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
263 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
264 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
265 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
266 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
267 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
268 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
269 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
270 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
271 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
272 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
273 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
274 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
275 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
276 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
277 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
278 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
279 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
280 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
281 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
282 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
283 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
284 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
285 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
286 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
287 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
288 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
289 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
290 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
291 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
292 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
293 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
294 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
295 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
296 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
297 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
298 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
299 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
300 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
301 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
302 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
303 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
304 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
305 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
306 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
307 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
308 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
309 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

View File

@@ -0,0 +1 @@
edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8 stemming-quality.csv

314
docs/benchmarks/index.md Normal file
View File

@@ -0,0 +1,314 @@
# 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 `98.0 ns/token`. Even with a deterministic 10% dictionary slice, it
keeps `92.868%` all-token exactness and `76.516%` changed-token exactness at `80.6 ns/token`.
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-07-23 language tables are generated from the unrounded JMH comparison report produced on the environment documented for this refresh. Readers should inspect the quality and speed dimensions side by side; no cross-language Pareto ranking is inferred from workloads with different dictionaries and token counts.
<!-- 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|3|
|Czech (`CS_CZ`)|LOWERCASE_GROUPS_ONLY|Radixor|0.997195|HUNSPELL CZECH LUCENE FILTER|0.144045088|no|3|
|Danish (`DA_DK`)|ALL_WORDS|Radixor|0.996243|SNOWBALL DANISH LUCENE FILTER|0.058337376|no|3|
|Danish (`DA_DK`)|LOWERCASE_GROUPS_ONLY|Radixor|0.996482|SNOWBALL DANISH DIRECT|0.058471663|no|3|
|Dutch (`NL_NL`)|ALL_WORDS|Radixor|0.988733|SNOWBALL DUTCH DIRECT|0.261639748|no|4|
|Dutch (`NL_NL`)|LOWERCASE_GROUPS_ONLY|Radixor|0.989114|SNOWBALL DUTCH DIRECT|0.258605347|no|4|
|English (`US_UK`)|ALL_WORDS|Radixor|0.965537|ENGLISH LUCENE PORTER COPIED|0.010741250|no|11|
|English (`US_UK`)|LOWERCASE_GROUPS_ONLY|Radixor|0.966202|ENGLISH LUCENE PORTER COPIED|0.011138557|no|11|
|Finnish (`FI_FI`)|ALL_WORDS|Radixor|0.984838|SNOWBALL FINNISH LUCENE FILTER|0.244558928|no|4|
|Finnish (`FI_FI`)|LOWERCASE_GROUPS_ONLY|Radixor|0.988242|SNOWBALL FINNISH DIRECT|0.249897933|no|4|
|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|PERSIAN LUCENE PERSIAN STEM FILTER|0.474147508|no|2|
|Persian (`FA_IR`)|LOWERCASE_GROUPS_ONLY|Radixor|0.976360|PERSIAN LUCENE PERSIAN STEM FILTER|0.474147508|no|2|
|Polish (`PL_PL`)|ALL_WORDS|Radixor|0.991105|POLISH LUCENE MORFOLOGIK FILTER|0.042712804|no|5|
|Polish (`PL_PL`)|LOWERCASE_GROUPS_ONLY|Radixor|0.991301|POLISH LUCENE MORFOLOGIK FILTER|0.042883749|no|5|
|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|2.000|2.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 DANISH DIRECT|1|0|0|1|3.000|3.000|
|SNOWBALL DANISH LUCENE FILTER|1|0|0|1|2.000|2.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 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|2.000|2.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 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 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- [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 -->

View 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `cs-cz-default` | `1.0.0` | `CS_CZ` | 5,113 | 56,612 | 10,049 | 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% | Full Radixor dictionary 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. |
## 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.395 | 0.066 | 72.9 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 381.189 | 32.563 | 8186.5 | 112.265 | Benchmark-only Czech Hunspell dictionary compared via Lucene HunspellStemFilter. |
| Lucene CzechStemFilter | `czechLuceneCzechStemFilter` | 3.125 | 0.042 | 67.1 | 0.920 | Czech suffix stemmer implemented as a Lucene TokenFilter. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 3 deterministic stemmers. The runner-up is `HUNSPELL CZECH LUCENE FILTER` at 0.854132, a difference of 0.142485. This rank does not imply leadership in throughput or every secondary metric.
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.997195** among 3 deterministic stemmers. The runner-up is `HUNSPELL CZECH LUCENE FILTER` at 0.853150, a difference of 0.144045. This rank does not imply leadership in throughput or every secondary metric.
### `ALL_WORDS`
This mode contains **7 result rows**, **3 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score.
#### `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%|
</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|
</details>
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | FowlkesMallows | 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|
</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|
</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, FowlkesMallows, 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 | FowlkesMallows | 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 **7 result rows**, **3 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score.
#### `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%|
</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|
</details>
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | FowlkesMallows | 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|
</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|
</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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,327 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `da-dk-default` | `1.0.0` | `DA_DK` | 4,179 | 32,256 | 8,356 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene SnowballFilter | 55.509% | 54.159% | 59.371% | Lucene TokenFilter integration path around the Snowball algorithm. |
| Official Snowball direct | 55.509% | 54.159% | 59.371% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
## 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.206 | 0.134 | 50.5 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Official Snowball direct | `snowballDirect[DANISH]` | 2.326 | 0.205 | 97.3 | 1.928 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[DANISH]` | 3.275 | 0.335 | 137.0 | 2.716 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 LUCENE FILTER` at 0.937905, a difference of 0.058337. This rank does not imply leadership in throughput or every secondary metric.
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.996482** among 3 deterministic stemmers. The runner-up is `SNOWBALL DANISH DIRECT` at 0.938010, a difference of 0.058472. This rank does not imply leadership in throughput or every secondary metric.
### `ALL_WORDS`
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 LUCENE FILTER|0.937905|0.001273%|12.417638%|
|3|SNOWBALL DANISH DIRECT|0.937839|0.001230%|12.431016%|
</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 LUCENE FILTER|PRIMARY_OUTPUT|0.940600|0.875824|0.999987|0.937905|0.999959|0.000041|
|3|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|0.942465|0.875690|0.999988|0.937839|0.999959|0.000041|
</details>
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | FowlkesMallows | MCC |
|---:|---|---|---:|---:|---:|---:|---:|---:|
|1|Radixor|PRIMARY_OUTPUT|0.998488|0.996229|0.993979|0.992486|0.996236|0.996235|
|2|SNOWBALL DANISH LUCENE FILTER|PRIMARY_OUTPUT|0.926889|0.907057|0.888055|0.829921|0.907634|0.907614|
|3|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|0.928307|0.907851|0.888277|0.831252|0.908464|0.908444|
</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 LUCENE FILTER|PRIMARY_OUTPUT|78557|4961|11138|389682504|4961 / 389687465|11138 / 89695|
|3|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|78545|4795|11150|389682670|4795 / 389687465|11150 / 89695|
</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, FowlkesMallows, 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 | FowlkesMallows | 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.938010|0.001235%|12.396694%|
|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.942392|0.876033|0.999988|0.938010|0.999959|0.000041|
|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 | FowlkesMallows | MCC |
|---:|---|---|---:|---:|---:|---:|---:|---:|
|1|Radixor|PRIMARY_OUTPUT|0.998585|0.996470|0.994363|0.992964|0.996476|0.996475|
|2|SNOWBALL DANISH DIRECT|PRIMARY_OUTPUT|0.928328|0.908002|0.888547|0.831505|0.908607|0.908587|
|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|78440|4795|11100|388399540|4795 / 388404335|11100 / 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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,351 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `nl-nl-default` | `1.0.0` | `NL_NL` | 4,992 | 31,466 | 9,981 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene HunspellStemFilter | 46.590% | 22.718% | 97.976% | Benchmark-only Dutch Hunspell dictionary compared via Lucene HunspellStemFilter. |
| Official Snowball direct | 15.954% | 8.992% | 30.939% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
| Lucene SnowballFilter | 12.620% | 5.441% | 28.073% | Lucene TokenFilter integration path around the Snowball algorithm. |
## Speed
Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower.
| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| Radixor | `radixor[DUTCH]` | 1.410 | 0.139 | 65.6 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 24.183 | 2.889 | 1125.6 | 17.156 | Benchmark-only Dutch Hunspell dictionary compared via Lucene HunspellStemFilter. |
| Official Snowball direct | `snowballDirect[DUTCH]` | 4.560 | 0.205 | 212.2 | 3.235 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[DUTCH]` | 7.762 | 0.262 | 361.3 | 5.506 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,419 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `us-uk-default` | `1.0.0` | `US_UK` | 396,939 | 1,004,374 | 793,874 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene EnglishMinimalStemFilter | 90.981% | 65.189% | 97.820% | Minimal English plural reduction, not a full stemmer. |
| Lucene KStemFilter | 80.076% | 76.608% | 80.996% | Krovetz-style English stemming TokenFilter; broader than minimal suffix reducers. |
| Lucene HunspellStemFilter | 80.243% | 12.750% | 98.139% | Benchmark-only English Hunspell dictionary compared via Lucene HunspellStemFilter. |
| Lucene EnglishPossessiveFilter | 79.032% | 0.003% | 99.987% | Possessive-ending remover only, not a full stemmer. |
| Snowball English / Porter2 | 40.342% | 46.296% | 38.763% | Porter2 rule-based suffix stemmer, distinct from original Porter. |
| Lucene PorterStemFilter | 39.538% | 46.201% | 37.772% | Lucene TokenFilter path for Porter suffix rules; not dictionary-root equivalent. |
| Lucene PorterStemmer direct copy | 39.538% | 46.201% | 37.772% | Direct Porter suffix-rule implementation generated under build for benchmark-only use. |
| OpenNLP PorterStemmer | 39.538% | 46.201% | 37.772% | Apache OpenNLP Porter suffix-rule implementation. |
| Snowball original Porter | 39.529% | 46.179% | 37.766% | Classic Porter rule-based suffix stemmer. |
| Paice/Husk Lancaster | 28.055% | 37.039% | 25.673% | Aggressive Paice/Husk rule stemmer that often produces shorter stems. |
## Speed
Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower.
| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| Radixor | `radixorUsUkProfiPreferredStem` | 17.489 | 1.380 | 83.1 | 1.000 | Full dictionary patch-command stemmer using compiled patch commands. |
| Lucene EnglishPossessiveFilter | `luceneEnglishPossessiveFilter` | 17.151 | 0.215 | 81.5 | 0.981 | Possessive-ending remover only; not a full stemmer. |
| Lucene EnglishMinimalStemFilter | `luceneEnglishMinimalStemFilter` | 18.522 | 0.152 | 88.0 | 1.059 | Narrow plural reduction filter; not a full stemmer. |
| Lucene PorterStemmer direct copy | `lucenePorterStemmerCopied` | 17.651 | 0.129 | 83.9 | 1.009 | Benchmark-only generated copy of Lucene package-private Porter implementation. |
| OpenNLP PorterStemmer | `opennlpPorterStemmer` | 17.681 | 0.139 | 84.0 | 1.011 | Apache OpenNLP Porter implementation. |
| Snowball original Porter | `snowballOriginalPorter` | 33.290 | 1.916 | 158.1 | 1.904 | Classic Porter suffix-rule stemmer; historical English baseline, not a dictionary-equivalent stemmer. |
| Lucene PorterStemFilter | `lucenePorterStemFilter` | 32.408 | 0.412 | 154.0 | 1.853 | Lucene TokenFilter integration path for Porter; includes TokenStream overhead. |
| Lucene KStemFilter | `luceneKStemFilter` | 45.877 | 0.425 | 217.9 | 2.623 | Krovetz-style English TokenFilter; broader than minimal suffix filters. |
| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 76.852 | 1.028 | 365.1 | 4.394 | Benchmark-only English Hunspell comparison using the benchmark Hunspell corpus. |
| Snowball English / Porter2 | `snowballEnglishPorter2` | 46.568 | 2.414 | 221.2 | 2.663 | Porter2 suffix-rule stemmer, distinct from original Porter. |
| Paice/Husk Lancaster | `paiceHuskLancaster` | 144.951 | 2.710 | 688.6 | 8.288 | Aggressive rule-based English stemmer. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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|&lt;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.954708|0.000212%|9.058097%|
|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|&lt;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.434174|0.909419|0.999998|0.954708|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 | FowlkesMallows | 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.484849|0.587747|0.746086|0.416176|0.628368|0.628367|
|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|284971|371381|28384|175199052749|371381 / 175199424130|28384 / 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, FowlkesMallows, 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|&lt;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 | FowlkesMallows | 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|&lt;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.955016|0.000228%|8.996666%|
|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|&lt;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.435017|0.910033|0.999998|0.955016|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 | FowlkesMallows | 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.485725|0.588647|0.746915|0.417080|0.629190|0.629189|
|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|283368|368027|28014|161561621611|368027 / 161561989638|28014 / 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, FowlkesMallows, 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|&lt;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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,335 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `fi-fi-default` | `1.0.0` | `FI_FI` | 57,027 | 1,865,215 | 110,525 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene SnowballFilter | 10.991% | 10.268% | 22.471% | Lucene TokenFilter integration path around the Snowball algorithm. |
| Official Snowball direct | 10.991% | 10.268% | 22.471% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
| Lucene FinnishLightStemFilter | 4.351% | 4.294% | 5.264% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. |
## 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` | 289.539 | 4.136 | 165.0 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene FinnishLightStemFilter | `finnishLuceneFinnishLightStemFilter` | 175.789 | 4.827 | 100.2 | 0.607 | Light Finnish suffix stemmer. |
| Official Snowball direct | `snowballDirect[FINNISH]` | 259.889 | 8.924 | 148.1 | 0.898 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[FINNISH]` | 332.524 | 9.490 | 189.5 | 1.148 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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.738344, a difference of 0.249898. 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|&lt;0.000001%|3.032474%|
|2|SNOWBALL FINNISH LUCENE FILTER|0.740279|0.000081%|51.944179%|
|3|SNOWBALL FINNISH DIRECT|0.739671|0.000060%|52.065724%|
|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.940611|0.479343|0.999999|0.739671|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 | FowlkesMallows | 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.788800|0.635056|0.531468|0.465262|0.671472|0.671468|
|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|15082807|952306|16382792|1599840787031|952306 / 1599841739337|16382792 / 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, FowlkesMallows, 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|&lt;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 | FowlkesMallows | 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|&lt;0.000001%|2.351587%|
|2|SNOWBALL FINNISH DIRECT|0.738344|0.000062%|52.331112%|
|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.939951|0.476689|0.999999|0.738344|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 | FowlkesMallows | MCC |
|---:|---|---|---:|---:|---:|---:|---:|---:|
|1|Radixor|PRIMARY_OUTPUT|0.995185|0.988089|0.981093|0.976459|0.988159|0.988159|
|2|SNOWBALL FINNISH DIRECT|PRIMARY_OUTPUT|0.786987|0.632573|0.528815|0.462601|0.669376|0.669372|
|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|14663371|936765|16097512|1504705198288|936765 / 1504706135053|16097512 / 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, FowlkesMallows, 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|&lt;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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,369 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `fr-fr-default` | `1.0.0` | `FR_FR` | 59,240 | 474,110 | 108,141 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene HunspellStemFilter | 68.923% | 63.617% | 86.876% | Benchmark-only French Hunspell dictionary compared via Lucene HunspellStemFilter. |
| Lucene FrenchMinimalStemFilter | 11.472% | 6.236% | 29.192% | Minimal suffix reducer; narrow baseline, not a full stemmer. |
| Lucene SnowballFilter | 8.551% | 5.183% | 19.952% | Lucene TokenFilter integration path around the Snowball algorithm. |
| Official Snowball direct | 8.462% | 5.067% | 19.952% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
| Lucene FrenchLightStemFilter | 6.377% | 3.965% | 14.540% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. |
## 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` | 49.340 | 0.986 | 134.8 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 1781.070 | 43.544 | 4866.7 | 36.098 | Benchmark-only French Hunspell dictionary compared via Lucene HunspellStemFilter. |
| Lucene FrenchMinimalStemFilter | `frenchLuceneFrenchMinimalStemFilter` | 19.093 | 0.681 | 52.2 | 0.387 | Minimal French suffix reducer; narrow baseline. |
| Lucene FrenchLightStemFilter | `frenchLuceneFrenchLightStemFilter` | 29.553 | 0.465 | 80.8 | 0.599 | Light French suffix stemmer. |
| Official Snowball direct | `snowballDirect[FRENCH]` | 121.376 | 0.865 | 331.7 | 2.460 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[FRENCH]` | 126.574 | 4.671 | 345.9 | 2.565 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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|&lt;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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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|&lt;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 | FowlkesMallows | 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, FowlkesMallows, 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|&lt;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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,389 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `de-de-default` | `1.0.0` | `DE_DE` | 54,092 | 333,036 | 90,535 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene HunspellStemFilter | 47.064% | 29.661% | 93.678% | Benchmark-only German Hunspell dictionary compared via Lucene HunspellStemFilter. |
| CISTEM (German) | 24.675% | 23.724% | 27.222% | Benchmark-only CISTEM implementation. |
| Lucene GermanLightStemFilter | 37.434% | 35.465% | 42.707% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. |
| Lucene GermanMinimalStemFilter | 27.640% | 24.951% | 34.844% | Minimal suffix reducer; narrow baseline, not a full stemmer. |
| Lucene SnowballFilter | 30.956% | 28.853% | 36.589% | Lucene TokenFilter integration path around the Snowball algorithm. |
| Official Snowball direct | 30.481% | 29.027% | 34.376% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
| Lucene GermanStemFilter | 21.559% | 19.312% | 27.576% | German Lucene stemming TokenFilter; broader than minimal/light variants. |
## Speed
Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower.
| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| Radixor | `germanRadixor` | 40.571 | 1.647 | 167.3 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| CISTEM | `germanCistem` | 305.166 | 4.590 | 1258.4 | 7.522 | Benchmark-only CISTEM implementation. |
| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 291.791 | 21.769 | 1203.3 | 7.192 | Benchmark-only German Hunspell dictionary compared via Lucene HunspellStemFilter. |
| Lucene GermanMinimalStemFilter | `germanLuceneGermanMinimalStemFilter` | 23.903 | 0.208 | 98.6 | 0.589 | Minimal German suffix reduction; narrow baseline. |
| Lucene GermanLightStemFilter | `germanLuceneGermanLightStemFilter` | 24.695 | 0.322 | 101.8 | 0.609 | Light German suffix stemmer; narrower than a dictionary stemmer. |
| Lucene GermanStemFilter | `germanLuceneGermanStemFilter` | 72.140 | 1.544 | 297.5 | 1.778 | Older German stemming TokenFilter with normalization requirements. |
| Lucene SnowballFilter | `luceneSnowballFilter[GERMAN]` | 110.086 | 2.315 | 454.0 | 2.713 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
| Official Snowball direct | `snowballDirect[GERMAN]` | 100.122 | 2.623 | 412.9 | 2.468 | Official Snowball generated Java stemmer; direct API. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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.776006|0.000171%|44.798684%|
|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.918570|0.552013|0.999998|0.776006|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 | FowlkesMallows | 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.810879|0.689608|0.599891|0.526260|0.712083|0.712076|
|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|742376|65811|602476|38436668082|65811 / 38436733893|602476 / 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, FowlkesMallows, 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 | FowlkesMallows | 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.794994|0.000391%|41.000819%|
|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.924303|0.589992|0.999996|0.794994|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 | FowlkesMallows | 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.830217|0.720244|0.635999|0.562799|0.738466|0.738450|
|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|506459|41477|351958|10594922057|41477 / 10594963534|351958 / 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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,301 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `he-il-default` | `1.0.0` | `HE_IL` | 2,358 | 61,071 | 4,715 | 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.921 | 0.140 | 69.6 | 1.000 | Full default-model Radixor dictionary patch-command stemmer. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the default language model used to train the compiled trie.
- 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,333 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `hu-hu-default` | `1.0.0` | `HU_HU` | 19,406 | 935,713 | 38,775 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene SnowballFilter | 66.445% | 66.938% | 55.043% | Lucene TokenFilter integration path around the Snowball algorithm. |
| Official Snowball direct | 66.445% | 66.938% | 55.043% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
| Lucene HungarianLightStemFilter | 14.748% | 14.777% | 14.086% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. |
## 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` | 61.205 | 0.944 | 68.2 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene HungarianLightStemFilter | `hungarianLuceneHungarianLightStemFilter` | 92.090 | 3.410 | 102.7 | 1.505 | Light Hungarian suffix stemmer. |
| Official Snowball direct | `snowballDirect[HUNGARIAN]` | 152.969 | 4.468 | 170.5 | 2.499 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[HUNGARIAN]` | 188.807 | 5.290 | 210.5 | 3.085 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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|&lt;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 | FowlkesMallows | 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, FowlkesMallows, 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|&lt;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 | FowlkesMallows | 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|&lt;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 | FowlkesMallows | 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, FowlkesMallows, 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|&lt;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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View 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.

View File

@@ -0,0 +1,332 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `it-it-default` | `1.0.0` | `IT_IT` | 10,009 | 337,546 | 20,004 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene ItalianLightStemFilter | 0.466% | 0.479% | 0.270% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. |
| Lucene SnowballFilter | 0.041% | 0.043% | 0.010% | Lucene TokenFilter integration path around the Snowball algorithm. |
| Official Snowball direct | 0.041% | 0.043% | 0.010% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
## Speed
Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower.
| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| Radixor | `italianRadixor` | 25.073 | 0.534 | 79.0 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene ItalianLightStemFilter | `italianLuceneItalianLightStemFilter` | 15.956 | 0.184 | 50.2 | 0.636 | Light Italian suffix stemmer. |
| Official Snowball direct | `snowballDirect[ITALIAN]` | 115.818 | 3.174 | 364.7 | 4.619 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[ITALIAN]` | 123.974 | 4.405 | 390.4 | 4.944 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,343 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `nb-no-default` | `1.0.0` | `NB_NO` | 17,929 | 90,757 | 33,376 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene NorwegianMinimalStemFilter | 57.107% | 53.913% | 62.599% | Minimal suffix reducer; narrow baseline, not a full stemmer. |
| Official Snowball direct | 54.824% | 51.791% | 60.040% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
| Lucene SnowballFilter | 54.803% | 51.780% | 60.001% | Lucene TokenFilter integration path around the Snowball algorithm. |
| Lucene NorwegianLightStemFilter | 52.136% | 50.616% | 54.749% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. |
## 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.401 | 0.055 | 59.3 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene NorwegianMinimalStemFilter | `norwegianBokmalLuceneNorwegianMinimalStemFilter` | 2.943 | 0.023 | 51.3 | 0.865 | Minimal Norwegian suffix reducer. |
| Lucene NorwegianLightStemFilter | `norwegianBokmalLuceneNorwegianLightStemFilter` | 3.358 | 0.036 | 58.5 | 0.987 | Light Norwegian suffix stemmer. |
| Official Snowball direct | `snowballDirect[NORWEGIAN_BOKMAL]` | 4.378 | 0.295 | 76.3 | 1.287 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[NORWEGIAN_BOKMAL]` | 6.114 | 0.436 | 106.5 | 1.797 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,323 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `nn-no-default` | `1.0.0` | `NN_NO` | 4,688 | 19,651 | 6,089 | 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% | Full Radixor dictionary patch-command stemmer. |
| Official Snowball direct | 60.974% | 60.212% | 62.670% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
| Lucene SnowballFilter | 60.918% | 60.146% | 62.638% | Lucene TokenFilter integration path around the Snowball algorithm. |
## Speed
Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower.
| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| Radixor | `radixor[NORWEGIAN_NYNORSK]` | 0.617 | 0.062 | 45.5 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Official Snowball direct | `snowballDirect[NORWEGIAN_NYNORSK]` | 0.955 | 0.076 | 70.4 | 1.548 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[NORWEGIAN_NYNORSK]` | 1.352 | 0.106 | 99.7 | 2.191 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,311 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `fa-ir-default` | `1.0.0` | `FA_IR` | 69 | 3,770 | 138 | 3,632 |
## 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene PersianStemFilter | 1.485% | 0.000% | 40.580% | Lucene Persian suffix stemmer with required normalization in the measured path. |
## 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.243 | 0.004 | 66.9 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene PersianStemFilter | `persianLucenePersianStemFilter` | 0.469 | 0.007 | 129.1 | 1.930 | Persian suffix stemmer with Lucene normalization in the measured path. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 2 deterministic stemmers. The runner-up is `PERSIAN LUCENE PERSIAN STEM FILTER` at 0.502212, a difference of 0.474148. This rank does not imply leadership in throughput or every secondary metric.
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.976360** among 2 deterministic stemmers. The runner-up is `PERSIAN LUCENE PERSIAN STEM FILTER` at 0.502212, a difference of 0.474148. This rank does not imply leadership in throughput or every secondary metric.
### `ALL_WORDS`
This mode contains **4 result rows**, **2 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score.
#### `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|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|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 | FowlkesMallows | MCC |
|---:|---|---|---:|---:|---:|---:|---:|---:|
|1|Radixor|PRIMARY_OUTPUT|0.990172|0.975787|0.961815|0.952720|0.976074|0.975715|
|2|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|0.021738|0.008811|0.005525|0.004425|0.066288|0.065774|
</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|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, FowlkesMallows, 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 | FowlkesMallows | 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 **4 result rows**, **2 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score.
#### `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|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|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 | FowlkesMallows | MCC |
|---:|---|---|---:|---:|---:|---:|---:|---:|
|1|Radixor|PRIMARY_OUTPUT|0.990172|0.975787|0.961815|0.952720|0.976074|0.975715|
|2|PERSIAN LUCENE PERSIAN STEM FILTER|PRIMARY_OUTPUT|0.021738|0.008811|0.005525|0.004425|0.066288|0.065774|
</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|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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,371 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `pl-pl-unimorph` | `1.0.0` | `PL_PL` | 9,990 | 132,308 | 19,957 | 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% | Full Radixor dictionary 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. |
## 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.972 | 0.203 | 79.9 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 524.081 | 35.121 | 4664.7 | 58.412 | Benchmark-only Polish Hunspell dictionary compared via Lucene HunspellStemFilter. |
| Lucene StempelStemmer direct | `polishLuceneStempelStemmerDirect` | 37.947 | 0.335 | 337.8 | 4.229 | Direct table-driven Polish Stempel stemmer API. |
| Lucene StempelFilter | `polishLuceneStempelFilter` | 43.090 | 0.411 | 383.5 | 4.803 | Lucene TokenFilter integration path for table-driven Polish Stempel. |
| Lucene MorfologikFilter | `polishLuceneMorfologikFilter` | 143.527 | 1.176 | 1277.5 | 15.997 | Dictionary-based Morfologik TokenFilter; may emit multiple terms. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 5 deterministic stemmers. The runner-up is `POLISH LUCENE MORFOLOGIK FILTER` at 0.948392, a difference of 0.042713. This rank does not imply leadership in throughput or every secondary metric.
- **LOWERCASE_GROUPS_ONLY:** `Radixor` ranks first by balanced accuracy at **0.991301** among 5 deterministic stemmers. The runner-up is `POLISH LUCENE MORFOLOGIK FILTER` at 0.948417, a difference of 0.042884. This rank does not imply leadership in throughput or every secondary metric.
### `ALL_WORDS`
This mode contains **11 result rows**, **5 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score.
#### `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%|
</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|
</details>
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | FowlkesMallows | 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|
</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|
</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, FowlkesMallows, 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 | FowlkesMallows | 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 **11 result rows**, **5 evaluated stemmers**, and **3 output policies**. Applied-row and form counts are shown per row because adapters share the language corpus but policy rows remain independently auditable. `PRIMARY_OUTPUT` and `ALL_CANDIDATES` rankings are ordered by unrounded balanced accuracy, followed by MCC, F1, over-stemming rate, over-stemming count, under-stemming rate, and stemmer. `ANY_CANDIDATE` has no single rank metric and is listed alphabetically. Balanced accuracy is a navigation metric, not a universally authoritative quality score.
#### `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%|
</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|
</details>
<details class="quality-details" markdown="1"><summary>Pair-relation metrics</summary>
| Rank | Stemmer | Output policy | F0.5 | F1 | F2 | Jaccard | FowlkesMallows | 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|
</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|
</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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,353 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `pt-pt-default` | `1.0.0` | `PT_PT` | 4,001 | 215,490 | 8,002 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene PortugueseLightStemFilter | 8.966% | 5.558% | 97.326% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. |
| Lucene PortugueseMinimalStemFilter | 5.539% | 1.896% | 100.000% | Minimal suffix reducer; narrow baseline, not a full stemmer. |
| Lucene SnowballFilter | 0.625% | 0.558% | 2.374% | Lucene TokenFilter integration path around the Snowball algorithm. |
| Official Snowball direct | 0.625% | 0.558% | 2.374% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
| Lucene PortugueseStemFilter | 0.312% | 0.308% | 0.425% | Portuguese RSLP-style Lucene TokenFilter stemmer. |
## Speed
Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower.
| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| Radixor | `portugueseRadixor` | 12.301 | 0.252 | 59.3 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene PortugueseLightStemFilter | `portugueseLucenePortugueseLightStemFilter` | 11.409 | 0.151 | 55.0 | 0.927 | Light Portuguese suffix stemmer. |
| Lucene PortugueseMinimalStemFilter | `portugueseLucenePortugueseMinimalStemFilter` | 15.619 | 0.084 | 75.3 | 1.270 | Minimal Portuguese suffix reducer. |
| Official Snowball direct | `snowballDirect[PORTUGUESE]` | 57.577 | 1.591 | 277.5 | 4.681 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[PORTUGUESE]` | 63.403 | 2.720 | 305.6 | 5.154 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
| Lucene PortugueseStemFilter | `portugueseLucenePortugueseStemFilter` | 158.014 | 5.150 | 761.6 | 12.845 | Portuguese RSLP-style Lucene TokenFilter. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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|&lt;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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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|&lt;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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,333 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `ru-ru-default` | `1.0.0` | `RU_RU` | 37,410 | 806,279 | 74,808 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene RussianLightStemFilter | 9.658% | 8.452% | 21.447% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. |
| Lucene SnowballFilter | 9.162% | 8.162% | 18.936% | Lucene TokenFilter integration path around the Snowball algorithm. |
| Official Snowball direct | 9.162% | 8.162% | 18.936% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
## Speed
Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower.
| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| Radixor | `russianRadixor` | 90.151 | 1.796 | 123.2 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene RussianLightStemFilter | `russianLuceneRussianLightStemFilter` | 59.456 | 2.102 | 81.3 | 0.660 | Light Russian suffix stemmer. |
| Official Snowball direct | `snowballDirect[RUSSIAN]` | 102.353 | 1.669 | 139.9 | 1.135 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[RUSSIAN]` | 138.597 | 4.727 | 189.5 | 1.537 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,377 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `es-es-default` | `1.0.0` | `ES_ES` | 65,059 | 926,393 | 120,121 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene HunspellStemFilter | 49.074% | 42.656% | 92.154% | Benchmark-only Spanish Hunspell dictionary compared via Lucene HunspellStemFilter. |
| Lucene SpanishMinimalStemFilter | 17.284% | 5.347% | 97.403% | Minimal suffix reducer; narrow baseline, not a full stemmer. |
| Lucene SpanishPluralStemFilter | 15.140% | 5.802% | 77.820% | Plural-focused suffix reducer; narrow baseline. |
| Lucene SpanishLightStemFilter | 9.577% | 7.088% | 26.279% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. |
| Lucene SnowballFilter | 4.889% | 4.287% | 8.932% | Lucene TokenFilter integration path around the Snowball algorithm. |
| Official Snowball direct | 4.889% | 4.287% | 8.930% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
## Speed
Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower.
| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| Radixor | `spanishRadixor` | 81.605 | 1.347 | 101.2 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 2033.430 | 14.863 | 2522.0 | 24.918 | Benchmark-only Spanish Hunspell dictionary compared via Lucene HunspellStemFilter. |
| Lucene SpanishMinimalStemFilter | `spanishLuceneSpanishMinimalStemFilter` | 42.144 | 1.556 | 52.3 | 0.516 | Minimal Spanish suffix reducer; narrow baseline. |
| Lucene SpanishLightStemFilter | `spanishLuceneSpanishLightStemFilter` | 44.479 | 1.291 | 55.2 | 0.545 | Light Spanish suffix stemmer. |
| Lucene SpanishPluralStemFilter | `spanishLuceneSpanishPluralStemFilter` | 96.537 | 3.418 | 119.7 | 1.183 | Plural-oriented Spanish suffix reducer. |
| Official Snowball direct | `snowballDirect[SPANISH]` | 172.151 | 7.261 | 213.5 | 2.110 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[SPANISH]` | 201.697 | 9.363 | 250.2 | 2.472 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 | FowlkesMallows | 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, FowlkesMallows, 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|&lt;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 | FowlkesMallows | 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|&lt;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 | FowlkesMallows | 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, FowlkesMallows, 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|&lt;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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,343 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `sv-se-default` | `1.0.0` | `SV_SE` | 12,371 | 110,468 | 24,731 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene SwedishMinimalStemFilter | 49.532% | 49.186% | 50.730% | Minimal suffix reducer; narrow baseline, not a full stemmer. |
| Lucene SwedishLightStemFilter | 45.672% | 46.383% | 43.209% | Light suffix stemmer; intentionally narrower than a dictionary-derived stemmer. |
| Official Snowball direct | 40.068% | 37.512% | 48.926% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
| Lucene SnowballFilter | 38.785% | 35.839% | 48.999% | Lucene TokenFilter integration path around the Snowball algorithm. |
## Speed
Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower.
| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| Radixor | `swedishRadixor` | 5.476 | 0.081 | 63.9 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene SwedishMinimalStemFilter | `swedishLuceneSwedishMinimalStemFilter` | 4.741 | 0.086 | 55.3 | 0.866 | Minimal Swedish suffix reducer. |
| Lucene SwedishLightStemFilter | `swedishLuceneSwedishLightStemFilter` | 4.893 | 0.053 | 57.1 | 0.893 | Light Swedish suffix stemmer. |
| Official Snowball direct | `snowballDirect[SWEDISH]` | 7.606 | 0.555 | 88.7 | 1.389 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[SWEDISH]` | 10.295 | 0.749 | 120.1 | 1.880 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,375 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `uk-ua-default` | `1.0.0` | `UK_UA` | 1,493 | 15,737 | 2,985 | 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene HunspellStemFilter | 86.815% | 83.759% | 99.866% | Benchmark-only Ukrainian Hunspell dictionary compared via Lucene HunspellStemFilter. |
| Lucene MorfologikFilter | 92.362% | 90.637% | 99.732% | Dictionary-based path; Morfologik can emit multiple terms. |
| Morfologik direct | 92.362% | 90.637% | 99.732% | Direct dictionary lookup; first returned stem is used for quality when no ranking weight is exposed. |
## Speed
Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower.
| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| Radixor | `ukrainianRadixor` | 0.639 | 0.009 | 50.1 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Lucene HunspellStemFilter | `luceneHunspellStemFilter` | 47.919 | 5.308 | 3757.8 | 74.957 | Benchmark-only Ukrainian Hunspell dictionary compared via Lucene HunspellStemFilter. |
| Morfologik direct | `ukrainianMorfologikDirect` | 8.662 | 0.105 | 679.3 | 13.550 | Direct Morfologik dictionary lookup; first returned stem is used for quality. |
| Lucene MorfologikFilter | `ukrainianLuceneMorfologikFilter` | 15.367 | 0.219 | 1205.1 | 24.038 | Dictionary-based Morfologik TokenFilter; may emit multiple terms. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View File

@@ -0,0 +1,322 @@
# 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 speed tokens |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| `yi-default` | `1.0.0` | `YI` | 802 | 4,300 | 1,524 | 2,776 |
## 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% | Full Radixor dictionary patch-command stemmer. |
| Lucene SnowballFilter | 2.837% | 2.558% | 3.346% | Lucene TokenFilter integration path around the Snowball algorithm. |
| Official Snowball direct | 2.837% | 2.558% | 3.346% | Official Snowball generated Java stemmer; rule-based suffix algorithm. |
## Speed
Speed uses JMH average time, 5 warmup iterations, 10 measurement iterations, 3 independent forks, and 1 thread. Relative factor is computed against the single Radixor row on this language page. Values below 1.000 are faster than that Radixor baseline; values above 1.000 are slower.
| Stemmer | Benchmark method | Score ms/op | Error ms | ns/token | Relative vs Radixor | Note |
| --- | --- | ---: | ---: | ---: | ---: | --- |
| Radixor | `radixor[YIDDISH]` | 0.249 | 0.001 | 89.6 | 1.000 | Full Radixor dictionary patch-command stemmer. |
| Official Snowball direct | `snowballDirect[YIDDISH]` | 1.574 | 0.066 | 567.1 | 6.330 | Official Snowball generated Java stemmer; direct API. |
| Lucene SnowballFilter | `luceneSnowballFilter[YIDDISH]` | 1.849 | 0.079 | 665.9 | 7.434 | Lucene TokenFilter path around Snowball; includes TokenStream overhead. |
## Interpretation Notes
- Radixor is a dictionary-derived patch-command stemmer. Its quality depends on the language resource used to train the compiled trie.
- 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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 | FowlkesMallows | 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, FowlkesMallows, 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 | FowlkesMallows | 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)`.
- FowlkesMallows 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- 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 -->

View 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. |

View 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.

View 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% | 20.627 | 2.117 | 98.0 |
| 90% | 90.000% | 97.047% | 94.913% | 97.613% | 21.713 | 2.104 | 103.2 |
| 80% | 80.000% | 96.635% | 92.768% | 97.661% | 17.408 | 1.438 | 82.7 |
| 70% | 70.000% | 96.209% | 90.565% | 97.705% | 16.946 | 1.531 | 80.5 |
| 60% | 60.000% | 95.750% | 88.384% | 97.703% | 15.735 | 1.278 | 74.8 |
| 50% | 50.000% | 95.262% | 86.107% | 97.690% | 14.714 | 1.089 | 69.9 |
| 40% | 40.000% | 94.753% | 83.855% | 97.643% | 15.090 | 1.254 | 71.7 |
| 30% | 30.000% | 94.208% | 81.651% | 97.537% | 13.773 | 1.071 | 65.4 |
| 20% | 20.000% | 93.633% | 79.366% | 97.416% | 15.396 | 2.497 | 73.1 |
| 10% | 10.000% | 92.868% | 76.516% | 97.204% | 16.970 | 2.847 | 80.6 |
## 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.

View 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-07-23 (Europe/Prague) |
| Corpus command | `./gradlew benchmarkCorpusReport --no-daemon` |
| Exact-root accuracy command | Direct JMH execution of the four `*BenchmarkQuality` classes selected in `stemmer-accuracy-2026-07-23.txt`; timing scores are discarded |
| Stemming-quality command | `./gradlew stemmingQuality --no-daemon` |
| Published speed command | `tools/run-published-speed-benchmarks.sh 2026-07-23` |
| Published speed run interval | 2026-07-23 12:58:50 to 15:15:43 Europe/Prague (2 h 16 min 53 s, including idle intervals and both JMH suites) |
| Stabilization intervals | 120 s before the main speed matrix; 60 s between the main matrix and coverage-speed suite |
| Corpus and command report | `build/reports/jmh/benchmark-corpora.csv` |
| Exact-root reports | `build/reports/jmh/stemmer-accuracy-2026-07-23.csv` and `.txt` |
| Speed reports | `build/reports/jmh/stemmer-speed-2026-07-23.csv` and `.txt` |
| English coverage accuracy reports | `build/reports/jmh/english-coverage-accuracy-2026-07-23.csv` and `.txt` |
| English coverage speed reports | `build/reports/jmh/english-coverage-speed-2026-07-23.csv` and `.txt` |
| Stemming-quality reports | `build/reports/stemming-quality/stemming-quality.csv` and `.md` |
| Environment report | `build/reports/jmh/performance-environment-2026-07-23.txt` |
| Selected speed methods | `build/reports/jmh/published-speed-benchmarks-2026-07-23.txt` |
| Comparison scope | Same-language methods used by the 20 language pages; `PolishPolimorfStemmerComparisonBenchmark`, all quality methods, the separate CISTEM gold-standard experiment, and internal trie microbenchmarks are excluded |
| Model scope | Exactly the 20 IDs declared by `Language.defaultModelId()`; Polish uses `pl-pl-unimorph`, and `pl-pl-polimorf` is not measured |
| Core base commit | `1f1b03c6a8d36a0918b92ebde698e5379a2a5946` |
| Measured source state | `release@4.0.0-dirty`; exact tracked changes and untracked-source checksums are retained as `measured-source-2026-07-23.patch` and `measured-untracked-2026-07-23.sha256` |
| 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 11.945%, with 6 of 102 rows above 10%; coverage-speed curve: maximum 16.775%, with 3 of 10 rows above 10%; no published row exceeded 20% |
| Deterministic measurements | Corpus, patch-command distribution, exact-root counters, coverage accuracy, and pairwise stemming quality are evaluated without interpreting runtime scores; no warmup is required |
| JVM reported by JMH | JDK 25.0.3, OpenJDK 64-Bit Server VM, 25.0.3+9 |
| Java runtime | OpenJDK Runtime Environment, Red Hat build 25.0.3+9 |
| JVM invoker | `/usr/lib/jvm/java-25-openjdk/bin/java` |
| Operating system | Fedora Linux 44 (MATE-Compiz) |
| Kernel | Linux 7.1.4-200.fc44.x86_64 |
| Architecture | x86_64 |
| CPU | AMD Ryzen 5 8600G w/ Radeon 760M Graphics |
| Physical / logical CPUs | 6 / 12 |
| CPU frequency policy | `amd-pstate-epp`; governor `performance` on every logical CPU; EPP `performance`; boost enabled |
| CPU affinity | Scheduler default; no explicit pinning |
| Installed memory | 60 GiB reported by the operating system |
| Pre-run idle state | Load average 0.25 / 0.36 / 0.71 after the 120 s idle interval; CPU Tctl 40.2 degrees Celsius; swap unused |
| End-of-run state | Load average 1.16 / 1.28 / 1.32; CPU Tctl 60.5 degrees Celsius |
| 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-07-23.csv`
- `build/reports/jmh/stemmer-accuracy-2026-07-23.txt`
- `build/reports/jmh/stemmer-speed-2026-07-23.csv`
- `build/reports/jmh/stemmer-speed-2026-07-23.txt`
- `build/reports/jmh/english-coverage-accuracy-2026-07-23.csv`
- `build/reports/jmh/english-coverage-accuracy-2026-07-23.txt`
- `build/reports/jmh/english-coverage-speed-2026-07-23.csv`
- `build/reports/jmh/english-coverage-speed-2026-07-23.txt`
- `build/reports/jmh/performance-environment-2026-07-23.txt`
- `build/reports/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.

View 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. |
| FowlkesMallows | `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.

View 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-07-23.txt`. Internal `FrequencyTrie*` microbenchmarks, quality methods, the CISTEM gold-standard experiment, and the optional `PolishPolimorfStemmerComparisonBenchmark` are not part of those results.
## 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-07-23.csv`
- `build/reports/jmh/stemmer-accuracy-2026-07-23.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).

View File

@@ -0,0 +1,86 @@
# 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: `edf16b07be8a535943ddf37caeb8807755c95e9e1fb13244145f28be74b491d8`
- Complete scenarios: 308
- Authoritative language universe: 20 languages
- Language-page scenarios: 308 across 20 benchmark pages
The CSV contains the model ID, independent model version, descriptor SHA-256, raw pair counts, raw over/under numerators and denominators, candidate statistics, and relation metrics. Reserved partition-metric columns remain empty because the gold standard is an overlapping cover. Documentation is regenerated from this file rather than manually transcribed. Publication fails when any row uses a model other than the language's registered default.
## Commands
```bash
./gradlew stemmingQuality
./gradlew publishStemmingQualityDocumentation
./gradlew verifyStemmingQualityDocumentation
./gradlew 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 speed and coverage-speed command is:
```bash
./gradlew writeJmhRuntimeClasspath --no-daemon
tools/run-published-speed-benchmarks.sh 2026-07-23
```
The runner refuses to start unless every CPU uses the `performance` governor, materializes the exact selected benchmark list, rejects quality/Polimorf/gold-standard methods, and requires the Hebrew speed path. It records hardware, JVM, source-state, JAR, classpath, corpus, quality, load, temperature, and governor provenance before running. The exact JMH configuration is listed in [Environment and reports](environment.md). Quality and performance reports are separate datasets and are not combined into an undocumented scalar.
## Recorded and unavailable provenance
The performance documentation records its 2026-07-23 environment, JDK 25.0.3, operating system, hardware, base revision, exact dirty patch, untracked-source checksums, executable JMH JAR checksum, and model descriptor checksums. The quality CSV embeds model identity and checksum in every row; run date, core source state, JVM, OS, and hardware are shared provenance on the environment page.
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.

View 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 | Model-dictionary-derived compiled patch trie. Default rows use each language's stable default model ID. |
| Apache Lucene language stem filters | Apache Lucene project | 10.5.0 | Adapter-declared language-specific subsets | TokenFilter lifecycle and language normalization match JMH; normally single-output | Light, minimal, possessive, and language stem filters deliberately implement different scopes. Narrow scope is not a defect. |
| Apache Lucene SnowballFilter | Apache Lucene project using Snowball algorithms | Lucene 10.5.0 | Snowball-supported subset of Radixor languages | Single primary token emitted through the Lucene TokenFilter path | Includes TokenStream overhead and required normalization. |
| Official Snowball Java | Snowball project | Repository preparation downloads the configured upstream Java distribution; an immutable revision was not recorded in the quality CSV | Same-language adapter subset | Direct generated Java API; single output | Rule-based suffix algorithms provide broad baselines rather than dictionary-root guarantees. |
| 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
View File

@@ -0,0 +1,3 @@
# Historical Builds
The Pages publication workflow replaces this local placeholder with the retained build index.

View File

@@ -1,256 +1,104 @@
# 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 Radixor defines a language enum value and publishes a corresponding default model artifact. It does not mean that a dictionary is embedded in the core JAR. Applications add model artifacts explicitly or use the optional standard pack.
## Overview
The language enum carries language identity, writing direction, a legacy resource-directory name, and the stable default model ID. A model descriptor carries the independently versioned model identity and resource. See [Model Selection and Loading](model-selection-and-loading.md) for the API and the generated [model catalog](stemmer-model-catalog.md) for versions, provenance, checksums, and sizes.
Bundled dictionaries are exposed through:
## Defaults and variants
```java
org.egothor.stemmer.StemmerPatchTrieLoader.Language
```
| Language | Enum | Default model ID | Default artifact | Optional variants |
|---|---|---|---|---|
| Czech | `CS_CZ` | `cs-cz-default` | `org.egothor:radixor-model-cs-cz-default` | — |
| Danish | `DA_DK` | `da-dk-default` | `org.egothor:radixor-model-da-dk-default` | — |
| German | `DE_DE` | `de-de-default` | `org.egothor:radixor-model-de-de-default` | — |
| Spanish | `ES_ES` | `es-es-default` | `org.egothor:radixor-model-es-es-default` | — |
| Persian | `FA_IR` | `fa-ir-default` | `org.egothor:radixor-model-fa-ir-default` | — |
| Finnish | `FI_FI` | `fi-fi-default` | `org.egothor:radixor-model-fi-fi-default` | — |
| French | `FR_FR` | `fr-fr-default` | `org.egothor:radixor-model-fr-fr-default` | — |
| Hebrew | `HE_IL` | `he-il-default` | `org.egothor:radixor-model-he-il-default` | — |
| Hungarian | `HU_HU` | `hu-hu-default` | `org.egothor:radixor-model-hu-hu-default` | — |
| Italian | `IT_IT` | `it-it-default` | `org.egothor:radixor-model-it-it-default` | — |
| Norwegian Bokmål | `NB_NO` | `nb-no-default` | `org.egothor:radixor-model-nb-no-default` | — |
| Dutch | `NL_NL` | `nl-nl-default` | `org.egothor:radixor-model-nl-nl-default` | — |
| Norwegian Nynorsk | `NN_NO` | `nn-no-default` | `org.egothor:radixor-model-nn-no-default` | — |
| Polish | `PL_PL` | `pl-pl-unimorph` | `org.egothor:radixor-model-pl-pl-unimorph` | `pl-pl-polimorf` / `org.egothor:radixor-model-pl-pl-polimorf` |
| Portuguese | `PT_PT` | `pt-pt-default` | `org.egothor:radixor-model-pt-pt-default` | — |
| Russian | `RU_RU` | `ru-ru-default` | `org.egothor:radixor-model-ru-ru-default` | — |
| Swedish | `SV_SE` | `sv-se-default` | `org.egothor:radixor-model-sv-se-default` | — |
| Ukrainian | `UK_UA` | `uk-ua-default` | `org.egothor:radixor-model-uk-ua-default` | — |
| English | `US_UK` | `us-uk-default` | `org.egothor:radixor-model-us-uk-default` | — |
| Yiddish | `YI` | `yi-default` | `org.egothor:radixor-model-yi-default` | — |
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 |
|---|---|---:|---|
| 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 |
UniMorph and PoliMorf have different lexical sources and provenance. Applications should compare outputs with application-specific regression tests before changing an explicit model choice.
## Basic usage
## Dependency patterns
Load a bundled dictionary like this:
Minimal English:
```java
import java.io.IOException;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.ReductionMode;
import org.egothor.stemmer.StemmerPatchTrieLoader;
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());
}
```groovy
dependencies {
implementation 'org.egothor:radixor:<radixor-version>'
runtimeOnly 'org.egothor:radixor-model-us-uk-default:1.0.0'
}
```
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
```java
import java.io.IOException;
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);
}
```groovy
dependencies {
implementation 'org.egothor:radixor:<radixor-version>'
runtimeOnly 'org.egothor:radixor-models-standard:<catalog-version>'
}
```
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.
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
## Loading a language default
```java
import java.io.IOException;
import java.nio.file.Path;
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(
final FrequencyTrie<CompiledPatchCommand> trie =
StemmerPatchTrieLoader.loadCompiled(
StemmerPatchTrieLoader.Language.US_UK,
true,
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`
- `StemmerPatchTrieLoader.Language`
- `FrequencyTrie`
- `PatchCommandEncoder`
- `WordTraversalDirection`
- `ReductionMode`
- `ReductionSettings`
- `StemmerPatchTrieBinaryIO`
- `FrequencyTrieBuilders`
See [Dictionary Format](dictionary-format.md), [CLI Compilation](cli-compilation.md), and [Stemmer Models](stemmer-models.md).
## Next steps
## Benchmark interpretation
- [Quick start](quick-start.md)
- [Dictionary format](dictionary-format.md)
- [CLI compilation](cli-compilation.md)
- [Programmatic usage](programmatic-usage.md)
## Summary
Radixors 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.
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).

View File

@@ -2,6 +2,8 @@
Radixor provides a command-line compiler for turning line-oriented dictionary files into compact binary stemmer artifacts.
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.
## What the CLI does
@@ -17,6 +19,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.
## 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
```bash
@@ -171,6 +177,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.
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
`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 +214,9 @@ The CLI is best used as a preparation step during packaging, deployment, or cont
### 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
@@ -215,6 +226,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.
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
### 1. Prepare a dictionary
@@ -237,11 +250,12 @@ java org.egothor.stemmer.Compile \
### 3. Load it in an application
```java
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.StemmerPatchTrieLoader;
final FrequencyTrie<String> trie =
StemmerPatchTrieLoader.loadBinary("english.radixor.gz");
final FrequencyTrie<CompiledPatchCommand> trie =
StemmerPatchTrieLoader.loadBinaryCompiled("english.radixor.gz");
```
## Exit codes and error handling
@@ -276,3 +290,5 @@ The CLI and the programmatic API implement the same conceptual preparation step.
- [Quick start](quick-start.md)
- [Programmatic usage](programmatic-usage.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).

View File

@@ -37,7 +37,7 @@ This API is expected to remain supportable across future versions. The preferred
Examples of likely additive evolution include:
- additional bundled language resources,
- additional independently versioned language models,
- fuller support for diacritics or native-script language resources,
- expanded documentation and operational tooling,
- 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
`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
@@ -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.
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
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 projects dire
- improved internal data structures,
- changes inside `org.egothor.stemmer.trie`,
- expanded bundled dictionaries,
- expanded model dictionaries,
- additional supported languages,
- improved native-script handling,
- 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.
### 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 projects 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
@@ -150,7 +161,7 @@ Users should avoid depending on:
- internal trie package details,
- undocumented internal classes or intermediate representations,
- 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.
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.

View File

@@ -2,7 +2,7 @@
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.
@@ -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.
### 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.
@@ -68,7 +68,7 @@ That convention belongs to the supplied dictionaries, not to the underlying algo
### 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
@@ -139,6 +139,14 @@ A dictionary should read like a curated lexical resource, not like an unfiltered
## 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:
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
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:

View File

@@ -2,6 +2,27 @@
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 | Authors, parser, CLI, or model preparation |
| Registered model resource | The same Radixor dictionary bytes under GZip, accompanied by index, descriptor, checksum, and license | `StemmerModelRegistry` and `StemmerPatchTrieLoader` |
| Persisted compiled trie | GZip-compressed Radixor binary format, commonly `.radixor.gz` | `loadBinaryCompiled(...)` |
The model file named `stemmer.gz` is not Java serialization and is not a pre-instantiated or persisted trie. It is compressed textual dictionary input parsed when the model is loaded.
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.
## Core structure
@@ -129,7 +150,11 @@ run running runs ran
## 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 projects traversal configuration.
@@ -235,3 +260,5 @@ To understand how those dictionary lines are transformed into compiled runtime a
- [CLI compilation](cli-compilation.md)
- [Programmatic usage](programmatic-usage.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).

138
docs/fast-track.md Normal file
View File

@@ -0,0 +1,138 @@
# Fast Track
This page is the shortest path from an empty Java project to a working Radixor stemmer.
It deliberately uses an external model artifact and the preferred compiled-command runtime API, so the
first result does not require writing a dictionary, running the CLI compiler, or understanding
reduction internals.
Use this page when the goal is:
- add the dependency,
- load a registered language model,
- stem a token,
- know where to go next.
For deeper production guidance, see [Integration Deep Dive](integration-deep-dive.md).
## 1. Add The Dependency
Radixor is published as:
```text
groupId: org.egothor
artifactId: radixor
```
Radixor 4 is not yet represented by a published release in this working tree. Replace the version placeholder with the reviewed release you deploy.
For a Gradle project:
```kotlin
dependencies {
implementation("org.egothor:radixor:<radixor-version>")
runtimeOnly("org.egothor:radixor-model-us-uk-default:1.0.0")
}
```
For a Maven project:
```xml
<dependency>
<groupId>org.egothor</groupId>
<artifactId>radixor</artifactId>
<version>${radixor.version}</version>
</dependency>
<dependency>
<groupId>org.egothor</groupId>
<artifactId>radixor-model-us-uk-default</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
```
Radixor targets modern Java and has a dependency-light runtime core. The project documentation and
benchmarks assume a current JDK; Java 21 or newer is the practical baseline for current releases.
## 2. Load An External Model Dictionary
The fastest path is to use a registered model through `StemmerPatchTrieLoader.Language`.
This example uses `US_UK`, whose default ID is `us-uk-default`; the runtime model dependency above must be present.
```java
import java.io.IOException;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.ReductionMode;
import org.egothor.stemmer.StemmerPatchTrieLoader;
public final class RadixorFirstStem {
private RadixorFirstStem() {
throw new AssertionError("No instances.");
}
public static void main(final String[] arguments) throws IOException {
final FrequencyTrie<CompiledPatchCommand> stemmer = StemmerPatchTrieLoader.loadCompiled(
StemmerPatchTrieLoader.Language.US_UK,
true,
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
final String token = "running";
final CompiledPatchCommand command = stemmer.get(token);
final String stem = command == null ? token : command.apply(token);
System.out.println(token + " -> " + stem);
}
}
```
The loaded `FrequencyTrie<CompiledPatchCommand>` has no mutating API. Load it once during application startup, publish it safely through application-owned lifecycle code, and reuse it for indexing and query processing.
## 3. Choose a Language Default or Explicit Model
Language defaults are exposed as enum constants. Common examples:
| Language | Enum constant |
| --- | --- |
| English | `US_UK` |
| German | `DE_DE` |
| French | `FR_FR` |
| Spanish | `ES_ES` |
| Italian | `IT_IT` |
| Polish | `PL_PL` |
| Russian | `RU_RU` |
| Czech | `CS_CZ` |
The full list, writing-direction notes, and benchmark links are in
[Built-in Languages](built-in-languages.md).
Polish has two models. `Language.PL_PL` selects `pl-pl-unimorph`; load the alternative explicitly with `StemmerPatchTrieLoader.loadCompiled("pl-pl-polimorf", true, reductionMode)`, or retain a registry and pass `registry.require("pl-pl-polimorf")` to the descriptor overload. See [Model Selection and Loading](model-selection-and-loading.md). Full PoliMorf construction requires substantially more startup heap than ordinary models; the repository verifies it in a dedicated 6 GiB test JVM.
## 4. Use The Same Stemmer On Both Sides
For search, use the same Radixor configuration during indexing and query processing. A typical
minimal integration flow is:
1. tokenize text with your application or search platform,
2. normalize tokens consistently,
3. call `stemmer.get(token)`,
4. apply the returned `CompiledPatchCommand`,
5. index or query with the resulting stem.
Do not load the trie per token. The compiled trie is the runtime artifact; per-token work should be
limited to lookup and patch application.
## 5. Next Step For Production
The fast path parses and compiles a registered model dictionary during startup. That is convenient for evaluation and
small services. For larger deployments, compile once, persist a `.radixor.gz` artifact, and load
that binary artifact at runtime.
Continue with:
- [Integration Deep Dive](integration-deep-dive.md) for production lifecycle guidance.
- [Loading and Building Stemmers](programmatic-loading-and-building.md) for all loading APIs.
- [Model Selection and Loading](model-selection-and-loading.md) for model dependencies, variants, and failures.
- [Built-in Languages](built-in-languages.md) for defaults and optional variants.
- [Benchmarking](benchmarking.md) for speed and quality interpretation.

View File

@@ -28,10 +28,26 @@ Radixor delivers:
Radixor is intended for teams that require consistent stemming quality at scale, while retaining the ability to evolve lexical resources after compilation and to handle ambiguous reductions with greater precision than traditional single-stem pipelines allow.
## Add the core and model data
The core `org.egothor:radixor` JAR contains no language dictionary. A minimal application adds one model; broad deployments may use the optional standard pack:
```groovy
dependencies {
implementation 'org.egothor:radixor:<radixor-version>'
runtimeOnly 'org.egothor:radixor-model-pl-pl-unimorph:1.0.0'
}
```
`StemmerPatchTrieLoader.loadCompiled(Language.PL_PL, ...)` resolves the default `pl-pl-unimorph`. `pl-pl-polimorf` is a separate optional model selected by stable model ID. Follow [Model Selection and Loading](model-selection-and-loading.md) for runnable examples or choose artifacts from the generated [model catalog](stemmer-model-catalog.md).
## Start here
- Read [Quick Start](quick-start.md) for immediate implementation guidance.
- Use [Programmatic Usage](programmatic-usage.md) for application integration patterns.
- Review [Benchmarking](benchmarking.md) for reproducible performance methodology.
- Read [Fast Track](fast-track.md) when you want the shortest path to a working bundled stemmer.
- Use [Model Selection and Loading](model-selection-and-loading.md) for default, explicit, dual-model, and ClassLoader examples.
- Use [Integration Deep Dive](integration-deep-dive.md) when you are wiring Radixor into a real application or search pipeline.
- Read [Quick Start](quick-start.md) for the broader developer walkthrough after the first result works.
- Use [Built-in Languages](built-in-languages.md) to interpret language defaults and optional model variants.
- Review [Benchmarking](benchmarking.md) and [Benchmark Results](benchmarks/index.md) for reproducible performance and quality methodology.
- Open [CI Reports](reports.md) to inspect published build artifacts and quality metrics.
- See the historical paper: [*Lemmatizer for Document Information Retrieval Systems in JAVA*](https://www.researchgate.net/publication/221512865_Lemmatizer_for_Document_Information_Retrieval_Systems_in_JAVA).

View File

@@ -0,0 +1,229 @@
# Integration Deep Dive
This page explains how to integrate Radixor into a real Java application after the first
fast-track experiment works. It covers dependencies, external model artifacts, runtime lifecycle,
deployment artifacts, and the decisions that matter in search or text-processing systems.
## Integration Model
Radixor has two separate phases:
| Phase | Work | Typical location |
| --- | --- | --- |
| Preparation | Parse dictionaries, derive patch commands, reduce and contract the trie, optionally persist a binary artifact. | Build pipeline, packaging job, admin tool, or startup for small services. |
| Runtime | Load an immutable compiled trie, look up patch commands, apply them to tokens. | Search indexing, query processing, text normalization, enrichment pipelines. |
The practical rule is simple: compile rarely, stem often.
For production systems, prefer a startup-owned or dependency-injected
`FrequencyTrie<CompiledPatchCommand>` per language/configuration. The compiled structure has no
mutating API. The project does not currently publish a formal cross-thread safety guarantee, so
applications should use normal safe-publication practices when sharing a loaded trie.
## Dependency Coordinates
The Maven coordinates are:
```text
org.egothor:radixor
```
Gradle:
```kotlin
dependencies {
implementation("org.egothor:radixor:<radixor-version>")
runtimeOnly("org.egothor:radixor-models-standard:<catalog-version>")
}
```
Maven:
```xml
<dependency>
<groupId>org.egothor</groupId>
<artifactId>radixor</artifactId>
<version>${radixor.version}</version>
</dependency>
<dependency>
<groupId>org.egothor</groupId>
<artifactId>radixor-models-standard</artifactId>
<version>${model.catalog.version}</version>
<scope>runtime</scope>
</dependency>
```
Replace the example versions with the independently selected core and catalog releases for your deployment.
The core Java module is:
```java
module org.egothor.radixor;
```
A named consuming module declares:
```java
module example.search {
requires org.egothor.radixor;
}
```
## Runtime Model Artifacts
The core ships no language dictionary. Add one or more `radixor-model-<model-id>` artifacts, or the optional metadata-only standard pack. Each model JAR contains an indexed descriptor and a namespaced GZip dictionary. `StemmerPatchTrieLoader.Language` represents language properties and a stable default model ID; it does not own embedded data.
The standard option is specifically a POM-only runtime dependency aggregate, not an all-model binary JAR. It resolves one default model JAR per language and excludes optional PoliMorf. The separate POM-only `radixor-models-bom` manages recommended versions without adding runtime artifacts. Repository tests and JMH attach individual model projects directly to non-production configurations, so neither path changes the root publication's dependency graph.
For minimal deployments choose only required model artifacts. For multiple Polish variants add both `pl-pl-unimorph` and `pl-pl-polimorf`, retain UniMorph as the language default, and request PoliMorf explicitly. See [Model Selection and Loading](model-selection-and-loading.md) for complete dependencies and [Built-in Languages](built-in-languages.md) for mappings.
Use `loadCompiled("pl-pl-polimorf", true, reductionMode)` for direct exact selection, or discover once and call `loadCompiled(descriptor, true, reductionMode)`. Neither form caches the trie. Complete PoliMorf startup is memory-intensive and is verified with a dedicated 6 GiB heap; construct it once during application initialization and retain the immutable result.
## Minimal Service Wrapper
A small service wrapper keeps loading, null handling, and fallback behavior in one place.
```java
import java.io.IOException;
import java.util.Objects;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.ReductionMode;
import org.egothor.stemmer.StemmerPatchTrieLoader;
public final class RadixorStemmerService {
private final FrequencyTrie<CompiledPatchCommand> trie;
public RadixorStemmerService(final StemmerPatchTrieLoader.Language language) throws IOException {
this.trie = StemmerPatchTrieLoader.loadCompiled(
Objects.requireNonNull(language, "language"),
true,
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
}
public String stem(final String token) {
final String checkedToken = Objects.requireNonNull(token, "token");
final CompiledPatchCommand command = trie.get(checkedToken);
return command == null ? checkedToken : command.apply(checkedToken);
}
}
```
The fallback behavior preserves the original token when the trie has no patch command for it. That
is usually the right default for search normalization, because unknown tokens should remain
searchable.
## Production Artifact Workflow
For a controlled deployment, compile once and deploy the binary artifact:
1. choose a registered model resource or caller-owned custom dictionary,
2. optionally extend it with domain vocabulary,
3. compile a contracted trie,
4. persist it as `.radixor.gz`,
5. deploy that artifact with the application,
6. load it with `StemmerPatchTrieLoader.loadBinaryCompiled(...)`.
Runtime loading then avoids dictionary parsing and preparation-time memory pressure.
```java
import java.io.IOException;
import java.nio.file.Path;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.StemmerPatchTrieLoader;
public final class BinaryStemmerLoader {
private BinaryStemmerLoader() {
throw new AssertionError("No instances.");
}
public static FrequencyTrie<CompiledPatchCommand> loadEnglish() throws IOException {
return StemmerPatchTrieLoader.loadBinaryCompiled(Path.of("stemmers", "english.radixor.gz"));
}
}
```
Use [CLI Compilation](cli-compilation.md) for command-line artifact creation, or
[Extending and Persisting Compiled Tries](programmatic-extending-and-persistence.md) for
programmatic artifact generation.
## Search Pipeline Guidance
Use Radixor consistently across indexing and querying:
- choose one language dictionary per field or per analysis chain,
- apply the same token normalization before stemming on both sides,
- keep the compiled trie in memory and reuse it,
- use `get(...)` for a single preferred stem,
- use `getAll(...)` when a retrieval model benefits from preserving alternatives,
- version custom `.radixor.gz` artifacts with the application or index schema.
For multilingual content, do not run every token through every language. Route text by field,
document metadata, or language detection before stemming.
## Choosing Registered Versus Custom Dictionaries
Start with registered model artifacts when:
- the language is supported,
- the application needs a strong baseline quickly,
- the vocabulary is general-purpose,
- the team is evaluating Radixor or building an initial integration.
Use custom or extended dictionaries when:
- domain vocabulary changes search quality,
- product names, technical terms, legal terms, or biomedical terms must be preserved consistently,
- stemming behavior must be curated and reviewed,
- a release process needs a versioned lexical artifact.
The dictionary format is intentionally simple and documented in
[Dictionary Format](dictionary-format.md). Contribution standards are described in
[Contributing Dictionaries](contributing-dictionaries.md).
## Performance Practices
The hot path should be only:
```text
token -> trie lookup -> compiled command application -> stem
```
Avoid these patterns in production request paths:
- loading or compiling dictionaries per request,
- applying serialized patch strings repeatedly instead of `CompiledPatchCommand`,
- rebuilding tries for short-lived batches,
- mixing different stemmer configurations between indexing and querying,
- interpreting speed without checking exact-root quality.
The current benchmark documentation separates methodology, corpora, environment, and language
results so performance claims remain auditable. Start with [Benchmarking](benchmarking.md), then
use [Benchmark Results](benchmarks/index.md) for the detailed reference tree.
## Operational Checklist
Before production rollout:
- dependency version is pinned,
- language, model ID, model artifact version, checksum, and reduction mode are documented,
- indexing and query pipelines use the same stemming configuration,
- custom artifacts are versioned and reproducible,
- fallback behavior for unknown tokens is explicit,
- benchmark expectations are read together with quality metrics,
- CI includes at least a smoke test that stems representative project vocabulary.
## Related Pages
- [Fast Track](fast-track.md)
- [Quick Start](quick-start.md)
- [Built-in Languages](built-in-languages.md)
- [Programmatic Usage](programmatic-usage.md)
- [Model Selection and Loading](model-selection-and-loading.md)
- [CLI Compilation](cli-compilation.md)
- [Benchmarking](benchmarking.md)

View File

@@ -6,7 +6,10 @@ Compiled trie nodes (`CompiledNode`) use three lookup strategies when resolving
2. linear scan for very small child counts,
3. binary search over sorted edge labels.
This page explains the dense path, what `maxExpandedIndex` controls, and how to tune it.
This page explains the dense path, what `maxExpandedIndex` controls, and how to tune it. These
edge lookup strategies operate after trie reduction and uniform-subtree contraction. If lookup
reaches an accepting contracted leaf, no child edge search is needed for the remaining input
characters.
## Runtime model of one node
@@ -75,7 +78,7 @@ The value never changes lookup semantics. It only changes the in-memory structur
This threshold is **not** stored in `TrieMetadata`.
- The binary format stores only trie payload and semantic metadata (`reduction`, `traversal`,
case/diacritic settings, and stream version).
case/diacritic settings, contraction settings, and stream version).
- `maxExpandedIndex` is chosen when materializing nodes in memory.
- You can therefore keep one persisted artifact and load it with different in-memory
trade-offs depending on deployment constraints.
@@ -135,15 +138,17 @@ At artifact load time, you can tune the same trade-off independently of persiste
```java
import java.nio.file.Path;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.StemmerPatchTrieLoader;
var defaultLookup = StemmerPatchTrieLoader.loadBinary(
final FrequencyTrie<CompiledPatchCommand> defaultLookup = StemmerPatchTrieLoader.loadBinaryCompiled(
Path.of("stemmers", "english.radixor.gz"));
var fastLookup = StemmerPatchTrieLoader.loadBinary(
final FrequencyTrie<CompiledPatchCommand> fastLookup = StemmerPatchTrieLoader.loadBinaryCompiled(
Path.of("stemmers", "english.radixor.gz"), 1024);
var compactLookup = StemmerPatchTrieLoader.loadBinary(
final FrequencyTrie<CompiledPatchCommand> compactLookup = StemmerPatchTrieLoader.loadBinaryCompiled(
Path.of("stemmers", "english.radixor.gz"), 0);
```

View File

@@ -0,0 +1,298 @@
# Migration and Backward Compatibility
## Radixor 3.x to 4.x architecture migration
Radixor 3.x published algorithm classes and language dictionaries together as `org.egothor:radixor`. Radixor 4 keeps that established coordinate for the algorithmic core but removes every dictionary from the core JAR. Applications must now choose independently versioned model artifacts. This is deliberately source-compatible where practical and deliberately different at runtime.
### Before and after: dependencies
| Deployment | 3.x | 4.x |
|---|---|---|
| Core | `org.egothor:radixor:<3.x-version>` included dictionaries | `org.egothor:radixor:<radixor-version>` contains code only |
| Minimal Polish | No separate data dependency | Add `radixor-model-pl-pl-unimorph:1.0.0` |
| All defaults | Implicitly embedded | Add optional `radixor-models-standard:<catalog-version>` |
| Optional Polish variant | Not independently selectable | Add and explicitly select `radixor-model-pl-pl-polimorf:1.0.0` |
Gradle, preserving the previous Polish default:
```groovy
dependencies {
implementation 'org.egothor:radixor:<radixor-version>'
runtimeOnly 'org.egothor:radixor-model-pl-pl-unimorph:1.0.0'
}
```
Gradle, broad default coverage:
```groovy
dependencies {
implementation 'org.egothor:radixor:<radixor-version>'
runtimeOnly 'org.egothor:radixor-models-standard:<catalog-version>'
}
```
Maven, preserving the Polish default:
```xml
<dependency>
<groupId>org.egothor</groupId>
<artifactId>radixor</artifactId>
<version>${radixor.version}</version>
</dependency>
<dependency>
<groupId>org.egothor</groupId>
<artifactId>radixor-model-pl-pl-unimorph</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
```
### Before and after: API behavior
Language-oriented calls remain source-compatible:
```java
final FrequencyTrie<CompiledPatchCommand> polish =
StemmerPatchTrieLoader.loadCompiled(
StemmerPatchTrieLoader.Language.PL_PL,
true,
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
```
In 4.x this call creates a registry and resolves `Language.PL_PL.defaultModelId()`, which is `pl-pl-unimorph`. Source compatibility does not imply runtime classpath compatibility: the call fails with `StemmerModelNotFoundException` unless that model is visible.
Explicit selection enables multiple variants:
```java
final StemmerModelRegistry registry = StemmerModelRegistry.fromContextClassLoader();
final StemmerModelDescriptor polimorf = registry.require("pl-pl-polimorf");
final FrequencyTrie<CompiledPatchCommand> trie =
StemmerPatchTrieLoader.loadCompiled(
polimorf,
true,
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
```
The existing `load(String, ...)` overload means a filesystem path. The compiled `loadCompiled(String, boolean, ReductionMode)` overload now means a stable model ID; use the `Path` overload for a filesystem dictionary. Descriptor-based compiled loading avoids rediscovery when an application retains a registry. See [Model Selection and Loading](model-selection-and-loading.md) for complete examples.
### Polish migration scenarios
1. **Preserve previous default behavior:** add `radixor-model-pl-pl-unimorph` and keep using `Language.PL_PL`.
2. **Use PoliMorf:** add `radixor-model-pl-pl-polimorf` and call `registry.require("pl-pl-polimorf")`.
3. **Deploy both:** add both runtime artifacts and load each descriptor by ID. They are not merged.
4. **Verify selection:** compare `registry.requireDefault(Language.PL_PL).id()` with `pl-pl-unimorph` through normal application control flow or a JUnit assertion, and inspect `registry.findByLanguage(Language.PL_PL)`.
5. **Diagnose absence:** read the exact `StemmerModelNotFoundException` message, then inspect the production `runtimeClasspath` rather than changing dependency order.
UniMorph and PoliMorf are not interchangeable quality datasets. They can differ in vocabulary, provenance, licensing, and stemming outputs.
Model migration does not erase source obligations. Each migrated UniMorph artifact packages its
language-specific notice with upstream attribution, Radixor modifications and contribution
statement, ShareAlike terms, and the canonical CC BY-SA 3.0 URI. The original imports did not
record exact UniMorph commits, so descriptors use
`source.revision=not-recorded-in-legacy-import` and disclose that fact. Future model imports must
record an exact upstream revision and source-archive checksum.
### Compatibility table
| Dimension | 4.x migration status |
|---|---|
| Source compatibility | Language-oriented loader signatures remain; external model dependencies are new |
| Binary compatibility | Removing resources is a major-version boundary; review all deployed artifacts |
| Runtime classpath | At least one selected model JAR is required |
| Model format | Descriptor format `radixor-dictionary-tsv-gzip` version `1` is validated by the registry |
| Model IDs | Stable runtime identities, independent of artifact discovery order |
| Core Maven coordinate | Remains `org.egothor:radixor` |
| Release versions | Core, each model, upstream source, format, and catalog versions evolve separately |
### Upgrade checklist
- Update the core dependency.
- Choose individual model artifacts or the standard pack.
- Put resource-only model dependencies on the production runtime classpath.
- Verify `Language.defaultModelId()` mappings used by the application.
- Inspect shaded, minimized, plugin, or modular packaging for indexes and resources.
- Run application-level vocabulary and output regression tests.
- Track model artifact versions and checksums separately from the core version.
### Roll back model choice
To return from optional PoliMorf to the default UniMorph behavior, add or retain `radixor-model-pl-pl-unimorph`, stop requesting `pl-pl-polimorf`, and load `Language.PL_PL` or explicitly request `pl-pl-unimorph`. Do not change the language constant. Remove the unused PoliMorf runtime dependency after verifying no explicit lookup still needs it.
Rolling the whole application back to 3.x instead requires restoring the reviewed 3.x core dependency and removing 4.x model assumptions. Do not combine 3.x embedded resources with the 4.x registry architecture.
Core, model, and catalog releases are independent:
```bash
git tag -a "release@4.0.0" -m "Release Radixor 4.0.0"
git tag -a "model/pl-pl-polimorf@1.0.0" -m "Release Polish PoliMorf model 1.0.0"
git tag -a "models-catalog@2026.1" -m "Release Radixor model catalog 2026.1"
```
A core tag publishes only the root `org.egothor:radixor` software artifacts, never model JARs. A model tag validates and publishes exactly its matching module, never core, standard, BOM, JMH, or the multilingual quality suite. A catalog tag publishes only BOM and standard aggregate metadata. Local model dry-run:
The catalog artifacts are POM-only: `radixor-models-standard` carries runtime dependencies on the 20 defaults, while `radixor-models-bom` carries dependency-management constraints for all 21 individual models. Neither publishes an empty binary, sources, or Javadoc JAR. This Maven BOM is distinct from the root CycloneDX SBOM report under `build/reports/sbom/`.
```bash
./tools/parse-model-release-tag.sh "model/pl-pl-polimorf@1.0.0" .
./gradlew --no-daemon :models:pl-pl-polimorf:check
./gradlew --no-daemon :models:pl-pl-polimorf:validateModelRelease -PmodelReleaseVersion=1.0.0
./gradlew --no-daemon :models:pl-pl-polimorf:packageModelReleaseCandidate -PmodelReleaseVersion=1.0.0
```
Model format compatibility is descriptor-level and does not alter migrated bytes. Version 1 is `radixor-dictionary-tsv-gzip`. Model versions come from each module's `model-version.txt` or the matching explicit release property; catalog version comes from `models/catalog-version.txt`; only core uses Git-derived `release@` versioning.
The model catalog used by the published documentation is generated under `build/mkdocs-source/`. Neither generated Markdown nor rendered MkDocs output belongs in Git.
The remainder of this page describes the earlier migration from repeated serialized patch-command application to compiled patch commands.
## Summary
Radixor patch commands are still encoded as compact strings when dictionaries are built and persisted. That serialized form remains the interchange format used by textual dictionaries, binary artifacts, and compilation tooling.
Runtime stemming should no longer repeatedly apply those serialized strings directly. Since 2.3.0, the String-based patch application API is deprecated. Code that stems live input should load or create `CompiledPatchCommand` values and reuse them. The deprecated API remains available for compatibility during the transition, but applications should migrate before 3.0.0.
The reason is performance. The old API parses the serialized P-command every time it is applied. `CompiledPatchCommand` parses it once and stores a concrete immutable command object, so repeated stemming avoids the same analysis work.
## Deprecated Runtime APIs
The following API family is kept for source compatibility but is no longer the preferred runtime path:
- `PatchCommandEncoder.apply(String, String)`
- `PatchCommandEncoder.apply(String, String, WordTraversalDirection)`
- `PatchCommandEncoder.applyTo(..., String, WordTraversalDirection, ...)`
- `PatchCommandEncoder.applyWithConfiguredDirection(String, String)`
- `StemmerPatchTrieLoader.load(...)` overloads returning `FrequencyTrie<String>`
- `StemmerPatchTrieLoader.loadBinary(...)` overloads returning `FrequencyTrie<String>`
Use the compiled equivalents for runtime stemming:
- `CompiledPatchCommand.compile(String, WordTraversalDirection)`
- `PatchCommandEncoder.compile(String)`
- `PatchCommandEncoder.compile(String, WordTraversalDirection)`
- `StemmerPatchTrieLoader.loadCompiled(...)`
- `StemmerPatchTrieLoader.loadBinaryCompiled(...)`
## Loading A Text Dictionary
Old runtime code:
```java
Path dictionary = Path.of("dictionary.txt");
ReductionSettings settings = ReductionSettings.withDefaults(
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
FrequencyTrie<String> trie = StemmerPatchTrieLoader.load(dictionary, true, settings);
String word = "running";
String patch = trie.get(word);
String stem = patch == null
? word
: PatchCommandEncoder.apply(word, patch, trie.traversalDirection());
```
New runtime code:
```java
Path dictionary = Path.of("dictionary.txt");
ReductionSettings settings = ReductionSettings.withDefaults(
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
FrequencyTrie<CompiledPatchCommand> trie = StemmerPatchTrieLoader.loadCompiled(dictionary, true, settings);
String word = "running";
CompiledPatchCommand patch = trie.get(word);
String stem = patch == null ? word : patch.apply(word);
```
## Loading A Binary Artifact
Old runtime code:
```java
FrequencyTrie<String> trie = StemmerPatchTrieLoader.loadBinary(Path.of("us-uk.radixor.gz"));
String word = "studies";
String patch = trie.get(word);
String stem = patch == null
? word
: PatchCommandEncoder.apply(word, patch, trie.traversalDirection());
```
New runtime code:
```java
FrequencyTrie<CompiledPatchCommand> trie =
StemmerPatchTrieLoader.loadBinaryCompiled(Path.of("us-uk.radixor.gz"));
String word = "studies";
CompiledPatchCommand patch = trie.get(word);
String stem = patch == null ? word : patch.apply(word);
```
Existing binary artifacts remain readable. `loadBinaryCompiled(...)` reads the stored serialized patch strings and compiles them during load setup, before live stemming begins.
## Manual Patch Encoding
Encoding still produces a serialized patch command because that is the compact stored representation:
```java
PatchCommandEncoder encoder = PatchCommandEncoder.builder().build();
String patch = encoder.encode("running", "run");
```
Old repeated application:
```java
String stem = PatchCommandEncoder.apply("running", patch);
```
New repeated application:
```java
CompiledPatchCommand compiled = encoder.compile(patch);
String stem = compiled.apply("running");
```
## Caller-Owned Output Buffers
Old buffer-oriented code:
```java
char[] output = new char[32];
int length = PatchCommandEncoder.applyTo(
"running",
patch,
WordTraversalDirection.BACKWARD,
output,
0,
output.length);
```
New buffer-oriented code:
```java
CompiledPatchCommand compiled = CompiledPatchCommand.compile(patch, WordTraversalDirection.BACKWARD);
char[] output = new char[32];
int length = compiled.applyTo("running", output, 0, output.length);
```
Both APIs return `CompiledPatchCommand.APPLY_INSUFFICIENT_CAPACITY` when the caller-owned output range is too small.
## Compatibility Rules
Serialized patch strings remain part of the dictionary and artifact format. The deprecation is about repeated runtime application of serialized strings, not about the stored representation itself.
Compatibility tests may continue to exercise the deprecated API to prove that old artifacts and source code still work during the transition. New production code, examples, and benchmark runtime paths should use `CompiledPatchCommand`.
The command-line compiler still writes artifacts containing serialized patch commands. Runtime loaders can expose those commands as compiled immutable objects through `loadCompiled(...)` and `loadBinaryCompiled(...)`.
## Contracted Trie Artifacts
Current compiled loaders and freshly written binary artifacts can use contracted compiled tries.
Contraction replaces a subtree with an accepting leaf when every reachable entry below that subtree
selects the same preferred patch command. This changes the physical trie shape and the binary
stream version, but it does not change the serialized patch-command language.
Existing binary artifacts remain readable through the compatibility reader. To obtain the
contracted runtime representation, rebuild the artifact with the current compiler or load the
source dictionary through the current `loadCompiled(...)` APIs. Applications that only consume
`CompiledPatchCommand` values through `get()` and `apply(...)` do not need code changes for this
optimization.

View File

@@ -0,0 +1,277 @@
# Model Selection and Loading
Radixor separates executable stemming code from language data. The core artifact supplies dictionary parsing, trie construction, patch commands, lookup, and the model registry. A model artifact supplies one indexed descriptor, one GZip-compressed Radixor dictionary, and its licensing material. The core JAR contains no language dictionary.
```text
Application
-> org.egothor:radixor (algorithmic core)
-> StemmerModelRegistry
-> indexed model descriptor
-> namespaced stemmer.gz resource
-> checksum verification and dictionary parsing
-> FrequencyTrie construction
-> patch lookup and stemming
```
## Language and model ID
These identifiers answer different questions:
| Concept | Example | Meaning |
|---|---|---|
| Language | `Language.PL_PL` | Polish as a linguistic identity |
| Model ID | `pl-pl-unimorph` | One concrete Polish model configuration |
| Model ID | `pl-pl-polimorf` | A different concrete Polish model configuration |
| Default model | `PL_PL -> pl-pl-unimorph` | The model selected by the language convenience API |
One language can have several models. `Language.PL_PL` is neither UniMorph nor PoliMorf. `loadCompiled(Language.PL_PL, ...)` resolves the stable default ID declared by `Language.defaultModelId()`. An explicit lookup requests exactly one ID. Registry ordering never changes either decision.
Licensing follows the selected artifact. Radixor Java software is BSD-3-Clause; UniMorph-derived
model data carries a model-specific CC BY-SA 3.0 notice, while PoliMorf carries its separate
BSD-2-Clause license. The UniMorph notice preserves upstream attribution and identifies the
Radixor transformations and limited protectable contributions without claiming the underlying data.
## Choose runtime dependencies
Radixor 4 is an architectural migration that is not yet represented by a published release in this working tree, so core and catalog versions below use placeholders. Every source-controlled model currently has model version `1.0.0`.
### Core plus the default Polish model
```groovy
dependencies {
implementation 'org.egothor:radixor:<radixor-version>'
runtimeOnly 'org.egothor:radixor-model-pl-pl-unimorph:1.0.0'
}
```
### Core plus optional PoliMorf
```groovy
dependencies {
implementation 'org.egothor:radixor:<radixor-version>'
runtimeOnly 'org.egothor:radixor-model-pl-pl-polimorf:1.0.0'
}
```
This dependency makes `pl-pl-polimorf` discoverable; it does not change the default for `PL_PL`.
### Both Polish models
```groovy
dependencies {
implementation 'org.egothor:radixor:<radixor-version>'
runtimeOnly 'org.egothor:radixor-model-pl-pl-unimorph:1.0.0'
runtimeOnly 'org.egothor:radixor-model-pl-pl-polimorf:1.0.0'
}
```
### Standard defaults
```groovy
dependencies {
implementation 'org.egothor:radixor:<radixor-version>'
runtimeOnly 'org.egothor:radixor-models-standard:<catalog-version>'
}
```
The standard aggregate is POM-only. Its POM supplies exactly one default model per supported language as transitive runtime dependencies and excludes optional PoliMorf. It publishes no empty binary JAR.
### BOM-managed versions
```groovy
dependencies {
implementation 'org.egothor:radixor:<radixor-version>'
implementation platform('org.egothor:radixor-models-bom:<catalog-version>')
runtimeOnly 'org.egothor:radixor-model-pl-pl-unimorph'
runtimeOnly 'org.egothor:radixor-model-pl-pl-polimorf'
}
```
Equivalent Maven dependencies use ordinary runtime scope:
```xml
<dependency>
<groupId>org.egothor</groupId>
<artifactId>radixor</artifactId>
<version>${radixor.version}</version>
</dependency>
<dependency>
<groupId>org.egothor</groupId>
<artifactId>radixor-model-pl-pl-unimorph</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
```
Use `implementation` for the core because application code imports its API. Models normally use `runtimeOnly` because they provide resources rather than Java types. Tests with a deliberately isolated model set use `testRuntimeOnly`. The repository attaches every default model and optional PoliMorf directly to `jmhRuntimeOnly`; test and quality configurations likewise use direct non-production model dependencies. No benchmark aggregate artifact exists, and no model dependency enters the root published POM.
## Load the documented default
Dependency prerequisite: core plus `radixor-model-pl-pl-unimorph` (or the standard pack).
```java
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.ReductionMode;
import org.egothor.stemmer.ReductionSettings;
import org.egothor.stemmer.StemmerPatchTrieLoader;
final FrequencyTrie<CompiledPatchCommand> polish =
StemmerPatchTrieLoader.loadCompiled(
StemmerPatchTrieLoader.Language.PL_PL,
true,
ReductionSettings.withDefaults(
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS));
final String word = "koty";
final CompiledPatchCommand patch = polish.get(word);
final String stem = patch == null ? word : patch.apply(word);
```
The loader creates a registry from the thread context class loader, resolves `PL_PL` to `pl-pl-unimorph`, verifies the compressed resource checksum, decompresses and parses the UTF-8 dictionary, constructs the trie, and compiles its patch commands. It does not load a serialized Java object. If the default artifact is absent, `StemmerModelNotFoundException` names the missing ID and suggested Maven artifact.
## Load PoliMorf explicitly
Dependency prerequisite: core plus `radixor-model-pl-pl-polimorf`.
```java
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.ReductionMode;
import org.egothor.stemmer.StemmerModelDescriptor;
import org.egothor.stemmer.StemmerModelRegistry;
import org.egothor.stemmer.StemmerPatchTrieLoader;
final StemmerModelRegistry registry = StemmerModelRegistry.fromContextClassLoader();
final StemmerModelDescriptor descriptor = registry.require("pl-pl-polimorf");
final FrequencyTrie<CompiledPatchCommand> polish =
StemmerPatchTrieLoader.loadCompiled(
descriptor,
true,
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
final String word = "koty";
final CompiledPatchCommand patch = polish.get(word);
final String stem = patch == null ? word : patch.apply(word);
```
The equivalent direct model-ID form is:
```java
final FrequencyTrie<CompiledPatchCommand> polimorf =
StemmerPatchTrieLoader.loadCompiled(
"pl-pl-polimorf",
true,
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
```
`require("pl-pl-polimorf")` and the direct overload are deterministic because registry keys are stable model IDs. Discovery order is sorted, duplicate IDs are rejected, and no “first Polish model on the classpath” fallback exists. Both overloads return compiled patch-command values and perform complete integrity checking, parsing, reduction, and trie construction.
!!! warning "PoliMorf startup memory"
Full construction of the PoliMorf model is memory-intensive. Radixor verifies it in one isolated JVM with a task-specific maximum heap of 6 GiB. Two measured verification runs completed full construction in 23.7 seconds and 23.5 seconds, producing 358,993 canonical trie nodes; the complete Gradle processes peaked at approximately 6.23 GiB resident memory. The compressed model is only 12,624,997 bytes (68,093,680 bytes decompressed), so JAR size is not a proxy for construction-time heap. Applications loading the complete model must provision sufficient startup heap. Radixor does not currently expose a measured retained-heap value, so do not infer one from the process peak.
## Use both Polish models
Dependency prerequisite: both Polish model artifacts.
```java
final StemmerModelRegistry registry = StemmerModelRegistry.fromContextClassLoader();
final StemmerModelDescriptor unimorph = registry.require("pl-pl-unimorph");
final StemmerModelDescriptor polimorf = registry.require("pl-pl-polimorf");
final StemmerModelDescriptor defaultPolish =
registry.requireDefault(StemmerPatchTrieLoader.Language.PL_PL);
if (!"pl-pl-unimorph".equals(defaultPolish.id())) {
throw new IllegalStateException(
"Unexpected default Polish model: " + defaultPolish.id());
}
final FrequencyTrie<CompiledPatchCommand> unimorphTrie =
StemmerPatchTrieLoader.loadCompiled(unimorph, true, reductionMode);
final FrequencyTrie<CompiledPatchCommand> polimorfTrie =
StemmerPatchTrieLoader.loadCompiled(polimorf, true, reductionMode);
```
The descriptors and tries coexist independently. The models are not merged, and adding PoliMorf does not alter the language default. An application that compares, votes across, or merges model outputs must implement that higher-level policy explicitly.
## Discover available models
```java
final StemmerModelRegistry registry = StemmerModelRegistry.fromContextClassLoader();
for (final StemmerModelDescriptor model : registry.models()) {
System.out.printf("%s %s %s %s/%d descriptor=%s%n",
model.id(), model.language(), model.version(),
model.format(), model.formatVersion(), model.source());
}
final java.util.List<StemmerModelDescriptor> polishModels =
registry.findByLanguage(StemmerPatchTrieLoader.Language.PL_PL);
```
Both lists use stable model-ID order. The public descriptor API exposes ID, model artifact version, language, display name, runtime resource, default flag, format, format version, checksum, and descriptor source URL. Packaged provenance properties such as `source.name` and `source.version` are not currently exposed as typed descriptor accessors; consult the generated [model catalog](stemmer-model-catalog.md) for them.
## Use an explicit ClassLoader
```java
final ClassLoader pluginLoader = plugin.getClass().getClassLoader();
final StemmerModelRegistry pluginModels =
StemmerModelRegistry.fromClassLoader(pluginLoader);
final StemmerModelDescriptor model = pluginModels.require("pl-pl-polimorf");
```
`fromContextClassLoader()` uses the current thread context loader, falling back to Radixor's defining loader when the context loader is `null`. `fromClassLoader(loader)` searches only what that loader can expose through `getResources(...)` and ordinary resource lookup. Plugin containers, application servers, and isolated tests can therefore observe different model sets. Pass a non-null loader and retain the registry associated with that deployment scope.
## Error handling
```java
try {
final StemmerModelRegistry registry = StemmerModelRegistry.fromContextClassLoader();
final StemmerModelDescriptor model = registry.require("pl-pl-polimorf");
// Load and cache the trie during application startup.
} catch (final StemmerModelNotFoundException exception) {
// Missing runtime dependency or model hidden from this ClassLoader.
throw exception;
} catch (final DuplicateStemmerModelException exception) {
// Conflicting artifacts or a fat JAR duplicated one stable ID.
throw exception;
} catch (final UnsupportedStemmerModelFormatException exception) {
// The model format or format version is not supported by this core.
throw exception;
} catch (final StemmerModelIntegrityException exception) {
// Malformed descriptor/index, missing resource, wrong language, or checksum failure.
throw exception;
} catch (final java.io.IOException exception) {
// Classpath enumeration or resource I/O failed.
throw new java.io.UncheckedIOException(exception);
}
```
Malformed metadata does not have a separate public exception: it is reported as `StemmerModelIntegrityException`. Missing explicit and default models both use `StemmerModelNotFoundException`; the default diagnostic additionally names the language and expected default ID. Never swallow these failures or choose an arbitrary model.
## Lifecycle and concurrency
`StemmerModelRegistry` copies discovered descriptors into an unmodifiable map, returns immutable list copies, and has no mutating API. `StemmerModelDescriptor` is final with final fields. These objects are safe to retain after discovery. Registry discovery is not globally cached: every call enumerates indexes and parses descriptors again. Model loading is also not cached: every call reads, hashes, decompresses, parses, and builds a new trie.
Compiled tries are immutable and thread-safe for concurrent reads. Load a registry and the required tries once during application startup, publish them safely, and reuse them. The loader does not cache model tries; do not repeatedly discover and compile models per token. When comparing both Polish models, account for the memory of two independent tries and avoid constructing them concurrently unless the deployment is sized for that peak.
## Troubleshooting
| Symptom | Meaning | Action |
|---|---|---|
| `No default model '...' is available` | The default artifact is absent from the selected loader | Add the named model as a runtime dependency and inspect `runtimeClasspath` |
| `No model 'pl-pl-polimorf' is available` | Explicit optional model is absent or invisible | Add `radixor-model-pl-pl-polimorf` to runtime, not only tests |
| Duplicate model ID | Two resources declare one stable ID | Remove the duplicate artifact or fix fat-JAR resource duplication; do not reorder the classpath |
| Checksum mismatch | Descriptor and compressed bytes differ | Replace the corrupted or incorrectly repackaged artifact |
| Unsupported format | Core supports neither the format name nor version | Use a compatible core/model pair; do not bypass validation |
| Works in tests, fails in production | The model is probably `testRuntimeOnly` | Inspect `./gradlew dependencies --configuration runtimeClasspath` |
| Visible with one loader only | Class loaders expose different resources | Call `fromClassLoader(...)` with the loader that owns the model JAR |
| PoliMorf is installed but language loading uses UniMorph | Expected default behavior | Select `pl-pl-polimorf` explicitly |
| Dependency minimization removed the model | Resource-only dependency was treated as unused | Preserve the model JAR, index, descriptor, license, and dictionary |
| Shaded JAR fails or reports duplicates | Indexes/resources were dropped or duplicated | Inspect with `jar tf app.jar | grep -E 'models.index|stemmer.gz'`; configure deterministic resource merging without duplicating IDs |
Useful Gradle diagnostics include `./gradlew dependencyInsight --dependency radixor-model --configuration runtimeClasspath` and `./gradlew dependencies --configuration testRuntimeClasspath`. Classpath order is not a remediation mechanism.
Continue with [Programmatic Usage](programmatic-usage.md), [Stemmer Models](stemmer-models.md), [Built-in Languages](built-in-languages.md), the generated [model catalog](stemmer-model-catalog.md), and [Architecture](architecture.md).

View File

@@ -94,7 +94,8 @@ This model works especially well when domain-specific extensions are added in la
After loading a compiled artifact, applications can inspect the persisted build descriptor directly:
```java
final FrequencyTrie<String> trie = StemmerPatchTrieLoader.loadBinary("build/stemmers/cs_cz.dat.gz");
final FrequencyTrie<CompiledPatchCommand> trie =
StemmerPatchTrieLoader.loadBinaryCompiled("build/stemmers/cs_cz.dat.gz");
final TrieMetadata metadata = trie.metadata();
System.out.println(metadata.formatVersion());

View File

@@ -2,25 +2,26 @@
This document explains how to acquire a compiled Radixor stemmer in Java.
## Load a bundled language dictionary
## Load a registered default model
Bundled language resources are simple to use and compile directly into a `FrequencyTrie<String>` during loading.
Language-oriented entry points resolve a registered default model and compile its GZip textual dictionary into a `FrequencyTrie<CompiledPatchCommand>`. The corresponding model JAR must be on the runtime classpath; the core contains no dictionary.
```java
import java.io.IOException;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.ReductionMode;
import org.egothor.stemmer.StemmerPatchTrieLoader;
public final class BundledLanguageExample {
public final class RegisteredLanguageModelExample {
private BundledLanguageExample() {
private RegisteredLanguageModelExample() {
throw new AssertionError("No instances.");
}
public static void main(final String[] arguments) throws IOException {
final FrequencyTrie<String> trie = StemmerPatchTrieLoader.load(
final FrequencyTrie<CompiledPatchCommand> trie = StemmerPatchTrieLoader.loadCompiled(
StemmerPatchTrieLoader.Language.US_UK,
true,
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
@@ -30,14 +31,22 @@ public final class BundledLanguageExample {
The `storeOriginal` flag controls whether the canonical stem is inserted as a no-op patch entry for the stem itself.
Language-oriented `loadCompiled(...)` entry points build the runtime trie with the same contracted
representation used by the published benchmarks. During compilation, uniform preferred-command
subtrees are collapsed into accepting leaves, so lookup can stop before consuming the entire input
when the remaining characters cannot change the selected patch command.
## Load a textual dictionary
Loading from a dictionary file follows the same preparation model as bundled resources, but the source comes from your own file or path. The input may be plain UTF-8 text or GZip-compressed UTF-8 text; the loader detects GZip data from the stream header. The textual format is tab-separated values, meaning that columns are separated by the tab character. Each non-empty logical line starts with the stem column and may contain zero or more variant columns. Input case normalization is controlled by `CaseProcessingMode` (default: `LOWERCASE_WITH_LOCALE_ROOT`), trailing remarks introduced by `#` or `//` are ignored, and dictionary items containing embedded whitespace are currently ignored with warning-level diagnostics.
Loading from a dictionary file follows the same trie preparation model as registered model resources, but the source comes from your own file or path and bypasses registry metadata. The input may be plain UTF-8 text or GZip-compressed UTF-8 text; the loader detects GZip data from the stream header. The textual format is tab-separated values, meaning that columns are separated by the tab character. Each non-empty logical line starts with the stem column and may contain zero or more variant columns. Input case normalization is controlled by `CaseProcessingMode` (default: `LOWERCASE_WITH_LOCALE_ROOT`), trailing remarks introduced by `#` or `//` are ignored, and dictionary items containing embedded whitespace are currently ignored with warning-level diagnostics.
For explicit model IDs, multiple variants, and ClassLoader control, see [Model Selection and Loading](model-selection-and-loading.md).
```java
import java.io.IOException;
import java.nio.file.Path;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.ReductionMode;
import org.egothor.stemmer.ReductionSettings;
@@ -50,7 +59,7 @@ public final class LoadTextDictionaryExample {
}
public static void main(final String[] arguments) throws IOException {
final FrequencyTrie<String> trie = StemmerPatchTrieLoader.load(
final FrequencyTrie<CompiledPatchCommand> trie = StemmerPatchTrieLoader.loadCompiled(
Path.of("data", "stemmer.tsv"),
true,
ReductionSettings.withDefaults(
@@ -59,7 +68,12 @@ public final class LoadTextDictionaryExample {
}
```
Additional `StemmerPatchTrieLoader.load(...)` overloads let callers provide explicit `WordTraversalDirection`, `CaseProcessingMode`, `DiacriticProcessingMode`, or a complete `TrieMetadata` instance. Use those overloads when a custom dictionary must be compiled with forward traversal for right-to-left languages, case-sensitive keys, or diacritic stripping.
Additional `StemmerPatchTrieLoader.loadCompiled(...)` overloads let callers provide explicit `WordTraversalDirection`, `CaseProcessingMode`, `DiacriticProcessingMode`, or a complete `TrieMetadata` instance. Use those overloads when a custom dictionary must be compiled with forward traversal for right-to-left languages, case-sensitive keys, or diacritic stripping.
When `ReductionSettings` are supplied through these compiled loader APIs, uniform-subtree
contraction is still enabled as an internal pre-reduction step. The public `ReductionMode` remains
the semantic policy for subtree equivalence after that contraction has removed regions whose
preferred command is already uniform.
## Load a compiled binary artifact
@@ -69,6 +83,7 @@ Binary loading is typically the preferred runtime path because it avoids reparsi
import java.io.IOException;
import java.nio.file.Path;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.StemmerPatchTrieLoader;
@@ -79,7 +94,7 @@ public final class LoadBinaryExample {
}
public static void main(final String[] arguments) throws IOException {
final FrequencyTrie<String> trie = StemmerPatchTrieLoader.loadBinary(
final FrequencyTrie<CompiledPatchCommand> trie = StemmerPatchTrieLoader.loadBinaryCompiled(
Path.of("stemmers", "english.radixor.gz"));
}
}
@@ -96,6 +111,7 @@ at load time. This does not change persisted metadata, only the materialized in-
import java.io.IOException;
import java.nio.file.Path;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.StemmerPatchTrieLoader;
@@ -106,14 +122,14 @@ public final class LoadBinaryWithDenseLookupExample {
}
public static void main(final String[] arguments) throws IOException {
final FrequencyTrie<String> balanced = StemmerPatchTrieLoader.loadBinary(
final FrequencyTrie<CompiledPatchCommand> balanced = StemmerPatchTrieLoader.loadBinaryCompiled(
Path.of("stemmers", "english.radixor.gz"));
final FrequencyTrie<String> fast = StemmerPatchTrieLoader.loadBinary(
final FrequencyTrie<CompiledPatchCommand> fast = StemmerPatchTrieLoader.loadBinaryCompiled(
Path.of("stemmers", "english.radixor.gz"),
1024);
final FrequencyTrie<String> compact = StemmerPatchTrieLoader.loadBinary(
final FrequencyTrie<CompiledPatchCommand> compact = StemmerPatchTrieLoader.loadBinaryCompiled(
Path.of("stemmers", "english.radixor.gz"),
0);
}

View File

@@ -10,7 +10,7 @@ This document explains how a compiled Radixor trie is queried and how ambiguity
```java
final String word = "running";
final String patch = trie.get(word);
final CompiledPatchCommand patch = trie.get(word);
```
### `getAll(...)`: ordered local values
@@ -18,7 +18,7 @@ final String patch = trie.get(word);
`FrequencyTrie.getAll(String)` returns all values stored at the addressed node, ordered by descending frequency using the same deterministic tie-breaking rules. The returned array is a defensive copy. If the key is missing or has no local values, an empty array is returned.
```java
final String[] patches = trie.getAll("axes");
final CompiledPatchCommand[] patches = trie.getAll("axes");
```
### `getEntries(...)`: values with counts
@@ -28,9 +28,10 @@ final String[] patches = trie.getAll("axes");
```java
import java.util.List;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.ValueCount;
final List<ValueCount<String>> entries = trie.getEntries("axes");
final List<ValueCount<CompiledPatchCommand>> entries = trie.getEntries("axes");
```
### Visitor lookup for hot paths
@@ -55,31 +56,29 @@ trie.getAllNormalized(token, 0, token.length, (patch, count, rank) -> {
`getAllNormalized(...)` bypasses `caseProcessingMode` and `diacriticProcessingMode`; callers are responsible for supplying canonical input. `maxResults == 0` visits nothing, negative values are rejected, and a sink returning `false` stops iteration after the current callback.
## Apply patch commands
## Apply compiled patch commands
A patch command is not the final stem. It must be applied to the original input token. `PatchCommandEncoder.apply(source, patchCommand)` performs that transformation directly on the serialized command format. If the source is `null`, the method returns `null`. If the patch is `null`, empty, or malformed in compatibility-relevant ways, the original source word is preserved. Equal source and target words are represented by the canonical no-op patch.
A patch command is not the final stem. It must be applied to the original input token. Runtime code should use `CompiledPatchCommand`, which parses the stored patch-command representation once during setup and then applies the concrete immutable command repeatedly.
```java
import org.egothor.stemmer.PatchCommandEncoder;
import org.egothor.stemmer.CompiledPatchCommand;
final String word = "running";
final String patch = trie.get(word);
final String stem = PatchCommandEncoder.apply(word, patch);
final CompiledPatchCommand patch = trie.get(word);
final String stem = patch == null ? word : patch.apply(word);
```
Hot paths can apply a patch into caller-owned character storage:
```java
final char[] output = new char[32];
final int produced = PatchCommandEncoder.applyTo(
final int produced = patch.applyTo(
word,
patch,
trie.traversalDirection(),
output,
0,
output.length);
if (produced != PatchCommandEncoder.APPLY_INSUFFICIENT_CAPACITY) {
if (produced != CompiledPatchCommand.APPLY_INSUFFICIENT_CAPACITY) {
final String stem = new String(output, 0, produced);
}
```
@@ -90,12 +89,14 @@ For multiple candidates:
```java
final String word = "axes";
for (final String patch : trie.getAll(word)) {
final String stem = PatchCommandEncoder.apply(word, patch);
for (final CompiledPatchCommand patch : trie.getAll(word)) {
final String stem = patch.apply(word);
System.out.println(word + " -> " + stem + " (" + patch + ")");
}
```
The historical `PatchCommandEncoder.apply(...)` API still exists for compatibility with code that directly handles serialized patch-command strings, but it is deprecated because it reparses the command on every call. See [Migration and Backward Compatibility](migration-and-backward-compatibility.md) for the old and new forms side by side.
## Understand reduction modes
Reduction mode determines how mutable subtrees are merged during compilation. All modes operate on full subtree semantics rather than only on local node content.

View File

@@ -1,75 +1,133 @@
# Programmatic Usage
This document provides the programmatic entry point to **Radixor**.
Radixor code and model data are separate runtime components. Every example on this page requires `org.egothor:radixor:<radixor-version>` as an `implementation` dependency and at least one model JAR as a runtime dependency. The core JAR contains no `stemmer.gz`.
Radixor follows a clear lifecycle:
For complete dependency patterns, lifecycle guidance, and troubleshooting, use [Model Selection and Loading](model-selection-and-loading.md). The generated [model catalog](stemmer-model-catalog.md) records the current artifacts, versions, checksums, and provenance.
1. acquire a compiled stemmer,
2. query it for patch commands,
3. apply those commands to produce stems,
4. reopen and extend the compiled structure when needed.
## 1. Minimal use: the Polish default
## Conceptual model
Dependency prerequisite:
Radixor is dictionary-driven, but runtime stemming does not operate by scanning raw dictionary files. A source dictionary is parsed as a sequence of canonical stems and their known variants. Each variant is converted into a compact patch command that transforms the variant into the stem, while the stem itself may optionally be stored as a canonical no-op patch. The mutable trie is then reduced into a compiled read-only structure that stores ordered values and their counts at addressed nodes.
Two consequences matter for developers:
- the quality and coverage of stemming behavior depend on dictionary richness,
- runtime usage is based on compiled patch-command lookup rather than on direct dictionary traversal.
This is why Radixor can generalize beyond explicitly listed forms and why compiled artifacts are well suited for deployment.
## Documentation map
The programmatic API is easier to understand when split by developer task:
- [Loading and Building Stemmers](programmatic-loading-and-building.md) explains how to acquire a compiled stemmer from bundled resources, textual dictionaries, binary artifacts, or direct builder usage.
- [Lookup Edge Optimization](lookup-edge-optimization.md) explains dense child lookup tuning and the speed/memory trade-off when materializing compiled tries.
- [Querying and Ambiguity Handling](programmatic-querying-and-ambiguity.md) explains `get(...)`, `getAll(...)`, `getEntries(...)`, patch application, and the practical meaning of reduction modes.
- [Extending and Persisting Compiled Tries](programmatic-extending-and-persistence.md) explains how to reopen compiled tries, add new lexical data, rebuild them, and store them as binary artifacts.
## Core types
The main types involved in programmatic usage are:
- `FrequencyTrie.Builder<V>` for mutable construction and extension,
- `FrequencyTrie<V>` for the compiled read-only trie,
- `PatchCommandEncoder` for creating and applying patch commands,
- `StemmerPatchTrieLoader` for loading bundled or textual dictionaries,
- `StemmerPatchTrieBinaryIO` for reading and writing compressed binary artifacts,
- `FrequencyTrieBuilders` for reconstructing a mutable builder from a compiled trie,
- `ReductionMode` and `ReductionSettings` for controlling compilation semantics.
## Java module system (JPMS)
The core artifact is published as an explicit JPMS module:
```java
module org.egothor.radixor;
```groovy
implementation 'org.egothor:radixor:<radixor-version>'
runtimeOnly 'org.egothor:radixor-model-pl-pl-unimorph:1.0.0'
```
A named consuming module uses:
```java
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.ReductionMode;
import org.egothor.stemmer.StemmerPatchTrieLoader;
final FrequencyTrie<CompiledPatchCommand> trie =
StemmerPatchTrieLoader.loadCompiled(
StemmerPatchTrieLoader.Language.PL_PL,
true,
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
final String word = "koty";
final CompiledPatchCommand patch = trie.get(word);
final String stem = patch == null ? word : patch.apply(word);
```
`Language.PL_PL` resolves to `pl-pl-unimorph`. The loader creates the registry internally through the thread context class loader.
## 2. Explicit model selection
Dependency prerequisite: replace or supplement the default dependency with `runtimeOnly 'org.egothor:radixor-model-pl-pl-polimorf:1.0.0'`.
```java
module example.consumer {
requires org.egothor.radixor;
final StemmerModelRegistry registry = StemmerModelRegistry.fromContextClassLoader();
final StemmerModelDescriptor polimorf = registry.require("pl-pl-polimorf");
final FrequencyTrie<CompiledPatchCommand> trie =
StemmerPatchTrieLoader.loadCompiled(
polimorf,
true,
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
```
The stable model-ID overload performs the same exact selection without a separately retained registry:
```java
final FrequencyTrie<CompiledPatchCommand> trie =
StemmerPatchTrieLoader.loadCompiled(
"pl-pl-polimorf",
true,
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
```
## 3. Multiple variants for one language
Dependency prerequisite: both `radixor-model-pl-pl-unimorph:1.0.0` and `radixor-model-pl-pl-polimorf:1.0.0` at runtime.
```java
final StemmerModelRegistry registry = StemmerModelRegistry.fromContextClassLoader();
final StemmerModelDescriptor unimorph = registry.require("pl-pl-unimorph");
final StemmerModelDescriptor polimorf = registry.require("pl-pl-polimorf");
final FrequencyTrie<CompiledPatchCommand> unimorphTrie =
StemmerPatchTrieLoader.loadCompiled(unimorph, true, reductionMode);
final FrequencyTrie<CompiledPatchCommand> polimorfTrie =
StemmerPatchTrieLoader.loadCompiled(polimorf, true, reductionMode);
final StemmerModelDescriptor defaultPolish =
registry.requireDefault(StemmerPatchTrieLoader.Language.PL_PL);
if (!"pl-pl-unimorph".equals(defaultPolish.id())) {
throw new IllegalStateException(
"Unexpected default Polish model: " + defaultPolish.id());
}
```
The core module is standalone and can be consumed directly as a normal Java module.
The tries remain independent. Radixor does not merge models or infer an alternative default from classpath order.
## Recommended reading order
## 4. Discovery
For most developers, the best order is:
Dependency prerequisite: whichever model artifacts the application intends to discover.
1. [Loading and Building Stemmers](programmatic-loading-and-building.md)
2. [Querying and Ambiguity Handling](programmatic-querying-and-ambiguity.md)
3. [Extending and Persisting Compiled Tries](programmatic-extending-and-persistence.md)
```java
final StemmerModelRegistry registry = StemmerModelRegistry.fromContextClassLoader();
## Next steps
for (final StemmerModelDescriptor descriptor : registry.models()) {
System.out.printf("%s %s %s %s/%d%n",
descriptor.id(), descriptor.language(), descriptor.version(),
descriptor.format(), descriptor.formatVersion());
}
- [Quick Start](quick-start.md)
- [CLI compilation](cli-compilation.md)
- [Dictionary format](dictionary-format.md)
- [Architecture and reduction](architecture-and-reduction.md)
final java.util.List<StemmerModelDescriptor> polish =
registry.findByLanguage(StemmerPatchTrieLoader.Language.PL_PL);
```
Results use deterministic model-ID order. See [Built-in Languages](built-in-languages.md) for default interpretation and the generated [catalog](stemmer-model-catalog.md) for provenance.
## 5. Advanced ClassLoader selection
Dependency prerequisite: the model JAR must be visible to the selected loader.
```java
final ClassLoader applicationLoader = application.getClass().getClassLoader();
final StemmerModelRegistry isolatedRegistry =
StemmerModelRegistry.fromClassLoader(applicationLoader);
```
This form is useful for plugin containers, isolated application servers, and tests. It can discover a different set from the thread context loader. See [ClassLoader troubleshooting](model-selection-and-loading.md#troubleshooting).
## 6. Error handling
Dependency prerequisite: none beyond core; this example demonstrates an absent optional model.
```java
try {
StemmerModelRegistry.fromContextClassLoader().require("pl-pl-polimorf");
} catch (final StemmerModelNotFoundException exception) {
System.err.println(exception.getMessage());
}
```
Missing models never produce an empty trie or arbitrary fallback. Duplicate IDs, unsupported formats, malformed descriptors, missing resources, and checksum mismatches are also fatal. The full exception mapping and remediation table are in [Model Selection and Loading](model-selection-and-loading.md#error-handling).
## Continue into the trie API
- [Loading and Building Stemmers](programmatic-loading-and-building.md)
- [Querying and Ambiguity Handling](programmatic-querying-and-ambiguity.md)
- [Extending and Persisting Compiled Tries](programmatic-extending-and-persistence.md)
- [Architecture](architecture.md)

View File

@@ -2,8 +2,25 @@
This guide introduces the fastest practical path to using **Radixor**.
If you are new to Radixor and want the shortest possible path to a first working stem, start with
[Fast Track](fast-track.md). This Quick Start is a broader developer walkthrough: it introduces the
main loading options, query methods, artifact workflow, and metadata model. For model-ID selection and failures, use [Model Selection and Loading](model-selection-and-loading.md).
Radixor separates preparation from runtime usage. Source dictionaries are used to derive patch commands and reduce them into a compact read-only trie. Runtime stemming then operates on that compiled structure rather than on the original dictionary text. A richer dictionary usually improves the quality and coverage of inferred transformations, including transformations that are applicable to words not explicitly present in the source material. The reduction step also removes a large amount of redundant lexical information, which is why very large dictionaries can still produce compact runtime artifacts. These artifacts can be persisted and loaded directly when needed.
From version 4 onward, the core and models are explicit dependencies:
```groovy
dependencies {
implementation 'org.egothor:radixor:<radixor-version>'
runtimeOnly 'org.egothor:radixor-models-standard:<catalog-version>'
}
```
The core JAR contains no dictionary. Replace the standard pack with `runtimeOnly 'org.egothor:radixor-model-us-uk-default:1.0.0'` for the minimal English example below. For Polish, `Language.PL_PL` resolves `pl-pl-unimorph`; installing optional `pl-pl-polimorf` does not select it automatically.
Explicit PoliMorf loading uses `StemmerPatchTrieLoader.loadCompiled("pl-pl-polimorf", true, reductionMode)`. Its complete dictionary is supported, but construction is exceptional enough that repository verification runs it separately with a 6 GiB maximum heap. See [Model Selection and Loading](model-selection-and-loading.md#load-polimorf-explicitly) for the complete dependency and Java example.
A practical workflow usually consists of two independent phases:
1. obtain a compiled stemmer,
@@ -13,25 +30,26 @@ A practical workflow usually consists of two independent phases:
A compiled stemmer can be obtained in three common ways.
### Use a bundled language dictionary
### Use an external language model
Radixor ships with bundled dictionaries for a set of supported languages. These resources are line-oriented dictionaries stored with the library and compiled into a `FrequencyTrie<String>` when loaded. The loader can also store the canonical stem itself as a no-op patch command. Compiled trie artifacts now persist self-describing metadata, including the traversal direction and compilation reduction settings used to build the artifact.
Language dictionaries are independently versioned model JARs discovered by `StemmerModelRegistry`. The root `org.egothor:radixor` JAR contains no dictionary bytes. The loader compiles a selected model into a `FrequencyTrie<CompiledPatchCommand>`; compiled trie artifacts retain self-describing traversal and reduction metadata.
```java
import java.io.IOException;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.ReductionMode;
import org.egothor.stemmer.StemmerPatchTrieLoader;
public final class BundledStemmerExample {
public final class RegisteredModelExample {
private BundledStemmerExample() {
private RegisteredModelExample() {
throw new AssertionError("No instances.");
}
public static void main(final String[] arguments) throws IOException {
final FrequencyTrie<String> trie = StemmerPatchTrieLoader.load(
final FrequencyTrie<CompiledPatchCommand> trie = StemmerPatchTrieLoader.loadCompiled(
StemmerPatchTrieLoader.Language.US_UK,
true,
ReductionMode.MERGE_SUBTREES_WITH_EQUIVALENT_RANKED_GET_ALL_RESULTS);
@@ -49,6 +67,7 @@ Compiled stemmers can be stored as GZip-compressed binary artifacts and loaded d
import java.io.IOException;
import java.nio.file.Path;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.StemmerPatchTrieLoader;
@@ -59,7 +78,7 @@ public final class LoadBinaryStemmerExample {
}
public static void main(final String[] arguments) throws IOException {
final FrequencyTrie<String> trie = StemmerPatchTrieLoader.loadBinary(
final FrequencyTrie<CompiledPatchCommand> trie = StemmerPatchTrieLoader.loadBinaryCompiled(
Path.of("stemmers", "english.radixor.gz"));
System.out.println("Canonical node count: " + trie.size());
@@ -73,6 +92,7 @@ You can tune in-memory child lookup density at load time without changing the ar
import java.io.IOException;
import java.nio.file.Path;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.StemmerPatchTrieLoader;
@@ -83,10 +103,10 @@ public final class LoadBinaryStemmerExampleTuned {
}
public static void main(final String[] arguments) throws IOException {
final FrequencyTrie<String> fast = StemmerPatchTrieLoader.loadBinary(
final FrequencyTrie<CompiledPatchCommand> fast = StemmerPatchTrieLoader.loadBinaryCompiled(
Path.of("stemmers", "english.radixor.gz"),
1024);
final FrequencyTrie<String> compact = StemmerPatchTrieLoader.loadBinary(
final FrequencyTrie<CompiledPatchCommand> compact = StemmerPatchTrieLoader.loadBinaryCompiled(
Path.of("stemmers", "english.radixor.gz"),
128);
@@ -107,7 +127,7 @@ A dedicated CLI compilation workflow deserves its own focused page and should re
## 2. Use the compiled stemmer
A compiled `FrequencyTrie<String>` stores patch commands, not final stems. Querying therefore has two steps:
A compiled `FrequencyTrie<CompiledPatchCommand>` stores patch commands, not final stems. Querying therefore has two steps:
1. retrieve one or more patch commands from the trie,
2. apply each patch command to the original input word.
@@ -121,8 +141,8 @@ Use `get(...)` when the application needs a single preferred transformation.
```java
import java.io.IOException;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.FrequencyTrie;
import org.egothor.stemmer.PatchCommandEncoder;
import org.egothor.stemmer.ReductionMode;
import org.egothor.stemmer.StemmerPatchTrieLoader;
@@ -133,14 +153,14 @@ public final class SingleStemExample {
}
public static void main(final String[] arguments) throws IOException {
final FrequencyTrie<String> trie = StemmerPatchTrieLoader.load(
final FrequencyTrie<CompiledPatchCommand> trie = StemmerPatchTrieLoader.loadCompiled(
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);
final CompiledPatchCommand patch = trie.get(word);
final String stem = patch == null ? word : patch.apply(word);
System.out.println(word + " -> " + stem + " (" + patch + ")");
}
@@ -153,10 +173,10 @@ Use `getAll(...)` when the application should preserve ambiguity instead of coll
```java
final String word = "axes";
final String[] patches = trie.getAll(word);
final CompiledPatchCommand[] patches = trie.getAll(word);
for (final String patch : patches) {
final String stem = PatchCommandEncoder.apply(word, patch);
for (final CompiledPatchCommand patch : patches) {
final String stem = patch.apply(word);
System.out.println(word + " -> " + stem + " (" + patch + ")");
}
```
@@ -168,11 +188,12 @@ For diagnostics or advanced ranking logic, use `getEntries(...)` to obtain value
```java
import java.util.List;
import org.egothor.stemmer.CompiledPatchCommand;
import org.egothor.stemmer.ValueCount;
final List<ValueCount<String>> entries = trie.getEntries("axes");
final List<ValueCount<CompiledPatchCommand>> entries = trie.getEntries("axes");
for (final ValueCount<String> entry : entries) {
for (final ValueCount<CompiledPatchCommand> entry : entries) {
System.out.println(entry.value() + " -> " + entry.count());
}
```
@@ -210,7 +231,11 @@ public final class ExtendCompiledStemmerExample {
String[]::new,
settings);
builder.put("microservices", "Na");
final PatchCommandEncoder encoder = PatchCommandEncoder.builder()
.traversalDirection(compiledTrie.traversalDirection())
.build();
builder.put("microservices", encoder.encode("microservices", "microservice"));
final FrequencyTrie<String> updatedTrie = builder.build();
@@ -239,3 +264,5 @@ Dictionary compilation is usually a one-time preparation step and is generally f
Every compiled trie artifact stores a `TrieMetadata` descriptor together with the immutable trie payload. That metadata currently records the binary format version, the `WordTraversalDirection`, the `ReductionSettings` used during compilation, the declared `DiacriticProcessingMode`, and the selected `CaseProcessingMode`. Traversal, case processing, and diacritic processing are applied during runtime lookup (`get`, `getAll`), and case/diacritic processing are also applied during dictionary insertion when a trie is built.
`DiacriticProcessingMode.AS_IS` keeps dictionary keys and lookup keys unchanged. `DiacriticProcessingMode.REMOVE` strips diacritics from dictionary keys and lookup keys (for Czech diacritics and broad European Latin-script variants). `DiacriticProcessingMode.AS_IS_AND_STRIPPED_FALLBACK` is currently not supported and raises an `UnsupportedOperationException`.
!!! 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).

View File

@@ -103,6 +103,24 @@ This turns reduction into a canonicalization process:
That is how Radixor eliminates duplicated equivalent subtrees.
## Uniform-subtree contraction
Radixor performs one additional internal reduction before each public reduction mode is applied.
When all reachable entries below a subtree have the same preferred patch command, the subtree is
contracted into an accepting leaf for that command.
This optimization is deliberately narrower than the public reduction modes:
- it is based on preferred `get()` behavior,
- it does not depend on child edge shape once the preferred command is uniform,
- it removes lookup depth that cannot affect the selected command,
- it preserves the standard single-result stemming path used by `StemmerPatchTrieLoader.loadCompiled(...)`.
The effect is especially visible in large dictionary tries with many inflected forms that map to
the same command class, such as no-op preservation or common suffix deletion. Runtime lookup can
return the accepting leaf as soon as it is reached instead of traversing the remaining characters
only to discover the same command deeper in the trie.
## Count aggregation and compiled state
When multiple original build-time subtrees collapse into one canonical reduced node, local counts may be aggregated.

View File

@@ -2,6 +2,8 @@
Radixor publishes durable build outputs to GitHub Pages from qualifying runs of `.github/workflows/pages.yml`.
The workflow builds maintained MkDocs documentation and the generated model catalog from the staged source tree under `build/mkdocs-source/`. It then merges the rendered site into the separate `gh-pages` publication worktree while preserving `builds/`. The main branch stores neither generated Markdown nor rendered site output. The publication retains the ten newest numbered report sets and maintains `builds/latest/` as a stable alias.
This page is the central entry point for published project artifacts, including build summaries, API documentation, test and quality reports, benchmark outputs, and software composition materials. It is intended both for routine project inspection and for linking stable report surfaces from external references such as the README, release notes, or development workflows.
## Stable entry points
@@ -42,15 +44,14 @@ They are useful for dependency inspection, downstream integration, compliance-or
## Benchmark outputs and badge metadata
These resources expose benchmark results and generated badge metadata derived from the latest published build:
These resources expose benchmark results and generated badge metadata derived from the latest published build. JMH benchmark reports are published as TXT and CSV files; the historical Porter comparison badge is no longer generated.
- [JMH benchmark results (TXT)](https://leogalambos.github.io/Radixor/builds/latest/jmh/jmh-results.txt)
- [JMH benchmark results (CSV)](https://leogalambos.github.io/Radixor/builds/latest/jmh/jmh-results.csv)
- [Coverage badge metadata](https://leogalambos.github.io/Radixor/builds/latest/metrics/coverage-badge.json)
- [Mutation badge metadata](https://leogalambos.github.io/Radixor/builds/latest/metrics/pitest-badge.json)
- [Benchmark badge metadata](https://leogalambos.github.io/Radixor/builds/latest/metrics/jmh-badge.json)
The benchmark outputs provide direct access to the published JMH result files, while the badge metadata endpoints are intended for status surfaces such as the project README or other generated dashboards.
The benchmark outputs provide direct access to the published JMH result files. Coverage and mutation badge metadata endpoints are intended for status surfaces such as the project README or other generated dashboards.
## Practical usage

View File

@@ -0,0 +1,25 @@
# Published Stemmer Model Catalog
| Model ID | Language | Default | Coordinates | Version | Source | Repository | Source version | Revision | Revision status | License | Attribution | SHA-256 | Bytes |
|---|---|---:|---|---:|---|---|---|---|---|---|---|---|---:|
| cs-cz-default | CS_CZ | true | org.egothor:radixor-model-cs-cz-default | 1.0.0 | UniMorph | https://github.com/unimorph/ces | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph; Witold Kieraś is credited for the separate MorfFlex-CZ conversion | 62afdaa6dc7a721b54a0dc278a0c648a63ad52a34a412d27b5b52fbcde9c1ce4 | 142365 |
| da-dk-default | DA_DK | true | org.egothor:radixor-model-da-dk-default | 1.0.0 | UniMorph | https://github.com/unimorph/dan | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and Wikipedia contributors | 3f7b670a0e7b872bda0381f5154ce058a4656297b39b7157b4ccf6560257cb90 | 73030 |
| de-de-default | DE_DE | true | org.egothor:radixor-model-de-de-default | 1.0.0 | UniMorph | https://github.com/unimorph/deu | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and English Wiktionary contributors | cbfa038122823f02e4bdb54b0035492c356b6ecd80f11eb11290d7a7248a59f5 | 838450 |
| es-es-default | ES_ES | true | org.egothor:radixor-model-es-es-default | 1.0.0 | UniMorph | https://github.com/unimorph/spa | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and English Wiktionary contributors | 7a1ec94cfdb1e9a95431289d62dc5579cb2a532d99532eeda90290072e569721 | 2269280 |
| fa-ir-default | FA_IR | true | org.egothor:radixor-model-fa-ir-default | 1.0.0 | UniMorph | https://github.com/unimorph/fas | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and Wikipedia contributors | b29a0d168a6a97f980666aa40b74a0edd8b6be4ab3320a7abfbb76b3529f4ea1 | 8934 |
| fi-fi-default | FI_FI | true | org.egothor:radixor-model-fi-fi-default | 1.0.0 | UniMorph | https://github.com/unimorph/fin | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and Wikipedia contributors | ca2628b3db31fee92f1b612ebbbd5e956a6dbbfb10e721325e55ef528f26072f | 4867450 |
| fr-fr-default | FR_FR | true | org.egothor:radixor-model-fr-fr-default | 1.0.0 | UniMorph | https://github.com/unimorph/fra | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and Wikipedia contributors | a988658758952fd599dc7360e0234178a6d65ac46e5cedc7dcd325a7cb7e71d9 | 1117956 |
| he-il-default | HE_IL | true | org.egothor:radixor-model-he-il-default | 1.0.0 | UniMorph | https://github.com/unimorph/heb | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph; Omer Goldman (annotator); Wiktionary contributors | 9a47dc69bb7dab21aba0266b73cd74cdaeb17db94363796a0a56111ac8518256 | 179776 |
| hu-hu-default | HU_HU | true | org.egothor:radixor-model-hu-hu-default | 1.0.0 | UniMorph | https://github.com/unimorph/hun | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph; Christo Kirov, Ryan Cotterell, and Khuyagbaatar Batsuren (conversion); Judit Ács and Gábor Bella (validation); English Wiktionary contributors | 359d46a01d751ec823705ad7f3dd1cc8f6663feb1a9d13cb04d0c6fb51ab646e | 2346297 |
| it-it-default | IT_IT | true | org.egothor:radixor-model-it-it-default | 1.0.0 | UniMorph | https://github.com/unimorph/ita | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and Wikipedia contributors | 5e03be31c9761e30dbf24a47a5ced3d6ec949dabd31e92632fdd9f7c67fc2e12 | 841105 |
| nb-no-default | NB_NO | true | org.egothor:radixor-model-nb-no-default | 1.0.0 | UniMorph | https://github.com/unimorph/nob | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and Wikipedia contributors | f495bffb44e79d27993e6e2e65d4b1204b29365dc93f481b2d8b96766fc90fd9 | 205645 |
| nl-nl-default | NL_NL | true | org.egothor:radixor-model-nl-nl-default | 1.0.0 | UniMorph | https://github.com/unimorph/nld | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and Wikipedia contributors | c098034adc42da2ca3e419160e6dd2c2b3868f8af334303b3a191e09caadaf5e | 70600 |
| nn-no-default | NN_NO | true | org.egothor:radixor-model-nn-no-default | 1.0.0 | UniMorph | https://github.com/unimorph/nno | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and Wikipedia contributors | 900cf2005605aea2a3d8d731ec0b0c1f47fb4469b4ba6b9134145d4d026a0398 | 51244 |
| pl-pl-polimorf | PL_PL | false | org.egothor:radixor-model-pl-pl-polimorf | 1.0.0 | PoliMorf 2.1 | https://github.com/morfologik/morfologik-stemming | 2.1 | 6e63b53 | recorded | BSD-2-Clause | Copyright (c) 2016, Marcin Miłkowski | 4fe4bf5e6c22c1beea5b3d57f1ce4c9ea5aac1ed8ab24c616fb06df745e40d15 | 12624997 |
| pl-pl-unimorph | PL_PL | true | org.egothor:radixor-model-pl-pl-unimorph | 1.0.0 | UniMorph | https://github.com/unimorph/pol | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph; SGJP authors Marcin Woliński, Zygmunt Saloni, Robert Wołosz, Włodzimierz Gruszczyński, Danuta Skowrońska, and Zbigniew Bronk; Witold Kieraś (conversion); Wiktionary contributors | 8191ed727097839cc808cbc5c56a1bd78b3c851e7733ad226ad9a51519a54721 | 334951 |
| pt-pt-default | PT_PT | true | org.egothor:radixor-model-pt-pt-default | 1.0.0 | UniMorph | https://github.com/unimorph/por | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and Wikipedia contributors | 7a035ff330a6f0548f446cd0d6617bc1cf4751292125a3564d3a255c5d6f516d | 509965 |
| ru-ru-default | RU_RU | true | org.egothor:radixor-model-ru-ru-default | 1.0.0 | UniMorph | https://github.com/unimorph/rus | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and Wikipedia contributors | df7ea25e63a875eeec7a4185be685bd5372a3c568db85c34c44fdf5d8d980a40 | 2414507 |
| sv-se-default | SV_SE | true | org.egothor:radixor-model-sv-se-default | 1.0.0 | UniMorph | https://github.com/unimorph/swe | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and English Wiktionary contributors | d9be72e3d67c776622c4281e04e4063b9381e8f84a823d98ebf08888b82dff0c | 256300 |
| uk-ua-default | UK_UA | true | org.egothor:radixor-model-uk-ua-default | 1.0.0 | UniMorph | https://github.com/unimorph/ukr | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph; Witold Kieraś and Maria Shvedova are credited for the separate VESUM conversion; Wiktionary contributors | cf3f612cfff16cb7763f99c55851069489b883c3bdd1a6576cd8c57a97e07eae | 47300 |
| us-uk-default | US_UK | true | org.egothor:radixor-model-us-uk-default | 1.0.0 | UniMorph | https://github.com/unimorph/eng | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph and Wikipedia contributors | 8c79122993499e437ea8b54b620832dca29019298f281c1f3132f4d1be885460 | 2713666 |
| yi-default | YI | true | org.egothor:radixor-model-yi-default | 1.0.0 | UniMorph | https://github.com/unimorph/yid | not-recorded-in-legacy-import | not-recorded-in-legacy-import | not-recorded-in-legacy-import | CC-BY-SA-3.0 | UniMorph | f47de665c27dcd72833a82904e49c68a945bb5aca769a7ec5a0164e2c981a6d3 | 12222 |

196
docs/stemmer-models.md Normal file
View File

@@ -0,0 +1,196 @@
# Stemmer Models
This page defines the model artifact and its maintenance lifecycle. Application developers should begin with [Model Selection and Loading](model-selection-and-loading.md); the generated [model catalog](stemmer-model-catalog.md) is the detailed inventory.
## Terminology
| Term | Definition |
|---|---|
| Radixor core | Java parsing, patch-command, trie, registry, descriptor, and loader code in `org.egothor:radixor` |
| Language | Locale-level identity such as `PL_PL`; not a dictionary or model |
| Model ID | Stable identity of one concrete model, such as `pl-pl-unimorph` |
| Model artifact | Independently versioned JAR containing one descriptor, one runtime dictionary, and licensing material |
| Source dictionary | Upstream lexical or morphological source recorded in provenance |
| Runtime dictionary | GZip-compressed UTF-8 Radixor tab-separated data consumed during trie construction |
| Compiled trie | In-memory lookup structure built by the loader; not the `stemmer.gz` resource |
| Default model | Stable ID selected by a language-oriented loader call |
| Optional model | Discoverable only when installed and selected explicitly; PoliMorf is optional for Polish |
Core version, model artifact version, catalog version, source dictionary version, and model format version are separate compatibility axes. Updating Java code need not republish unchanged model bytes; updating one model need not release core or every other model.
## Model artifact identity and layout
A module named `models/<model-id>` publishes:
```text
org.egothor:radixor-model-<model-id>:<model-version>
```
The built PoliMorf JAR has this effective tree:
```text
META-INF/
LICENSES/PoliMorf-BSD-2-Clause.txt
MANIFEST.MF
radixor/
models.index
models/pl-pl-polimorf.properties
org/egothor/stemmer/models/pl-pl-polimorf/stemmer.gz
```
Each UniMorph-derived model instead contains one model-specific
`META-INF/NOTICE/<model-id>-data.txt`. That notice records the upstream attribution, the Radixor
transformations and contribution statement, the ShareAlike distribution terms, and the canonical
CC BY-SA 3.0 URI. The repository has no root CC license directory because CC BY-SA applies to
these model-data artifacts, not to the BSD-3-Clause Radixor Java software. PoliMorf retains only
its BSD-2-Clause data license.
`models.index` contains the descriptor path. The descriptor contains the exact resource path. No Java provider class is required, and model modules do not compile against a core API.
## Discovery and integrity
`StemmerModelRegistry` asks the selected `ClassLoader` for every `META-INF/radixor/models.index`. It sorts index URLs, validates each non-comment entry, loads the named descriptors, sorts descriptors by model ID, and rejects duplicate IDs. It does not scan arbitrary JAR entries.
Descriptor parsing verifies:
- the model-ID syntax;
- required nonblank runtime properties;
- a known `Language` enum name;
- format `radixor-dictionary-tsv-gzip` and format version `1`;
- the exact namespaced resource path;
- presence of the runtime resource;
- a lowercase 64-character SHA-256 value.
Loading then reads the compressed resource bytes through the descriptor's discovering class loader, compares their SHA-256 digest, opens GZip, parses UTF-8 Radixor dictionary rows, and constructs a trie. Duplicate-ID and checksum checks make selection independent of classpath order.
## Descriptor fields
The convention plugin generates these fields:
| Property | Role | Meaning |
|---|---|---|
| `model.id` | Authoritative runtime identity | Stable model ID |
| `model.version` | Authoritative artifact identity | Independently managed model version |
| `model.language` | Authoritative selection metadata | Existing `Language` enum value |
| `model.displayName` | Display metadata | Human-readable name |
| `model.resource` | Authoritative loading metadata | Namespaced GZip resource |
| `model.default` | Catalog/build declaration | Whether the module declares itself a default; runtime language selection uses `Language.defaultModelId()` |
| `model.format` | Authoritative compatibility metadata | `radixor-dictionary-tsv-gzip` |
| `model.formatVersion` | Authoritative compatibility metadata | Currently `1` |
| `model.sha256` | Authoritative integrity metadata | Digest of the compressed source bytes |
| `model.rightToLeft` | Processing metadata | Language direction recorded by the build |
| `model.caseProcessing` | Processing metadata | `LOWERCASE_WITH_LOCALE_ROOT` |
| `model.diacriticProcessing` | Processing metadata | `AS_IS` |
| `model.storeOriginal` | Processing metadata | Currently `true` |
| `source.name` | Provenance | Source dictionary name |
| `source.version` | Provenance | Upstream version or the legacy-import sentinel |
| `source.project` | Provenance | Upstream project |
| `source.repository` | Provenance | Official language repository |
| `source.dataset` | Provenance | Upstream dataset and lexical-source identity |
| `source.revision` | Provenance | Exact revision or `not-recorded-in-legacy-import` |
| `source.revisionStatus` | Provenance | `recorded` or `not-recorded-in-legacy-import` |
| `source.license` | Provenance | SPDX or project license reference |
| `source.licenseUri` | Provenance | Canonical license URI |
| `source.attribution` | Provenance | Attribution supplied by the official source |
| `source.verificationDate` | Provenance | Date the maintained upstream information was checked |
| `transformations.summary` | Provenance | Material Radixor conversion operations |
| `compiler.radixorVersion` | Provenance | Compiler lineage recorded by the plugin |
| `compiler.radixorCommit` | Provenance | Commit when available; currently `unavailable` |
| `statistics.groups` | Provenance/statistics | Currently `unavailable` |
| `statistics.forms` | Provenance/statistics | Currently `unavailable` |
The current registry consumes the authoritative `model.*` identity, format, resource, and checksum fields. Processing and provenance fields remain packaged for audit and catalog generation but are not all exposed as typed `StemmerModelDescriptor` accessors. The generated catalog is the supported documentation view of source name, version, license, checksum, and size.
## Immutable input to runtime model
The packaging sequence is:
```text
models/<id>/src/modelInput/stemmer.gz
-> validate GZip, strict UTF-8, rows, metadata, version, and license
-> copy identical bytes into build/generated/modelResources
-> generate descriptor, index, and packaged license
-> package radixor-model-<id>-<version>.jar
-> discover from the application's runtime classpath
-> verify checksum, parse dictionary, and build a trie
```
Application runtime never reads `src/modelInput` from a source checkout.
For PoliMorf, the immutable input is exactly:
`models/pl-pl-polimorf/src/modelInput/stemmer.gz`
Its required upstream license is:
`models/pl-pl-polimorf/src/modelInput/LICENSE-BSD-2-Clause.txt`
The final runtime resource is exactly:
`org/egothor/stemmer/models/pl-pl-polimorf/stemmer.gz`
## Aggregate projects
| Project | Published coordinate | Contents and purpose |
|---|---|---|
| `models/standard` | `org.egothor:radixor-models-standard:<catalog-version>` | POM-only aggregate with one transitive runtime default per language; excludes PoliMorf |
| `models/bom` | `org.egothor:radixor-models-bom:<catalog-version>` | POM-only Maven dependency-management constraints for all individual published model versions |
Neither catalog artifact publishes a binary, sources, or Javadoc JAR. The standard aggregate resolves model JARs because its POM contains runtime dependencies. Importing the BOM only manages versions and resolves no model by itself. JMH, tests, and quality evaluation depend directly on individual model projects through non-production Gradle configurations.
The Maven dependency BOM is not a software bill of materials. The root `cyclonedxDirectBom` task generates the project-wide CycloneDX SBOM under `build/reports/sbom/`; it does not write into `models/bom/build/`.
`models/build/` is an ignored Gradle output directory for the implicit lifecycle parent `:models`, not a source module. CycloneDX direct tasks exposed on subprojects by the root plugin are disabled, so the supported build does not write an SBOM there. Aggregate model reports are owned by the root project under `build/reports/models/`; individual model reports and publication files stay under `models/<model-id>/build/`.
## Create or update a model module
1. Choose a stable lowercase model ID matching the module directory.
2. Add `models/<id>/model-version.txt`; do not derive it from core.
3. Apply `org.egothor.radixor.model` in the module build script.
4. Declare `modelId`, `language`, `displayName`, `defaultModel`, repository, dataset, revision and status, license URI, attribution, verification date, and transformations.
5. Put immutable `stemmer.gz` and a model-specific `NOTICE-model-data.txt` under `src/modelInput/`. The notice must identify the applicable data license and canonical URI, upstream attribution, transformations, and derived-data contributions without implying that the core software uses that license.
6. Add the module ID and its `default` or `optional` build-topology role to `models/model-projects.properties`. `settings.gradle`, verification, standard membership, BOM constraints, tests, and JMH all consume that list; descriptor metadata remains authoritative for model identity and language properties.
7. Run:
```bash
./gradlew --no-daemon :models:<model-id>:validateModelInput
./gradlew --no-daemon :models:<model-id>:prepareModelResources
./gradlew --no-daemon :models:<model-id>:verifyModelDescriptor
./gradlew --no-daemon :models:<model-id>:verifyModelJar
./gradlew --no-daemon :models:<model-id>:check
./gradlew --no-daemon runtimeModelIntegrationTest -PmodelId=<model-id>
```
Validation fails for missing inputs, notices, attribution, repository, revision status, Radixor contribution and transformation disclosures, ShareAlike and no-endorsement statements, notice byte identity, unsafe or mismatched ID, invalid semantic version, invalid GZip/UTF-8, invalid dictionary rows, checksum mismatch, wrong packaged path, duplicate dictionaries, or dictionaries in sources/Javadoc artifacts. The explicit legacy revision sentinel is valid; an absent revision or status is not. The PoliMorf module separately validates its complete BSD-2-Clause license and attribution.
Copying an arbitrary `stemmer.gz` into an application is insufficient: registry discovery requires an index, a valid descriptor, namespaced resource, checksum, version, language, format declaration, and licensing material.
## Release boundaries
| Tag | Publishes | Does not publish |
|---|---|---|
| `release@<core-version>` | Root `org.egothor:radixor` software artifacts | Model JARs, standard pack, or BOM |
| `model/<model-id>@<model-version>` | Exactly the matching independently versioned model | Core, other models, standard pack, BOM, JMH, or full quality suite |
| `models-catalog@<catalog-version>` | Standard aggregate and models BOM | Individual model JARs or core |
Local validation for PoliMorf 1.0.0 is:
```bash
./tools/parse-model-release-tag.sh "model/pl-pl-polimorf@1.0.0" .
./gradlew --no-daemon :models:pl-pl-polimorf:check
./gradlew --no-daemon runtimeModelIntegrationTest -PmodelId=pl-pl-polimorf
./gradlew --no-daemon :models:pl-pl-polimorf:validateModelRelease \
-PmodelReleaseVersion=1.0.0
./gradlew --no-daemon :models:pl-pl-polimorf:packageModelReleaseCandidate \
-PmodelReleaseVersion=1.0.0
```
`runtimeModelIntegrationTest` uses an isolated JVM, defaults to a 6 GiB maximum heap, and can be overridden with `-PradixorLargeModelMaxHeap=10g`. For PoliMorf, `validateModelRelease` depends on this complete runtime construction and real stemming smoke verification in addition to descriptor, checksum, license, and package validation. The generic release workflow still selects and publishes only the requested model. The commands above are local validation only; repository owners control tags and publication.
## Documentation and troubleshooting
`publishModelCatalogDocumentation` updates the checked-in catalog used by a direct local `mkdocs serve`.
`prepareMkDocsSource` independently regenerates the same catalog under `build/mkdocs-source/` for the
publication workflow, and `verifyModelCatalogDocumentation` fails when the two copies differ. Rendered
site content remains untracked. For runtime failures, dependency inspection, ClassLoader isolation, and
fat-JAR guidance, see [Model Selection and Loading](model-selection-and-loading.md#troubleshooting).

87
docs/stemming-quality.md Normal file
View File

@@ -0,0 +1,87 @@
# Stemming quality evaluation
The explicit `stemmingQuality` analysis measures agreement between stemmer outputs and gold-standard equivalence classes represented by registered multilingual model dictionary rows. Dictionary text remains unchanged; reports and diagnostics use English.
JMH adapters, registries, third-party versions, language mappings, and preparation remain in `src/jmh`. The evaluator, reports, audits, and tests reside in the standard `src/test` source set. The former `src/stemmingQualityTest` source set was removed, and neither analytical nor JMH classes enter the production JAR.
## Language and adapter coverage
The authoritative Radixor universe is the validated one-to-one reconciliation of every `StemmerPatchTrieLoader.Language` value with its registered default model descriptor. All 20 current values have exactly one documented default. Optional comparison models, including `pl-pl-polimorf`, are identified separately and never replace default benchmark rows. Third-party combinations come only from explicit JMH adapter metadata.
Default Polish evaluation is therefore `Radixor` with model `pl-pl-unimorph`. A future PoliMorf evaluation is a distinct `Radixor` / `pl-pl-polimorf` row. Evaluation classpaths receive individual models through direct non-production Gradle dependencies; ordinary applications inherit none of them from the core.
Complete PoliMorf trie construction and deterministic stemming smoke fixtures are runtime-verified separately. That functional verification is not a linguistic-quality measurement and does not enter the current default-model quality snapshot.
The expected matrix is constructed before evaluation from stemmer, language, dictionary mode, and supported output policy. Generation fails on missing, duplicate, unexpected, or stale keys.
## Dictionary groups and modes
Every usable parsed row contributes one gold-standard group. A distinct surface form is one evaluated item and may belong to several groups. `ALL_WORDS` preserves every valid form. `LOWERCASE_GROUPS_ONLY` excludes a complete group containing an uppercase or titlecase Unicode code point. Retained words are not lowercased or normalized by the evaluator.
## Output policies
`PRIMARY_OUTPUT` uses the deterministic JMH output and defines a strict partition.
For multi-output adapters, `C(w)` is the immutable, sorted, exactly deduplicated candidate set. It is non-null, non-empty, contains no null, and contains the primary output. Radixor obtains alternatives through `getAll`. The repository's Morphologik lookups can return distinct lemma strings and are multi-output. Configured Hunspell filters can emit several stems at one token position. Other adapters emit only primary rows.
`ANY_CANDIDATE` is an optimistic oracle-assisted pairwise upper bound. A gold-related pair succeeds when its sets intersect. A gold-negative pair is an error only when both sets are the same singleton; otherwise unequal candidates can be selected for that pair. Choices may vary between pairs and need not form one realizable global assignment.
`ALL_CANDIDATES` activates every candidate. Two forms are predicted as related when their sets intersect, for both gold-related and gold-negative pairs. This relation can overlap and need not be transitive. A pair sharing several candidates is counted once.
The evaluator verifies:
```text
ANY under <= PRIMARY under
ALL under <= PRIMARY under
ANY under = ALL under
ANY over <= PRIMARY over
ALL over >= PRIMARY over
```
## Pair definitions and efficient counting
For the unique form population `W`:
```text
underPossible = |{{u,v} subset W : G(u) intersection G(v) is not empty}|
overPossible = C2(|W|) - underPossible
```
For each distinct form `w`, let `G(w)` be its set of included dictionary groups. Two forms are gold-related exactly when their membership sets intersect. Under-stemming counts gold-related pairs that the output relation separates. Over-stemming counts gold-negative pairs that the output relation conflates. A form is processed once, and a pair sharing several groups is counted once. Primary output uses global and per-group stem frequencies with explicit overlap corrections. Candidate sets are canonical signatures counted globally and per group. An inverted candidate-to-signature index discovers intersections, and signature pairs shared through several candidates are deduplicated. `ANY_CANDIDATE` over-stemming uses only equal singleton signatures. All pair arithmetic uses checked `long` operations; complete production word pairs are never enumerated.
## Confusion and aggregate metrics
```text
TP = underPossible - underError
FN = underError
FP = overError
TN = overPossible - overError
```
Under-stemming is Paice UI `FN/(TP+FN)` and over-stemming is Paice OI `FP/(TN+FP)`; their denominators differ. This is an explicit pairwise generalization of Paice's disjoint lemma groups to the overlapping gold relation above. For `PRIMARY_OUTPUT` and `ALL_CANDIDATES`, the CSV also publishes TP/FP/FN/TN, precision, recall, specificity, accuracy, balanced accuracy, F0.5, F1, F2, Jaccard, Fowlkes-Mallows, Matthews correlation coefficient, and pairwise error rate. `ANY_CANDIDATE` uses different optimistic oracle conditions for positive and negative pairs and therefore has no single confusion matrix; its TP/FP/FN/TN and aggregate classification fields are empty, while its explicit under/over numerators and denominators remain available. Metrics use raw counts, not rounded rates. Zero denominators produce `n/a` in Markdown and empty CSV fields.
Standard Adjusted Rand Index, homogeneity, completeness, V-measure, and normalized mutual information are not calculated. Their ordinary contingency-table definitions require an exclusive gold partition, while these dictionary groups form an overlapping cover.
Micro summaries sum confusion counts before calculation. Macro summaries average defined language values and retain coverage counts. Common-language comparisons use the exact language intersection and never score unsupported languages as zero. Rankings are separated by policy and metric; the default F0.5 choice is navigation, not a universal scientific preference.
Pearson and average-tie-rank Spearman reports use unrounded values and separate dictionary-mode and output-policy cohorts. Fewer than three observations, undefined inputs, and zero variance produce documented missing values. The reports provide reproducible data and make no automatic scientific conclusion.
## Exact accuracy and pairwise under-stemming
Exact textual accuracy and pairwise grouping use different denominators. One erroneous form in a 12-form group creates 11 erroneous pairs: with 88 singleton groups, exact accuracy can be 99% while pairwise under-stemming is `11/C2(12) = 16.666667%`. Singleton groups affect word accuracy but add no within-group pairs.
## Running the analysis
```bash
./gradlew stemmingQuality
./gradlew stemmingQuality -PstemmingQualityStemmer=Radixor -PstemmingQualityLanguage=DE_DE -PstemmingQualityMode=ALL_WORDS -PstemmingQualityAudit=true
```
Optional properties are `stemmingQualityLanguage`, `stemmingQualityStemmer`, `stemmingQualityMode`, `stemmingQualityOutputPolicy`, `stemmingQualityRankMetric`, `stemmingQualityAudit`, and `stemmingQualityAuditLimit`. Policies are `PRIMARY_OUTPUT`, `ANY_CANDIDATE`, and `ALL_CANDIDATES`. Filtered reports carry `-filtered` and cannot overwrite complete output.
Generated files under `build/reports/stemming-quality/` include `stemming-quality.md`, `stemming-quality.csv`, `metric-correlations-pearson.csv`, `metric-correlations-spearman.csv`, and optional audit Markdown. Every CSV scenario records the exact dictionary model ID, independent model version, and descriptor SHA-256.
## Limitations
These measurements evaluate agreement with the available dictionary grouping. They do not capture every semantic, morphological, downstream, or dataset-specific property. `ANY_CANDIDATE` is optimistic and may not be globally realizable. `ALL_CANDIDATES` measures an overlap graph rather than a partition. Language coverage must remain visible in cross-stemmer comparisons. No single published metric establishes universal superiority; multiple metrics and their correlations are provided for transparent scientific assessment.
The checked-in quality snapshot is regenerated from all 20 current default models. The optional PoliMorf model is not part of it and must not be attributed to the default Polish results. See [Model Selection and Loading](model-selection-and-loading.md) and the generated [model catalog](stemmer-model-catalog.md).

View File

@@ -145,6 +145,14 @@ defensive fallback in case of future tag drift.
All examples use Gradle with JUnit Platform integration:
- Default fast test run:
```
./gradlew test
```
The default `test` task excludes `slow` tests. Supplying `-DincludeTags` or `-PincludeTags` still excludes `slow` unless the include expression contains `slow`; supplying an explicit exclude expression replaces the default. Long-running bundled-dictionary compilation and full-language loading checks therefore run only through an explicit tag expression such as `-DincludeTags=slow` or a dedicated profile such as `ciSlow`.
- Only unit tests:
```
@@ -154,7 +162,7 @@ All examples use Gradle with JUnit Platform integration:
- Integration tests only:
```
./gradlew test -DincludeTags=integration
./gradlew test -DincludeTags=integration -DexcludeTags=slow
```
- Only trie subsystem tests:

View File

@@ -5,15 +5,15 @@ antlr:antlr:2.7.7=pitest
com.github.oowekyala.ooxml:nice-xml-messages:3.1=pmd
com.google.code.gson:gson:2.13.2=pmd
com.google.errorprone:error_prone_annotations:2.41.0=pmd
net.bytebuddy:byte-buddy-agent:1.17.7=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.17.7=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
net.jqwik:jqwik-api:1.9.3=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
net.jqwik:jqwik-engine:1.9.3=jmhRuntimeClasspath,testRuntimeClasspath
net.jqwik:jqwik-time:1.9.3=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
net.jqwik:jqwik-web:1.9.3=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
net.jqwik:jqwik:1.9.3=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy-agent:1.17.7=testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.17.7=testCompileClasspath,testRuntimeClasspath
net.jqwik:jqwik-api:1.9.3=testCompileClasspath,testRuntimeClasspath
net.jqwik:jqwik-engine:1.9.3=testRuntimeClasspath
net.jqwik:jqwik-time:1.9.3=testCompileClasspath,testRuntimeClasspath
net.jqwik:jqwik-web:1.9.3=testCompileClasspath,testRuntimeClasspath
net.jqwik:jqwik:1.9.3=testCompileClasspath,testRuntimeClasspath
net.sf.jopt-simple:jopt-simple:4.9=pitest
net.sf.jopt-simple:jopt-simple:5.0.4=jmh,jmhCompileClasspath,jmhRuntimeClasspath
net.sf.jopt-simple:jopt-simple:5.0.4=jmh,jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
net.sf.saxon:Saxon-HE:12.9=pmd
net.sourceforge.pmd:pmd-ant:7.20.0=pmd
net.sourceforge.pmd:pmd-core:7.20.0=pmd
@@ -22,37 +22,45 @@ org.antlr:antlr4-runtime:4.9.3=pmd
org.antlr:stringtemplate:3.2.1=pitest
org.apache.commons:commons-lang3:3.18.0=pitest
org.apache.commons:commons-lang3:3.20.0=pmd
org.apache.commons:commons-math3:3.6.1=jmh,jmhCompileClasspath,jmhRuntimeClasspath
org.apache.commons:commons-math3:3.6.1=jmh,jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.apache.commons:commons-text:1.14.0=pitest
org.apiguardian:apiguardian-api:1.1.2=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.lucene:lucene-analysis-common:10.5.0=jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.apache.lucene:lucene-analysis-morfologik:10.5.0=jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.apache.lucene:lucene-analysis-stempel:10.5.0=jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.apache.lucene:lucene-core:10.5.0=jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.apache.opennlp:opennlp-tools:2.5.4=jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testRuntimeClasspath
org.carrot2:morfologik-fsa:2.1.9=jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.carrot2:morfologik-polish:2.1.9=jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.carrot2:morfologik-stemming:2.1.9=jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.checkerframework:checker-qual:3.52.1=pmd
org.jacoco:org.jacoco.agent:0.8.14=jacocoAgent,jacocoAnt
org.jacoco:org.jacoco.ant:0.8.14=jacocoAnt
org.jacoco:org.jacoco.core:0.8.14=jacocoAnt
org.jacoco:org.jacoco.report:0.8.14=jacocoAnt
org.junit.jupiter:junit-jupiter-api:5.14.3=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.14.3=jmhRuntimeClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.14.3=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter:5.14.3=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.14.3=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.14.3=jmhRuntimeClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.14.3=jmhRuntimeClasspath,testRuntimeClasspath
org.junit:junit-bom:5.14.3=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-core:5.23.0=jmhRuntimeClasspath,mockitoAgent,testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.23.0=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.objenesis:objenesis:3.3=jmhRuntimeClasspath,testRuntimeClasspath
org.openjdk.jmh:jmh-core:1.37=jmh,jmhCompileClasspath,jmhRuntimeClasspath
org.openjdk.jmh:jmh-generator-asm:1.37=jmh,jmhCompileClasspath,jmhRuntimeClasspath
org.openjdk.jmh:jmh-generator-bytecode:1.37=jmh,jmhCompileClasspath,jmhRuntimeClasspath
org.openjdk.jmh:jmh-generator-reflection:1.37=jmh,jmhCompileClasspath,jmhRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.14.3=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.14.3=testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.14.3=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter:5.14.3=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.14.3=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.14.3=testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.14.3=testRuntimeClasspath
org.junit:junit-bom:5.14.3=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-core:5.23.0=mockitoAgent,testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.23.0=testCompileClasspath,testRuntimeClasspath
org.objenesis:objenesis:3.3=testRuntimeClasspath
org.openjdk.jmh:jmh-core:1.37=jmh,jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.openjdk.jmh:jmh-generator-asm:1.37=jmh,jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.openjdk.jmh:jmh-generator-bytecode:1.37=jmh,jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.openjdk.jmh:jmh-generator-reflection:1.37=jmh,jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-analysis:9.9.1=pitest
org.ow2.asm:asm-commons:9.9=jacocoAnt
org.ow2.asm:asm-commons:9.9.1=pitest
org.ow2.asm:asm-tree:9.9=jacocoAnt
org.ow2.asm:asm-tree:9.9.1=pitest
org.ow2.asm:asm-util:9.9.1=pitest
org.ow2.asm:asm:9.0=jmh,jmhCompileClasspath,jmhRuntimeClasspath
org.ow2.asm:asm:9.0=jmh,jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.ow2.asm:asm:9.9=jacocoAnt
org.ow2.asm:asm:9.9.1=pitest,pmd
org.pcollections:pcollections:4.0.2=pmd
@@ -62,5 +70,7 @@ org.pitest:pitest-html-report:1.22.1=pitest
org.pitest:pitest-junit5-plugin:1.2.3=pitest
org.pitest:pitest:1.22.1=pitest
org.slf4j:jul-to-slf4j:1.7.36=pmd
org.slf4j:slf4j-api:2.0.17=jmhCompileClasspath,jmhRuntimeClasspath,stemmingQualityJmhRuntime
org.xmlresolver:xmlresolver:5.3.3=pmd
ua.net.nlp:morfologik-ukrainian-search:4.9.1=jmhRuntimeClasspath,stemmingQualityJmhRuntime
empty=annotationProcessor,compileClasspath,cyclonedxBom,jmhAnnotationProcessor,mainPmdAuxClasspath,runtimeClasspath,testAnnotationProcessor

View File

@@ -0,0 +1,69 @@
def cistemGoldStandardBaseUrl = 'https://raw.githubusercontent.com/LeonieWeissweiler/CISTEM/refs/heads/master/gold_standards'
def cistemGoldStandardFiles = [
'goldstandard1.txt',
'goldstandard2.txt'
]
def cistemGoldStandardDownloadDirectory = layout.buildDirectory.dir('third-party/cistem-gold-standards')
def cistemGoldStandardGeneratedResourcesDirectory = layout.buildDirectory.dir('generated/resources/cistem-gold-standards')
def cistemGoldStandardDownloadedFiles = cistemGoldStandardFiles.collect { String fileName ->
cistemGoldStandardDownloadDirectory.map { it.file(fileName) }
}
tasks.register('downloadCistemGoldStandards') {
group = 'build setup'
description = 'Downloads benchmark-only CISTEM German gold standards.'
outputs.files(cistemGoldStandardDownloadedFiles)
doLast {
cistemGoldStandardFiles.each { String fileName ->
final File targetFile = cistemGoldStandardDownloadDirectory.get().file(fileName).asFile
targetFile.parentFile.mkdirs()
if (!targetFile.exists()) {
final URL sourceUrl = new URL("${cistemGoldStandardBaseUrl}/${fileName}")
try {
sourceUrl.withInputStream { inputStream ->
targetFile.withOutputStream { outputStream ->
outputStream << inputStream
}
}
} catch (FileNotFoundException exception) {
throw new GradleException(
"Unable to download CISTEM gold standard ${fileName} from ${sourceUrl}.",
exception)
}
}
if (targetFile.length() <= 0L) {
throw new GradleException("Downloaded CISTEM gold standard ${fileName} was empty.")
}
}
}
}
tasks.register('prepareCistemGoldStandardResources', Copy) {
group = 'build setup'
description = 'Copies benchmark-only CISTEM German gold standards into the JMH resource output.'
dependsOn(tasks.named('downloadCistemGoldStandards'))
from(cistemGoldStandardDownloadDirectory) {
include 'goldstandard1.txt'
include 'goldstandard2.txt'
}
into(cistemGoldStandardGeneratedResourcesDirectory)
}
sourceSets {
jmh {
resources {
srcDir(cistemGoldStandardGeneratedResourcesDirectory)
}
}
}
tasks.named('processJmhResources') {
dependsOn(tasks.named('prepareCistemGoldStandardResources'))
}

View File

@@ -0,0 +1,121 @@
import org.gradle.plugins.ide.eclipse.model.SourceFolder
def hunspellDictionaryBaseUrl = 'https://raw.githubusercontent.com/wooorm/dictionaries/main/dictionaries'
def hunspellDictionaryLanguages = [
en: 'English',
cs: 'Czech',
de: 'German',
es: 'Spanish',
fr: 'French',
nl: 'Dutch',
pl: 'Polish',
uk: 'Ukrainian'
]
def hunspellDownloadDirectory = layout.buildDirectory.dir('third-party/hunspell')
def hunspellGeneratedResourcesDirectory = layout.buildDirectory.dir('generated/resources/hunspell')
def hunspellGeneratedResourcesPath = provider {
project.relativePath(hunspellGeneratedResourcesDirectory.get().asFile)
}
def hunspellEclipseClasspathAttributes = [
gradle_scope : 'jmh',
gradle_used_by_scope: 'jmh',
test : 'true'
]
def hunspellIsAbsolutePath = { String path ->
path.startsWith('/') || path ==~ /^[A-Za-z]:[\\\/].*/
}
def hunspellDownloadedFiles = hunspellDictionaryLanguages.keySet().collectMany { String code ->
[
hunspellDownloadDirectory.map { it.file("${code}/index.aff") },
hunspellDownloadDirectory.map { it.file("${code}/index.dic") },
hunspellDownloadDirectory.map { it.file("${code}/license") }
]
}
tasks.register('downloadHunspellBenchmarkDictionaries') {
group = 'build setup'
description = 'Downloads benchmark-only Hunspell dictionaries from wooorm/dictionaries.'
outputs.files(hunspellDownloadedFiles)
doLast {
hunspellDictionaryLanguages.each { String code, String displayName ->
['index.aff', 'index.dic', 'license'].each { String fileName ->
final File targetFile = hunspellDownloadDirectory.get().file("${code}/${fileName}").asFile
targetFile.parentFile.mkdirs()
if (!targetFile.exists()) {
final URL sourceUrl = new URL("${hunspellDictionaryBaseUrl}/${code}/${fileName}")
try {
sourceUrl.withInputStream { inputStream ->
targetFile.withOutputStream { outputStream ->
outputStream << inputStream
}
}
} catch (FileNotFoundException exception) {
throw new GradleException(
"Unable to download Hunspell ${fileName} file for ${displayName} (${code}) from ${sourceUrl}.",
exception)
}
}
if (targetFile.length() <= 0L) {
throw new GradleException("Downloaded Hunspell ${fileName} file for ${displayName} was empty.")
}
}
}
}
}
tasks.register('prepareHunspellBenchmarkResources', Copy) {
group = 'build setup'
description = 'Copies benchmark-only Hunspell dictionaries into the JMH resource output.'
dependsOn(tasks.named('downloadHunspellBenchmarkDictionaries'))
from(hunspellDownloadDirectory) {
include '**/index.aff'
include '**/index.dic'
include '**/license'
into 'hunspell'
}
into(hunspellGeneratedResourcesDirectory)
}
sourceSets {
jmh {
resources {
srcDir(hunspellGeneratedResourcesDirectory)
}
}
}
tasks.named('processJmhResources') {
dependsOn(tasks.named('prepareHunspellBenchmarkResources'))
}
eclipse {
classpath {
file {
whenMerged { classpath ->
String generatedPath = hunspellGeneratedResourcesPath.get()
classpath.entries.removeAll { entry ->
entry.hasProperty('path') && (
entry.path == generatedPath ||
hunspellIsAbsolutePath(entry.path)
)
}
SourceFolder hunspellEntry = new SourceFolder(generatedPath, null)
hunspellEntry.output = 'bin/jmh'
hunspellEclipseClasspathAttributes.each { String name, String value ->
hunspellEntry.entryAttributes[name] = value
}
classpath.entries.add(hunspellEntry)
}
}
}
}

View File

@@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (C) 2026, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/

View File

@@ -0,0 +1,223 @@
import org.gradle.plugins.ide.eclipse.model.SourceFolder
def luceneVersion = '10.5.0'
def luceneRootRelativePath = 'third-party/lucene'
def luceneSourceArtifacts = ['lucene-analysis-common', 'lucene-analyzers-common']
def luceneSourceDirectory = layout.buildDirectory.dir("${luceneRootRelativePath}/source/analyzers-common")
def luceneGeneratedSourceDirectory = layout.buildDirectory.dir('generated/sources/lucene')
def luceneGeneratedPorterFile = luceneGeneratedSourceDirectory.map { it.file('org/egothor/stemmer/benchmark/LucenePorterStemmerCopied.java') }
def luceneSourceDownloadFile = layout.buildDirectory.file("${luceneRootRelativePath}/lucene-${luceneVersion}-sources.jar")
dependencies {
jmhImplementation "org.apache.lucene:lucene-analysis-common:${luceneVersion}"
jmhImplementation "org.apache.lucene:lucene-analysis-stempel:${luceneVersion}"
jmhImplementation "org.apache.lucene:lucene-analysis-morfologik:${luceneVersion}"
}
def buildLuceneSourcesName = { final String artifact ->
"${artifact}-${luceneVersion}-sources.jar"
}
def buildLuceneSourcesUrl = { final String artifact ->
"https://repo1.maven.org/maven2/org/apache/lucene/${artifact}/${luceneVersion}/${buildLuceneSourcesName(artifact)}"
}
def isLuceneSourcesDownloadable = { final String artifact ->
try {
final URL sourceUrl = new URL(buildLuceneSourcesUrl(artifact))
final java.net.HttpURLConnection connection = (java.net.HttpURLConnection) sourceUrl.openConnection()
connection.requestMethod = 'HEAD'
connection.instanceFollowRedirects = true
connection.connectTimeout = 10000
connection.readTimeout = 10000
final int responseCode = connection.responseCode
connection.disconnect()
return responseCode == 200
} catch (Exception ignored) {
return false
}
}
def downloadLuceneSourcesJar = { ->
final File targetFile = luceneSourceDownloadFile.get().asFile
for (String artifact : luceneSourceArtifacts) {
if (!isLuceneSourcesDownloadable(artifact)) {
continue
}
final String sourceUrl = buildLuceneSourcesUrl(artifact)
final File tempFile = new File(targetFile.parentFile, "${artifact}.${luceneVersion}.tmp")
try {
new URL(sourceUrl).withInputStream { inputStream ->
tempFile.parentFile.mkdirs()
tempFile.withOutputStream { outputStream ->
outputStream << inputStream
}
}
if (!tempFile.exists() || tempFile.length() <= 0L) {
throw new GradleException("Downloaded Lucene source artifact for ${artifact} was empty.")
}
targetFile.delete()
if (!tempFile.renameTo(targetFile)) {
throw new GradleException("Failed to persist downloaded Lucene source artifact for ${artifact}.")
}
return
} catch (Exception ignored) {
tempFile.delete()
}
}
throw new GradleException(
"Failed to download Apache Lucene source artifacts ${luceneSourceArtifacts} for version ${luceneVersion}.")
}
def luceneSourceClasspathPath = provider {
project.relativePath(luceneGeneratedSourceDirectory.get().asFile)
}
def luceneEclipseClasspathAttributes = [
gradle_scope : 'jmh',
gradle_used_by_scope: 'jmh',
test : 'true'
]
def isAbsoluteClasspathPath = { String path ->
path.startsWith('/') || path ==~ /^[A-Za-z]:[\\\/].*/
}
def luceneGeneratedPorterNotice = '''
/**
* Generated at benchmark execution time from Apache Lucene source.
*
* This source copy is compiled only for the JMH benchmark source set and is
* not committed as production code.
*/
'''
def transformPorterStemmerSource = { final File sourceFile, final File targetFile ->
if (!sourceFile.exists()) {
throw new GradleException("Apache Lucene PorterStemmer source was not available at ${sourceFile}.")
}
final String sourceText = sourceFile.getText('UTF-8')
String transformedText = sourceText
if (transformedText.contains('package org.apache.lucene.analysis.en;')) {
transformedText = transformedText.replaceFirst(/(?m)^\s*package\s+org\.apache\.lucene\.analysis\.en\s*;/,
'package org.egothor.stemmer.benchmark;')
} else {
throw new GradleException(
'Expected Lucene package-private PorterStemmer in org.apache.lucene.analysis.en package was not found in downloaded source.')
}
transformedText = transformedText.replaceFirst(/(?m)^\s*class\s+PorterStemmer\s*\{/, 'public final class LucenePorterStemmerCopied {')
transformedText = transformedText.replaceFirst(/(?m)^\s*public\s+PorterStemmer\(\)/, 'public LucenePorterStemmerCopied()')
if (!transformedText.contains('class LucenePorterStemmerCopied')) {
throw new GradleException("Failed to rename PorterStemmer class when generating ${targetFile}.")
}
targetFile.parentFile.mkdirs()
targetFile.text = transformedText
}
def resolveLucenePorterStemmerSource = { ->
final File sourceRoot = luceneSourceDirectory.get().asFile
final List<String> candidates = [
'org/apache/lucene/analysis/en/org/apache/lucene/analysis/en/PorterStemmer.java',
'org/apache/lucene/analysis/en/PorterStemmer.java',
'org/apache/lucene/analysis/en/org/tartarus/snowball/ext/PorterStemmer.java'
]
for (String candidate : candidates) {
final File file = new File(sourceRoot, candidate)
if (file.exists()) {
return file
}
}
final FileTree porterCandidates = fileTree(sourceRoot).matching { include '**/PorterStemmer.java' }
for (File file : porterCandidates.files) {
if (file.text.contains('class PorterStemmer') && file.text.contains('package org.apache.lucene.analysis.en;')) {
return file
}
}
throw new GradleException('Unable to resolve Lucene PorterStemmer source file from extracted artifact.')
}
tasks.register('downloadLuceneAnalyzersSources') {
group = 'build setup'
description = 'Downloads Apache Lucene analysis sources for benchmark-only code generation.'
outputs.file(luceneSourceDownloadFile)
doLast {
if (!luceneSourceDownloadFile.get().asFile.exists()) {
downloadLuceneSourcesJar()
}
}
}
tasks.register('extractLuceneAnalyzersSources', Copy) {
group = 'build setup'
description = 'Extracts Apache Lucene analysis source JAR for benchmark-only extraction.'
dependsOn(tasks.named('downloadLuceneAnalyzersSources'))
from(zipTree(luceneSourceDownloadFile))
into(luceneSourceDirectory)
}
tasks.register('generateLucenePorterStemmerCopied') {
group = 'build setup'
description = 'Generates LucenePorterStemmerCopied into the build-only benchmark source directory.'
dependsOn(tasks.named('extractLuceneAnalyzersSources'))
inputs.dir(luceneSourceDirectory)
outputs.file(luceneGeneratedPorterFile)
doLast {
final File sourceFile = resolveLucenePorterStemmerSource()
transformPorterStemmerSource(sourceFile, luceneGeneratedPorterFile.get().asFile)
}
}
sourceSets {
jmh {
java {
srcDir(luceneGeneratedSourceDirectory)
}
}
}
tasks.named('compileJmhJava') {
dependsOn(tasks.named('generateLucenePorterStemmerCopied'))
}
eclipse {
classpath {
file {
whenMerged { classpath ->
String generatedPath = luceneSourceClasspathPath.get()
classpath.entries.removeAll { entry ->
entry.hasProperty('path') && (
entry.path == generatedPath ||
isAbsoluteClasspathPath(entry.path)
)
}
SourceFolder luceneEntry = new SourceFolder(generatedPath, null)
luceneEntry.output = 'bin/jmh'
luceneEclipseClasspathAttributes.each { String name, String value ->
luceneEntry.entryAttributes[name] = value
}
classpath.entries.add(luceneEntry)
}
}
}
}

View File

@@ -51,11 +51,6 @@ publishing {
url = pomLicenseUrl
distribution = pomLicenseDistribution
}
license {
name = pomStemmerDataLicenseName
url = pomStemmerDataLicenseUrl
distribution = pomLicenseDistribution
}
}
developers {
@@ -104,8 +99,6 @@ tasks.register('validateReleaseMetadata') {
if (pomScmDeveloperConnection == null || pomScmDeveloperConnection.isBlank()) missing.add('pomScmDeveloperConnection')
if (pomLicenseName == null || pomLicenseName.isBlank()) missing.add('pomLicenseName')
if (pomLicenseUrl == null || pomLicenseUrl.isBlank()) missing.add('pomLicenseUrl')
if (pomStemmerDataLicenseName == null || pomStemmerDataLicenseName.isBlank()) missing.add('pomStemmerDataLicenseName')
if (pomStemmerDataLicenseUrl == null || pomStemmerDataLicenseUrl.isBlank()) missing.add('pomStemmerDataLicenseUrl')
if (signingKey == null || signingKey.isBlank()) missing.add('pomSigningKey / SIGNING_KEY')
if (signingPassword == null || signingPassword.isBlank()) missing.add('pomSigningPassword / SIGNING_PASSWORD')

View File

@@ -0,0 +1,5 @@
def openNlpVersion = '2.5.4'
dependencies {
jmhImplementation "org.apache.opennlp:opennlp-tools:${openNlpVersion}"
}

View File

@@ -0,0 +1,224 @@
import org.gradle.plugins.ide.eclipse.model.SourceFolder
def paicehuskVersion = 'master'
def paicehuskArchiveName = "paice-husk-stemmer-${paicehuskVersion}.zip"
def paicehuskDownloadUrl = "https://github.com/Hopper262/paice-husk-stemmer/archive/refs/heads/${paicehuskVersion}.zip"
def paicehuskDownloadFile = layout.buildDirectory.file("third-party/paicehusk/${paicehuskArchiveName}")
def paicehuskExtractDirectory = layout.buildDirectory.dir('third-party/paicehusk/source')
def paicehuskArchiveDirectory = paicehuskExtractDirectory.map { it.dir('paice-husk-stemmer-master') }
def paicehuskJavaFile = paicehuskArchiveDirectory.map { it.file('paicehusk_java.java') }
def paicehuskRulesFile = paicehuskArchiveDirectory.map { it.file('paicehusk_rules.txt') }
def paicehuskGeneratedSourceDirectory = layout.buildDirectory.dir('generated/sources/paicehusk')
def paicehuskGeneratedStemmerFile = paicehuskGeneratedSourceDirectory.map { it.file('org/egothor/stemmer/benchmark/PaiceHuskLancasterStemmer.java') }
def paicehuskGeneratedSourcePath = provider {
project.relativePath(paicehuskGeneratedSourceDirectory.get().asFile)
}
def paicehuskSourceEclipseClasspathAttributes = [
gradle_scope : 'jmh',
gradle_used_by_scope: 'jmh',
test : 'true'
]
def paicehuskIsAbsolutePath = { String path ->
path.startsWith('/') || path ==~ /^[A-Za-z]:[\\\/].*/
}
def paicehuskGeneratedNotice = '''
/**
* Generated at benchmark execution time from upstream
* https://github.com/Hopper262/paice-husk-stemmer .
*
* This source copy is compiled only for the JMH benchmark source set and is
* not committed as production code.
*/
'''
def escapeForJava = { final String text ->
return text.replace('\\\\', '\\\\\\\\')
.replace('\"', '\\\"')
}
def toRuleLines = { final File rulesFile ->
final List<String> lines = rulesFile.readLines('UTF-8')
final StringBuilder ruleLines = new StringBuilder()
for (int index = 0; index < lines.size(); index++) {
final String line = lines.get(index)
ruleLines.append(' "')
ruleLines.append(escapeForJava(line))
ruleLines.append('"')
if (index < lines.size() - 1) {
ruleLines.append(',')
}
ruleLines.append('\n')
}
return ruleLines.toString()
}
def paicehuskEngineInsertion = { final String ruleLines ->
return """
public static final String[] RULE_LINES = {
${ruleLines}
};
private static final java.util.HashMap RULES = createRulesFromEmbeddedRules();
/**
* Creates benchmark stemmer instance.
*/
public PaiceHuskLancasterStemmer() {
}
/**
* Applies Paice/Husk stemming to one token.
*
* @param token input token
* @return stemmed token
*/
public String stem(final String token) {
if (token == null) {
return null;
}
return stemWord(token, RULES, null);
}
/**
* Loads bundled rule lines directly from the generated benchmark source.
*
* @return initialized rule map
*/
private static java.util.HashMap createRulesFromEmbeddedRules() {
try {
final java.io.File ruleFile = java.io.File.createTempFile("paicehusk-rules", ".txt");
ruleFile.deleteOnExit();
try (java.io.PrintWriter writer = new java.io.PrintWriter(new java.io.FileWriter(ruleFile))) {
for (String line : RULE_LINES) {
writer.println(line);
}
}
return loadRules(ruleFile.getAbsolutePath());
} catch (Exception exception) {
throw new IllegalStateException("Unable to initialize benchmark Paice/Husk rules.", exception);
}
}
"""
}
def transformPaiceHuskSource = { final File sourceFile, final File rulesFile, final File targetFile ->
if (!sourceFile.exists()) {
throw new GradleException("Paice/Husk Java source was not available at ${sourceFile}.")
}
if (!rulesFile.exists()) {
throw new GradleException("Paice/Husk rule file was not available at ${rulesFile}.")
}
final String sourceText = sourceFile.getText('UTF-8')
String transformedText = sourceText
transformedText = 'package org.egothor.stemmer.benchmark;' + '\n\n' + transformedText
transformedText = transformedText.replaceFirst(/(?m)^\s*class\s+PaiceHusk\s*\{/, 'public final class PaiceHuskLancasterStemmer {')
transformedText = transformedText.replace('new Character(rule.letter)', 'Character.valueOf(rule.letter)')
transformedText = transformedText.replace('new Character(stem.charAt(stem.length() - 1))',
'Character.valueOf(stem.charAt(stem.length() - 1))')
transformedText = transformedText.replaceFirst(/(?m)^(\s*)static HashMap loadRules\(/,
'$1@SuppressWarnings("unchecked")\n$1static HashMap loadRules(')
final int packageEnd = transformedText.indexOf('\n', transformedText.indexOf('package org.egothor.stemmer.benchmark;'))
if (packageEnd >= 0) {
transformedText = transformedText.substring(0, packageEnd + 1) + '\n' + paicehuskGeneratedNotice + transformedText.substring(packageEnd + 1)
}
final String marker = '\n} // end class PaiceHusk'
final int markerIndex = transformedText.lastIndexOf(marker)
if (markerIndex < 0) {
throw new GradleException("Unexpected Paice/Husk source structure at ${sourceFile}.")
}
final String replacement = paicehuskEngineInsertion(toRuleLines(rulesFile))
transformedText = transformedText.substring(0, markerIndex) + '\n' + replacement + '\n}'
targetFile.parentFile.mkdirs()
targetFile.text = transformedText
}
tasks.register('downloadPaiceHuskStemmer') {
group = 'build setup'
description = 'Downloads the upstream Paice/Husk benchmark source for dynamic extraction.'
outputs.file(paicehuskDownloadFile)
doLast {
final File targetFile = paicehuskDownloadFile.get().asFile
targetFile.parentFile.mkdirs()
if (!targetFile.exists()) {
new URL(paicehuskDownloadUrl).withInputStream { inputStream ->
targetFile.withOutputStream { outputStream ->
outputStream << inputStream
}
}
}
}
}
tasks.register('extractPaiceHuskStemmer', Copy) {
group = 'build setup'
description = 'Extracts the upstream Paice/Husk benchmark archive.'
dependsOn(tasks.named('downloadPaiceHuskStemmer'))
from(zipTree(paicehuskDownloadFile))
into(paicehuskExtractDirectory)
}
tasks.register('generatePaiceHuskLancasterStemmer') {
group = 'build setup'
description = 'Generates PaiceHuskLancasterStemmer into a benchmark-only generated source directory.'
dependsOn(tasks.named('extractPaiceHuskStemmer'))
inputs.files(paicehuskJavaFile, paicehuskRulesFile)
outputs.file(paicehuskGeneratedStemmerFile)
doLast {
transformPaiceHuskSource(paicehuskJavaFile.get().asFile, paicehuskRulesFile.get().asFile, paicehuskGeneratedStemmerFile.get().asFile)
}
}
sourceSets {
jmh {
java {
srcDir(paicehuskGeneratedSourceDirectory)
}
}
}
tasks.named('compileJmhJava') {
dependsOn(tasks.named('generatePaiceHuskLancasterStemmer'))
}
eclipse {
classpath {
file {
whenMerged { classpath ->
String generatedPath = paicehuskGeneratedSourcePath.get()
classpath.entries.removeAll { entry ->
entry.hasProperty('path') && (
entry.path == generatedPath ||
paicehuskIsAbsolutePath(entry.path)
)
}
SourceFolder paicehuskEntry = new SourceFolder(generatedPath, null)
paicehuskEntry.output = 'bin/jmh'
paicehuskSourceEclipseClasspathAttributes.each { String name, String value ->
paicehuskEntry.entryAttributes[name] = value
}
classpath.entries.add(paicehuskEntry)
}
}
}
}

View File

@@ -7,13 +7,43 @@ def snowballDistributionDirectoryName = "libstemmer_java-${snowballVersion}"
def snowballRootRelativePath = 'third-party/snowball'
def snowballSourceRelativePath = "${snowballRootRelativePath}/source"
def snowballJavaSourceRelativePath = "${snowballSourceRelativePath}/${snowballDistributionDirectoryName}/java"
def snowballGeneratedSourceRelativePath = 'generated/sources/snowball'
def snowballDownloadUrl = "https://snowballstem.org/dist/${snowballArchiveName}"
def snowballDownloadFile = layout.buildDirectory.file("${snowballRootRelativePath}/${snowballArchiveName}")
def snowballExtractDirectory = layout.buildDirectory.dir(snowballSourceRelativePath)
def snowballJavaSourceDirectory = layout.buildDirectory.dir(snowballJavaSourceRelativePath)
def snowballGeneratedSourceDirectory = layout.buildDirectory.dir(snowballGeneratedSourceRelativePath)
def snowballJavaSourceClasspathPath = provider {
project.relativePath(snowballJavaSourceDirectory.get().asFile)
}
def snowballGeneratedSourceClasspathPath = provider {
project.relativePath(snowballGeneratedSourceDirectory.get().asFile)
}
def transformSnowballSourceText = { final String sourceText ->
String transformedText = sourceText
transformedText = transformedText.replaceAll(/(?m)^\s*package\s+org\.tartarus\.snowball\.ext\s*;/,
'package org.egothor.stemmer.benchmark.snowball.ext;')
transformedText = transformedText.replaceAll(/(?m)^\s*package\s+org\.tartarus\.snowball\s*;/,
'package org.egothor.stemmer.benchmark.snowball;')
transformedText = transformedText.replace('org.tartarus.snowball.', 'org.egothor.stemmer.benchmark.snowball.')
return transformedText
}
def copySnowballSourcesWithPackageIsolation = { final File sourceDirectory, final File targetDirectory ->
final FileTree sourceFiles = fileTree(sourceDirectory).matching { include '**/*.java' }
if (targetDirectory.exists()) {
targetDirectory.deleteDir()
}
for (File sourceFile : sourceFiles.files) {
final String relativePath = sourceDirectory.toPath().relativize(sourceFile.toPath()).toString()
final File outputFile = new File(targetDirectory, relativePath)
outputFile.parentFile.mkdirs()
outputFile.text = transformSnowballSourceText(sourceFile.getText('UTF-8'))
}
}
def snowballEclipseClasspathAttributes = [
gradle_scope : 'jmh',
gradle_used_by_scope: 'jmh',
@@ -53,16 +83,33 @@ tasks.register('extractSnowballJava', Copy) {
into(snowballExtractDirectory)
}
tasks.register('generateIsolatedSnowballSources') {
group = 'build setup'
description = 'Copies Snowball source to benchmark-only package-isolated package paths.'
dependsOn(tasks.named('extractSnowballJava'))
inputs.dir(snowballJavaSourceDirectory)
outputs.dir(snowballGeneratedSourceDirectory)
doLast {
copySnowballSourcesWithPackageIsolation(
snowballJavaSourceDirectory.get().asFile,
snowballGeneratedSourceDirectory.get().asFile
)
}
}
sourceSets {
jmh {
java {
srcDir(snowballJavaSourceDirectory)
srcDir(snowballGeneratedSourceDirectory)
}
}
}
tasks.named('compileJmhJava') {
dependsOn(tasks.named('extractSnowballJava'))
dependsOn(tasks.named('generateIsolatedSnowballSources'))
}
eclipse {
@@ -70,17 +117,19 @@ eclipse {
file {
whenMerged { classpath ->
String generatedSnowballPath = snowballJavaSourceClasspathPath.get()
String generatedIsolatedSnowballPath = snowballGeneratedSourceClasspathPath.get()
String modelSnowballPath = snowballJavaSourceRelativePath
classpath.entries.removeAll { entry ->
entry.hasProperty('path') && (
entry.path == generatedSnowballPath ||
entry.path == generatedIsolatedSnowballPath ||
entry.path == modelSnowballPath ||
isAbsoluteClasspathPath(entry.path)
)
}
SourceFolder snowballEntry = new SourceFolder(generatedSnowballPath, null)
SourceFolder snowballEntry = new SourceFolder(generatedIsolatedSnowballPath, null)
snowballEntry.output = 'bin/jmh'
snowballEclipseClasspathAttributes.each { String name, String value ->
snowballEntry.entryAttributes[name] = value

View File

@@ -248,11 +248,24 @@
<sha256 value="a151df1e2e0b48618d8b06a180748a29b3abb39b1b2396f6a1c879a727488c6e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.errorprone" name="error_prone_annotations" version="2.47.0">
<artifact name="error_prone_annotations-2.47.0.jar">
<sha256 value="5364bc6f22e72e98195e406a58d3ba1c09ffa11dea0729592cb870dc2de4056d" origin="Generated by Gradle"/>
</artifact>
<artifact name="error_prone_annotations-2.47.0.pom">
<sha256 value="d80c889a4a6f711f6945fbee79e05ec247b178a567e9d5abf58eb26ebf0a0752" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.errorprone" name="error_prone_parent" version="2.41.0">
<artifact name="error_prone_parent-2.41.0.pom">
<sha256 value="c538388d760a5c1c98dcf06f6ed3cfe5f11a651827db5cbd2ed8288c795cad42" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.errorprone" name="error_prone_parent" version="2.47.0">
<artifact name="error_prone_parent-2.47.0.pom">
<sha256 value="2368a990c7a63095e1d0d44459d5a4092f0eb31f8562bd12cdf0e1c877b6a685" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.guava" name="failureaccess" version="1.0.3">
<artifact name="failureaccess-1.0.3.jar">
<sha256 value="cbfc3906b19b8f55dd7cfd6dfe0aa4532e834250d7f080bd8d211a3e246b59cb" origin="Generated by Gradle"/>
@@ -274,6 +287,14 @@
<sha256 value="77ed42c8c8b2cebbb93ac9e07543ff6418aa24bdb8517580cf5324e9a6510956" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.guava" name="guava" version="33.6.0-jre">
<artifact name="guava-33.6.0-jre.jar">
<sha256 value="dc573e1fca4fd5454f4a5fd3d7da2df03002876a4175bafc14a95980dd7713b3" origin="Generated by Gradle"/>
</artifact>
<artifact name="guava-33.6.0-jre.module">
<sha256 value="2baf73ce839ae48e4b9e0083e256b0e58fc3bf8fc78fc3fbe797bbc89011216e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.guava" name="guava-parent" version="26.0-android">
<artifact name="guava-parent-26.0-android.pom">
<sha256 value="f8698ab46ca996ce889c1afc8ca4f25eb8ac6b034dc898d4583742360016cc04" origin="Generated by Gradle"/>
@@ -294,6 +315,11 @@
<sha256 value="68719e687c6e4c9ff3e0fecbef7bd20896f0f4f7b314743ed33c72f962568215" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.guava" name="guava-parent" version="33.6.0-jre">
<artifact name="guava-parent-33.6.0-jre.pom">
<sha256 value="374bd31f61b1cf612bee9ab2e4d70bbdf77dd85a49b431f809d4fbdc901f2dd4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.guava" name="listenablefuture" version="9999.0-empty-to-avoid-conflict-with-guava">
<artifact name="listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar">
<sha256 value="b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99" origin="Generated by Gradle"/>
@@ -524,11 +550,24 @@
<sha256 value="6d849ae7454ab391718e5fc70e2716418ef3ed264472345bd80c6de64e00b6c4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="me.champeau.jmh" name="jmh-gradle-plugin" version="0.7.3">
<artifact name="jmh-gradle-plugin-0.7.3.jar">
<sha256 value="d7097e619541d90e0a970b2a68573e22ad01d2999ee5365d56d59830765bf98f" origin="Generated by Gradle"/>
</artifact>
<artifact name="jmh-gradle-plugin-0.7.3.module">
<sha256 value="3487d1aba24fe0af527c6d5f78b5f0e8fd64fe9878708b460e6600e39a47bc43" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="me.champeau.jmh" name="me.champeau.jmh.gradle.plugin" version="0.7.2">
<artifact name="me.champeau.jmh.gradle.plugin-0.7.2.pom">
<sha256 value="57e0c23ac60945aefb5a0c4a9339bea68a295364ca47c7a9079a032f79013abb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="me.champeau.jmh" name="me.champeau.jmh.gradle.plugin" version="0.7.3">
<artifact name="me.champeau.jmh.gradle.plugin-0.7.3.pom">
<sha256 value="d516226b3b114e4b32d42544d1d2796c732c5465d5dae7cc846be6b23bed8d1d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="net.bytebuddy" name="byte-buddy" version="1.17.7">
<artifact name="byte-buddy-1.17.7.jar">
<sha256 value="3575dcb8a98faf943d3c1595c47a16047c4fce8a83ebbb26262f1a2f67546357" origin="Generated by Gradle"/>
@@ -710,6 +749,11 @@
<sha256 value="d78bd8524c5f8380a190a6525686629a95dfe512df21111383a6d8c0923a4415" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache" name="apache" version="34">
<artifact name="apache-34.pom">
<sha256 value="3671ae9d4d062ae3bb985731c76088bb2f6f7d7254e2d304ee9f690b97651328" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache" name="apache" version="35">
<artifact name="apache-35.pom">
<sha256 value="ea297dcd114136e8b8e8b630230d52a76c2fc69f6c5db25d672b1857000728b8" origin="Generated by Gradle"/>
@@ -720,6 +764,11 @@
<sha256 value="524ec4787aff73af6b3a9fafa154c7f1881b648299b663fdbfcadda1286f2353" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache" name="apache" version="38">
<artifact name="apache-38.pom">
<sha256 value="9b0a5f28ddfb4b7500a37022bee8245efdd044fb9a3d79fb827550923eccc4b5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.commons" name="commons-collections4" version="4.5.0">
<artifact name="commons-collections4-4.5.0.jar">
<sha256 value="00f93263c267be201b8ae521b44a7137271b16688435340bf629db1bac0a5845" origin="Generated by Gradle"/>
@@ -845,6 +894,21 @@
<sha256 value="5e5168687bcedaea2aa8556b8b32f5afa08efcbc31d11b947c8676987bc9d239" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.groovy" name="groovy" version="4.0.29">
<artifact name="groovy-4.0.29.module">
<sha256 value="e7af04464263212199236dd8f136a2ef56662654ff9125fe3573f1968b147738" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.groovy" name="groovy-ant" version="4.0.29">
<artifact name="groovy-ant-4.0.29.module">
<sha256 value="92665f78934afbc5214f8085bec1ad3e26f2dd95a9df8d2e40ea8921c500b0d9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.groovy" name="groovy-astbuilder" version="4.0.29">
<artifact name="groovy-astbuilder-4.0.29.module">
<sha256 value="ef9116453bb919264040acb4800173c1dfd247a8244c55f2cc99a3a10c7a0e84" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.groovy" name="groovy-bom" version="4.0.11">
<artifact name="groovy-bom-4.0.11.module">
<sha256 value="6aeb51768ac49d58bf515a570f966a4cd6cf823a8a919e9e87dfdde4152101e4" origin="Generated by Gradle"/>
@@ -853,6 +917,49 @@
<sha256 value="27553a483d0f46df6aa86a1ce1f4126dca34aa957b2f88aaeb63136fd847fe18" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.groovy" name="groovy-bom" version="4.0.29">
<artifact name="groovy-bom-4.0.29.module">
<sha256 value="2392853c20ff3b7474791d66f831909f25396cb397cf63bf15e1998a5c0b82fc" origin="Generated by Gradle"/>
</artifact>
<artifact name="groovy-bom-4.0.29.pom">
<sha256 value="c24277dec93f146bcda25f5ae4391d6527e384e2132efa32184c1e852b42bca9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.groovy" name="groovy-datetime" version="4.0.29">
<artifact name="groovy-datetime-4.0.29.module">
<sha256 value="6a62fbfc964c749115de2dad43ac356113d426ada3d67e9e5c4d5a5714dbc375" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.groovy" name="groovy-dateutil" version="4.0.29">
<artifact name="groovy-dateutil-4.0.29.module">
<sha256 value="9ca3cfb3ff7b8b9e368dd5f56d530a698b48144ad70849198b40cb0b20a7eb68" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.groovy" name="groovy-groovydoc" version="4.0.29">
<artifact name="groovy-groovydoc-4.0.29.module">
<sha256 value="36f314ccab47b8183f7bb7061b0929452f1485f90c8f53054bea49987808875b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.groovy" name="groovy-json" version="4.0.29">
<artifact name="groovy-json-4.0.29.module">
<sha256 value="8b82fd7951cc0659ea3a87fce02f8ff7a9461ac30240e9f5e9d89ee72106db65" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.groovy" name="groovy-nio" version="4.0.29">
<artifact name="groovy-nio-4.0.29.module">
<sha256 value="4f68a402a10b30dcdd4990c5418036329da4d5d224b35fceadd9b71a577c18dc" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.groovy" name="groovy-templates" version="4.0.29">
<artifact name="groovy-templates-4.0.29.module">
<sha256 value="12d98916ff0244b1daa3ff0fd90693ae901371a9d8b290b9fc1a040874c7ad74" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.groovy" name="groovy-xml" version="4.0.29">
<artifact name="groovy-xml-4.0.29.module">
<sha256 value="34f940a5c1cdec2d3550b020d4c9aecd4779d4200570e1972261da5e21b7391d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.httpcomponents" name="httpcomponents-parent" version="14">
<artifact name="httpcomponents-parent-14.pom">
<sha256 value="5dceb6ab206a419179273f15d0d2b472f9472bbb747c6c8b7e96daaf78cc4527" origin="Generated by Gradle"/>
@@ -905,6 +1012,14 @@
<sha256 value="d8ef04000565affac019b7a55de5bb7cc82ab0403295285ef49f6c8c2745afeb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.lucene" name="lucene-analysis-common" version="10.5.0">
<artifact name="lucene-analysis-common-10.5.0.jar">
<sha256 value="922e217fe5cc88305b5a8e057cd80a30b7de96be2ff20dc4327fb524d26e6a25" origin="Generated by Gradle"/>
</artifact>
<artifact name="lucene-analysis-common-10.5.0.pom">
<sha256 value="8ad3288be355a6dca42678ae6abaa4726fe4a320b1657e7a7a455ae684edad27" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.lucene" name="lucene-analysis-common" version="9.12.3">
<artifact name="lucene-analysis-common-9.12.3.jar">
<sha256 value="fa571bd7caf0f0b4faf46a72ca004a7836f348c31d92bd522dddcc3d128d287e" origin="Generated by Gradle"/>
@@ -913,6 +1028,30 @@
<sha256 value="3242d6696252c6ce33744087bbb821c384373c7819c3a054539b59876e3df39a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.lucene" name="lucene-analysis-morfologik" version="10.5.0">
<artifact name="lucene-analysis-morfologik-10.5.0.jar">
<sha256 value="7438fa8afd11dc9b606e911ef5a922cf642a3d45e32c5bbd456e54d731810dbe" origin="Generated by Gradle"/>
</artifact>
<artifact name="lucene-analysis-morfologik-10.5.0.pom">
<sha256 value="df9863e0db7416ba1d0d1fa657b2728e31a2ac8150bdd87fa272b100aa0838fb" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.lucene" name="lucene-analysis-stempel" version="10.5.0">
<artifact name="lucene-analysis-stempel-10.5.0.jar">
<sha256 value="f46699a4457e1cec1035737c37be1f061eaf32d96b6679968bbda19d1b12ed2b" origin="Generated by Gradle"/>
</artifact>
<artifact name="lucene-analysis-stempel-10.5.0.pom">
<sha256 value="da627cd84d6e29eeae63fd1881420b1b9f1b3619c106016a5ba0338351a292ac" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.lucene" name="lucene-core" version="10.5.0">
<artifact name="lucene-core-10.5.0.jar">
<sha256 value="ec05ee432860dc6116765fc6bd9ffccf65311cb0430a42a3a90f09fffcd310b1" origin="Generated by Gradle"/>
</artifact>
<artifact name="lucene-core-10.5.0.pom">
<sha256 value="356efef0e44ed7e1979af9d39c4403fea268edc5ede1c6fe4c006e58f218a0dc" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.lucene" name="lucene-core" version="9.12.3">
<artifact name="lucene-core-9.12.3.jar">
<sha256 value="b64a3f8098a7572034fb30085cdee01b34ec81fb0e5a31b471536af58dc6c01b" origin="Generated by Gradle"/>
@@ -958,6 +1097,11 @@
<sha256 value="6f4bb954198678a528dfc8b2887a84cc3f54ae4a0b8b75c191fa28b04963e607" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven" version="3.9.16">
<artifact name="maven-3.9.16.pom">
<sha256 value="5a761e32d3f3b5d65a70345cab4a327730c1d2000bb935bae7276dcc8fa81738" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-artifact" version="3.9.14">
<artifact name="maven-artifact-3.9.14.jar">
<sha256 value="1effa70eacbf0aa4d94ad9c7b225be031ce4317fa07da59e23b02b3e4e1231a3" origin="Generated by Gradle"/>
@@ -966,6 +1110,14 @@
<sha256 value="e668c936d22fd2c11edff52eac72c6e7fc13ba57c949096048be8debf0f9ffd2" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-artifact" version="3.9.16">
<artifact name="maven-artifact-3.9.16.jar">
<sha256 value="54cc1c1ef932e3d4a903352111b42b4d3c3ed8e7a1d0de73b625309d9c3ad3e8" origin="Generated by Gradle"/>
</artifact>
<artifact name="maven-artifact-3.9.16.pom">
<sha256 value="85d313bbbdbce67e199aadb4336e100c40a147881142ea4368cdebeafc02baec" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-builder-support" version="3.9.14">
<artifact name="maven-builder-support-3.9.14.jar">
<sha256 value="2109ff808046e4f8b356b1064060a3f224b0b0aad07ecceaf7696c3bdc0b2296" origin="Generated by Gradle"/>
@@ -974,6 +1126,14 @@
<sha256 value="6ed1ab2a239c5954b074dd8b75e70dbba55866840e73c53ae8b1f99a35afb7b5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-builder-support" version="3.9.16">
<artifact name="maven-builder-support-3.9.16.jar">
<sha256 value="02972384eae3495801565fd27abb84cfd75a8e6d2cbb1ae0c556752ebc2b1cde" origin="Generated by Gradle"/>
</artifact>
<artifact name="maven-builder-support-3.9.16.pom">
<sha256 value="c8366af883eeec0e2fd13e14cd8245969284b6e66df131f7b7c03d270f72f613" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-core" version="3.9.14">
<artifact name="maven-core-3.9.14.jar">
<sha256 value="db009d57b90a714efe86c81c7a518febb276d01ba3daf2f301e382f6560e8a58" origin="Generated by Gradle"/>
@@ -982,6 +1142,14 @@
<sha256 value="a7967fb392197e5fa73c7b7c3fb728f77fc50f4ad7f03679c0bbabee5c0132b3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-core" version="3.9.16">
<artifact name="maven-core-3.9.16.jar">
<sha256 value="5d45c72e3dbfab8b68d15ad4f12777b7d9b5fe4d4adc99c3bd51fb9641fab009" origin="Generated by Gradle"/>
</artifact>
<artifact name="maven-core-3.9.16.pom">
<sha256 value="186f17628c5235d03e34c593122d05fdc1be9694440a54d8213b3f957d6379a4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-model" version="3.9.14">
<artifact name="maven-model-3.9.14.jar">
<sha256 value="684f573b1b37933c5d62c1c21d5da4335b049fb8b3d9754281597a38dbbc4044" origin="Generated by Gradle"/>
@@ -990,6 +1158,14 @@
<sha256 value="e999c4ae0f12bff7585bffcaf5b0e6cb69226f27d21d5421fa4ae4e76779152a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-model" version="3.9.16">
<artifact name="maven-model-3.9.16.jar">
<sha256 value="f59d86a507c241bf17bbf050050d1b8b9c0d34d5010a7e2386c3cab7c83b93de" origin="Generated by Gradle"/>
</artifact>
<artifact name="maven-model-3.9.16.pom">
<sha256 value="64883ffcfd91ddaadb4181740236a823bfe0880e0c20f5beee74bc32a93914a7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-model-builder" version="3.9.14">
<artifact name="maven-model-builder-3.9.14.jar">
<sha256 value="9a6f4deb11bd6fe3f8b11036ed46f34cded3b00fc638f242327537bfb53c9d3f" origin="Generated by Gradle"/>
@@ -998,6 +1174,14 @@
<sha256 value="7548856c413b3ef5f3d35c2e812beae847dd21809f8e5d3621d5da2d7bdcfe6f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-model-builder" version="3.9.16">
<artifact name="maven-model-builder-3.9.16.jar">
<sha256 value="002be86d1f53b36f559a0786034a17598d71087f1935f205a1f9e51d4dd124b3" origin="Generated by Gradle"/>
</artifact>
<artifact name="maven-model-builder-3.9.16.pom">
<sha256 value="6e2a59eadd77b244fe60fc60b7ed3a9b2dee704d44a4f5baca4f9d3be6e53e10" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-parent" version="39">
<artifact name="maven-parent-39.pom">
<sha256 value="cfe4820aa1d96ae51d1dc5b0e2a9dc582c42478c24c95ca8238f547e60bef721" origin="Generated by Gradle"/>
@@ -1008,6 +1192,11 @@
<sha256 value="82d0112ba1907ff5fd13a2485829c97df66c6a81e075359a561a422f7d1582d3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-parent" version="48">
<artifact name="maven-parent-48.pom">
<sha256 value="cc9eed84b90a96cbc33aefecc93facb9a49f960ad678909162c579356cfe12c9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-plugin-api" version="3.9.14">
<artifact name="maven-plugin-api-3.9.14.jar">
<sha256 value="062445ef3ae988e245cca68ccec915de64703ec615badf2d9d61da9ee6f1a245" origin="Generated by Gradle"/>
@@ -1016,6 +1205,14 @@
<sha256 value="754d855dfe4c605400620b0c7ef8708c9a413ef629b07f214767ebb15ab7f99a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-plugin-api" version="3.9.16">
<artifact name="maven-plugin-api-3.9.16.jar">
<sha256 value="37cb5e483e23327cf4ba18f920b45e000d20eec0428a086a5c1bd6bbdecf088c" origin="Generated by Gradle"/>
</artifact>
<artifact name="maven-plugin-api-3.9.16.pom">
<sha256 value="6fe60dbb3157b9466a6b18f70a4c6eca7f68978eaf015ce164bb2471e1acbc12" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-repository-metadata" version="3.9.14">
<artifact name="maven-repository-metadata-3.9.14.jar">
<sha256 value="307e4920b17a8fdd764b556a7569de9bdd384d6b5f3f6a63dfb2ef186f02da2a" origin="Generated by Gradle"/>
@@ -1024,6 +1221,14 @@
<sha256 value="9c399dc0a741c5023a28a2a38d4a49a80059078431be99b3d4571831c3915fdc" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-repository-metadata" version="3.9.16">
<artifact name="maven-repository-metadata-3.9.16.jar">
<sha256 value="bc3dd413b89a16b695f35a5d0496b58ea2787c30b7b6062c130d24d6d764720f" origin="Generated by Gradle"/>
</artifact>
<artifact name="maven-repository-metadata-3.9.16.pom">
<sha256 value="1c018bbd6cd513b43df5dd64c82579b14c414d35f99e06a0c3878b05ae070912" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-resolver-provider" version="3.9.14">
<artifact name="maven-resolver-provider-3.9.14.jar">
<sha256 value="a5bc340ffe35325c55a01762feee374abb2a9e2b14191ea39c1ef646c2754f65" origin="Generated by Gradle"/>
@@ -1032,6 +1237,14 @@
<sha256 value="355db0ea3f355a1c575523a13e31f8b9fdace732474c74afae6ad90fce722e33" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-resolver-provider" version="3.9.16">
<artifact name="maven-resolver-provider-3.9.16.jar">
<sha256 value="72a2d6aad3708e2c708b659b292ae9587a4d170ec88f48466290318730221118" origin="Generated by Gradle"/>
</artifact>
<artifact name="maven-resolver-provider-3.9.16.pom">
<sha256 value="33723bba45ff2a1581bc2840836b7d9c9aa262af676f4f61222e179b56ea8818" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-settings" version="3.9.14">
<artifact name="maven-settings-3.9.14.jar">
<sha256 value="0e5492e07136565b1ef72a981e99797999183e54f589e00aa608cbadc4cb9bda" origin="Generated by Gradle"/>
@@ -1040,6 +1253,14 @@
<sha256 value="a49d6f6434b40c1ef8b63dccf8615aa9505d236d0e9fe6045e5f9513a42aadbe" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-settings" version="3.9.16">
<artifact name="maven-settings-3.9.16.jar">
<sha256 value="322ae5b23f4b7b6ce7896bd33a793143dbbbbcc16c4475e2d7eebbd8f755da92" origin="Generated by Gradle"/>
</artifact>
<artifact name="maven-settings-3.9.16.pom">
<sha256 value="d3df811fe57832933adef90c119cbf3abb941884cf840cc9777aa639cf1a145d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-settings-builder" version="3.9.14">
<artifact name="maven-settings-builder-3.9.14.jar">
<sha256 value="1d3cf59f9dc6af77f7a1052aea598535b4f59926a9fe92cecce3ecfb5b91ff1f" origin="Generated by Gradle"/>
@@ -1048,6 +1269,14 @@
<sha256 value="8ca00532860ab13c7b5398df095fc3820c223955c32974c5d5b6bc6e45357c61" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven" name="maven-settings-builder" version="3.9.16">
<artifact name="maven-settings-builder-3.9.16.jar">
<sha256 value="226f1cbb0b4d414eff773fb151a8977837c98073e14d5963794e6cf464e6adbb" origin="Generated by Gradle"/>
</artifact>
<artifact name="maven-settings-builder-3.9.16.pom">
<sha256 value="bce946240bf297982f524666674acff5c14878a24849f1f3c3489cd1d829ed48" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.maven.resolver" name="maven-resolver" version="1.9.27">
<artifact name="maven-resolver-1.9.27.pom">
<sha256 value="8924b41711cce058f83c46d79ad83d6e04edcf13ed5631ec11135656e0b87f56" origin="Generated by Gradle"/>
@@ -1106,6 +1335,19 @@
<sha256 value="a941745d7faeb8dc9a75edc2c330c994b7440b9a44d21142716b6053967a41c1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.opennlp" name="opennlp" version="2.5.4">
<artifact name="opennlp-2.5.4.pom">
<sha256 value="433d0873ec27cfe1b1a20ea643773a2f29a40a1fdea1bb5d1828b2f35350c29b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.opennlp" name="opennlp-tools" version="2.5.4">
<artifact name="opennlp-tools-2.5.4.jar">
<sha256 value="5efedb26d0e97e53707a8d2f0e2e28f05fcf654fce2a33f8bfe11c2d4c2a4fe9" origin="Generated by Gradle"/>
</artifact>
<artifact name="opennlp-tools-2.5.4.pom">
<sha256 value="fd4557916e65775956c37e8cdda7d9ad2bca778a29c98d6a5c6e31b61f83d560" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.apache.velocity" name="velocity-engine-core" version="2.4.1">
<artifact name="velocity-engine-core-2.4.1.jar">
<sha256 value="1c19157d1171d560088e485be97c93a7a2f7e9f56e517f0a30273c5c39df6231" origin="Generated by Gradle"/>
@@ -1137,6 +1379,35 @@
<sha256 value="22b87dda9aab83fa1d0f3ea409b524e7a44921cf8f5f87999cf59046f0fe0bc3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.carrot2" name="morfologik-fsa" version="2.1.9">
<artifact name="morfologik-fsa-2.1.9.jar">
<sha256 value="1bfefce937df14cc94d32a98ce59c33f4d5b6c0eddbb436b6bfe27ff2120a23d" origin="Generated by Gradle"/>
</artifact>
<artifact name="morfologik-fsa-2.1.9.pom">
<sha256 value="1097b12e6ede04b5a4e09b77233ac0943d8a6020edced7fd65ec97f2b02e103c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.carrot2" name="morfologik-parent" version="2.1.9">
<artifact name="morfologik-parent-2.1.9.pom">
<sha256 value="59c72168787ba151785125e34472e7841c5ff18bde176c2db7349c06917a0627" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.carrot2" name="morfologik-polish" version="2.1.9">
<artifact name="morfologik-polish-2.1.9.jar">
<sha256 value="e503682b3f4e8bb7a5d05820b0e2a4a19d4bad43dae20f64741786658a9cf478" origin="Generated by Gradle"/>
</artifact>
<artifact name="morfologik-polish-2.1.9.pom">
<sha256 value="85595c01c592576b91f59600c040bf2ceabaf61afc25ffa8507e4da981a54fb5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.carrot2" name="morfologik-stemming" version="2.1.9">
<artifact name="morfologik-stemming-2.1.9.jar">
<sha256 value="6170895b2315b697f4da5630caf57c6c441f1cb419d89d1cb5326b0673293e8a" origin="Generated by Gradle"/>
</artifact>
<artifact name="morfologik-stemming-2.1.9.pom">
<sha256 value="0b1495ad4d54b8dd4d309e1b445625ff2c788cc68818b5ded5cfe4f0d2f891a2" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.checkerframework" name="checker-qual" version="3.52.1">
<artifact name="checker-qual-3.52.1.jar">
<sha256 value="934641a18c8461bf66d7e939b2b054bf2a518ed4188fd7d6836a65b038f5364a" origin="Generated by Gradle"/>
@@ -1165,6 +1436,11 @@
<sha256 value="89a1bc79e46c35ab108b7e215bb2c5c215ff8f3af1ae3cfef82d9a2b33b06c51" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.codehaus.plexus" name="plexus" version="25">
<artifact name="plexus-25.pom">
<sha256 value="faa7947c2020967ad0c92b259ee9fa361d05e90cd036d17c37098bb1edaea3a3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.codehaus.plexus" name="plexus" version="8">
<artifact name="plexus-8.pom">
<sha256 value="ffa349db04e7abf65885bdc5a2062f4197c0ff9d3f1f4e2aa5720b77233f742c" origin="Generated by Gradle"/>
@@ -1178,6 +1454,14 @@
<sha256 value="04842f331b0225b85a5e20439710d228ea7a6302abe6d53c9c9846fbc5bf99ff" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.codehaus.plexus" name="plexus-classworlds" version="2.11.0">
<artifact name="plexus-classworlds-2.11.0.jar">
<sha256 value="8971f135490070bc5fde7413fcc8db7c997fda4bebfb5c31185900d66edcbbb2" origin="Generated by Gradle"/>
</artifact>
<artifact name="plexus-classworlds-2.11.0.pom">
<sha256 value="281d317bf8a5fe818708cdd00e377dd234ec949f498d30f4b363f6b9771e1fa2" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.codehaus.plexus" name="plexus-classworlds" version="2.9.0">
<artifact name="plexus-classworlds-2.9.0.jar">
<sha256 value="1ad3292cd563381e3fd632f3fded1988f9e9b2be7a9f3db63ff4c4cedba13fa5" origin="Generated by Gradle"/>
@@ -1223,6 +1507,14 @@
<sha256 value="6138300481471c7fe6aeb115f912961f886e1a46ee9c2bd2841b65184824da28" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.codehaus.plexus" name="plexus-utils" version="3.6.1">
<artifact name="plexus-utils-3.6.1.jar">
<sha256 value="05a63effd67e2d6b9d610cc82e2bd7473289d34802e57a529b28110f28af5679" origin="Generated by Gradle"/>
</artifact>
<artifact name="plexus-utils-3.6.1.pom">
<sha256 value="c8397373781af640a76c5da88f1674293b4fc9a2391d0768ee3fc791883b040d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.codehaus.woodstox" name="stax2-api" version="4.2.2">
<artifact name="stax2-api-4.2.2.jar">
<sha256 value="a61c48d553efad78bc01fffc4ac528bebbae64cbaec170b2a5e39cf61eb51abe" origin="Generated by Gradle"/>
@@ -1247,11 +1539,24 @@
<sha256 value="efe3734bc5b5e390b7ddd5cc7e86a5aca1a0377534e3420962f0931327c88d10" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.cyclonedx" name="cyclonedx-gradle-plugin" version="3.3.0">
<artifact name="cyclonedx-gradle-plugin-3.3.0.jar">
<sha256 value="9bf283e7e451cedf536b263733cf4ddca2329b3cffe19a05ccb8cc1f90a098e8" origin="Generated by Gradle"/>
</artifact>
<artifact name="cyclonedx-gradle-plugin-3.3.0.module">
<sha256 value="92c20482c05782eec05b9c0db1b2ed615160147e52c179c3e080538103e26239" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.cyclonedx.bom" name="org.cyclonedx.bom.gradle.plugin" version="3.2.4">
<artifact name="org.cyclonedx.bom.gradle.plugin-3.2.4.pom">
<sha256 value="9a8e381d2369288b6c3198b3062e8099229abddafd0a49beb631fd999ea07b9a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.cyclonedx.bom" name="org.cyclonedx.bom.gradle.plugin" version="3.3.0">
<artifact name="org.cyclonedx.bom.gradle.plugin-3.3.0.pom">
<sha256 value="f59df2c670269e7f5e3d9b2b539b9f435db5e88a2d545ab4811f2217d2cc5c68" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.eclipse.ee4j" name="project" version="1.0.5">
<artifact name="project-1.0.5.pom">
<sha256 value="916b4794d8d8220a59a3fdf6a64dbe794aeb23395e888b81ae36a9b5a2c591a6" origin="Generated by Gradle"/>
@@ -1479,6 +1784,14 @@
<sha256 value="08a02856e487c9357f9b29e38745f8ae805848111e72d15aad0352338f1632e1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.junit" name="junit-bom" version="5.14.4">
<artifact name="junit-bom-5.14.4.module">
<sha256 value="8a5e98d131de7d7aadb1ee88bfd86d66e62a7c2e2a4074a3b2498b03d236eb64" origin="Generated by Gradle"/>
</artifact>
<artifact name="junit-bom-5.14.4.pom">
<sha256 value="5706e8f29a0a07f56efbbea4a0670793414194bb8d24d8143ba1e787a2f32856" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.junit" name="junit-bom" version="5.9.3">
<artifact name="junit-bom-5.9.3.module">
<sha256 value="b401fd25901e582a524aa5343c4b39e28bc56e24961c1069bf2b4bbfcee46b93" origin="Generated by Gradle"/>
@@ -1955,6 +2268,14 @@
<sha256 value="ba01ae7a744cb52fe8ecf3b023cbc32e0ccc8c6beef9f26de77a47808239447d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="ua.net.nlp" name="morfologik-ukrainian-search" version="4.9.1">
<artifact name="morfologik-ukrainian-search-4.9.1.jar">
<sha256 value="463d9054b8d4cfacb9cd69566395826a6fe32fb6e7da91dbf79d37ddf7d56ba0" origin="Generated by Gradle"/>
</artifact>
<artifact name="morfologik-ukrainian-search-4.9.1.pom">
<sha256 value="6a66d8efe6a5c774932401b04411a8feca7d6628631e4ef80819cfafa133dc38" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="us.springett" name="cpe-parser" version="3.0.1">
<artifact name="cpe-parser-3.0.1.jar">
<sha256 value="f98a50dce0a381e08f0f0ac067801c7f4c51dc7f8f1fe7a3c9960c684a809705" origin="Generated by Gradle"/>

View File

@@ -38,17 +38,26 @@ markdown_extensions:
nav:
- Home: index.md
- Getting Started:
- Start:
- Fast Track: fast-track.md
- Quick Start: quick-start.md
- Built-in Languages: built-in-languages.md
- Dictionary Format: dictionary-format.md
- CLI Compilation: cli-compilation.md
- Integration Deep Dive: integration-deep-dive.md
- Programmatic Usage:
- Integration:
- Overview: programmatic-usage.md
- Model Selection and Loading: model-selection-and-loading.md
- Loading and Building Stemmers: programmatic-loading-and-building.md
- Querying and Ambiguity Handling: programmatic-querying-and-ambiguity.md
- Extending and Persisting Compiled Tries: programmatic-extending-and-persistence.md
- Migration and Backward Compatibility: migration-and-backward-compatibility.md
- CLI Compilation: cli-compilation.md
- Dictionaries and Languages:
- Stemmer Models: stemmer-models.md
- Published Model Catalog: stemmer-model-catalog.md
- Built-in Languages: built-in-languages.md
- Dictionary Format: dictionary-format.md
- Contributing Dictionaries: contributing-dictionaries.md
- Architecture and Semantics:
- Overview: architecture-and-reduction.md
@@ -57,11 +66,44 @@ nav:
- Lookup Edge Optimization: lookup-edge-optimization.md
- Compatibility and Guarantees: compatibility-and-guarantees.md
- Dictionaries:
- Contributing Dictionaries: contributing-dictionaries.md
- Benchmarks:
- How to Read Benchmarks: benchmarking.md
- Benchmark Results: benchmarks/index.md
- Reference:
- Methodology: benchmarks/reference/methodology.md
- Linguistic Quality Methodology: benchmarks/reference/linguistic-quality.md
- Tested Stemmers: benchmarks/reference/tested-stemmers.md
- Reproducibility and Raw Data: benchmarks/reference/reproducibility.md
- Corpora: benchmarks/reference/corpora.md
- Environment and Reports: benchmarks/reference/environment.md
- English Dictionary Coverage: benchmarks/reference/english-coverage.md
- Candidate Evaluation: benchmarks/reference/candidates.md
- Language Results:
- Overview: benchmarks/languages/index.md
- Czech: benchmarks/languages/czech.md
- Danish: benchmarks/languages/danish.md
- Dutch: benchmarks/languages/dutch.md
- English: benchmarks/languages/english.md
- Finnish: benchmarks/languages/finnish.md
- French: benchmarks/languages/french.md
- German: benchmarks/languages/german.md
- Hebrew: benchmarks/languages/hebrew.md
- Hungarian: benchmarks/languages/hungarian.md
- Italian: benchmarks/languages/italian.md
- Norwegian Bokmal: benchmarks/languages/norwegian-bokmal.md
- Norwegian Nynorsk: benchmarks/languages/norwegian-nynorsk.md
- Persian: benchmarks/languages/persian.md
- Polish: benchmarks/languages/polish.md
- Portuguese: benchmarks/languages/portuguese.md
- Russian: benchmarks/languages/russian.md
- Spanish: benchmarks/languages/spanish.md
- Swedish: benchmarks/languages/swedish.md
- Ukrainian: benchmarks/languages/ukrainian.md
- Yiddish: benchmarks/languages/yiddish.md
- Quality and Operations:
- Quality and Operations: quality-and-operations.md
- Benchmarking: benchmarking.md
- Stemming Quality: stemming-quality.md
- Reports: reports.md
- Historical Builds: builds.md
- Test taxonomy and execution filtering: test-taxonomy-and-filtering.md

104
models/bom/build.gradle Normal file
View File

@@ -0,0 +1,104 @@
import groovy.xml.XmlParser
plugins {
id 'java-platform'
id 'maven-publish'
id 'signing'
}
group = 'org.egothor'
version = providers.fileContents(rootProject.layout.projectDirectory.file('models/catalog-version.txt'))
.asText.map(String::trim).get()
Properties modelTopology = new Properties()
rootProject.file('models/model-projects.properties').withInputStream { InputStream input ->
modelTopology.load(input)
}
List<String> modelIds = modelTopology.stringPropertyNames().toList().sort()
dependencies {
constraints {
modelIds.each { String modelId ->
api project(":models:${modelId}")
}
}
}
publishing {
publications {
bom(MavenPublication) {
from components.javaPlatform
artifactId = 'radixor-models-bom'
pom {
name = 'Radixor Stemmer Models BOM'
description = 'Maven dependency-management BOM containing recommended versions for published Radixor models.'
packaging = 'pom'
url = 'https://github.com/leogalambos/Radixor'
licenses {
license {
name = 'BSD-3-Clause'
url = 'https://spdx.org/licenses/BSD-3-Clause.html'
distribution = 'repo'
}
}
developers {
developer {
id = 'egothor'
name = 'Leo Galambos'
email = 'egothor@gmail.com'
}
}
scm {
url = 'https://github.com/leogalambos/Radixor'
connection = 'scm:git:https://github.com/leogalambos/Radixor.git'
developerConnection = 'scm:git:ssh://git@github.com/leogalambos/Radixor.git'
}
}
}
}
repositories {
maven {
name = 'catalogStaging'
url = rootProject.layout.buildDirectory.dir('model-catalog-staging-repository').get().asFile.toURI()
}
}
}
String signingKey = providers.environmentVariable('SIGNING_KEY').orNull
String signingPassword = providers.environmentVariable('SIGNING_PASSWORD').orNull
signing {
required = { providers.environmentVariable('GITHUB_REF_TYPE').orNull == 'tag' }
if (signingKey != null && !signingKey.isBlank()) {
useInMemoryPgpKeys(signingKey, signingPassword)
sign publishing.publications.bom
}
}
def bomPublicationPom = layout.buildDirectory.file('publications/bom/pom-default.xml')
def expectedBomArtifactIds = modelIds.collect { String modelId -> "radixor-model-${modelId}" }
def bomHasJarTasks = !tasks.withType(Jar).isEmpty()
tasks.register('verifyPomOnlyPlatform') {
group = 'verification'
description = 'Verifies the POM-only model dependency-management platform.'
dependsOn(tasks.named('generatePomFileForBomPublication'))
inputs.file(bomPublicationPom)
doLast {
File pomFile = bomPublicationPom.get().asFile
Node pom = new XmlParser().parse(pomFile)
List<Node> constraints = pom.dependencyManagement.dependencies.dependency as List<Node>
List<String> artifactIds = constraints.collect { Node dependency -> dependency.artifactId.text() }
if (pom.packaging.text() != 'pom' || artifactIds != expectedBomArtifactIds) {
throw new GradleException('radixor-models-bom must publish exactly the ordered model constraints as Maven packaging pom.')
}
if (!pom.dependencies.isEmpty()) {
throw new GradleException('radixor-models-bom must not introduce runtime model dependencies.')
}
if (bomHasJarTasks) {
throw new GradleException('radixor-models-bom must not create binary, sources, or Javadoc JARs.')
}
}
}
tasks.named('check') {
dependsOn(tasks.named('verifyPomOnlyPlatform'))
}

View File

@@ -0,0 +1 @@
2026.1

View File

@@ -0,0 +1,23 @@
plugins {
id 'org.egothor.radixor.model'
}
radixorModel {
modelId = 'cs-cz-default'
language = 'CS_CZ'
displayName = 'Czech default model'
defaultModel = true
sourceName = 'UniMorph'
sourceVersion = 'not-recorded-in-legacy-import'
sourceRevision = 'not-recorded-in-legacy-import'
sourceProject = 'UniMorph'
sourceRepository = 'https://github.com/unimorph/ces'
sourceDataset = 'UniMorph Czech morphological dataset (`ces`); repository also documents non-distributed MorfFlex-CZ data'
sourceRevisionStatus = 'not-recorded-in-legacy-import'
sourceLicense = 'CC-BY-SA-3.0'
sourceLicenseUri = 'https://creativecommons.org/licenses/by-sa/3.0/'
sourceAttribution = 'UniMorph; Witold Kieraś is credited for the separate MorfFlex-CZ conversion'
sourceVerificationDate = '2026-07-22'
transformationsSummary = 'Cleaning, normalization, grouping inflected forms by lemma, deduplication, filtering invalid rows, reformatting into Radixor dictionary groups, GZip packaging, and generation of runtime descriptor and checksum metadata'
noticeFileName = 'NOTICE-model-data.txt'
}

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,38 @@
Radixor model-data notice
Radixor-derived model data
Copyright (C) 2026, Leo Galambos.
Copyright and, where applicable, database rights are claimed in the
Radixor-specific selection, verification, cleaning, normalization,
grouping, deduplication, filtering, reformatting, metadata preparation,
and packaging of this model, to the extent protected by applicable law.
The underlying morphological data remains attributed to UniMorph and
the upstream contributors identified in this notice.
This derived model data, including Radixor's protectable contributions,
is distributed under Creative Commons Attribution-ShareAlike 3.0
Unported.
Model ID: cs-cz-default
Radixor language: CS_CZ
Source project: UniMorph
Official repository: https://github.com/unimorph/ces
Upstream dataset: UniMorph Czech morphological dataset (`ces`); repository also documents non-distributed MorfFlex-CZ data
Upstream lexical source: Wiktionary; the CC BY-NC-SA MorfFlex-CZ dataset is excluded
Attribution: UniMorph; Witold Kieraś is credited for the separate MorfFlex-CZ conversion
License:
Creative Commons Attribution-ShareAlike 3.0 Unported
Canonical license URI: https://creativecommons.org/licenses/by-sa/3.0/
Source revision: not-recorded-in-legacy-import
Revision status: not-recorded-in-legacy-import
The exact UniMorph commit used for the original Radixor import was not recorded. The model remains attributed to the official UniMorph language repository and is distributed under the repository's stated data license.
Radixor modifications: Cleaning, normalization, grouping inflected forms by lemma, deduplication, filtering invalid rows, reformatting into Radixor dictionary groups, GZip packaging, and generation of runtime descriptor and checksum metadata.
The derived model data is distributed under CC BY-SA 3.0. UniMorph supplies morphological data; Radixor constructs its own patch-command trie at runtime. Neither UniMorph nor any upstream contributor endorses Radixor.
Upstream information verified: 2026-07-22

View File

@@ -0,0 +1,23 @@
plugins {
id 'org.egothor.radixor.model'
}
radixorModel {
modelId = 'da-dk-default'
language = 'DA_DK'
displayName = 'Danish default model'
defaultModel = true
sourceName = 'UniMorph'
sourceVersion = 'not-recorded-in-legacy-import'
sourceRevision = 'not-recorded-in-legacy-import'
sourceProject = 'UniMorph'
sourceRepository = 'https://github.com/unimorph/dan'
sourceDataset = 'UniMorph Danish morphological dataset (`dan`)'
sourceRevisionStatus = 'not-recorded-in-legacy-import'
sourceLicense = 'CC-BY-SA-3.0'
sourceLicenseUri = 'https://creativecommons.org/licenses/by-sa/3.0/'
sourceAttribution = 'UniMorph and Wikipedia contributors'
sourceVerificationDate = '2026-07-22'
transformationsSummary = 'Cleaning, normalization, grouping inflected forms by lemma, deduplication, filtering invalid rows, reformatting into Radixor dictionary groups, GZip packaging, and generation of runtime descriptor and checksum metadata'
noticeFileName = 'NOTICE-model-data.txt'
}

View File

@@ -0,0 +1 @@
1.0.0

Some files were not shown because too many files have changed in this diff Show More