fuzzgpu 0.4.0__tar.gz → 0.4.1__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 (70) hide show
  1. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/Cargo.lock +44 -44
  2. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/PKG-INFO +30 -13
  3. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/README.md +29 -12
  4. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/Cargo.toml +1 -1
  5. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/gpu.rs +28 -13
  6. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/lib.rs +8 -0
  7. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/shaders/needleman_wavefront.wgsl +9 -9
  8. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/simd.rs +9 -3
  9. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/tests/differential.rs +3 -0
  10. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/tests/kernel_registration.rs +2 -0
  11. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-python/Cargo.toml +2 -2
  12. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-python/src/lib.rs +30 -5
  13. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-wasm/Cargo.toml +1 -1
  14. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/pyproject.toml +1 -1
  15. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/__init__.pyi +35 -8
  16. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/Levenshtein.py +20 -8
  17. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/fuzz.pyi +14 -0
  18. fuzzgpu-0.4.1/python/fuzzgpu/py.typed +1 -0
  19. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/tests/test_out_buffers.py +8 -0
  20. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/tests/test_rapidfuzz_differential.py +13 -0
  21. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/Cargo.toml +0 -0
  22. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/LICENSE +0 -0
  23. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/assets/logo.png +0 -0
  24. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/benches/bench.rs +0 -0
  25. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/damerau.rs +0 -0
  26. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/fuzz.rs +0 -0
  27. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/jaro.rs +0 -0
  28. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/levenshtein.rs +0 -0
  29. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/needleman.rs +0 -0
  30. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/shaders/damerau.wgsl +0 -0
  31. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/shaders/damerau_matrix.wgsl +0 -0
  32. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/shaders/jaro.wgsl +0 -0
  33. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/shaders/jaro_matrix.wgsl +0 -0
  34. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/shaders/levenshtein.wgsl +0 -0
  35. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/shaders/levenshtein_cdist_myers.wgsl +0 -0
  36. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/shaders/levenshtein_matrix.wgsl +0 -0
  37. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/shaders/levenshtein_myers.wgsl +0 -0
  38. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/shaders/levenshtein_short.wgsl +0 -0
  39. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/src/shaders/needleman_affine.wgsl +0 -0
  40. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/tests/differential.proptest-regressions +0 -0
  41. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-core/tests/fixtures/broken.wgsl +0 -0
  42. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-wasm/src/lib.rs +0 -0
  43. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-wasm/tests/differential_harness.js +0 -0
  44. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/crates/fuzzgpu-wasm/tests/js_api.test.cjs +0 -0
  45. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/__init__.py +0 -0
  46. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/DamerauLevenshtein.py +0 -0
  47. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/Hamming.py +0 -0
  48. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/Indel.py +0 -0
  49. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/Jaro.py +0 -0
  50. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/JaroWinkler.py +0 -0
  51. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/LCSseq.py +0 -0
  52. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/OSA.py +0 -0
  53. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/Postfix.py +0 -0
  54. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/Prefix.py +0 -0
  55. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/__init__.py +0 -0
  56. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/__init__.pyi +0 -0
  57. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/distance/_common.py +0 -0
  58. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/fuzz.py +0 -0
  59. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/process.py +0 -0
  60. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/python/fuzzgpu/process.pyi +0 -0
  61. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/tests/test_api_signatures.py +0 -0
  62. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/tests/test_basic.py +0 -0
  63. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/tests/test_concurrency.py +0 -0
  64. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/tests/test_edge_cases.py +0 -0
  65. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/tests/test_gpu_health.py +0 -0
  66. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/tests/test_gpu_threshold_env.py +0 -0
  67. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/tests/test_invariants.py +0 -0
  68. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/tests/test_rapidfuzz_compat.py +0 -0
  69. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/tests/test_stress.py +0 -0
  70. {fuzzgpu-0.4.0 → fuzzgpu-0.4.1}/tests/wasm_python_differential.py +0 -0
@@ -91,9 +91,9 @@ checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
91
91
 
92
92
  [[package]]
93
93
  name = "bitflags"
94
- version = "2.13.1"
94
+ version = "2.13.2"
95
95
  source = "registry+https://github.com/rust-lang/crates.io-index"
96
- checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
96
+ checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
97
97
 
98
98
  [[package]]
99
99
  name = "block2"
