werpy 3.2.0__tar.gz → 3.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. {werpy-3.2.0 → werpy-3.3.0}/CHANGELOG.md +15 -0
  2. {werpy-3.2.0 → werpy-3.3.0}/PKG-INFO +6 -2
  3. {werpy-3.2.0 → werpy-3.3.0}/README.md +1 -1
  4. {werpy-3.2.0 → werpy-3.3.0}/docs/source/conf.py +1 -1
  5. {werpy-3.2.0 → werpy-3.3.0}/meson.build +1 -1
  6. {werpy-3.2.0 → werpy-3.3.0}/pyproject.toml +6 -2
  7. {werpy-3.2.0 → werpy-3.3.0}/werpy/__init__.py +1 -1
  8. {werpy-3.2.0 → werpy-3.3.0}/werpy/metrics.pyx +180 -6
  9. {werpy-3.2.0 → werpy-3.3.0}/werpy/wer.py +6 -6
  10. {werpy-3.2.0 → werpy-3.3.0}/werpy/wers.py +5 -5
  11. {werpy-3.2.0 → werpy-3.3.0}/.circleci/config.yml +0 -0
  12. {werpy-3.2.0 → werpy-3.3.0}/.github/assets/images/werpy-example-summary-results-word-error-rate-breakdown.png +0 -0
  13. {werpy-3.2.0 → werpy-3.3.0}/.github/assets/images/werpy-example-summaryp-results-word-error-rate-breakdown.png +0 -0
  14. {werpy-3.2.0 → werpy-3.3.0}/.github/assets/images/werpy-logo-word-error-rate.png +0 -0
  15. {werpy-3.2.0 → werpy-3.3.0}/.github/dependabot.yml +0 -0
  16. {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/bandit.yml +0 -0
  17. {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/codacy.yml +0 -0
  18. {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/codeql.yml +0 -0
  19. {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/mypy.yml +0 -0
  20. {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/pylint.yml +0 -0
  21. {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/release-test.yml +0 -0
  22. {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/release.yml +0 -0
  23. {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/reuse.yml +0 -0
  24. {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/ruff.yml +0 -0
  25. {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/scorecards.yml +0 -0
  26. {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/wheels.yml +0 -0
  27. {werpy-3.2.0 → werpy-3.3.0}/.gitignore +0 -0
  28. {werpy-3.2.0 → werpy-3.3.0}/.pylintrc +0 -0
  29. {werpy-3.2.0 → werpy-3.3.0}/.readthedocs.yaml +0 -0
  30. {werpy-3.2.0 → werpy-3.3.0}/CITATION.cff +0 -0
  31. {werpy-3.2.0 → werpy-3.3.0}/CODE_OF_CONDUCT.md +0 -0
  32. {werpy-3.2.0 → werpy-3.3.0}/CONTRIBUTING.md +0 -0
  33. {werpy-3.2.0 → werpy-3.3.0}/LICENSE +0 -0
  34. {werpy-3.2.0 → werpy-3.3.0}/LICENSES/BSD-3-Clause.txt +0 -0
  35. {werpy-3.2.0 → werpy-3.3.0}/REUSE.toml +0 -0
  36. {werpy-3.2.0 → werpy-3.3.0}/SECURITY.md +0 -0
  37. {werpy-3.2.0 → werpy-3.3.0}/benchmarks/speed_comparison_librispeech_full.py +0 -0
  38. {werpy-3.2.0 → werpy-3.3.0}/docs/Makefile +0 -0
  39. {werpy-3.2.0 → werpy-3.3.0}/docs/make.bat +0 -0
  40. {werpy-3.2.0 → werpy-3.3.0}/docs/requirements.in +0 -0
  41. {werpy-3.2.0 → werpy-3.3.0}/docs/requirements.txt +0 -0
  42. {werpy-3.2.0 → werpy-3.3.0}/docs/source/index.rst +0 -0
  43. {werpy-3.2.0 → werpy-3.3.0}/docs/source/installation.rst +0 -0
  44. {werpy-3.2.0 → werpy-3.3.0}/docs/source/modules.rst +0 -0
  45. {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/images/werpy-example-summary-results-word-error-rate-breakdown.png +0 -0
  46. {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/images/werpy-example-summaryp-results-word-error-rate-breakdown.png +0 -0
  47. {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/index.rst +0 -0
  48. {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/normalization.rst +0 -0
  49. {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/summarization.rst +0 -0
  50. {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/weighted-word-error-rate.rst +0 -0
  51. {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/word-error-rate.rst +0 -0
  52. {werpy-3.2.0 → werpy-3.3.0}/tests/test_normalize.py +0 -0
  53. {werpy-3.2.0 → werpy-3.3.0}/tests/test_summary.py +0 -0
  54. {werpy-3.2.0 → werpy-3.3.0}/tests/test_summaryp.py +0 -0
  55. {werpy-3.2.0 → werpy-3.3.0}/tests/test_wer.py +0 -0
  56. {werpy-3.2.0 → werpy-3.3.0}/tests/test_werp.py +0 -0
  57. {werpy-3.2.0 → werpy-3.3.0}/tests/test_werps.py +0 -0
  58. {werpy-3.2.0 → werpy-3.3.0}/tests/test_wers.py +0 -0
  59. {werpy-3.2.0 → werpy-3.3.0}/werpy/errorhandler.py +0 -0
  60. {werpy-3.2.0 → werpy-3.3.0}/werpy/normalize.py +0 -0
  61. {werpy-3.2.0 → werpy-3.3.0}/werpy/py.typed +0 -0
  62. {werpy-3.2.0 → werpy-3.3.0}/werpy/summary.py +0 -0
  63. {werpy-3.2.0 → werpy-3.3.0}/werpy/summaryp.py +0 -0
  64. {werpy-3.2.0 → werpy-3.3.0}/werpy/werp.py +0 -0
  65. {werpy-3.2.0 → werpy-3.3.0}/werpy/werps.py +0 -0
@@ -4,6 +4,21 @@
4
4
  This changelog file outlines a chronologically ordered list of the changes made on this project.
5
5
  It is organized by version and release date followed by a list of Enhancements, New Features, Bug Fixes, and/or Breaking Changes.
6
6
 
7
+ ## Version 3.3.0
8
+
9
+ **Released:** December 19, 2025
10
+ **Tag:** v3.3.0
11
+
12
+ ### Enhancements
13
+
14
+ - Implemented ultra-fast WER-only path with space-optimized 2-row dynamic programming algorithm and batch buffer reuse. Added four new functions (`calculations_wer_only()`, `_calculations_wer_only_reuse_ptr()`, `_metrics_batch_wer_only()`, `metrics_wer_only()`) that eliminate backtrace overhead and use O(n) memory instead of O(m×n). This optimization uses pointer swapping instead of value copying and reuses DP buffers across entire batches, providing significant performance gains for `wer()` and `wers()` functions that only need the WER metric without error counts or word lists.
15
+
16
+ - Fixed portability issue in WER-only batch processing by replacing platform-dependent `int*` pointers with guaranteed 32-bit `cnp.int32_t*` pointers. This ensures correct behavior on all platforms where `sizeof(int)` may differ from 4 bytes, while also removing unnecessary type casts for cleaner code that follows NumPy/Cython best practices.
17
+
18
+ - Expanded benchmarking support by adding optional third-party WER libraries (`pywer`, `evaluate`, `universal-edit-distance`, `torchmetrics`) to `pyproject.toml` under the `benchmarks` extra. Updated `benchmark_synthetic_data_local.py` to safely import optional dependencies, ensure all benchmark functions are always defined, and enforce consistent numeric return types. This fixes static analysis warnings, prevents runtime errors when optional packages are missing, and enables more comprehensive and reliable cross-package performance comparisons.
19
+
20
+ - Standardized all Levenshtein dynamic programming buffers and memoryviews to use cnp.int32_t instead of platform-dependent int. This ensures strict dtype alignment with NumPy int32 arrays, removes undefined behavior on platforms where sizeof(int) != 4, and improves type safety without impacting performance.
21
+
7
22
  ## Version 3.2.0
8
23
 
9
24
  **Released:** December 15, 2025
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: werpy
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: A powerful yet lightweight Python package to calculate and analyze the Word Error Rate (WER).
5
5
  Keywords: werpy,wer,word error rate,asr,automatic speech recognition,stt,speech-to-text,levenshtein distance,text similarity,nlp,accuracy metrics
6
6
  Author-Email: Ross Armstrong <ross.armstrong@analyticsinmotion.com>
@@ -68,6 +68,10 @@ Provides-Extra: benchmarks
68
68
  Requires-Dist: datasets>=4.4.1; extra == "benchmarks"
69
69
  Requires-Dist: werx>=0.3.1; extra == "benchmarks"
70
70
  Requires-Dist: jiwer>=4.0.0; extra == "benchmarks"
71
+ Requires-Dist: pywer>=0.1.1; extra == "benchmarks"
72
+ Requires-Dist: evaluate>=0.4.6; extra == "benchmarks"
73
+ Requires-Dist: universal-edit-distance>=0.4.3; extra == "benchmarks"
74
+ Requires-Dist: torchmetrics; extra == "benchmarks"
71
75
  Description-Content-Type: text/markdown
72
76
 
73
77
 
@@ -85,7 +89,7 @@ Description-Content-Type: text/markdown
85
89
  | | |
86
90
  | --- | --- |
87
91
  | Meta | [![Python Version](https://img.shields.io/badge/python-3.10%7C3.11%7C3.12%7C3.13-blue?logo=python&logoColor=ffdd54)](https://www.python.org/downloads/)&nbsp;&nbsp; [![Black Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)&nbsp;&nbsp; [![Documentation Status](https://readthedocs.org/projects/werpy/badge/?version=latest)](https://werpy.readthedocs.io/en/latest/?badge=latest)&nbsp;&nbsp; [![Analytics in Motion](https://raw.githubusercontent.com/analyticsinmotion/.github/main/assets/images/analytics-in-motion-github-badge-rounded.svg)](https://www.analyticsinmotion.com) |
88
- | License | [![werpy License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://github.com/analyticsinmotion/werpy/blob/main/LICENSE)&nbsp;&nbsp; [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fanalyticsinmotion%2Fwerpy.svg?type=small)](https://app.fossa.com/projects/git%2Bgithub.com%2Fanalyticsinmotion/werpy?ref=badge_small)&nbsp;&nbsp; [![REUSE status](https://api.reuse.software/badge/github.com/analyticsinmotion/werpy)](https://api.reuse.software/info/github.com/analyticsinmotion/werpy) |
92
+ | License | [![werpy License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://github.com/analyticsinmotion/werpy/blob/main/LICENSE)&nbsp;&nbsp; [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fanalyticsinmotion%2Fwerpy.svg?type=small)](https://app.fossa.com/projects/git+github.com%2Fanalyticsinmotion%2Fwerpy?ref=badge_small)&nbsp;&nbsp; [![REUSE status](https://api.reuse.software/badge/github.com/analyticsinmotion/werpy)](https://api.reuse.software/info/github.com/analyticsinmotion/werpy) |
89
93
  | Security | [![CodeQL](https://github.com/analyticsinmotion/werpy/actions/workflows/codeql.yml/badge.svg)](https://github.com/analyticsinmotion/werpy/actions/workflows/codeql.yml)&nbsp;&nbsp; [![Codacy Security Scan](https://github.com/analyticsinmotion/werpy/actions/workflows/codacy.yml/badge.svg)](https://github.com/analyticsinmotion/werpy/actions/workflows/codacy.yml)&nbsp;&nbsp; [![Bandit](https://github.com/analyticsinmotion/werpy/actions/workflows/bandit.yml/badge.svg)](https://github.com/analyticsinmotion/werpy/actions/workflows/bandit.yml) |
90
94
  | Testing | [![CodeFactor](https://www.codefactor.io/repository/github/analyticsinmotion/werpy/badge)](https://www.codefactor.io/repository/github/analyticsinmotion/werpy)&nbsp;&nbsp; [![CircleCI](https://dl.circleci.com/status-badge/img/gh/analyticsinmotion/werpy/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/analyticsinmotion/werpy/tree/main)&nbsp;&nbsp; [![codecov](https://codecov.io/gh/analyticsinmotion/werpy/graph/badge.svg?token=GGT823AVM8)](https://codecov.io/gh/analyticsinmotion/werpy) |
91
95
  | Package | [![Pypi](https://img.shields.io/pypi/v/werpy?label=PyPI&color=blue)](https://pypi.org/project/werpy/)&nbsp;&nbsp; [![PyPI Downloads](https://img.shields.io/pypi/dm/werpy?label=PyPI%20downloads)](https://pypi.org/project/werpy/)&nbsp;&nbsp; [![Downloads](https://static.pepy.tech/badge/werpy)](https://pepy.tech/project/werpy)&nbsp;&nbsp; [![PyPI - Trusted Publisher](https://img.shields.io/badge/PyPI-Trusted%20Publisher-blue)](https://pypi.org/project/werpy/) |
@@ -13,7 +13,7 @@
13
13
  | | |
14
14
  | --- | --- |
15
15
  | Meta | [![Python Version](https://img.shields.io/badge/python-3.10%7C3.11%7C3.12%7C3.13-blue?logo=python&logoColor=ffdd54)](https://www.python.org/downloads/)&nbsp;&nbsp; [![Black Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)&nbsp;&nbsp; [![Documentation Status](https://readthedocs.org/projects/werpy/badge/?version=latest)](https://werpy.readthedocs.io/en/latest/?badge=latest)&nbsp;&nbsp; [![Analytics in Motion](https://raw.githubusercontent.com/analyticsinmotion/.github/main/assets/images/analytics-in-motion-github-badge-rounded.svg)](https://www.analyticsinmotion.com) |
16
- | License | [![werpy License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://github.com/analyticsinmotion/werpy/blob/main/LICENSE)&nbsp;&nbsp; [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fanalyticsinmotion%2Fwerpy.svg?type=small)](https://app.fossa.com/projects/git%2Bgithub.com%2Fanalyticsinmotion/werpy?ref=badge_small)&nbsp;&nbsp; [![REUSE status](https://api.reuse.software/badge/github.com/analyticsinmotion/werpy)](https://api.reuse.software/info/github.com/analyticsinmotion/werpy) |
16
+ | License | [![werpy License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://github.com/analyticsinmotion/werpy/blob/main/LICENSE)&nbsp;&nbsp; [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fanalyticsinmotion%2Fwerpy.svg?type=small)](https://app.fossa.com/projects/git+github.com%2Fanalyticsinmotion%2Fwerpy?ref=badge_small)&nbsp;&nbsp; [![REUSE status](https://api.reuse.software/badge/github.com/analyticsinmotion/werpy)](https://api.reuse.software/info/github.com/analyticsinmotion/werpy) |
17
17
  | Security | [![CodeQL](https://github.com/analyticsinmotion/werpy/actions/workflows/codeql.yml/badge.svg)](https://github.com/analyticsinmotion/werpy/actions/workflows/codeql.yml)&nbsp;&nbsp; [![Codacy Security Scan](https://github.com/analyticsinmotion/werpy/actions/workflows/codacy.yml/badge.svg)](https://github.com/analyticsinmotion/werpy/actions/workflows/codacy.yml)&nbsp;&nbsp; [![Bandit](https://github.com/analyticsinmotion/werpy/actions/workflows/bandit.yml/badge.svg)](https://github.com/analyticsinmotion/werpy/actions/workflows/bandit.yml) |
18
18
  | Testing | [![CodeFactor](https://www.codefactor.io/repository/github/analyticsinmotion/werpy/badge)](https://www.codefactor.io/repository/github/analyticsinmotion/werpy)&nbsp;&nbsp; [![CircleCI](https://dl.circleci.com/status-badge/img/gh/analyticsinmotion/werpy/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/analyticsinmotion/werpy/tree/main)&nbsp;&nbsp; [![codecov](https://codecov.io/gh/analyticsinmotion/werpy/graph/badge.svg?token=GGT823AVM8)](https://codecov.io/gh/analyticsinmotion/werpy) |
19
19
  | Package | [![Pypi](https://img.shields.io/pypi/v/werpy?label=PyPI&color=blue)](https://pypi.org/project/werpy/)&nbsp;&nbsp; [![PyPI Downloads](https://img.shields.io/pypi/dm/werpy?label=PyPI%20downloads)](https://pypi.org/project/werpy/)&nbsp;&nbsp; [![Downloads](https://static.pepy.tech/badge/werpy)](https://pepy.tech/project/werpy)&nbsp;&nbsp; [![PyPI - Trusted Publisher](https://img.shields.io/badge/PyPI-Trusted%20Publisher-blue)](https://pypi.org/project/werpy/) |
@@ -19,7 +19,7 @@ from datetime import datetime
19
19
  project = "werpy"
20
20
  copyright = f'{datetime.now().year} <a href="https://www.analyticsinmotion.com">Analytics in Motion</a>'
21
21
  author = "Ross Armstrong"
22
- release = "3.2.0"
22
+ release = "3.3.0"
23
23
 
24
24
  # -- General configuration ---------------------------------------------------
25
25
  # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -1,7 +1,7 @@
1
1
  project(
2
2
  'werpy',
3
3
  'c', 'cython',
4
- version : '3.2.0',
4
+ version : '3.3.0',
5
5
  license: 'BSD-3',
6
6
  meson_version: '>= 1.1.0',
7
7
  default_options : [
@@ -9,7 +9,7 @@ requires = [
9
9
 
10
10
  [project]
11
11
  name = 'werpy'
12
- version = '3.2.0'
12
+ version = '3.3.0'
13
13
  description = 'A powerful yet lightweight Python package to calculate and analyze the Word Error Rate (WER).'
14
14
  readme = 'README.md'
15
15
  requires-python = '>=3.10'
@@ -75,4 +75,8 @@ benchmarks = [
75
75
  "datasets>=4.4.1",
76
76
  "werx>=0.3.1",
77
77
  "jiwer>=4.0.0",
78
- ]
78
+ "pywer>=0.1.1",
79
+ "evaluate>=0.4.6",
80
+ "universal-edit-distance>=0.4.3",
81
+ "torchmetrics",
82
+ ]
@@ -5,7 +5,7 @@
5
5
  The werpy package provides tools for calculating word error rates (WERs) and related metrics on text data.
6
6
  """
7
7
 
8
- __version__ = "3.2.0"
8
+ __version__ = "3.3.0"
9
9
 
10
10
  from .errorhandler import error_handler
11
11
  from .normalize import normalize
@@ -55,13 +55,13 @@ cpdef cnp.ndarray calculations(object reference, object hypothesis):
55
55
  # SAFETY: All cells are explicitly initialized below (row 0, col 0, then DP loop).
56
56
  # Allocate the (m+1) x (n+1) DP matrix without zero-initialization to avoid
57
57
  # redundant memory writes. Boundary conditions are initialized explicitly.
58
- cdef int[:, :] ldm = np.empty((m + 1, n + 1), dtype=np.int32)
58
+ cdef cnp.int32_t[:, :] ldm = np.empty((m + 1, n + 1), dtype=np.int32)
59
59
 
60
60
  # Initialize first column and first row (boundary conditions)
61
61
  for i in range(m + 1):
62
- ldm[i, 0] = <int>i
62
+ ldm[i, 0] = <cnp.int32_t>i
63
63
  for j in range(n + 1):
64
- ldm[0, j] = <int>j
64
+ ldm[0, j] = <cnp.int32_t>j
65
65
 
66
66
  # Fill the Levenshtein distance matrix
67
67
  # Compute edit distances using a branch-free inner loop and manual minimum
@@ -181,13 +181,13 @@ cpdef cnp.ndarray calculations_fast(object reference, object hypothesis):
181
181
  cdef int cost, del_cost, ins_cost, sub_cost, best
182
182
 
183
183
  # Allocate the (m+1) x (n+1) DP matrix without zero-initialization
184
- cdef int[:, :] ldm = np.empty((m + 1, n + 1), dtype=np.int32)
184
+ cdef cnp.int32_t[:, :] ldm = np.empty((m + 1, n + 1), dtype=np.int32)
185
185
 
186
186
  # Initialize first column and first row (boundary conditions)
187
187
  for i in range(m + 1):
188
- ldm[i, 0] = <int>i
188
+ ldm[i, 0] = <cnp.int32_t>i
189
189
  for j in range(n + 1):
190
- ldm[0, j] = <int>j
190
+ ldm[0, j] = <cnp.int32_t>j
191
191
 
192
192
  # Fill the Levenshtein distance matrix
193
193
  for i in range(1, m + 1):
@@ -268,3 +268,177 @@ cpdef object metrics_fast(object reference, object hypothesis):
268
268
  if isinstance(reference, (list, np.ndarray)) and isinstance(hypothesis, (list, np.ndarray)):
269
269
  return _metrics_batch_fast(list(reference), list(hypothesis))
270
270
  return calculations_fast(reference, hypothesis)
271
+
272
+
273
+ @cython.boundscheck(False)
274
+ @cython.wraparound(False)
275
+ cpdef cnp.ndarray calculations_wer_only(object reference, object hypothesis):
276
+ """
277
+ WER-only fast path - 2-row DP (O(n) memory), no backtrace.
278
+ Returns only [wer, ld, m] without error counts or word tracking.
279
+
280
+ This is the fastest path for pure WER calculation, using space-optimized
281
+ Wagner-Fischer algorithm with rolling 2-row buffer instead of full matrix.
282
+
283
+ Returns (3,) float64 array: [wer, ld, m]
284
+ """
285
+ cdef list reference_word = reference.split()
286
+ cdef list hypothesis_word = hypothesis.split()
287
+
288
+ cdef Py_ssize_t m = len(reference_word)
289
+ cdef Py_ssize_t n = len(hypothesis_word)
290
+
291
+ cdef Py_ssize_t i, j
292
+ cdef int cost, del_cost, ins_cost, sub_cost, best, ld
293
+ cdef double wer
294
+
295
+ cdef cnp.ndarray prev_arr = np.empty(n + 1, dtype=np.int32)
296
+ cdef cnp.ndarray curr_arr = np.empty(n + 1, dtype=np.int32)
297
+
298
+ cdef cnp.int32_t[:] prev = prev_arr
299
+ cdef cnp.int32_t[:] curr = curr_arr
300
+
301
+ for j in range(n + 1):
302
+ prev[j] = <cnp.int32_t>j
303
+
304
+ for i in range(1, m + 1):
305
+ curr[0] = <cnp.int32_t>i
306
+ for j in range(1, n + 1):
307
+ cost = 0 if reference_word[i - 1] == hypothesis_word[j - 1] else 1
308
+
309
+ del_cost = prev[j] + 1
310
+ ins_cost = curr[j - 1] + 1
311
+ sub_cost = prev[j - 1] + cost
312
+
313
+ best = del_cost
314
+ if ins_cost < best:
315
+ best = ins_cost
316
+ if sub_cost < best:
317
+ best = sub_cost
318
+
319
+ curr[j] = best
320
+
321
+ prev, curr = curr, prev
322
+
323
+ ld = prev[n]
324
+ wer = (<double>ld) / m if m > 0 else 0.0
325
+
326
+ return np.array([wer, <double>ld, <double>m], dtype=np.float64)
327
+
328
+
329
+ @cython.boundscheck(False)
330
+ @cython.wraparound(False)
331
+ cdef inline void _calculations_wer_only_reuse_ptr(
332
+ object reference,
333
+ object hypothesis,
334
+ cnp.int32_t* prev,
335
+ cnp.int32_t* curr,
336
+ double* out3,
337
+ ) except *:
338
+ """
339
+ Internal WER-only DP using caller-provided buffers and pointer swap (no copying).
340
+ Writes: out3[0]=wer, out3[1]=ld, out3[2]=m
341
+
342
+ This implementation uses true pointer swapping instead of copying values,
343
+ eliminating O(n) copy overhead per outer iteration.
344
+ """
345
+ cdef list reference_word = reference.split()
346
+ cdef list hypothesis_word = hypothesis.split()
347
+
348
+ cdef Py_ssize_t m = len(reference_word)
349
+ cdef Py_ssize_t n = len(hypothesis_word)
350
+
351
+ cdef Py_ssize_t i, j
352
+ cdef int cost, del_cost, ins_cost, sub_cost, best, ld
353
+ cdef cnp.int32_t* tmp
354
+
355
+ # Initialize base row: prev[j] = j for j=0..n
356
+ for j in range(n + 1):
357
+ prev[j] = j
358
+
359
+ for i in range(1, m + 1):
360
+ curr[0] = i
361
+ for j in range(1, n + 1):
362
+ cost = 0 if reference_word[i - 1] == hypothesis_word[j - 1] else 1
363
+
364
+ del_cost = prev[j] + 1
365
+ ins_cost = curr[j - 1] + 1
366
+ sub_cost = prev[j - 1] + cost
367
+
368
+ best = del_cost
369
+ if ins_cost < best:
370
+ best = ins_cost
371
+ if sub_cost < best:
372
+ best = sub_cost
373
+
374
+ curr[j] = best
375
+
376
+ # Swap prev and curr pointers (zero-cost operation)
377
+ tmp = prev
378
+ prev = curr
379
+ curr = tmp
380
+
381
+ ld = prev[n]
382
+ out3[0] = (<double>ld) / m if m > 0 else 0.0
383
+ out3[1] = <double>ld
384
+ out3[2] = <double>m
385
+
386
+
387
+ @cython.boundscheck(False)
388
+ @cython.wraparound(False)
389
+ cdef cnp.ndarray _metrics_batch_wer_only(list references, list hypotheses):
390
+ """
391
+ Fast batch processing for WER-only calculations with buffer reuse and pointer swapping.
392
+
393
+ Eliminates repeated buffer allocations by reusing prev/curr arrays across all pairs
394
+ in the batch, sized to the maximum hypothesis length. Uses true pointer swapping
395
+ instead of value copying for optimal performance.
396
+
397
+ Returns (n, 3) float64 array where each row contains:
398
+ [wer, ld, m]
399
+ """
400
+ cdef Py_ssize_t n_pairs = len(references)
401
+ cdef Py_ssize_t idx
402
+
403
+ cdef cnp.ndarray out = np.empty((n_pairs, 3), dtype=np.float64)
404
+
405
+ # Find max hypothesis token length to size buffers once
406
+ cdef Py_ssize_t max_n = 0
407
+ cdef Py_ssize_t this_n
408
+ cdef object h
409
+ cdef list h_words
410
+ for idx in range(n_pairs):
411
+ h = hypotheses[idx]
412
+ h_words = h.split()
413
+ this_n = len(h_words)
414
+ if this_n > max_n:
415
+ max_n = this_n
416
+
417
+ # Allocate reusable DP buffers once for the entire batch
418
+ cdef cnp.ndarray prev_arr = np.empty(max_n + 1, dtype=np.int32)
419
+ cdef cnp.ndarray curr_arr = np.empty(max_n + 1, dtype=np.int32)
420
+
421
+ # Get raw pointers for zero-cost swapping
422
+ cdef cnp.int32_t* prev = <cnp.int32_t*>cnp.PyArray_DATA(prev_arr)
423
+ cdef cnp.int32_t* curr = <cnp.int32_t*>cnp.PyArray_DATA(curr_arr)
424
+
425
+ # Process each pair using shared buffers, writing directly to output rows
426
+ cdef double* out_row
427
+ for idx in range(n_pairs):
428
+ out_row = <double*>cnp.PyArray_DATA(out) + (idx * 3)
429
+ _calculations_wer_only_reuse_ptr(references[idx], hypotheses[idx], prev, curr, out_row)
430
+
431
+ return out
432
+
433
+
434
+ cpdef object metrics_wer_only(object reference, object hypothesis):
435
+ """
436
+ WER-only metrics entry point (fastest path).
437
+
438
+ Returns:
439
+ - strings: (3,) float64 array [wer, ld, m]
440
+ - sequences: (n, 3) float64 array, one row per pair
441
+ """
442
+ if isinstance(reference, (list, np.ndarray)) and isinstance(hypothesis, (list, np.ndarray)):
443
+ return _metrics_batch_wer_only(list(reference), list(hypothesis))
444
+ return calculations_wer_only(reference, hypothesis)
@@ -12,7 +12,7 @@ This module defines the following function:
12
12
 
13
13
  import numpy as np
14
14
  from .errorhandler import error_handler
15
- from .metrics import metrics_fast
15
+ from .metrics import metrics_wer_only
16
16
 
17
17
 
18
18
  def wer(reference, hypothesis) -> float | np.float64 | None:
@@ -57,17 +57,17 @@ def wer(reference, hypothesis) -> float | np.float64 | None:
57
57
  """
58
58
  try:
59
59
  error_handler(reference, hypothesis)
60
- result = metrics_fast(reference, hypothesis)
60
+ result = metrics_wer_only(reference, hypothesis)
61
61
  except (ValueError, AttributeError, ZeroDivisionError) as err:
62
62
  print(f"{type(err).__name__}: {str(err)}")
63
63
  return None
64
64
 
65
- # Batch: (n, 6) float64
65
+ # Batch: (n, 3) float64, columns [wer, ld, m]
66
66
  if isinstance(result, np.ndarray) and result.ndim == 2:
67
- den = np.sum(result[:, 2])
68
- return float(np.sum(result[:, 1]) / den) if den else 0.0
67
+ den = np.sum(result[:, 2]) # m column
68
+ return float(np.sum(result[:, 1]) / den) if den else 0.0 # ld column
69
69
 
70
- # Single: (6,) float64, WER is at index 0
70
+ # Single: (3,) float64, WER is at index 0
71
71
  if isinstance(result, np.ndarray) and getattr(result, "ndim", 0) == 0:
72
72
  result = result.item()
73
73
 
@@ -11,7 +11,7 @@ This module defines the following function:
11
11
 
12
12
  import numpy as np
13
13
  from .errorhandler import error_handler
14
- from .metrics import metrics_fast
14
+ from .metrics import metrics_wer_only
15
15
 
16
16
 
17
17
  def wers(reference, hypothesis):
@@ -50,16 +50,16 @@ def wers(reference, hypothesis):
50
50
  """
51
51
  try:
52
52
  error_handler(reference, hypothesis)
53
- result = metrics_fast(reference, hypothesis)
53
+ result = metrics_wer_only(reference, hypothesis)
54
54
  except (ValueError, AttributeError, ZeroDivisionError) as err:
55
55
  print(f"{type(err).__name__}: {str(err)}")
56
56
  return None
57
57
 
58
- # Batch: (n, 6) float64
58
+ # Batch: (n, 3) float64, columns [wer, ld, m]
59
59
  if isinstance(result, np.ndarray) and result.ndim == 2:
60
- return result[:, 0].tolist()
60
+ return result[:, 0].tolist() # Return wer column
61
61
 
62
- # Single: (6,) float64, WER is at index 0
62
+ # Single: (3,) float64, WER is at index 0
63
63
  if isinstance(result, np.ndarray) and getattr(result, "ndim", 0) == 0:
64
64
  result = result.item()
65
65
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes