usearch 2.15.2 → 2.16.0
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.
- package/README.md +1 -1
- package/binding.gyp +1 -1
- package/c/CMakeLists.txt +10 -3
- package/cpp/README.md +1 -1
- package/cpp/bench.cpp +2 -2
- package/cpp/test.cpp +16 -0
- package/include/usearch/index.hpp +44 -17
- package/include/usearch/index_dense.hpp +51 -89
- package/include/usearch/index_plugins.hpp +468 -43
- package/javascript/lib.cpp +17 -5
- package/javascript/usearch.test.js +205 -34
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/simsimd/.git-blame-ignore-revs +2 -0
- package/simsimd/CODE_OF_CONDUCT.md +128 -0
- package/simsimd/CONTRIBUTING.md +84 -26
- package/simsimd/Cargo.lock +1 -1
- package/simsimd/Cargo.toml +4 -4
- package/simsimd/MANIFEST.in +3 -0
- package/simsimd/Package.swift +15 -3
- package/simsimd/README.md +534 -177
- package/simsimd/binding.gyp +6 -2
- package/simsimd/build.rs +13 -1
- package/simsimd/c/lib.c +266 -53
- package/simsimd/include/simsimd/binary.h +321 -112
- package/simsimd/include/simsimd/curved.h +803 -0
- package/simsimd/include/simsimd/dot.h +694 -521
- package/simsimd/include/simsimd/elementwise.h +1437 -0
- package/simsimd/include/simsimd/geospatial.h +9 -3
- package/simsimd/include/simsimd/mesh.h +69 -0
- package/simsimd/include/simsimd/probability.h +234 -206
- package/simsimd/include/simsimd/simsimd.h +1476 -617
- package/simsimd/include/simsimd/sparse.h +1428 -0
- package/simsimd/include/simsimd/spatial.h +1556 -742
- package/simsimd/include/simsimd/types.h +276 -132
- package/simsimd/javascript/lib.c +1 -2
- package/simsimd/javascript/simsimd.ts +4 -4
- package/simsimd/package.json +4 -4
- package/simsimd/rust/lib.rs +179 -1
- package/simsimd/scripts/bench.cxx +1070 -0
- package/simsimd/scripts/bench.ipynb +187 -0
- package/simsimd/{javascript → scripts}/bench.js +1 -1
- package/simsimd/{rust/benches/sqeuclidean.rs → scripts/bench_sqeuclidean.rs} +6 -6
- package/simsimd/scripts/bench_vectors.py +700 -0
- package/simsimd/scripts/bench_vectors_live.py +213 -0
- package/simsimd/scripts/test.c +155 -0
- package/simsimd/{javascript → scripts}/test.mjs +2 -2
- package/simsimd/scripts/test.py +1410 -0
- package/simsimd/swift/{source/SimSIMD.swift → SimSIMD.swift} +6 -2
- package/simsimd/swift/{tests/SimSIMDTests.swift → Test.swift} +1 -1
- /package/simsimd/{rust/benches/cosine.rs → scripts/bench_cosine.rs} +0 -0
- /package/simsimd/{rust/benches/native.rs → scripts/bench_native.rs} +0 -0
package/simsimd/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simsimd",
|
|
3
|
-
"version": "5.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "5.9.6",
|
|
4
|
+
"description": "Portable mixed-precision BLAS-like vector math library for x86 and ARM",
|
|
5
5
|
"homepage": "https://github.com/ashvardanian/simsimd",
|
|
6
6
|
"author": "Ash Vardanian",
|
|
7
7
|
"license": "Apache 2.0",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"prebuild-arm64": "prebuildify --arch arm64 --napi --strip --target=10.4.0",
|
|
28
28
|
"prebuild-darwin-x64+arm64": "prebuildify --arch arm64+x64 --napi --strip --target=10.4.0",
|
|
29
29
|
"build-js": "rm -fr javascript/dist/* && tsc -p javascript/tsconfig-esm.json && tsc -p javascript/tsconfig-cjs.json && cp javascript/dist-package-esm.json javascript/dist/esm/package.json && cp javascript/dist-package-cjs.json javascript/dist/cjs/package.json",
|
|
30
|
-
"test": "node --test ./
|
|
31
|
-
"bench": "node ./
|
|
30
|
+
"test": "node --test ./scripts/test.mjs",
|
|
31
|
+
"bench": "node ./scripts/bench.js"
|
|
32
32
|
},
|
|
33
33
|
"main": "javascript/dist/cjs/simsimd.js",
|
|
34
34
|
"module": "javascript/dist/esm/simsimd.js",
|
package/simsimd/rust/lib.rs
CHANGED
|
@@ -54,24 +54,29 @@ type ComplexProduct = (f64, f64);
|
|
|
54
54
|
extern "C" {
|
|
55
55
|
|
|
56
56
|
fn simsimd_dot_f16(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
57
|
+
fn simsimd_dot_bf16(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
57
58
|
fn simsimd_dot_f32(a: *const f32, b: *const f32, c: usize, d: *mut Distance);
|
|
58
59
|
fn simsimd_dot_f64(a: *const f64, b: *const f64, c: usize, d: *mut Distance);
|
|
59
60
|
|
|
60
61
|
fn simsimd_dot_f16c(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
62
|
+
fn simsimd_dot_bf16c(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
61
63
|
fn simsimd_dot_f32c(a: *const f32, b: *const f32, c: usize, d: *mut Distance);
|
|
62
64
|
fn simsimd_dot_f64c(a: *const f64, b: *const f64, c: usize, d: *mut Distance);
|
|
63
65
|
|
|
64
66
|
fn simsimd_vdot_f16c(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
67
|
+
fn simsimd_vdot_bf16c(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
65
68
|
fn simsimd_vdot_f32c(a: *const f32, b: *const f32, c: usize, d: *mut Distance);
|
|
66
69
|
fn simsimd_vdot_f64c(a: *const f64, b: *const f64, c: usize, d: *mut Distance);
|
|
67
70
|
|
|
68
71
|
fn simsimd_cos_i8(a: *const i8, b: *const i8, c: usize, d: *mut Distance);
|
|
69
72
|
fn simsimd_cos_f16(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
73
|
+
fn simsimd_cos_bf16(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
70
74
|
fn simsimd_cos_f32(a: *const f32, b: *const f32, c: usize, d: *mut Distance);
|
|
71
75
|
fn simsimd_cos_f64(a: *const f64, b: *const f64, c: usize, d: *mut Distance);
|
|
72
76
|
|
|
73
77
|
fn simsimd_l2sq_i8(a: *const i8, b: *const i8, c: usize, d: *mut Distance);
|
|
74
78
|
fn simsimd_l2sq_f16(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
79
|
+
fn simsimd_l2sq_bf16(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
75
80
|
fn simsimd_l2sq_f32(a: *const f32, b: *const f32, c: usize, d: *mut Distance);
|
|
76
81
|
fn simsimd_l2sq_f64(a: *const f64, b: *const f64, c: usize, d: *mut Distance);
|
|
77
82
|
|
|
@@ -79,10 +84,12 @@ extern "C" {
|
|
|
79
84
|
fn simsimd_jaccard_b8(a: *const u8, b: *const u8, c: usize, d: *mut Distance);
|
|
80
85
|
|
|
81
86
|
fn simsimd_js_f16(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
87
|
+
fn simsimd_js_bf16(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
82
88
|
fn simsimd_js_f32(a: *const f32, b: *const f32, c: usize, d: *mut Distance);
|
|
83
89
|
fn simsimd_js_f64(a: *const f64, b: *const f64, c: usize, d: *mut Distance);
|
|
84
90
|
|
|
85
91
|
fn simsimd_kl_f16(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
92
|
+
fn simsimd_kl_bf16(a: *const u16, b: *const u16, c: usize, d: *mut Distance);
|
|
86
93
|
fn simsimd_kl_f32(a: *const f32, b: *const f32, c: usize, d: *mut Distance);
|
|
87
94
|
fn simsimd_kl_f64(a: *const f64, b: *const f64, c: usize, d: *mut Distance);
|
|
88
95
|
|
|
@@ -99,6 +106,8 @@ extern "C" {
|
|
|
99
106
|
fn simsimd_uses_ice() -> i32;
|
|
100
107
|
fn simsimd_uses_genoa() -> i32;
|
|
101
108
|
fn simsimd_uses_sapphire() -> i32;
|
|
109
|
+
fn simsimd_uses_turin() -> i32;
|
|
110
|
+
fn simsimd_uses_sierra() -> i32;
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
/// A half-precision floating point number.
|
|
@@ -107,6 +116,12 @@ pub struct f16(u16);
|
|
|
107
116
|
|
|
108
117
|
impl f16 {}
|
|
109
118
|
|
|
119
|
+
/// A half-precision floating point number, called brain float.
|
|
120
|
+
#[repr(transparent)]
|
|
121
|
+
pub struct bf16(u16);
|
|
122
|
+
|
|
123
|
+
impl bf16 {}
|
|
124
|
+
|
|
110
125
|
/// The `capabilities` module provides functions for detecting the hardware features
|
|
111
126
|
/// available on the current system.
|
|
112
127
|
pub mod capabilities {
|
|
@@ -162,6 +177,14 @@ pub mod capabilities {
|
|
|
162
177
|
pub fn uses_sapphire() -> bool {
|
|
163
178
|
unsafe { crate::simsimd_uses_sapphire() != 0 }
|
|
164
179
|
}
|
|
180
|
+
|
|
181
|
+
pub fn uses_turin() -> bool {
|
|
182
|
+
unsafe { crate::simsimd_uses_turin() != 0 }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
pub fn uses_sierra() -> bool {
|
|
186
|
+
unsafe { crate::simsimd_uses_sierra() != 0 }
|
|
187
|
+
}
|
|
165
188
|
}
|
|
166
189
|
|
|
167
190
|
/// `SpatialSimilarity` provides a set of trait methods for computing similarity
|
|
@@ -370,6 +393,50 @@ impl SpatialSimilarity for f16 {
|
|
|
370
393
|
}
|
|
371
394
|
}
|
|
372
395
|
|
|
396
|
+
impl SpatialSimilarity for bf16 {
|
|
397
|
+
fn cos(a: &[Self], b: &[Self]) -> Option<Distance> {
|
|
398
|
+
if a.len() != b.len() {
|
|
399
|
+
return None;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Explicitly cast `*const bf16` to `*const u16`
|
|
403
|
+
let a_ptr = a.as_ptr() as *const u16;
|
|
404
|
+
let b_ptr = b.as_ptr() as *const u16;
|
|
405
|
+
let mut distance_value: Distance = 0.0;
|
|
406
|
+
let distance_ptr: *mut Distance = &mut distance_value as *mut Distance;
|
|
407
|
+
unsafe { simsimd_cos_bf16(a_ptr, b_ptr, a.len(), distance_ptr) };
|
|
408
|
+
Some(distance_value)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
fn dot(a: &[Self], b: &[Self]) -> Option<Distance> {
|
|
412
|
+
if a.len() != b.len() {
|
|
413
|
+
return None;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Explicitly cast `*const bf16` to `*const u16`
|
|
417
|
+
let a_ptr = a.as_ptr() as *const u16;
|
|
418
|
+
let b_ptr = b.as_ptr() as *const u16;
|
|
419
|
+
let mut distance_value: Distance = 0.0;
|
|
420
|
+
let distance_ptr: *mut Distance = &mut distance_value as *mut Distance;
|
|
421
|
+
unsafe { simsimd_dot_bf16(a_ptr, b_ptr, a.len(), distance_ptr) };
|
|
422
|
+
Some(distance_value)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
fn l2sq(a: &[Self], b: &[Self]) -> Option<Distance> {
|
|
426
|
+
if a.len() != b.len() {
|
|
427
|
+
return None;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Explicitly cast `*const bf16` to `*const u16`
|
|
431
|
+
let a_ptr = a.as_ptr() as *const u16;
|
|
432
|
+
let b_ptr = b.as_ptr() as *const u16;
|
|
433
|
+
let mut distance_value: Distance = 0.0;
|
|
434
|
+
let distance_ptr: *mut Distance = &mut distance_value as *mut Distance;
|
|
435
|
+
unsafe { simsimd_l2sq_bf16(a_ptr, b_ptr, a.len(), distance_ptr) };
|
|
436
|
+
Some(distance_value)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
373
440
|
impl SpatialSimilarity for f32 {
|
|
374
441
|
fn cos(a: &[Self], b: &[Self]) -> Option<Distance> {
|
|
375
442
|
if a.len() != b.len() {
|
|
@@ -464,6 +531,36 @@ impl ProbabilitySimilarity for f16 {
|
|
|
464
531
|
}
|
|
465
532
|
}
|
|
466
533
|
|
|
534
|
+
impl ProbabilitySimilarity for bf16 {
|
|
535
|
+
fn jensenshannon(a: &[Self], b: &[Self]) -> Option<Distance> {
|
|
536
|
+
if a.len() != b.len() {
|
|
537
|
+
return None;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Explicitly cast `*const bf16` to `*const u16`
|
|
541
|
+
let a_ptr = a.as_ptr() as *const u16;
|
|
542
|
+
let b_ptr = b.as_ptr() as *const u16;
|
|
543
|
+
let mut distance_value: Distance = 0.0;
|
|
544
|
+
let distance_ptr: *mut Distance = &mut distance_value as *mut Distance;
|
|
545
|
+
unsafe { simsimd_js_bf16(a_ptr, b_ptr, a.len(), distance_ptr) };
|
|
546
|
+
Some(distance_value)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
fn kullbackleibler(a: &[Self], b: &[Self]) -> Option<Distance> {
|
|
550
|
+
if a.len() != b.len() {
|
|
551
|
+
return None;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Explicitly cast `*const bf16` to `*const u16`
|
|
555
|
+
let a_ptr = a.as_ptr() as *const u16;
|
|
556
|
+
let b_ptr = b.as_ptr() as *const u16;
|
|
557
|
+
let mut distance_value: Distance = 0.0;
|
|
558
|
+
let distance_ptr: *mut Distance = &mut distance_value as *mut Distance;
|
|
559
|
+
unsafe { simsimd_kl_bf16(a_ptr, b_ptr, a.len(), distance_ptr) };
|
|
560
|
+
Some(distance_value)
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
467
564
|
impl ProbabilitySimilarity for f32 {
|
|
468
565
|
fn jensenshannon(a: &[Self], b: &[Self]) -> Option<Distance> {
|
|
469
566
|
if a.len() != b.len() {
|
|
@@ -536,6 +633,36 @@ impl ComplexProducts for f16 {
|
|
|
536
633
|
}
|
|
537
634
|
}
|
|
538
635
|
|
|
636
|
+
impl ComplexProducts for bf16 {
|
|
637
|
+
fn dot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
|
|
638
|
+
if a.len() != b.len() {
|
|
639
|
+
return None;
|
|
640
|
+
}
|
|
641
|
+
// Prepare the output array where the real and imaginary parts will be stored
|
|
642
|
+
let mut product: [Distance; 2] = [0.0, 0.0];
|
|
643
|
+
let product_ptr: *mut Distance = &mut product[0] as *mut _;
|
|
644
|
+
// Explicitly cast `*const bf16` to `*const u16`
|
|
645
|
+
let a_ptr = a.as_ptr() as *const u16;
|
|
646
|
+
let b_ptr = b.as_ptr() as *const u16;
|
|
647
|
+
unsafe { simsimd_dot_bf16c(a_ptr, b_ptr, a.len(), product_ptr) };
|
|
648
|
+
Some((product[0], product[1]))
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
fn vdot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
|
|
652
|
+
if a.len() != b.len() {
|
|
653
|
+
return None;
|
|
654
|
+
}
|
|
655
|
+
// Prepare the output array where the real and imaginary parts will be stored
|
|
656
|
+
let mut product: [Distance; 2] = [0.0, 0.0];
|
|
657
|
+
let product_ptr: *mut Distance = &mut product[0] as *mut _;
|
|
658
|
+
// Explicitly cast `*const bf16` to `*const u16`
|
|
659
|
+
let a_ptr = a.as_ptr() as *const u16;
|
|
660
|
+
let b_ptr = b.as_ptr() as *const u16;
|
|
661
|
+
unsafe { simsimd_vdot_bf16c(a_ptr, b_ptr, a.len(), product_ptr) };
|
|
662
|
+
Some((product[0], product[1]))
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
539
666
|
impl ComplexProducts for f32 {
|
|
540
667
|
fn dot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
|
|
541
668
|
if a.len() != b.len() {
|
|
@@ -583,6 +710,7 @@ impl ComplexProducts for f64 {
|
|
|
583
710
|
#[cfg(test)]
|
|
584
711
|
mod tests {
|
|
585
712
|
use super::*;
|
|
713
|
+
use half::bf16 as HalfBF16;
|
|
586
714
|
use half::f16 as HalfF16;
|
|
587
715
|
|
|
588
716
|
#[test]
|
|
@@ -592,7 +720,8 @@ mod tests {
|
|
|
592
720
|
|| capabilities::uses_skylake()
|
|
593
721
|
|| capabilities::uses_ice()
|
|
594
722
|
|| capabilities::uses_genoa()
|
|
595
|
-
|| capabilities::uses_sapphire()
|
|
723
|
+
|| capabilities::uses_sapphire()
|
|
724
|
+
|| capabilities::uses_turin();
|
|
596
725
|
|
|
597
726
|
// The CPU can't simultaneously support ARM and x86 SIMD extensions
|
|
598
727
|
if uses_arm {
|
|
@@ -609,6 +738,8 @@ mod tests {
|
|
|
609
738
|
println!("- uses_ice: {}", capabilities::uses_ice());
|
|
610
739
|
println!("- uses_genoa: {}", capabilities::uses_genoa());
|
|
611
740
|
println!("- uses_sapphire: {}", capabilities::uses_sapphire());
|
|
741
|
+
println!("- uses_turin: {}", capabilities::uses_turin());
|
|
742
|
+
println!("- uses_sierra: {}", capabilities::uses_sierra());
|
|
612
743
|
}
|
|
613
744
|
|
|
614
745
|
//
|
|
@@ -786,6 +917,26 @@ mod tests {
|
|
|
786
917
|
}
|
|
787
918
|
}
|
|
788
919
|
|
|
920
|
+
#[test]
|
|
921
|
+
fn test_cos_bf16_same() {
|
|
922
|
+
// Assuming these u16 values represent bf16 bit patterns, and they are identical
|
|
923
|
+
let a_u16: &[u16] = &[15360, 16384, 17408]; // Corresponding to some bf16 values
|
|
924
|
+
let b_u16: &[u16] = &[15360, 16384, 17408]; // Same as above for simplicity
|
|
925
|
+
|
|
926
|
+
// Reinterpret cast from &[u16] to &[bf16]
|
|
927
|
+
// SAFETY: This is safe as long as the representations are guaranteed to be identical,
|
|
928
|
+
// which they are for transparent structs wrapping the same type.
|
|
929
|
+
let a_bf16: &[bf16] =
|
|
930
|
+
unsafe { std::slice::from_raw_parts(a_u16.as_ptr() as *const bf16, a_u16.len()) };
|
|
931
|
+
let b_bf16: &[bf16] =
|
|
932
|
+
unsafe { std::slice::from_raw_parts(b_u16.as_ptr() as *const bf16, b_u16.len()) };
|
|
933
|
+
|
|
934
|
+
if let Some(result) = SpatialSimilarity::cosine(a_bf16, b_bf16) {
|
|
935
|
+
println!("The result of cos_bf16 is {:.8}", result);
|
|
936
|
+
assert_almost_equal(0.0, result, 0.01); // Example value, adjust according to actual expected value
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
789
940
|
#[test]
|
|
790
941
|
fn test_cos_f16_interop() {
|
|
791
942
|
let a_half: Vec<HalfF16> = vec![1.0, 2.0, 3.0]
|
|
@@ -812,4 +963,31 @@ mod tests {
|
|
|
812
963
|
assert_almost_equal(0.025, result, 0.01);
|
|
813
964
|
}
|
|
814
965
|
}
|
|
966
|
+
|
|
967
|
+
#[test]
|
|
968
|
+
fn test_cos_bf16_interop() {
|
|
969
|
+
let a_half: Vec<HalfBF16> = vec![1.0, 2.0, 3.0]
|
|
970
|
+
.iter()
|
|
971
|
+
.map(|&x| HalfBF16::from_f32(x))
|
|
972
|
+
.collect();
|
|
973
|
+
let b_half: Vec<HalfBF16> = vec![4.0, 5.0, 6.0]
|
|
974
|
+
.iter()
|
|
975
|
+
.map(|&x| HalfBF16::from_f32(x))
|
|
976
|
+
.collect();
|
|
977
|
+
|
|
978
|
+
// SAFETY: This is safe as long as the memory representations are guaranteed to be identical,
|
|
979
|
+
// which they are due to both being #[repr(transparent)] wrappers around u16.
|
|
980
|
+
let a_simsimd: &[bf16] =
|
|
981
|
+
unsafe { std::slice::from_raw_parts(a_half.as_ptr() as *const bf16, a_half.len()) };
|
|
982
|
+
let b_simsimd: &[bf16] =
|
|
983
|
+
unsafe { std::slice::from_raw_parts(b_half.as_ptr() as *const bf16, b_half.len()) };
|
|
984
|
+
|
|
985
|
+
// Use the reinterpret-casted slices with your SpatialSimilarity implementation
|
|
986
|
+
if let Some(result) = SpatialSimilarity::cosine(a_simsimd, b_simsimd) {
|
|
987
|
+
// Expected value might need adjustment depending on actual cosine functionality
|
|
988
|
+
// Assuming identical vectors yield cosine similarity of 1.0
|
|
989
|
+
println!("The result of cos_bf16 (interop) is {:.8}", result);
|
|
990
|
+
assert_almost_equal(0.025, result, 0.01);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
815
993
|
}
|