@@ -121,13 +121,13 @@ dependencies = [
121
121
 
122
122
  [[package]]
123
123
  name = "bytemuck_derive"
124
- version = "1.12.0"
124
+ version = "1.12.1"
125
125
  source = "registry+https://github.com/rust-lang/crates.io-index"
126
- checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f"
126
+ checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c"
127
127
  dependencies = [
128
128
  "proc-macro2",
129
129
  "quote",
130
- "syn 3.0.3",
130
+ "syn 3.0.5",
131
131
  ]
132
132
 
133
133
  [[package]]
@@ -249,9 +249,9 @@ dependencies = [
249
249
 
250
250
  [[package]]
251
251
  name = "crossbeam-deque"
252
- version = "0.8.7"
252
+ version = "0.8.8"
253
253
  source = "registry+https://github.com/rust-lang/crates.io-index"
254
- checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
254
+ checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
255
255
  dependencies = [
256
256
  "crossbeam-epoch",
257
257
  "crossbeam-utils",
@@ -259,18 +259,18 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "crossbeam-epoch"
262
- version = "0.9.20"
262
+ version = "0.9.21"
263
263
  source = "registry+https://github.com/rust-lang/crates.io-index"
264
- checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
264
+ checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
265
265
  dependencies = [
266
266
  "crossbeam-utils",
267
267
  ]
268
268
 
269
269
  [[package]]
270
270
  name = "crossbeam-utils"
271
- version = "0.8.22"
271
+ version = "0.8.23"
272
272
  source = "registry+https://github.com/rust-lang/crates.io-index"
273
- checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
273
+ checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
274
274
 
275
275
  [[package]]
276
276
  name = "crunchy"
@@ -372,7 +372,7 @@ dependencies = [
372
372
 
373
373
  [[package]]
374
374
  name = "fuzzgpu-core"
375
- version = "0.4.0"
375
+ version = "0.4.1"
376
376
  dependencies = [
377
377
  "bytemuck",
378
378
  "criterion",
@@ -386,7 +386,7 @@ dependencies = [
386
386
 
387
387
  [[package]]
388
388
  name = "fuzzgpu-python"
389
- version = "0.4.0"
389
+ version = "0.4.1"
390
390
  dependencies = [
391
391
  "fuzzgpu-core",
392
392
  "log",
@@ -504,15 +504,15 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
504
504
 
505
505
  [[package]]
506
506
  name = "hermit-abi"
507
- version = "0.5.2"
507
+ version = "0.5.3"
508
508
  source = "registry+https://github.com/rust-lang/crates.io-index"
509
- checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
509
+ checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
510
510
 
511
511
  [[package]]
512
512
  name = "indexmap"
513
- version = "2.14.0"
513
+ version = "2.14.2"
514
514
  source = "registry+https://github.com/rust-lang/crates.io-index"
515
- checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
515
+ checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
516
516
  dependencies = [
517
517
  "equivalent",
518
518
  "hashbrown 0.17.1",
@@ -574,9 +574,9 @@ dependencies = [
574
574
 
575
575
  [[package]]
576
576
  name = "js-sys"
577
- version = "0.3.104"
577
+ version = "0.3.105"
578
578
  source = "registry+https://github.com/rust-lang/crates.io-index"
579
- checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
579
+ checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
580
580
  dependencies = [
581
581
  "cfg-if",
582
582
  "futures-util",
@@ -955,9 +955,9 @@ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
955
955
 
956
956
  [[package]]
957
957
  name = "portable-atomic-util"
958
- version = "0.2.7"
958
+ version = "0.2.8"
959
959
  source = "registry+https://github.com/rust-lang/crates.io-index"
960
- checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
960
+ checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715"
961
961
  dependencies = [
962
962
  "portable-atomic",
963
963
  ]
@@ -1312,7 +1312,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
1312
1312
  dependencies = [
1313
1313
  "proc-macro2",
1314
1314
  "quote",
1315
- "syn 3.0.3",
1315
+ "syn 3.0.5",
1316
1316
  ]
1317
1317
 
1318
1318
  [[package]]
@@ -1345,9 +1345,9 @@ dependencies = [
1345
1345
 
1346
1346
  [[package]]
1347
1347
  name = "smallvec"
1348
- version = "1.15.2"
1348
+ version = "1.16.1"
1349
1349
  source = "registry+https://github.com/rust-lang/crates.io-index"
1350
- checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
1350
+ checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891"
1351
1351
 
1352
1352
  [[package]]
1353
1353
  name = "spirv"
@@ -1377,9 +1377,9 @@ dependencies = [
1377
1377
 
1378
1378
  [[package]]
1379
1379
  name = "syn"
1380
- version = "3.0.3"
1380
+ version = "3.0.5"
1381
1381
  source = "registry+https://github.com/rust-lang/crates.io-index"
1382
- checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
1382
+ checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
1383
1383
  dependencies = [
1384
1384
  "proc-macro2",
1385
1385
  "quote",
@@ -1431,7 +1431,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
1431
1431
  dependencies = [
1432
1432
  "proc-macro2",
1433
1433
  "quote",
1434
- "syn 3.0.3",
1434
+ "syn 3.0.5",
1435
1435
  ]
1436
1436
 
1437
1437
  [[package]]
@@ -1498,9 +1498,9 @@ dependencies = [
1498
1498
 
1499
1499
  [[package]]
1500
1500
  name = "wasm-bindgen"
1501
- version = "0.2.127"
1501
+ version = "0.2.128"
1502
1502
  source = "registry+https://github.com/rust-lang/crates.io-index"
1503
- checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
1503
+ checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
1504
1504
  dependencies = [
1505
1505
  "cfg-if",
1506
1506
  "once_cell",
@@ -1511,9 +1511,9 @@ dependencies = [
1511
1511
 
1512
1512
  [[package]]
1513
1513
  name = "wasm-bindgen-futures"
1514
- version = "0.4.77"
1514
+ version = "0.4.78"
1515
1515
  source = "registry+https://github.com/rust-lang/crates.io-index"
1516
- checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950"
1516
+ checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928"
1517
1517
  dependencies = [
1518
1518
  "js-sys",
1519
1519
  "wasm-bindgen",
@@ -1521,9 +1521,9 @@ dependencies = [
1521
1521
 
1522
1522
  [[package]]
1523
1523
  name = "wasm-bindgen-macro"
1524
- version = "0.2.127"
1524
+ version = "0.2.128"
1525
1525
  source = "registry+https://github.com/rust-lang/crates.io-index"
1526
- checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
1526
+ checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
1527
1527
  dependencies = [
1528
1528
  "quote",
1529
1529
  "wasm-bindgen-macro-support",
@@ -1531,22 +1531,22 @@ dependencies = [
1531
1531
 
1532
1532
  [[package]]
1533
1533
  name = "wasm-bindgen-macro-support"
1534
- version = "0.2.127"
1534
+ version = "0.2.128"
1535
1535
  source = "registry+https://github.com/rust-lang/crates.io-index"
1536
- checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
1536
+ checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
1537
1537
  dependencies = [
1538
1538
  "bumpalo",
1539
1539
  "proc-macro2",
1540
1540
  "quote",
1541
- "syn 2.0.119",
1541
+ "syn 3.0.5",
1542
1542
  "wasm-bindgen-shared",
1543
1543
  ]
1544
1544
 
1545
1545
  [[package]]
1546
1546
  name = "wasm-bindgen-shared"
1547
- version = "0.2.127"
1547
+ version = "0.2.128"
1548
1548
  source = "registry+https://github.com/rust-lang/crates.io-index"
1549
- checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
1549
+ checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
1550
1550
  dependencies = [
1551
1551
  "unicode-ident",
1552
1552
  ]
@@ -1565,9 +1565,9 @@ dependencies = [
1565
1565
 
1566
1566
  [[package]]
1567
1567
  name = "web-sys"
1568
- version = "0.3.104"
1568
+ version = "0.3.105"
1569
1569
  source = "registry+https://github.com/rust-lang/crates.io-index"
1570
- checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30"
1570
+ checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8"
1571
1571
  dependencies = [
1572
1572
  "js-sys",
1573
1573
  "wasm-bindgen",
@@ -1888,18 +1888,18 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
1888
1888
 
1889
1889
  [[package]]
1890
1890
  name = "zerocopy"
1891
- version = "0.8.56"
1891
+ version = "0.8.57"
1892
1892
  source = "registry+https://github.com/rust-lang/crates.io-index"
1893
- checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
1893
+ checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
1894
1894
  dependencies = [
1895
1895
  "zerocopy-derive",
1896
1896
  ]
1897
1897
 
1898
1898
  [[package]]
1899
1899
  name = "zerocopy-derive"
1900
- version = "0.8.56"
1900
+ version = "0.8.57"
1901
1901
  source = "registry+https://github.com/rust-lang/crates.io-index"
1902
- checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
1902
+ checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
1903
1903
  dependencies = [
1904
1904
  "proc-macro2",
1905
1905
  "quote",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fuzzgpu
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -38,7 +38,7 @@ Project-URL: Repository, https://github.com/kuntal-devrat/fuzzgpu
38
38
  *Cross-platform GPU compute via WebGPU (`wgpu`) & Multi-Core CPU parallelism with Rayon. Zero CUDA dependencies.*
39
39
 
40
40
  [![PyPI
41
- Version](https://img.shields.io/badge/pypi-v0.4.0-blue.svg?style=flat-square)](https://pypi.org/project/fuzzgpu/)
41
+ Version](https://img.shields.io/badge/pypi-v0.4.1-blue.svg?style=flat-square)](https://pypi.org/project/fuzzgpu/)
42
42
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](https://opensource.org/licenses/MIT)
43
43
  [![Rust](https://img.shields.io/badge/rust-1.87+-orange.svg?style=flat-square)](https://www.rust-lang.org)
44
44
  [![Cross Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux%20%7C%20WASM-lightgrey.svg?style=flat-square)](https://github.com/kuntal-devrat/fuzzgpu)
@@ -61,7 +61,21 @@ No NVIDIA CUDA drivers or complex toolkits required.
61
61
 
62
62
  ---
63
63
 
64
- ## What's New in v0.4.0
64
+ ## What's New in v0.4.1
65
+
66
+ ### Production hardening & Performance
67
+ - **Fail-fast contiguous buffer validation** — `*_into` zero-allocation APIs now validate numpy C-contiguity before any GPU/CPU dispatch, preventing wasted compute on strided arrays.
68
+ - **RapidFuzz weighted Levenshtein parity** — `similarity`, `normalized_distance`, and `normalized_similarity` now support `weights=(1, 1, 1)` with exact weighted maximum distance calculation.
69
+ - **Wavefront WGSL register optimization** — replaced ~1.5 KB private array stack copies with direct workgroup indexing in Gotoh affine gap DP.
70
+ - **Zero-heap Myers SIMD fallback** — eliminated heap allocations on non-ASCII input paths with `std::str::from_utf8` / `Cow`.
71
+ - **PEP 561 type safety** — shipped `py.typed` and comprehensive `.pyi` type stubs for all 54 public APIs and submodules.
72
+ - **Community governance & Health files** — added CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, and GitHub issue/PR templates.
73
+ - **Cross-platform release CI** — made CPU wheel publishing portable across Linux, Windows, and macOS runners.
74
+
75
+ ---
76
+
77
+ <details>
78
+ <summary><b>Previous (v0.4.0)</b></summary>
65
79
 
66
80
  ### Production hardening
67
81
  - **WRatio score_cutoff parity** — WRatio now threads a rescaled running cutoff through its token/partial-token steps exactly as rapidfuzz does; high cutoffs no longer leak scaled token ratios (verified against rapidfuzz 3.14.5).
@@ -72,10 +86,7 @@ No NVIDIA CUDA drivers or complex toolkits required.
72
86
  - **Flaky stress asserts gated** — wall-clock budgets in `test_stress.py` are disabled via `FUZZGPU_SKIP_PERF_ASSERTS=1` (correctness checks always run).
73
87
  - **Accurate GPU timeout message** — readback timeouts now report the actual configured timeout.
74
88
 
75
- ### Includes all v0.3.0 fixes
76
- All fixes from v0.3.0 are included.
77
-
78
- ---
89
+ </details>
79
90
 
80
91
  <details>
81
92
  <summary><b>Previous (v0.3.0)</b></summary>
@@ -245,10 +256,16 @@ The full Python layer is now byte-identical to rapidfuzz 3.14.5 over a 169,744-p
245
256
  pip install fuzzgpu
246
257
  ```
247
258
 
259
+ For a smaller, dependency-light install with no GPU/WebGPU support (CPU + Rayon only):
260
+
261
+ ```bash
262
+ pip install fuzzgpu-cpu
263
+ ```
264
+
248
265
  ```toml
249
266
  # Rust
250
267
  [dependencies]
251
- fuzzgpu-core = "0.4.0"
268
+ fuzzgpu-core = "0.4.1"
252
269
  ```
253
270
 
254
271
  ---
@@ -268,7 +285,7 @@ candidates = ["hallo", "hullo", "jello", "yellow", "hello world"] * 10_000
268
285
  distances = fuzzgpu.levenshtein_batch("hello", candidates)
269
286
  jw_scores = fuzzgpu.jaro_winkler_batch("hello", candidates, p=0.1)
270
287
  nw_scores = fuzzgpu.needleman_wunsch_affine_batch(
271
- "AGTACGCA", candidates, match=2, mismatch=-1, gap_open=-3, gap_extend=-1
288
+ "AGTACGCA", candidates, match_score=2, mismatch_score=-1, gap_open=-3, gap_extend=-1
272
289
  )
273
290
 
274
291
  # ── Cross-product distance matrix ─────────────────────────────────────────────
@@ -344,12 +361,12 @@ fuzzgpu.set_cpu_only(True) # force CPU-only mode
344
361
 
345
362
  ```toml
346
363
  [dependencies]
347
- fuzzgpu-core = "0.4.0" # GPU + CPU fallback
348
- # fuzzgpu-core = { version = "0.4.0", default-features = false } # CPU-only
364
+ fuzzgpu-core = "0.4.1" # GPU + CPU fallback
365
+ # fuzzgpu-core = { version = "0.4.1", default-features = false } # CPU-only
349
366
  ```
350
367
 
351
368
  ```rust
352
- use fuzzgpu_core::levenshtein::gpu_ext::GpuLevenshteinKernel;
369
+ use fuzzgpu_core::GpuLevenshteinKernel;
353
370
 
354
371
  fn main() -> fuzzgpu_core::Result<()> {
355
372
  let kernel = GpuLevenshteinKernel::get()?;
@@ -490,7 +507,7 @@ cargo test --workspace
490
507
  | `FUZZGPU_DEBUG` | Log GPU→CPU fallback decisions |
491
508
  | `FUZZGPU_SIMD` | Force ISA: `portable\|neon\|avx2\|avx512` |
492
509
  | `FUZZGPU_READBACK_TIMEOUT_MS` | GPU readback timeout (default 10000 ms) |
493
- | `FUZZGPU_SKIP_DISPATCH_LOCK` | Opt-in GPU dispatch serialization (safety valve for the rare gfx-rs/wgpu#10085 crash class on Intel D3D12; dispatch is fully concurrent by default) |
510
+ | `FUZZGPU_SERIALIZE_DISPATCH` | Opt-in GPU dispatch serialization (safety valve for rare driver heap corruption on Intel D3D12; dispatch is fully concurrent by default). Legacy alias: `FUZZGPU_SKIP_DISPATCH_LOCK` |
494
511
  | `FUZZGPU_REQUIRE_GPU` | In tests: fail instead of skip when no GPU |
495
512
  | `WGPU_BACKEND` | Force wgpu backend: `vulkan\|metal\|dx12` |
496
513
  | `PROPTEST_CASES` | Override proptest case count |
@@ -9,7 +9,7 @@
9
9
  *Cross-platform GPU compute via WebGPU (`wgpu`) & Multi-Core CPU parallelism with Rayon. Zero CUDA dependencies.*
10
10
 
11
11
  [![PyPI
12
- Version](https://img.shields.io/badge/pypi-v0.4.0-blue.svg?style=flat-square)](https://pypi.org/project/fuzzgpu/)
12
+ Version](https://img.shields.io/badge/pypi-v0.4.1-blue.svg?style=flat-square)](https://pypi.org/project/fuzzgpu/)
13
13
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](https://opensource.org/licenses/MIT)
14
14
  [![Rust](https://img.shields.io/badge/rust-1.87+-orange.svg?style=flat-square)](https://www.rust-lang.org)
15
15
  [![Cross Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux%20%7C%20WASM-lightgrey.svg?style=flat-square)](https://github.com/kuntal-devrat/fuzzgpu)
@@ -32,7 +32,21 @@ No NVIDIA CUDA drivers or complex toolkits required.
32
32
 
33
33
  ---
34
34
 
35
- ## What's New in v0.4.0
35
+ ## What's New in v0.4.1
36
+
37
+ ### Production hardening & Performance
38
+ - **Fail-fast contiguous buffer validation** — `*_into` zero-allocation APIs now validate numpy C-contiguity before any GPU/CPU dispatch, preventing wasted compute on strided arrays.
39
+ - **RapidFuzz weighted Levenshtein parity** — `similarity`, `normalized_distance`, and `normalized_similarity` now support `weights=(1, 1, 1)` with exact weighted maximum distance calculation.
40
+ - **Wavefront WGSL register optimization** — replaced ~1.5 KB private array stack copies with direct workgroup indexing in Gotoh affine gap DP.
41
+ - **Zero-heap Myers SIMD fallback** — eliminated heap allocations on non-ASCII input paths with `std::str::from_utf8` / `Cow`.
42
+ - **PEP 561 type safety** — shipped `py.typed` and comprehensive `.pyi` type stubs for all 54 public APIs and submodules.
43
+ - **Community governance & Health files** — added CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, and GitHub issue/PR templates.
44
+ - **Cross-platform release CI** — made CPU wheel publishing portable across Linux, Windows, and macOS runners.
45
+
46
+ ---
47
+
48
+ <details>
49
+ <summary><b>Previous (v0.4.0)</b></summary>
36
50
 
37
51
  ### Production hardening
38
52
  - **WRatio score_cutoff parity** — WRatio now threads a rescaled running cutoff through its token/partial-token steps exactly as rapidfuzz does; high cutoffs no longer leak scaled token ratios (verified against rapidfuzz 3.14.5).
@@ -43,10 +57,7 @@ No NVIDIA CUDA drivers or complex toolkits required.
43
57
  - **Flaky stress asserts gated** — wall-clock budgets in `test_stress.py` are disabled via `FUZZGPU_SKIP_PERF_ASSERTS=1` (correctness checks always run).
44
58
  - **Accurate GPU timeout message** — readback timeouts now report the actual configured timeout.
45
59
 
46
- ### Includes all v0.3.0 fixes
47
- All fixes from v0.3.0 are included.
48
-
49
- ---
60
+ </details>
50
61
 
51
62
  <details>
52
63
  <summary><b>Previous (v0.3.0)</b></summary>
@@ -216,10 +227,16 @@ The full Python layer is now byte-identical to rapidfuzz 3.14.5 over a 169,744-p
216
227
  pip install fuzzgpu
217
228
  ```
218
229
 
230
+ For a smaller, dependency-light install with no GPU/WebGPU support (CPU + Rayon only):
231
+
232
+ ```bash
233
+ pip install fuzzgpu-cpu
234
+ ```
235
+
219
236
  ```toml
220
237
  # Rust
221
238
  [dependencies]
222
- fuzzgpu-core = "0.4.0"
239
+ fuzzgpu-core = "0.4.1"
223
240
  ```
224
241
 
225
242
  ---
@@ -239,7 +256,7 @@ candidates = ["hallo", "hullo", "jello", "yellow", "hello world"] * 10_000
239
256
  distances = fuzzgpu.levenshtein_batch("hello", candidates)
240
257
  jw_scores = fuzzgpu.jaro_winkler_batch("hello", candidates, p=0.1)
241
258
  nw_scores = fuzzgpu.needleman_wunsch_affine_batch(
242
- "AGTACGCA", candidates, match=2, mismatch=-1, gap_open=-3, gap_extend=-1
259
+ "AGTACGCA", candidates, match_score=2, mismatch_score=-1, gap_open=-3, gap_extend=-1
243
260
  )
244
261
 
245
262
  # ── Cross-product distance matrix ─────────────────────────────────────────────
@@ -315,12 +332,12 @@ fuzzgpu.set_cpu_only(True) # force CPU-only mode
315
332
 
316
333
  ```toml
317
334
  [dependencies]
318
- fuzzgpu-core = "0.4.0" # GPU + CPU fallback
319
- # fuzzgpu-core = { version = "0.4.0", default-features = false } # CPU-only
335
+ fuzzgpu-core = "0.4.1" # GPU + CPU fallback
336
+ # fuzzgpu-core = { version = "0.4.1", default-features = false } # CPU-only
320
337
  ```
321
338
 
322
339
  ```rust
323
- use fuzzgpu_core::levenshtein::gpu_ext::GpuLevenshteinKernel;
340
+ use fuzzgpu_core::GpuLevenshteinKernel;
324
341
 
325
342
  fn main() -> fuzzgpu_core::Result<()> {
326
343
  let kernel = GpuLevenshteinKernel::get()?;
@@ -461,7 +478,7 @@ cargo test --workspace
461
478
  | `FUZZGPU_DEBUG` | Log GPU→CPU fallback decisions |
462
479
  | `FUZZGPU_SIMD` | Force ISA: `portable\|neon\|avx2\|avx512` |
463
480
  | `FUZZGPU_READBACK_TIMEOUT_MS` | GPU readback timeout (default 10000 ms) |
464
- | `FUZZGPU_SKIP_DISPATCH_LOCK` | Opt-in GPU dispatch serialization (safety valve for the rare gfx-rs/wgpu#10085 crash class on Intel D3D12; dispatch is fully concurrent by default) |
481
+ | `FUZZGPU_SERIALIZE_DISPATCH` | Opt-in GPU dispatch serialization (safety valve for rare driver heap corruption on Intel D3D12; dispatch is fully concurrent by default). Legacy alias: `FUZZGPU_SKIP_DISPATCH_LOCK` |
465
482
  | `FUZZGPU_REQUIRE_GPU` | In tests: fail instead of skip when no GPU |
466
483
  | `WGPU_BACKEND` | Force wgpu backend: `vulkan\|metal\|dx12` |
467
484
  | `PROPTEST_CASES` | Override proptest case count |
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fuzzgpu-core"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  edition = "2021"
5
5
  description = "GPU-accelerated fuzzy string matching engine"
6
6
  license = "MIT"
@@ -25,21 +25,24 @@ pub fn require_gpu() -> bool {
25
25
  .unwrap_or(false)
26
26
  }
27
27
 
28
- /// Opt-in dispatch serialization (safety valve). `FUZZGPU_SKIP_DISPATCH_LOCK=1`
29
- /// re-enables the GPU-dispatch serialization that used to be the default
30
- /// workaround for the rare gfx-rs/wgpu#10085 crash class (heap corruption on
31
- /// Intel D3D12 under >=3 concurrent dispatchers on a shared device). Dispatch
32
- /// is fully concurrent by default; set this env var on affected Intel hardware
33
- /// to serialize both the production dispatch path and the test suite.
28
+ /// Opt-in dispatch serialization (safety valve). `FUZZGPU_SERIALIZE_DISPATCH=1`
29
+ /// (or legacy alias `FUZZGPU_SKIP_DISPATCH_LOCK=1`) re-enables the GPU-dispatch
30
+ /// serialization that used to be the default workaround for the rare gfx-rs/wgpu#10085
31
+ /// crash class (heap corruption on Intel D3D12 under >=3 concurrent dispatchers on a
32
+ /// shared device). Dispatch is fully concurrent by default; set this env var on affected
33
+ /// Intel hardware to serialize both the production dispatch path and the test suite.
34
34
  pub(crate) fn dispatch_serialize() -> bool {
35
- std::env::var("FUZZGPU_SKIP_DISPATCH_LOCK")
36
- .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
37
- .unwrap_or(false)
35
+ let check = |var: &str| {
36
+ std::env::var(var)
37
+ .map(|v| v == "1" || v.eq_ignore_ascii_case("true") || v.eq_ignore_ascii_case("yes"))
38
+ .unwrap_or(false)
39
+ };
40
+ check("FUZZGPU_SERIALIZE_DISPATCH") || check("FUZZGPU_SKIP_DISPATCH_LOCK")
38
41
  }
39
42
 
40
43
  /// Serialize GPU access across tests. Only active when
41
- /// `FUZZGPU_SKIP_DISPATCH_LOCK=1` (opt-in safety valve); by default returns
42
- /// `None` so the test suite runs with fully concurrent GPU dispatch.
44
+ /// `FUZZGPU_SERIALIZE_DISPATCH=1` or `FUZZGPU_SKIP_DISPATCH_LOCK=1` (opt-in safety valve);
45
+ /// by default returns `None` so the test suite runs with fully concurrent GPU dispatch.
43
46
  pub fn gpu_test_lock() -> Option<std::sync::MutexGuard<'static, ()>> {
44
47
  if !dispatch_serialize() {
45
48
  return None;
@@ -802,13 +805,25 @@ mod tests {
802
805
  disarm_shader_error_fault();
803
806
  }
804
807
 
805
- /// dispatch_serialize must read FUZZGPU_SKIP_DISPATCH_LOCK and return
806
- /// true only when it is set to '1' or 'true' (case-insensitive). The
808
+ /// dispatch_serialize must read FUZZGPU_SERIALIZE_DISPATCH or FUZZGPU_SKIP_DISPATCH_LOCK
809
+ /// and return true only when set to '1' or 'true' (case-insensitive). The
807
810
  /// serialization is opt-in: unset (default) means fully concurrent.
808
811
  #[test]
809
812
  fn test_dispatch_serialize_opt_in() {
813
+ std::env::remove_var("FUZZGPU_SERIALIZE_DISPATCH");
810
814
  std::env::remove_var("FUZZGPU_SKIP_DISPATCH_LOCK");
811
815
  assert!(!dispatch_serialize());
816
+
817
+ // Primary self-documenting variable
818
+ std::env::set_var("FUZZGPU_SERIALIZE_DISPATCH", "1");
819
+ assert!(dispatch_serialize());
820
+ std::env::set_var("FUZZGPU_SERIALIZE_DISPATCH", "true");
821
+ assert!(dispatch_serialize());
822
+ std::env::set_var("FUZZGPU_SERIALIZE_DISPATCH", "0");
823
+ assert!(!dispatch_serialize());
824
+ std::env::remove_var("FUZZGPU_SERIALIZE_DISPATCH");
825
+
826
+ // Backward-compatible alias
812
827
  std::env::set_var("FUZZGPU_SKIP_DISPATCH_LOCK", "1");
813
828
  assert!(dispatch_serialize());
814
829
  std::env::set_var("FUZZGPU_SKIP_DISPATCH_LOCK", "true");
@@ -7,8 +7,16 @@ pub mod levenshtein;
7
7
  pub mod needleman;
8
8
  pub mod simd;
9
9
 
10
+ #[cfg(feature = "gpu")]
11
+ pub use damerau::gpu_ext::GpuDamerauKernel;
10
12
  #[cfg(feature = "gpu")]
11
13
  pub use gpu::{GpuEngine, GpuInfo, Result};
14
+ #[cfg(feature = "gpu")]
15
+ pub use jaro::gpu_ext::GpuJaroKernel;
16
+ #[cfg(feature = "gpu")]
17
+ pub use levenshtein::gpu_ext::GpuLevenshteinKernel;
18
+ #[cfg(feature = "gpu")]
19
+ pub use needleman::gpu_ext::GpuNeedlemanAffineKernel;
12
20
 
13
21
  #[cfg(not(feature = "gpu"))]
14
22
  pub type Result<T> = std::result::Result<T, String>;
@@ -104,8 +104,8 @@ fn main(
104
104
  let j = j_start + t;
105
105
  let i = k - j;
106
106
 
107
- let prev = diags[(k - 1u) % 3u];
108
- let prev2 = diags[(k - 2u) % 3u];
107
+ let prev_idx = (k - 1u) % 3u;
108
+ let prev2_idx = (k - 2u) % 3u;
109
109
 
110
110
  var cell: State;
111
111
  if (i == 0u) {
@@ -122,18 +122,18 @@ fn main(
122
122
  let sub = select(params.mismatch_score, params.match_score, a_char == b_char);
123
123
 
124
124
  // M(i,j) = max(M,Ix,Iy)(i-1,j-1) + sub -- diagonal k-2.
125
- let best_diag = max(prev2[j - 1u].m, max(prev2[j - 1u].ix, prev2[j - 1u].iy));
125
+ let best_diag = max(diags[prev2_idx][j - 1u].m, max(diags[prev2_idx][j - 1u].ix, diags[prev2_idx][j - 1u].iy));
126
126
  let cell_m = best_diag + sub;
127
127
 
128
128
  // Ix(i,j) from (i,j-1) -- diagonal k-1, column j-1.
129
- let cell_ix = max(prev[j - 1u].ix + params.gap_extend,
130
- max(prev[j - 1u].m + params.gap_open + params.gap_extend,
131
- prev[j - 1u].iy + params.gap_open + params.gap_extend));
129
+ let cell_ix = max(diags[prev_idx][j - 1u].ix + params.gap_extend,
130
+ max(diags[prev_idx][j - 1u].m + params.gap_open + params.gap_extend,
131
+ diags[prev_idx][j - 1u].iy + params.gap_open + params.gap_extend));
132
132
 
133
133
  // Iy(i,j) from (i-1,j) -- diagonal k-1, column j.
134
- let cell_iy = max(prev[j].iy + params.gap_extend,
135
- max(prev[j].m + params.gap_open + params.gap_extend,
136
- prev[j].ix + params.gap_open + params.gap_extend));
134
+ let cell_iy = max(diags[prev_idx][j].iy + params.gap_extend,
135
+ max(diags[prev_idx][j].m + params.gap_open + params.gap_extend,
136
+ diags[prev_idx][j].ix + params.gap_open + params.gap_extend));
137
137
 
138
138
  cell = State(cell_m, cell_ix, cell_iy);
139
139
  }
@@ -19,9 +19,15 @@ pub fn levenshtein_myers(a: &[u8], b: &[u8]) -> u32 {
19
19
  // back to the Unicode path. Assert it here so direct Rust callers can't
20
20
  // silently get byte-semantics results.
21
21
  if !a.is_ascii() || !b.is_ascii() {
22
- let a = String::from_utf8_lossy(a);
23
- let b = String::from_utf8_lossy(b);
24
- return crate::levenshtein::levenshtein_distance_raw(&a, &b);
22
+ let a_str = match std::str::from_utf8(a) {
23
+ Ok(s) => std::borrow::Cow::Borrowed(s),
24
+ Err(_) => String::from_utf8_lossy(a),
25
+ };
26
+ let b_str = match std::str::from_utf8(b) {
27
+ Ok(s) => std::borrow::Cow::Borrowed(s),
28
+ Err(_) => String::from_utf8_lossy(b),
29
+ };
30
+ return crate::levenshtein::levenshtein_distance_raw(&a_str, &b_str);
25
31
  }
26
32
  let (m, n) = (a.len(), b.len());
27
33
  if m == 0 {
@@ -43,6 +43,7 @@ fn any_string(max_len: usize) -> impl Strategy<Value = String> {
43
43
 
44
44
  /// ASCII string, length `1..=max_len` (non-empty: the row-wise Myers cdist
45
45
  /// kernel requires a non-empty pattern per row).
46
+ #[allow(dead_code)]
46
47
  fn ascii_string_nz(max_len: usize) -> impl Strategy<Value = String> {
47
48
  (1usize..=max_len).prop_flat_map(move |len| {
48
49
  prop::collection::vec(prop::char::range('a', 'z'), len..=len)
@@ -50,12 +51,14 @@ fn ascii_string_nz(max_len: usize) -> impl Strategy<Value = String> {
50
51
  })
51
52
  }
52
53
 
54
+ #[allow(dead_code)]
53
55
  fn any_pair(max_len: usize) -> impl Strategy<Value = (String, String)> {
54
56
  (any_string(max_len), any_string(max_len))
55
57
  }
56
58
 
57
59
  /// A pair of ASCII strings (the GPU Damerau kernel's gate is ASCII ≤ 32
58
60
  /// chars, so ASCII pairs are what actually runs on the shader).
61
+ #[allow(dead_code)]
59
62
  fn ascii_pair(max_len: usize) -> impl Strategy<Value = (String, String)> {
60
63
  (ascii_string(max_len), ascii_string(max_len))
61
64
  }
@@ -1,3 +1,5 @@
1
+ #![cfg(feature = "gpu")]
2
+
1
3
  //! End-to-end coverage of the `FuzzGpuError::ShaderError` path from the
2
4
  //! public API.
3
5
  //!
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fuzzgpu-python"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  edition = "2021"
5
5
  description = "Python bindings for FuzzGPU: GPU-accelerated fuzzy string matching engine"
6
6
  license = "MIT"
@@ -17,7 +17,7 @@ default = ["gpu"]
17
17
  gpu = ["fuzzgpu-core/gpu"]
18
18
 
19
19
  [dependencies]
20
- fuzzgpu-core = { path = "../fuzzgpu-core", version = "0.4.0", default-features = false }
20
+ fuzzgpu-core = { path = "../fuzzgpu-core", version = "0.4.1", default-features = false }
21
21
  pyo3 = { workspace = true, features = ["abi3-py310"] }
22
22
  numpy = "0.29"
23
23
  pollster = { workspace = true }
@@ -1,5 +1,6 @@
1
1
  use pyo3::prelude::*;
2
2
  use pyo3::types::{PyString, PyStringMethods};
3
+ #[cfg(feature = "gpu")]
3
4
  use std::sync::OnceLock;
4
5
 
5
6
  #[cfg(feature = "gpu")]
@@ -7,9 +8,12 @@ use fuzzgpu_core::gpu::GpuEngine;
7
8
 
8
9
  // ── Cached Environment Configuration ─────────────────────────
9
10
 
11
+ #[cfg(feature = "gpu")]
10
12
  static FORCE_GPU_CACHE: OnceLock<bool> = OnceLock::new();
13
+ #[cfg(feature = "gpu")]
11
14
  static DEBUG_CACHE: OnceLock<bool> = OnceLock::new();
12
15
 
16
+ #[cfg(feature = "gpu")]
13
17
  #[inline]
14
18
  fn is_force_gpu() -> bool {
15
19
  *FORCE_GPU_CACHE.get_or_init(|| {
@@ -21,6 +25,7 @@ fn is_force_gpu() -> bool {
21
25
  })
22
26
  }
23
27
 
28
+ #[cfg(feature = "gpu")]
24
29
  #[inline]
25
30
  fn is_debug_mode() -> bool {
26
31
  *DEBUG_CACHE.get_or_init(|| {
@@ -95,7 +100,7 @@ fn checked_u32_array1<'py>(
95
100
  ) -> PyResult<numpy::PyReadwriteArray1<'py, u32>> {
96
101
  use numpy::{PyArray1, PyUntypedArrayMethods};
97
102
  let arr = out.cast::<PyArray1<u32>>()?;
98
- let rw = readwrite_or_err(arr)?;
103
+ let mut rw = readwrite_or_err(arr)?;
99
104
  if rw.len() != expected {
100
105
  return Err(pyo3::exceptions::PyValueError::new_err(format!(
101
106
  "out must hold exactly {} uint32 elements, got {}",
@@ -103,6 +108,11 @@ fn checked_u32_array1<'py>(
103
108
  rw.len()
104
109
  )));
105
110
  }
111
+ if rw.as_slice_mut().is_err() {
112
+ return Err(pyo3::exceptions::PyBufferError::new_err(
113
+ "out must be C-contiguous",
114
+ ));
115
+ }
106
116
  Ok(rw)
107
117
  }
108
118
 
@@ -114,7 +124,7 @@ fn checked_f64_array1<'py>(
114
124
  ) -> PyResult<numpy::PyReadwriteArray1<'py, f64>> {
115
125
  use numpy::{PyArray1, PyUntypedArrayMethods};
116
126
  let arr = out.cast::<PyArray1<f64>>()?;
117
- let rw = readwrite_or_err(arr)?;
127
+ let mut rw = readwrite_or_err(arr)?;
118
128
  if rw.len() != expected {
119
129
  return Err(pyo3::exceptions::PyValueError::new_err(format!(
120
130
  "out must hold exactly {} float64 elements, got {}",
@@ -122,6 +132,11 @@ fn checked_f64_array1<'py>(
122
132
  rw.len()
123
133
  )));
124
134
  }
135
+ if rw.as_slice_mut().is_err() {
136
+ return Err(pyo3::exceptions::PyBufferError::new_err(
137
+ "out must be C-contiguous",
138
+ ));
139
+ }
125
140
  Ok(rw)
126
141
  }
127
142
 
@@ -134,13 +149,18 @@ fn checked_u32_array2<'py>(
134
149
  ) -> PyResult<numpy::PyReadwriteArray2<'py, u32>> {
135
150
  use numpy::{PyArray2, PyUntypedArrayMethods};
136
151
  let arr = out.cast::<PyArray2<u32>>()?;
137
- let rw = readwrite_or_err(arr)?;
152
+ let mut rw = readwrite_or_err(arr)?;
138
153
  if rw.shape() != [rows, cols] {
139
154
  return Err(pyo3::exceptions::PyValueError::new_err(format!(
140
155
  "out must have shape ({rows}, {cols}), got {:?}",
141
156
  rw.shape()
142
157
  )));
143
158
  }
159
+ if rw.as_slice_mut().is_err() {
160
+ return Err(pyo3::exceptions::PyBufferError::new_err(
161
+ "out must be C-contiguous",
162
+ ));
163
+ }
144
164
  Ok(rw)
145
165
  }
146
166
 
@@ -153,13 +173,18 @@ fn checked_f64_array2<'py>(
153
173
  ) -> PyResult<numpy::PyReadwriteArray2<'py, f64>> {
154
174
  use numpy::{PyArray2, PyUntypedArrayMethods};
155
175
  let arr = out.cast::<PyArray2<f64>>()?;
156
- let rw = readwrite_or_err(arr)?;
176
+ let mut rw = readwrite_or_err(arr)?;
157
177
  if rw.shape() != [rows, cols] {
158
178
  return Err(pyo3::exceptions::PyValueError::new_err(format!(
159
179
  "out must have shape ({rows}, {cols}), got {:?}",
160
180
  rw.shape()
161
181
  )));
162
182
  }
183
+ if rw.as_slice_mut().is_err() {
184
+ return Err(pyo3::exceptions::PyBufferError::new_err(
185
+ "out must be C-contiguous",
186
+ ));
187
+ }
163
188
  Ok(rw)
164
189
  }
165
190
 
@@ -370,11 +395,11 @@ fn damerau_levenshtein_distance(py: Python, a: &str, b: &str) -> PyResult<u32> {
370
395
  /// Shared compute for `damerau_levenshtein_batch` / `..._into`: GPU
371
396
  /// Lowrance-Wagner kernel (auto-routed, CPU fallback).
372
397
  fn damerau_batch_core(py: Python<'_>, query: &str, cands: &[&str]) -> PyResult<Vec<u32>> {
373
- let pairs: Vec<(&str, &str)> = cands.iter().map(|c| (query, *c)).collect();
374
398
  #[cfg(feature = "gpu")]
375
399
  {
376
400
  if !GpuEngine::is_cpu_only() {
377
401
  if let Ok(kernel) = fuzzgpu_core::damerau::gpu_ext::GpuDamerauKernel::get() {
402
+ let pairs: Vec<(&str, &str)> = cands.iter().map(|c| (query, *c)).collect();
378
403
  match py.detach(|| kernel.compute_batch(&pairs)) {
379
404
  Ok(res) => return Ok(res),
380
405
  Err(e) => {
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fuzzgpu-wasm"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  edition = "2021"
5
5
 
6
6
  [workspace]
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "fuzzgpu"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "Hardware-accelerated fuzzy string matching & sequence alignment — cross-platform, no CUDA required"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -5,6 +5,10 @@ from typing import Callable, Iterable, Optional, Sequence, Tuple
5
5
  import numpy as np
6
6
  from numpy.typing import NDArray
7
7
 
8
+ from . import fuzz as fuzz
9
+ from . import process as process
10
+ from . import distance as distance
11
+
8
12
  Processor = Optional[Callable[[str], str]]
9
13
  Scorer = Callable[..., float]
10
14
 
@@ -34,6 +38,7 @@ def damerau_ratio(a: str, b: str) -> float: ...
34
38
  def needleman_wunsch_score(a: str, b: str, match_score: int, mismatch_score: int, gap_penalty: int) -> int: ...
35
39
  def needleman_wunsch(a: str, b: str, match_score: int, mismatch_score: int, gap_penalty: int) -> int: ...
36
40
  def needleman_wunsch_batch(query: str, candidates: Sequence[str], match_score: int, mismatch_score: int, gap_penalty: int) -> list[int]: ...
41
+ def needleman_wunsch_batch_fn(query: str, candidates: Sequence[str], match_score: int, mismatch_score: int, gap_penalty: int) -> list[int]: ...
37
42
  def needleman_wunsch_affine(a: str, b: str, match_score: int, mismatch_score: int, gap_open: int, gap_extend: int) -> int: ...
38
43
  def needleman_wunsch_affine_batch(query: str, candidates: Sequence[str], match_score: int, mismatch_score: int, gap_open: int, gap_extend: int) -> list[int]: ...
39
44
  def needleman_wunsch_striped(a: str, b: str, match_score: int, mismatch_score: int, gap_penalty: int) -> int: ...
@@ -45,6 +50,7 @@ def jaro_winkler_similarity(a: str, b: str, p: float = 0.1) -> float: ...
45
50
  def jaro_winkler(a: str, b: str, p: float = 0.1) -> float: ...
46
51
  def jaro_optimized(a: str, b: str) -> float: ...
47
52
  def jaro_winkler_batch(query: str, candidates: Sequence[str], p: float = 0.1) -> list[float]: ...
53
+ def jaro_winkler_batch_fn(query: str, candidates: Sequence[str], p: float = 0.1) -> list[float]: ...
48
54
  def jaro_winkler_batch_into(query: str, candidates: Sequence[str], out: NDArray[np.float64], p: float = 0.1) -> None: ...
49
55
  def jaro_winkler_cdist(list_a: Sequence[str], list_b: Sequence[str], p: float = 0.1) -> list[list[float]]: ...
50
56
  def jaro_winkler_cdist_into(list_a: Sequence[str], list_b: Sequence[str], out: NDArray[np.float64], p: float = 0.1) -> None: ...
@@ -66,6 +72,22 @@ def WRatio(s1: str, s2: str, *, processor: Processor = None, score_cutoff: float
66
72
  def wratio(s1: str, s2: str, *, processor: Processor = None, score_cutoff: float = 0.0) -> float: ...
67
73
  def ratio_batch(query: str, candidates: Sequence[str], *, processor: Processor = None, score_cutoff: float = 0.0, workers: Optional[int] = None) -> list[float]: ...
68
74
 
75
+ # ── Native extension exports ──────────────────────────────────
76
+ def fuzz_ratio(a: str, b: str, score_cutoff: float = 0.0) -> float: ...
77
+ def fuzz_partial_ratio(a: str, b: str, score_cutoff: float = 0.0) -> float: ...
78
+ def fuzz_partial_ratio_alignment(a: str, b: str, score_cutoff: float = 0.0) -> Tuple[float, int, int, int, int]: ...
79
+ def fuzz_token_sort_ratio(a: str, b: str, score_cutoff: float = 0.0) -> float: ...
80
+ def fuzz_token_set_ratio(a: str, b: str, score_cutoff: float = 0.0) -> float: ...
81
+ def fuzz_token_ratio(a: str, b: str, score_cutoff: float = 0.0) -> float: ...
82
+ def fuzz_wratio(a: str, b: str, score_cutoff: float = 0.0) -> float: ...
83
+ def fuzz_ratio_batch(query: str, candidates: Sequence[str]) -> list[float]: ...
84
+ def fuzz_extract(query: str, choices: Sequence[str], score_cutoff: float = 0.0, limit: int = 5) -> list[Tuple[str, float, int]]: ...
85
+ def fuzz_extract_one(query: str, choices: Sequence[str], score_cutoff: float = 0.0) -> Optional[Tuple[str, float, int]]: ...
86
+ def fuzz_partial_token_sort_ratio(a: str, b: str, score_cutoff: float = 0.0) -> float: ...
87
+ def fuzz_partial_token_set_ratio(a: str, b: str, score_cutoff: float = 0.0) -> float: ...
88
+ def fuzz_partial_token_ratio(a: str, b: str, score_cutoff: float = 0.0) -> float: ...
89
+ def fuzz_qratio(a: str, b: str, score_cutoff: float = 0.0) -> float: ...
90
+
69
91
  # ── Search ───────────────────────────────────────────────────
70
92
  def extract(
71
93
  query: str,
@@ -101,15 +123,10 @@ def extract_one(
101
123
  scorer_kwargs: Optional[dict] = None,
102
124
  ) -> Optional[Tuple[str, float, int]]: ...
103
125
 
104
- # ── Utilities ────────────────────────────────────────────────
105
- def set_cpu_only(cpu_only: bool) -> None: ...
106
- def set_gpu_threshold(threshold: Optional[int]) -> None: ...
107
- def is_gpu_available() -> bool: ...
108
- def warmup() -> tuple[bool, str]: ...
109
- def gpu_info() -> str: ...
110
- def hardware_info() -> str: ...
111
-
112
126
  # ── Alignment helpers (rapidfuzz.distance API) ───────────────
127
+ def editops(s1: str, s2: str) -> Editops: ...
128
+ def opcodes(s1: str, s2: str) -> Opcodes: ...
129
+
113
130
  class Editop(Tuple[str, int, int]):
114
131
  tag: str
115
132
  src_pos: int
@@ -166,3 +183,13 @@ class ScoreAlignment(Tuple[float, int, int, int, int]):
166
183
  src_end: int
167
184
  dest_start: int
168
185
  dest_end: int
186
+
187
+ # ── Utilities ────────────────────────────────────────────────
188
+ def set_cpu_only(cpu_only: bool) -> None: ...
189
+ def set_gpu_threshold(threshold: Optional[int]) -> None: ...
190
+ def is_gpu_available() -> bool: ...
191
+ def warmup() -> tuple[bool, str]: ...
192
+ def gpu_info() -> str: ...
193
+ def hardware_info() -> str: ...
194
+
195
+ __all__: list[str]
@@ -27,30 +27,42 @@ def distance(s1, s2, *, weights=(1, 1, 1), processor=None, score_cutoff=None, sc
27
27
  return cutoff_distance(value, score_cutoff)
28
28
 
29
29
 
30
- def similarity(s1, s2, *, processor=None, score_cutoff=None, score_hint=None):
30
+ def _max_distance(len1, len2, weights):
31
+ if weights == (1, 1, 1):
32
+ return max(len1, len2)
33
+ ins, dele, sub = weights
34
+ s = min(sub, ins + dele)
35
+ if len1 >= len2:
36
+ return len2 * s + (len1 - len2) * dele
37
+ else:
38
+ return len1 * s + (len2 - len1) * ins
39
+
40
+
41
+ def similarity(s1, s2, *, weights=(1, 1, 1), processor=None, score_cutoff=None, score_hint=None):
31
42
  del score_hint
32
43
  if processor:
33
44
  s1, s2 = processor(s1), processor(s2)
34
45
  # Compute maximum AFTER processing so len() reflects the processed strings.
35
- maximum = max(len(s1), len(s2))
36
- value = maximum - distance(s1, s2)
46
+ maximum = _max_distance(len(s1), len(s2), weights)
47
+ value = maximum - distance(s1, s2, weights=weights)
37
48
  return value if score_cutoff is None or value >= score_cutoff else 0
38
49
 
39
50
 
40
- def normalized_distance(s1, s2, *, processor=None, score_cutoff=None, score_hint=None):
51
+ def normalized_distance(s1, s2, *, weights=(1, 1, 1), processor=None, score_cutoff=None, score_hint=None):
41
52
  del score_hint
42
53
  if processor:
43
54
  s1, s2 = processor(s1), processor(s2)
44
- maximum = max(len(s1), len(s2))
45
- value = _normalized(distance(s1, s2), maximum)
55
+ maximum = _max_distance(len(s1), len(s2), weights)
56
+ value = _normalized(distance(s1, s2, weights=weights), maximum)
46
57
  return value if score_cutoff is None or value <= score_cutoff else 1.0
47
58
 
48
59
 
49
- def normalized_similarity(s1, s2, *, processor=None, score_cutoff=None, score_hint=None):
60
+ def normalized_similarity(s1, s2, *, weights=(1, 1, 1), processor=None, score_cutoff=None, score_hint=None):
50
61
  del score_hint
51
62
  if processor:
52
63
  s1, s2 = processor(s1), processor(s2)
53
- value = 1.0 - _normalized(distance(s1, s2), max(len(s1), len(s2)))
64
+ maximum = _max_distance(len(s1), len(s2), weights)
65
+ value = 1.0 - _normalized(distance(s1, s2, weights=weights), maximum)
54
66
  return value if score_cutoff is None or value >= score_cutoff else 0.0
55
67
 
56
68
 
@@ -40,6 +40,20 @@ def extractOne(
40
40
  extract_one = extractOne
41
41
  damerau_ratio: Callable[[str, str], float]
42
42
 
43
+ def cdist(
44
+ queries: Iterable[str],
45
+ choices: Iterable[str],
46
+ *,
47
+ scorer: Optional[Callable] = ...,
48
+ processor: Optional[Processor] = ...,
49
+ score_cutoff: Optional[float] = ...,
50
+ score_hint: Optional[float] = ...,
51
+ score_multiplier: float = ...,
52
+ dtype: Optional[object] = ...,
53
+ workers: Optional[int] = ...,
54
+ scorer_kwargs: Optional[dict] = ...,
55
+ ) -> object: ...
56
+
43
57
  class ScoreAlignment(Tuple[float, int, int, int, int]):
44
58
  score: float
45
59
  src_start: int
@@ -0,0 +1 @@
1
+ # Marker file for PEP 561.
@@ -144,6 +144,14 @@ def test_cdist_into_non_contiguous_raises():
144
144
  fuzzgpu.levenshtein_cdist_into(a, b, out)
145
145
 
146
146
 
147
+ def test_batch_into_non_contiguous_raises():
148
+ query = "hello"
149
+ cands = ["world", "help"]
150
+ out = np.zeros(4, dtype=np.uint32)[::2] # strided 1D view
151
+ with pytest.raises(BufferError, match=r"C-contiguous"):
152
+ fuzzgpu.levenshtein_batch_into(query, cands, out)
153
+
154
+
147
155
  def test_jaro_batch_into_invalid_p_raises():
148
156
  out = np.zeros(1, dtype=np.float64)
149
157
  with pytest.raises(ValueError):
@@ -98,6 +98,19 @@ def test_distance_modules_match_rapidfuzz(mod, fn):
98
98
  assert r == g, f"{mod}.{fn}({a!r}, {b!r}): rapidfuzz={r} fuzzgpu={g}"
99
99
 
100
100
 
101
+ @pytest.mark.parametrize("fn", ["distance", "similarity", "normalized_distance", "normalized_similarity"])
102
+ @pytest.mark.parametrize("weights", [(1, 1, 1), (1, 1, 2), (2, 1, 1), (1, 2, 1), (2, 3, 4)])
103
+ def test_levenshtein_weighted_matches_rapidfuzz(fn, weights):
104
+ rmod, gmod = rd.Levenshtein, fd.Levenshtein
105
+ for a, b in _PAIRS[:20]:
106
+ r = getattr(rmod, fn)(a, b, weights=weights)
107
+ g = getattr(gmod, fn)(a, b, weights=weights)
108
+ if isinstance(r, float):
109
+ _assert_close(r, g, f"Levenshtein.{fn}({a!r}, {b!r}, weights={weights})")
110
+ else:
111
+ assert r == g, f"Levenshtein.{fn}({a!r}, {b!r}, weights={weights}): rapidfuzz={r} fuzzgpu={g}"
112
+
113
+
101
114
  @pytest.mark.parametrize("mod", ["Levenshtein", "Indel"])
102
115
  def test_editops_length_matches_rapidfuzz(mod):
103
116
  rmod, gmod = getattr(rd, mod), getattr(fd, mod)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes