usearch 2.21.2 → 2.21.4

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.
@@ -7,7 +7,7 @@
7
7
  //!
8
8
  //! ## Implemented distance functions include:
9
9
  //!
10
- //! * Euclidean (L2), Inner Distance, and Cosine (Angular) spatial distances.
10
+ //! * Euclidean (L2), inner product, and cosine (angular) spatial distances.
11
11
  //! * Hamming (~ Manhattan) and Jaccard (~ Tanimoto) binary distances.
12
12
  //! * Kullback-Leibler and Jensen-Shannon divergences for probability distributions.
13
13
  //!
@@ -19,8 +19,8 @@
19
19
  //! let a = &[1, 2, 3];
20
20
  //! let b = &[4, 5, 6];
21
21
  //!
22
- //! // Compute cosine similarity
23
- //! let cos_sim = i8::cos(a, b);
22
+ //! // Compute cosine distance
23
+ //! let cos_dist = i8::cos(a, b);
24
24
  //!
25
25
  //! // Compute dot product distance
26
26
  //! let dot_product = i8::dot(a, b);
@@ -40,12 +40,12 @@
40
40
  //! // Work with half-precision floats
41
41
  //! let half_a: Vec<f16> = vec![1.0, 2.0, 3.0].iter().map(|&x| f16::from_f32(x)).collect();
42
42
  //! let half_b: Vec<f16> = vec![4.0, 5.0, 6.0].iter().map(|&x| f16::from_f32(x)).collect();
43
- //! let half_cos = f16::cos(&half_a, &half_b);
43
+ //! let half_cos_dist = f16::cos(&half_a, &half_b);
44
44
  //!
45
45
  //! // Work with brain floats
46
46
  //! let brain_a: Vec<bf16> = vec![1.0, 2.0, 3.0].iter().map(|&x| bf16::from_f32(x)).collect();
47
47
  //! let brain_b: Vec<bf16> = vec![4.0, 5.0, 6.0].iter().map(|&x| bf16::from_f32(x)).collect();
48
- //! let brain_cos = bf16::cos(&brain_a, &brain_b);
48
+ //! let brain_cos_dist = bf16::cos(&brain_a, &brain_b);
49
49
  //!
50
50
  //! // Direct bit manipulation
51
51
  //! let half = f16::from_f32(3.14);
@@ -57,14 +57,14 @@
57
57
  //!
58
58
  //! The `SpatialSimilarity` trait covers following methods:
59
59
  //!
60
- //! - `cosine(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes cosine similarity between two slices.
60
+ //! - `cosine(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes cosine distance (1 - similarity) between two slices.
61
61
  //! - `dot(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes dot product distance between two slices.
62
62
  //! - `sqeuclidean(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes squared Euclidean distance between two slices.
63
63
  //!
64
64
  //! The `BinarySimilarity` trait covers following methods:
65
65
  //!
66
66
  //! - `hamming(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes Hamming distance between two slices.
67
- //! - `jaccard(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes Jaccard index between two slices.
67
+ //! - `jaccard(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes Jaccard distance between two slices.
68
68
  //!
69
69
  //! The `ProbabilitySimilarity` trait covers following methods:
70
70
  //!
@@ -77,6 +77,12 @@
77
77
  pub type Distance = f64;
78
78
  pub type ComplexProduct = (f64, f64);
79
79
 
80
+ /// Compatibility function for pre 1.85 Rust versions lacking `f32::abs`.
81
+ #[inline(always)]
82
+ fn f32_abs_compat(x: f32) -> f32 {
83
+ f32::from_bits(x.to_bits() & 0x7FFF_FFFF)
84
+ }
85
+
80
86
  #[link(name = "simsimd")]
81
87
  extern "C" {
82
88
 
@@ -194,10 +200,10 @@ pub struct f16(pub u16);
194
200
  impl f16 {
195
201
  /// Positive zero.
196
202
  pub const ZERO: Self = f16(0);
197
-
203
+
198
204
  /// Positive one.
199
205
  pub const ONE: Self = f16(0x3C00);
200
-
206
+
201
207
  /// Negative one.
202
208
  pub const NEG_ONE: Self = f16(0xBC00);
203
209
 
@@ -251,11 +257,11 @@ impl f16 {
251
257
  /// Returns the absolute value of self.
252
258
  #[inline(always)]
253
259
  pub fn abs(self) -> Self {
254
- Self::from_f32(self.to_f32().abs())
260
+ Self::from_f32(f32_abs_compat(self.to_f32()))
255
261
  }
256
262
 
257
263
  /// Returns the largest integer less than or equal to a number.
258
- ///
264
+ ///
259
265
  /// This method is only available when the `std` feature is enabled.
260
266
  #[cfg(feature = "std")]
261
267
  #[inline(always)]
@@ -264,7 +270,7 @@ impl f16 {
264
270
  }
265
271
 
266
272
  /// Returns the smallest integer greater than or equal to a number.
267
- ///
273
+ ///
268
274
  /// This method is only available when the `std` feature is enabled.
269
275
  #[cfg(feature = "std")]
270
276
  #[inline(always)]
@@ -273,7 +279,7 @@ impl f16 {
273
279
  }
274
280
 
275
281
  /// Returns the nearest integer to a number. Round half-way cases away from 0.0.
276
- ///
282
+ ///
277
283
  /// This method is only available when the `std` feature is enabled.
278
284
  #[cfg(feature = "std")]
279
285
  #[inline(always)]
@@ -368,10 +374,10 @@ pub struct bf16(pub u16);
368
374
  impl bf16 {
369
375
  /// Positive zero.
370
376
  pub const ZERO: Self = bf16(0);
371
-
377
+
372
378
  /// Positive one.
373
379
  pub const ONE: Self = bf16(0x3F80);
374
-
380
+
375
381
  /// Negative one.
376
382
  pub const NEG_ONE: Self = bf16(0xBF80);
377
383
 
@@ -425,11 +431,11 @@ impl bf16 {
425
431
  /// Returns the absolute value of self.
426
432
  #[inline(always)]
427
433
  pub fn abs(self) -> Self {
428
- Self::from_f32(self.to_f32().abs())
434
+ Self::from_f32(f32_abs_compat(self.to_f32()))
429
435
  }
430
436
 
431
437
  /// Returns the largest integer less than or equal to a number.
432
- ///
438
+ ///
433
439
  /// This method is only available when the `std` feature is enabled.
434
440
  #[cfg(feature = "std")]
435
441
  #[inline(always)]
@@ -438,7 +444,7 @@ impl bf16 {
438
444
  }
439
445
 
440
446
  /// Returns the smallest integer greater than or equal to a number.
441
- ///
447
+ ///
442
448
  /// This method is only available when the `std` feature is enabled.
443
449
  #[cfg(feature = "std")]
444
450
  #[inline(always)]
@@ -447,7 +453,7 @@ impl bf16 {
447
453
  }
448
454
 
449
455
  /// Returns the nearest integer to a number. Round half-way cases away from 0.0.
450
- ///
456
+ ///
451
457
  /// This method is only available when the `std` feature is enabled.
452
458
  #[cfg(feature = "std")]
453
459
  #[inline(always)]
@@ -596,8 +602,8 @@ pub mod capabilities {
596
602
  ///
597
603
  /// # Returns
598
604
  ///
599
- /// Returns `true` if dynamic dispatch is enabled, `false` otherwise.
600
- /// Currently always returns `false` as dynamic dispatch is not implemented.
605
+ /// Returns `true` when the C backend is compiled with dynamic dispatch
606
+ /// (default for this crate via `build.rs`), otherwise `false`.
601
607
  pub fn uses_dynamic_dispatch() -> bool {
602
608
  unsafe { crate::simsimd_uses_dynamic_dispatch() != 0 }
603
609
  }
@@ -605,19 +611,21 @@ pub mod capabilities {
605
611
 
606
612
  /// `SpatialSimilarity` provides a set of trait methods for computing similarity
607
613
  /// or distance between spatial data vectors in SIMD (Single Instruction, Multiple Data) context.
608
- /// These methods can be used to calculate metrics like cosine similarity, dot product,
614
+ /// These methods can be used to calculate metrics like cosine distance, dot product,
609
615
  /// and squared Euclidean distance between two slices of data.
610
616
  ///
611
617
  /// Each method takes two slices of data (a and b) and returns an Option<Distance>.
612
618
  /// The result is `None` if the slices are not of the same length, as these operations
613
619
  /// require one-to-one correspondence between the elements of the slices.
614
- /// Otherwise, it returns the computed similarity or distance as `Some(f32)`.
620
+ /// Otherwise, it returns the computed similarity or distance as `Some(f64)`.
621
+ /// Convenience methods like `cosine`/`sqeuclidean` delegate to the core methods
622
+ /// `cos`/`l2sq` implemented by this trait.
615
623
  pub trait SpatialSimilarity
616
624
  where
617
625
  Self: Sized,
618
626
  {
619
- /// Computes the cosine similarity between two slices.
620
- /// The cosine similarity is a measure of similarity between two non-zero vectors
627
+ /// Computes the cosine distance between two slices.
628
+ /// The cosine distance is 1 minus the cosine similarity between two non-zero vectors
621
629
  /// of an dot product space that measures the cosine of the angle between them.
622
630
  fn cos(a: &[Self], b: &[Self]) -> Option<Distance>;
623
631
 
@@ -659,8 +667,8 @@ where
659
667
  SpatialSimilarity::dot(a, b)
660
668
  }
661
669
 
662
- /// Computes the cosine similarity between two slices.
663
- /// The cosine similarity is a measure of similarity between two non-zero vectors
670
+ /// Computes the cosine distance between two slices.
671
+ /// The cosine distance is 1 minus the cosine similarity between two non-zero vectors
664
672
  /// of an dot product space that measures the cosine of the angle between them.
665
673
  fn cosine(a: &[Self], b: &[Self]) -> Option<Distance> {
666
674
  SpatialSimilarity::cos(a, b)
@@ -1121,7 +1129,7 @@ impl ProbabilitySimilarity for f64 {
1121
1129
 
1122
1130
  impl ComplexProducts for f16 {
1123
1131
  fn dot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
1124
- if a.len() != b.len() {
1132
+ if a.len() != b.len() || a.len() % 2 != 0 {
1125
1133
  return None;
1126
1134
  }
1127
1135
  // Prepare the output array where the real and imaginary parts will be stored
@@ -1130,26 +1138,28 @@ impl ComplexProducts for f16 {
1130
1138
  // Explicitly cast `*const f16` to `*const u16`
1131
1139
  let a_ptr = a.as_ptr() as *const u16;
1132
1140
  let b_ptr = b.as_ptr() as *const u16;
1133
- unsafe { simsimd_dot_f16c(a_ptr, b_ptr, a.len(), product_ptr) };
1141
+ // The C function expects the number of complex pairs, not the total number of f16 elements
1142
+ unsafe { simsimd_dot_f16c(a_ptr, b_ptr, a.len() / 2, product_ptr) };
1134
1143
  Some((product[0], product[1]))
1135
1144
  }
1136
1145
 
1137
1146
  fn vdot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
1138
- if a.len() != b.len() {
1147
+ if a.len() != b.len() || a.len() % 2 != 0 {
1139
1148
  return None;
1140
1149
  }
1141
1150
  let mut product: [Distance; 2] = [0.0, 0.0];
1142
1151
  let product_ptr: *mut Distance = &mut product[0] as *mut _;
1143
1152
  let a_ptr = a.as_ptr() as *const u16;
1144
1153
  let b_ptr = b.as_ptr() as *const u16;
1145
- unsafe { simsimd_vdot_f16c(a_ptr, b_ptr, a.len(), product_ptr) };
1154
+ // The C function expects the number of complex pairs, not the total number of f16 elements
1155
+ unsafe { simsimd_vdot_f16c(a_ptr, b_ptr, a.len() / 2, product_ptr) };
1146
1156
  Some((product[0], product[1]))
1147
1157
  }
1148
1158
  }
1149
1159
 
1150
1160
  impl ComplexProducts for bf16 {
1151
1161
  fn dot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
1152
- if a.len() != b.len() {
1162
+ if a.len() != b.len() || a.len() % 2 != 0 {
1153
1163
  return None;
1154
1164
  }
1155
1165
  // Prepare the output array where the real and imaginary parts will be stored
@@ -1158,12 +1168,13 @@ impl ComplexProducts for bf16 {
1158
1168
  // Explicitly cast `*const bf16` to `*const u16`
1159
1169
  let a_ptr = a.as_ptr() as *const u16;
1160
1170
  let b_ptr = b.as_ptr() as *const u16;
1161
- unsafe { simsimd_dot_bf16c(a_ptr, b_ptr, a.len(), product_ptr) };
1171
+ // The C function expects the number of complex pairs, not the total number of bf16 elements
1172
+ unsafe { simsimd_dot_bf16c(a_ptr, b_ptr, a.len() / 2, product_ptr) };
1162
1173
  Some((product[0], product[1]))
1163
1174
  }
1164
1175
 
1165
1176
  fn vdot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
1166
- if a.len() != b.len() {
1177
+ if a.len() != b.len() || a.len() % 2 != 0 {
1167
1178
  return None;
1168
1179
  }
1169
1180
  // Prepare the output array where the real and imaginary parts will be stored
@@ -1172,51 +1183,56 @@ impl ComplexProducts for bf16 {
1172
1183
  // Explicitly cast `*const bf16` to `*const u16`
1173
1184
  let a_ptr = a.as_ptr() as *const u16;
1174
1185
  let b_ptr = b.as_ptr() as *const u16;
1175
- unsafe { simsimd_vdot_bf16c(a_ptr, b_ptr, a.len(), product_ptr) };
1186
+ // The C function expects the number of complex pairs, not the total number of bf16 elements
1187
+ unsafe { simsimd_vdot_bf16c(a_ptr, b_ptr, a.len() / 2, product_ptr) };
1176
1188
  Some((product[0], product[1]))
1177
1189
  }
1178
1190
  }
1179
1191
 
1180
1192
  impl ComplexProducts for f32 {
1181
1193
  fn dot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
1182
- if a.len() != b.len() {
1194
+ if a.len() != b.len() || a.len() % 2 != 0 {
1183
1195
  return None;
1184
1196
  }
1185
1197
  let mut product: [Distance; 2] = [0.0, 0.0];
1186
1198
  let product_ptr: *mut Distance = &mut product[0] as *mut _;
1187
- unsafe { simsimd_dot_f32c(a.as_ptr(), b.as_ptr(), a.len(), product_ptr) };
1199
+ // The C function expects the number of complex pairs, not the total number of floats
1200
+ unsafe { simsimd_dot_f32c(a.as_ptr(), b.as_ptr(), a.len() / 2, product_ptr) };
1188
1201
  Some((product[0], product[1]))
1189
1202
  }
1190
1203
 
1191
1204
  fn vdot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
1192
- if a.len() != b.len() {
1205
+ if a.len() != b.len() || a.len() % 2 != 0 {
1193
1206
  return None;
1194
1207
  }
1195
1208
  let mut product: [Distance; 2] = [0.0, 0.0];
1196
1209
  let product_ptr: *mut Distance = &mut product[0] as *mut _;
1197
- unsafe { simsimd_vdot_f32c(a.as_ptr(), b.as_ptr(), a.len(), product_ptr) };
1210
+ // The C function expects the number of complex pairs, not the total number of floats
1211
+ unsafe { simsimd_vdot_f32c(a.as_ptr(), b.as_ptr(), a.len() / 2, product_ptr) };
1198
1212
  Some((product[0], product[1]))
1199
1213
  }
1200
1214
  }
1201
1215
 
1202
1216
  impl ComplexProducts for f64 {
1203
1217
  fn dot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
1204
- if a.len() != b.len() {
1218
+ if a.len() != b.len() || a.len() % 2 != 0 {
1205
1219
  return None;
1206
1220
  }
1207
1221
  let mut product: [Distance; 2] = [0.0, 0.0];
1208
1222
  let product_ptr: *mut Distance = &mut product[0] as *mut _;
1209
- unsafe { simsimd_dot_f64c(a.as_ptr(), b.as_ptr(), a.len(), product_ptr) };
1223
+ // The C function expects the number of complex pairs, not the total number of floats
1224
+ unsafe { simsimd_dot_f64c(a.as_ptr(), b.as_ptr(), a.len() / 2, product_ptr) };
1210
1225
  Some((product[0], product[1]))
1211
1226
  }
1212
1227
 
1213
1228
  fn vdot(a: &[Self], b: &[Self]) -> Option<ComplexProduct> {
1214
- if a.len() != b.len() {
1229
+ if a.len() != b.len() || a.len() % 2 != 0 {
1215
1230
  return None;
1216
1231
  }
1217
1232
  let mut product: [Distance; 2] = [0.0, 0.0];
1218
1233
  let product_ptr: *mut Distance = &mut product[0] as *mut _;
1219
- unsafe { simsimd_vdot_f64c(a.as_ptr(), b.as_ptr(), a.len(), product_ptr) };
1234
+ // The C function expects the number of complex pairs, not the total number of floats
1235
+ unsafe { simsimd_vdot_f64c(a.as_ptr(), b.as_ptr(), a.len() / 2, product_ptr) };
1220
1236
  Some((product[0], product[1]))
1221
1237
  }
1222
1238
  }
@@ -1228,7 +1244,7 @@ mod tests {
1228
1244
  use half::f16 as HalfF16;
1229
1245
 
1230
1246
  #[test]
1231
- fn test_hardware_features_detection() {
1247
+ fn hardware_features_detection() {
1232
1248
  let uses_arm = capabilities::uses_neon() || capabilities::uses_sve();
1233
1249
  let uses_x86 = capabilities::uses_haswell()
1234
1250
  || capabilities::uses_skylake()
@@ -1246,7 +1262,13 @@ mod tests {
1246
1262
  }
1247
1263
 
1248
1264
  println!("- uses_neon: {}", capabilities::uses_neon());
1265
+ println!("- uses_neon_f16: {}", capabilities::uses_neon_f16());
1266
+ println!("- uses_neon_bf16: {}", capabilities::uses_neon_bf16());
1267
+ println!("- uses_neon_i8: {}", capabilities::uses_neon_i8());
1249
1268
  println!("- uses_sve: {}", capabilities::uses_sve());
1269
+ println!("- uses_sve_f16: {}", capabilities::uses_sve_f16());
1270
+ println!("- uses_sve_bf16: {}", capabilities::uses_sve_bf16());
1271
+ println!("- uses_sve_i8: {}", capabilities::uses_sve_i8());
1250
1272
  println!("- uses_haswell: {}", capabilities::uses_haswell());
1251
1273
  println!("- uses_skylake: {}", capabilities::uses_skylake());
1252
1274
  println!("- uses_ice: {}", capabilities::uses_ice());
@@ -1265,127 +1287,109 @@ mod tests {
1265
1287
  }
1266
1288
 
1267
1289
  #[test]
1268
- fn test_cos_i8() {
1290
+ fn cos_i8() {
1269
1291
  let a = &[3, 97, 127];
1270
1292
  let b = &[3, 97, 127];
1271
1293
 
1272
1294
  if let Some(result) = SpatialSimilarity::cosine(a, b) {
1273
- println!("The result of cos_i8 is {:.8}", result);
1274
1295
  assert_almost_equal(0.00012027938, result, 0.01);
1275
1296
  }
1276
1297
  }
1277
1298
 
1278
1299
  #[test]
1279
- fn test_cos_f32() {
1300
+ fn cos_f32() {
1280
1301
  let a = &[1.0, 2.0, 3.0];
1281
1302
  let b = &[4.0, 5.0, 6.0];
1282
1303
 
1283
1304
  if let Some(result) = SpatialSimilarity::cosine(a, b) {
1284
- println!("The result of cos_f32 is {:.8}", result);
1285
1305
  assert_almost_equal(0.025, result, 0.01);
1286
1306
  }
1287
1307
  }
1288
1308
 
1289
1309
  #[test]
1290
- fn test_dot_i8() {
1310
+ fn dot_i8() {
1291
1311
  let a = &[1, 2, 3];
1292
1312
  let b = &[4, 5, 6];
1293
1313
 
1294
1314
  if let Some(result) = SpatialSimilarity::dot(a, b) {
1295
- println!("The result of dot_i8 is {:.8}", result);
1296
1315
  assert_almost_equal(32.0, result, 0.01);
1297
1316
  }
1298
1317
  }
1299
1318
 
1300
1319
  #[test]
1301
- fn test_dot_f32() {
1320
+ fn dot_f32() {
1302
1321
  let a = &[1.0, 2.0, 3.0];
1303
1322
  let b = &[4.0, 5.0, 6.0];
1304
1323
 
1305
1324
  if let Some(result) = SpatialSimilarity::dot(a, b) {
1306
- println!("The result of dot_f32 is {:.8}", result);
1307
1325
  assert_almost_equal(32.0, result, 0.01);
1308
1326
  }
1309
1327
  }
1310
1328
 
1311
1329
  #[test]
1312
- fn test_dot_f32_complex() {
1330
+ fn dot_f32_complex() {
1313
1331
  // Let's consider these as complex numbers where every pair is (real, imaginary)
1314
1332
  let a: &[f32; 4] = &[1.0, 2.0, 3.0, 4.0]; // Represents two complex numbers: 1+2i, 3+4i
1315
1333
  let b: &[f32; 4] = &[5.0, 6.0, 7.0, 8.0]; // Represents two complex numbers: 5+6i, 7+8i
1316
1334
 
1317
1335
  if let Some((real, imag)) = ComplexProducts::dot(a, b) {
1318
- println!(
1319
- "The result of dot_f32_complex is real: {:.8}, imag: {:.8}",
1320
- real, imag
1321
- );
1322
- // These values should be replaced with the expected real and imaginary parts of the result
1323
- assert_almost_equal(-18.0, real, 0.01); // Corrected expected real part
1324
- assert_almost_equal(68.0, imag, 0.01); // Corrected expected imaginary part
1336
+ assert_almost_equal(-18.0, real, 0.01);
1337
+ assert_almost_equal(68.0, imag, 0.01);
1325
1338
  }
1326
1339
  }
1327
1340
 
1328
1341
  #[test]
1329
- fn test_vdot_f32_complex() {
1342
+ fn vdot_f32_complex() {
1330
1343
  // Here we're assuming a similar setup to the previous test, but for the Hermitian (conjugate) dot product
1331
1344
  let a: &[f32; 4] = &[1.0, 2.0, 3.0, 4.0]; // Represents two complex numbers: 1+2i, 3+4i
1332
1345
  let b: &[f32; 4] = &[5.0, 6.0, 7.0, 8.0]; // Represents two complex numbers: 5+6i, 7+8i
1333
1346
 
1334
1347
  if let Some((real, imag)) = ComplexProducts::vdot(a, b) {
1335
- println!(
1336
- "The result of vdot_f32_complex is real: {:.8}, imag: {:.8}",
1337
- real, imag
1338
- );
1339
- // Replace these with the actual expected values
1340
- assert_almost_equal(70.0, real, 0.01); // Example expected real part
1341
- assert_almost_equal(-8.0, imag, 0.01); // Example expected imaginary part
1348
+ assert_almost_equal(70.0, real, 0.01);
1349
+ assert_almost_equal(-8.0, imag, 0.01);
1342
1350
  }
1343
1351
  }
1344
1352
 
1345
1353
  #[test]
1346
- fn test_l2sq_i8() {
1354
+ fn l2sq_i8() {
1347
1355
  let a = &[1, 2, 3];
1348
1356
  let b = &[4, 5, 6];
1349
1357
 
1350
1358
  if let Some(result) = SpatialSimilarity::sqeuclidean(a, b) {
1351
- println!("The result of l2sq_i8 is {:.8}", result);
1352
1359
  assert_almost_equal(27.0, result, 0.01);
1353
1360
  }
1354
1361
  }
1355
1362
 
1356
1363
  #[test]
1357
- fn test_l2sq_f32() {
1364
+ fn l2sq_f32() {
1358
1365
  let a = &[1.0, 2.0, 3.0];
1359
1366
  let b = &[4.0, 5.0, 6.0];
1360
1367
 
1361
1368
  if let Some(result) = SpatialSimilarity::sqeuclidean(a, b) {
1362
- println!("The result of l2sq_f32 is {:.8}", result);
1363
1369
  assert_almost_equal(27.0, result, 0.01);
1364
1370
  }
1365
1371
  }
1366
1372
 
1367
1373
  #[test]
1368
- fn test_l2_f32() {
1374
+ fn l2_f32() {
1369
1375
  let a: &[f32; 3] = &[1.0, 2.0, 3.0];
1370
1376
  let b: &[f32; 3] = &[4.0, 5.0, 6.0];
1371
1377
  if let Some(result) = SpatialSimilarity::euclidean(a, b) {
1372
- println!("The result of l2_f32 is {:.8}", result);
1373
1378
  assert_almost_equal(5.2, result, 0.01);
1374
1379
  }
1375
1380
  }
1376
1381
 
1377
1382
  #[test]
1378
- fn test_l2_f64() {
1383
+ fn l2_f64() {
1379
1384
  let a: &[f64; 3] = &[1.0, 2.0, 3.0];
1380
1385
  let b: &[f64; 3] = &[4.0, 5.0, 6.0];
1381
1386
  if let Some(result) = SpatialSimilarity::euclidean(a, b) {
1382
- println!("The result of l2_f64 is {:.8}", result);
1383
1387
  assert_almost_equal(5.2, result, 0.01);
1384
1388
  }
1385
1389
  }
1386
1390
 
1387
1391
  #[test]
1388
- fn test_l2_f16() {
1392
+ fn l2_f16() {
1389
1393
  let a_half: Vec<HalfF16> = vec![1.0, 2.0, 3.0]
1390
1394
  .iter()
1391
1395
  .map(|&x| HalfF16::from_f32(x))
@@ -1401,110 +1405,98 @@ mod tests {
1401
1405
  unsafe { std::slice::from_raw_parts(b_half.as_ptr() as *const f16, b_half.len()) };
1402
1406
 
1403
1407
  if let Some(result) = SpatialSimilarity::euclidean(&a_simsimd, &b_simsimd) {
1404
- println!("The result of l2_f16 is {:.8}", result);
1405
1408
  assert_almost_equal(5.2, result, 0.01);
1406
1409
  }
1407
1410
  }
1408
1411
 
1409
1412
  #[test]
1410
- fn test_l2_i8() {
1413
+ fn l2_i8() {
1411
1414
  let a = &[1, 2, 3];
1412
1415
  let b = &[4, 5, 6];
1413
1416
 
1414
1417
  if let Some(result) = SpatialSimilarity::euclidean(a, b) {
1415
- println!("The result of l2_i8 is {:.8}", result);
1416
1418
  assert_almost_equal(5.2, result, 0.01);
1417
1419
  }
1418
1420
  }
1419
1421
  // Adding new tests for bit-level distances
1420
1422
  #[test]
1421
- fn test_hamming_u8() {
1422
- let a = &[0b01010101, 0b11110000, 0b10101010]; // Binary representations for clarity
1423
+ fn hamming_u8() {
1424
+ let a = &[0b01010101, 0b11110000, 0b10101010];
1423
1425
  let b = &[0b01010101, 0b11110000, 0b10101010];
1424
1426
 
1425
1427
  if let Some(result) = BinarySimilarity::hamming(a, b) {
1426
- println!("The result of hamming_u8 is {:.8}", result);
1427
- assert_almost_equal(0.0, result, 0.01); // Perfect match
1428
+ assert_almost_equal(0.0, result, 0.01);
1428
1429
  }
1429
1430
  }
1430
1431
 
1431
1432
  #[test]
1432
- fn test_jaccard_u8() {
1433
+ fn jaccard_u8() {
1433
1434
  // For binary data, treat each byte as a set of bits
1434
1435
  let a = &[0b11110000, 0b00001111, 0b10101010];
1435
1436
  let b = &[0b11110000, 0b00001111, 0b01010101];
1436
1437
 
1437
1438
  if let Some(result) = BinarySimilarity::jaccard(a, b) {
1438
- println!("The result of jaccard_u8 is {:.8}", result);
1439
- assert_almost_equal(0.5, result, 0.01); // Example value
1439
+ assert_almost_equal(0.5, result, 0.01);
1440
1440
  }
1441
1441
  }
1442
1442
 
1443
1443
  // Adding new tests for probability similarities
1444
1444
  #[test]
1445
- fn test_js_f32() {
1445
+ fn js_f32() {
1446
1446
  let a: &[f32; 3] = &[0.1, 0.9, 0.0];
1447
1447
  let b: &[f32; 3] = &[0.2, 0.8, 0.0];
1448
1448
 
1449
1449
  if let Some(result) = ProbabilitySimilarity::jensenshannon(a, b) {
1450
- println!("The result of js_f32 is {:.8}", result);
1451
- assert_almost_equal(0.099, result, 0.01); // Example value
1450
+ assert_almost_equal(0.099, result, 0.01);
1452
1451
  }
1453
1452
  }
1454
1453
 
1455
1454
  #[test]
1456
- fn test_kl_f32() {
1455
+ fn kl_f32() {
1457
1456
  let a: &[f32; 3] = &[0.1, 0.9, 0.0];
1458
1457
  let b: &[f32; 3] = &[0.2, 0.8, 0.0];
1459
1458
 
1460
1459
  if let Some(result) = ProbabilitySimilarity::kullbackleibler(a, b) {
1461
- println!("The result of kl_f32 is {:.8}", result);
1462
- assert_almost_equal(0.036, result, 0.01); // Example value
1460
+ assert_almost_equal(0.036, result, 0.01);
1463
1461
  }
1464
1462
  }
1465
1463
 
1466
1464
  #[test]
1467
- fn test_cos_f16_same() {
1465
+ fn cos_f16_same() {
1468
1466
  // Assuming these u16 values represent f16 bit patterns, and they are identical
1469
1467
  let a_u16: &[u16] = &[15360, 16384, 17408]; // Corresponding to some f16 values
1470
1468
  let b_u16: &[u16] = &[15360, 16384, 17408]; // Same as above for simplicity
1471
1469
 
1472
1470
  // Reinterpret cast from &[u16] to &[f16]
1473
- // SAFETY: This is safe as long as the representations are guaranteed to be identical,
1474
- // which they are for transparent structs wrapping the same type.
1475
1471
  let a_f16: &[f16] =
1476
1472
  unsafe { std::slice::from_raw_parts(a_u16.as_ptr() as *const f16, a_u16.len()) };
1477
1473
  let b_f16: &[f16] =
1478
1474
  unsafe { std::slice::from_raw_parts(b_u16.as_ptr() as *const f16, b_u16.len()) };
1479
1475
 
1480
1476
  if let Some(result) = SpatialSimilarity::cosine(a_f16, b_f16) {
1481
- println!("The result of cos_f16 is {:.8}", result);
1482
- assert_almost_equal(0.0, result, 0.01); // Example value, adjust according to actual expected value
1477
+ assert_almost_equal(0.0, result, 0.01);
1483
1478
  }
1484
1479
  }
1485
1480
 
1486
1481
  #[test]
1487
- fn test_cos_bf16_same() {
1482
+ fn cos_bf16_same() {
1488
1483
  // Assuming these u16 values represent bf16 bit patterns, and they are identical
1489
1484
  let a_u16: &[u16] = &[15360, 16384, 17408]; // Corresponding to some bf16 values
1490
1485
  let b_u16: &[u16] = &[15360, 16384, 17408]; // Same as above for simplicity
1491
1486
 
1492
1487
  // Reinterpret cast from &[u16] to &[bf16]
1493
- // SAFETY: This is safe as long as the representations are guaranteed to be identical,
1494
- // which they are for transparent structs wrapping the same type.
1495
1488
  let a_bf16: &[bf16] =
1496
1489
  unsafe { std::slice::from_raw_parts(a_u16.as_ptr() as *const bf16, a_u16.len()) };
1497
1490
  let b_bf16: &[bf16] =
1498
1491
  unsafe { std::slice::from_raw_parts(b_u16.as_ptr() as *const bf16, b_u16.len()) };
1499
1492
 
1500
1493
  if let Some(result) = SpatialSimilarity::cosine(a_bf16, b_bf16) {
1501
- println!("The result of cos_bf16 is {:.8}", result);
1502
- assert_almost_equal(0.0, result, 0.01); // Example value, adjust according to actual expected value
1494
+ assert_almost_equal(0.0, result, 0.01);
1503
1495
  }
1504
1496
  }
1505
1497
 
1506
1498
  #[test]
1507
- fn test_cos_f16_interop() {
1499
+ fn cos_f16_interop() {
1508
1500
  let a_half: Vec<HalfF16> = vec![1.0, 2.0, 3.0]
1509
1501
  .iter()
1510
1502
  .map(|&x| HalfF16::from_f32(x))
@@ -1523,15 +1515,12 @@ mod tests {
1523
1515
 
1524
1516
  // Use the reinterpret-casted slices with your SpatialSimilarity implementation
1525
1517
  if let Some(result) = SpatialSimilarity::cosine(a_simsimd, b_simsimd) {
1526
- // Expected value might need adjustment depending on actual cosine functionality
1527
- // Assuming identical vectors yield cosine similarity of 1.0
1528
- println!("The result of cos_f16 (interop) is {:.8}", result);
1529
1518
  assert_almost_equal(0.025, result, 0.01);
1530
1519
  }
1531
1520
  }
1532
1521
 
1533
1522
  #[test]
1534
- fn test_cos_bf16_interop() {
1523
+ fn cos_bf16_interop() {
1535
1524
  let a_half: Vec<HalfBF16> = vec![1.0, 2.0, 3.0]
1536
1525
  .iter()
1537
1526
  .map(|&x| HalfBF16::from_f32(x))
@@ -1550,44 +1539,38 @@ mod tests {
1550
1539
 
1551
1540
  // Use the reinterpret-casted slices with your SpatialSimilarity implementation
1552
1541
  if let Some(result) = SpatialSimilarity::cosine(a_simsimd, b_simsimd) {
1553
- // Expected value might need adjustment depending on actual cosine functionality
1554
- // Assuming identical vectors yield cosine similarity of 1.0
1555
- println!("The result of cos_bf16 (interop) is {:.8}", result);
1556
1542
  assert_almost_equal(0.025, result, 0.01);
1557
1543
  }
1558
1544
  }
1559
1545
 
1560
1546
  #[test]
1561
- fn test_intersect_u16() {
1547
+ fn intersect_u16() {
1562
1548
  {
1563
1549
  let a_u16: &[u16] = &[153, 16384, 17408];
1564
- let b_u16: &[u16] = &[15360, 16384, 7408];
1550
+ let b_u16: &[u16] = &[7408, 15360, 16384];
1565
1551
 
1566
1552
  if let Some(result) = Sparse::intersect(a_u16, b_u16) {
1567
- println!("The result of intersect_u16 is {:.8}", result);
1568
1553
  assert_almost_equal(1.0, result, 0.0001);
1569
1554
  }
1570
1555
  }
1571
1556
 
1572
1557
  {
1573
- let a_u16: &[u16] = &[153, 11638, 08];
1574
- let b_u16: &[u16] = &[15360, 16384, 7408];
1558
+ let a_u16: &[u16] = &[8, 153, 11638];
1559
+ let b_u16: &[u16] = &[7408, 15360, 16384];
1575
1560
 
1576
1561
  if let Some(result) = Sparse::intersect(a_u16, b_u16) {
1577
- println!("The result of intersect_u16 is {:.8}", result);
1578
1562
  assert_almost_equal(0.0, result, 0.0001);
1579
1563
  }
1580
1564
  }
1581
1565
  }
1582
1566
 
1583
1567
  #[test]
1584
- fn test_intersect_u32() {
1568
+ fn intersect_u32() {
1585
1569
  {
1586
1570
  let a_u32: &[u32] = &[11, 153];
1587
1571
  let b_u32: &[u32] = &[11, 153, 7408, 16384];
1588
1572
 
1589
1573
  if let Some(result) = Sparse::intersect(a_u32, b_u32) {
1590
- println!("The result of intersect_u32 is {:.8}", result);
1591
1574
  assert_almost_equal(2.0, result, 0.0001);
1592
1575
  }
1593
1576
  }
@@ -1597,34 +1580,194 @@ mod tests {
1597
1580
  let b_u32: &[u32] = &[153, 7408, 11638];
1598
1581
 
1599
1582
  if let Some(result) = Sparse::intersect(a_u32, b_u32) {
1600
- println!("The result of intersect_u32 is {:.8}", result);
1601
1583
  assert_almost_equal(3.0, result, 0.0001);
1602
1584
  }
1603
1585
  }
1604
1586
  }
1605
1587
 
1588
+ /// Reference implementation of set intersection using Rust's standard library
1589
+ fn reference_intersect<T: Ord>(a: &[T], b: &[T]) -> usize {
1590
+ let mut a_iter = a.iter();
1591
+ let mut b_iter = b.iter();
1592
+ let mut a_current = a_iter.next();
1593
+ let mut b_current = b_iter.next();
1594
+ let mut count = 0;
1595
+
1596
+ while let (Some(a_val), Some(b_val)) = (a_current, b_current) {
1597
+ match a_val.cmp(b_val) {
1598
+ core::cmp::Ordering::Less => a_current = a_iter.next(),
1599
+ core::cmp::Ordering::Greater => b_current = b_iter.next(),
1600
+ core::cmp::Ordering::Equal => {
1601
+ count += 1;
1602
+ a_current = a_iter.next();
1603
+ b_current = b_iter.next();
1604
+ }
1605
+ }
1606
+ }
1607
+ count
1608
+ }
1609
+
1610
+ /// Generate test arrays with various sizes and patterns for intersection testing
1611
+ /// Includes empty, small, medium, large arrays with different overlap characteristics
1612
+ fn generate_intersection_test_arrays<T>() -> Vec<Vec<T>>
1613
+ where
1614
+ T: core::convert::TryFrom<u32> + Copy,
1615
+ <T as core::convert::TryFrom<u32>>::Error: core::fmt::Debug,
1616
+ {
1617
+ vec![
1618
+ // Empty array
1619
+ vec![],
1620
+ // Single element
1621
+ vec![T::try_from(42).unwrap()],
1622
+ // Very small arrays (< 16 elements) - tests serial fallback
1623
+ vec![
1624
+ T::try_from(1).unwrap(),
1625
+ T::try_from(5).unwrap(),
1626
+ T::try_from(10).unwrap(),
1627
+ ],
1628
+ vec![
1629
+ T::try_from(2).unwrap(),
1630
+ T::try_from(4).unwrap(),
1631
+ T::try_from(6).unwrap(),
1632
+ T::try_from(8).unwrap(),
1633
+ T::try_from(10).unwrap(),
1634
+ T::try_from(12).unwrap(),
1635
+ T::try_from(14).unwrap(),
1636
+ ],
1637
+ // Small arrays (< 32 elements) - boundary case for Turin
1638
+ (0..14).map(|x| T::try_from(x * 10).unwrap()).collect(),
1639
+ (5..20).map(|x| T::try_from(x * 10).unwrap()).collect(),
1640
+ // Medium arrays (32-64 elements) - tests one or two SIMD iterations
1641
+ (0..40).map(|x| T::try_from(x * 2).unwrap()).collect(),
1642
+ (10..50).map(|x| T::try_from(x * 2).unwrap()).collect(), // 50% overlap with previous
1643
+ (0..45).map(|x| T::try_from(x * 3).unwrap()).collect(), // Different stride
1644
+ // Large arrays (> 64 elements) - tests main SIMD loop
1645
+ (0..100).map(|x| T::try_from(x * 2).unwrap()).collect(),
1646
+ (50..150).map(|x| T::try_from(x * 2).unwrap()).collect(), // 50% overlap
1647
+ (0..100).map(|x| T::try_from(x * 5).unwrap()).collect(), // Sparse overlap
1648
+ (0..150)
1649
+ .filter(|x| x % 7 == 0)
1650
+ .map(|x| T::try_from(x).unwrap())
1651
+ .collect(),
1652
+ // Very large arrays (> 256 elements) - stress test
1653
+ (0..500).map(|x| T::try_from(x * 3).unwrap()).collect(),
1654
+ (100..600).map(|x| T::try_from(x * 3).unwrap()).collect(), // Large overlap
1655
+ (0..600).map(|x| T::try_from(x * 7).unwrap()).collect(), // Minimal overlap
1656
+ // Edge cases: no overlap at all
1657
+ (0..50).map(|x| T::try_from(x * 2).unwrap()).collect(),
1658
+ (1000..1050).map(|x| T::try_from(x * 2).unwrap()).collect(), // Completely disjoint
1659
+ // Dense arrays at boundaries
1660
+ (0..16).map(|x| T::try_from(x).unwrap()).collect(), // Exactly 16 elements
1661
+ (0..32).map(|x| T::try_from(x).unwrap()).collect(), // Exactly 32 elements
1662
+ (0..64).map(|x| T::try_from(x).unwrap()).collect(), // Exactly 64 elements
1663
+ ]
1664
+ }
1665
+
1606
1666
  #[test]
1607
- fn test_f16_arithmetic() {
1667
+ fn intersect_u32_comprehensive() {
1668
+ let test_arrays: Vec<Vec<u32>> = generate_intersection_test_arrays();
1669
+
1670
+ for (i, array_a) in test_arrays.iter().enumerate() {
1671
+ for (j, array_b) in test_arrays.iter().enumerate() {
1672
+ let expected = reference_intersect(array_a, array_b);
1673
+ let result =
1674
+ Sparse::intersect(array_a.as_slice(), array_b.as_slice()).unwrap() as usize;
1675
+
1676
+ assert_eq!(
1677
+ expected,
1678
+ result,
1679
+ "Intersection mismatch for arrays[{}] (len={}) and arrays[{}] (len={})",
1680
+ i,
1681
+ array_a.len(),
1682
+ j,
1683
+ array_b.len()
1684
+ );
1685
+ }
1686
+ }
1687
+ }
1688
+
1689
+ #[test]
1690
+ fn intersect_u16_comprehensive() {
1691
+ let test_arrays: Vec<Vec<u16>> = generate_intersection_test_arrays();
1692
+
1693
+ for (i, array_a) in test_arrays.iter().enumerate() {
1694
+ for (j, array_b) in test_arrays.iter().enumerate() {
1695
+ let expected = reference_intersect(array_a, array_b);
1696
+ let result =
1697
+ Sparse::intersect(array_a.as_slice(), array_b.as_slice()).unwrap() as usize;
1698
+
1699
+ assert_eq!(
1700
+ expected,
1701
+ result,
1702
+ "Intersection mismatch for arrays[{}] (len={}) and arrays[{}] (len={})",
1703
+ i,
1704
+ array_a.len(),
1705
+ j,
1706
+ array_b.len()
1707
+ );
1708
+ }
1709
+ }
1710
+ }
1711
+
1712
+ #[test]
1713
+ fn intersect_edge_cases() {
1714
+ // Test empty arrays
1715
+ let empty: &[u32] = &[];
1716
+ let non_empty: &[u32] = &[1, 2, 3];
1717
+ assert_eq!(Sparse::intersect(empty, empty), Some(0.0));
1718
+ assert_eq!(Sparse::intersect(empty, non_empty), Some(0.0));
1719
+ assert_eq!(Sparse::intersect(non_empty, empty), Some(0.0));
1720
+
1721
+ // Test single element matches
1722
+ assert_eq!(Sparse::intersect(&[42u32], &[42u32]), Some(1.0));
1723
+ assert_eq!(Sparse::intersect(&[42u32], &[43u32]), Some(0.0));
1724
+
1725
+ // Test no overlap
1726
+ let a: &[u32] = &[1, 2, 3, 4, 5];
1727
+ let b: &[u32] = &[10, 20, 30, 40, 50];
1728
+ assert_eq!(Sparse::intersect(a, b), Some(0.0));
1729
+
1730
+ // Test complete overlap
1731
+ let c: &[u32] = &[10, 20, 30, 40, 50];
1732
+ assert_eq!(Sparse::intersect(c, c), Some(5.0));
1733
+
1734
+ // Test one element at boundary (exactly at 16, 32, 64 element boundaries)
1735
+ let boundary_16: Vec<u32> = (0..16).collect();
1736
+ let boundary_32: Vec<u32> = (0..32).collect();
1737
+ let boundary_64: Vec<u32> = (0..64).collect();
1738
+
1739
+ assert_eq!(Sparse::intersect(&boundary_16, &boundary_16), Some(16.0));
1740
+ assert_eq!(Sparse::intersect(&boundary_32, &boundary_32), Some(32.0));
1741
+ assert_eq!(Sparse::intersect(&boundary_64, &boundary_64), Some(64.0));
1742
+
1743
+ // Test partial overlap at boundaries
1744
+ let first_half: Vec<u32> = (0..32).collect();
1745
+ let second_half: Vec<u32> = (16..48).collect();
1746
+ assert_eq!(Sparse::intersect(&first_half, &second_half), Some(16.0));
1747
+ }
1748
+
1749
+ #[test]
1750
+ fn f16_arithmetic() {
1608
1751
  let a = f16::from_f32(3.5);
1609
1752
  let b = f16::from_f32(2.0);
1610
-
1753
+
1611
1754
  // Test basic arithmetic
1612
1755
  assert!((a + b).to_f32() - 5.5 < 0.01);
1613
1756
  assert!((a - b).to_f32() - 1.5 < 0.01);
1614
1757
  assert!((a * b).to_f32() - 7.0 < 0.01);
1615
1758
  assert!((a / b).to_f32() - 1.75 < 0.01);
1616
1759
  assert!((-a).to_f32() + 3.5 < 0.01);
1617
-
1760
+
1618
1761
  // Test constants
1619
1762
  assert!(f16::ZERO.to_f32() == 0.0);
1620
1763
  assert!((f16::ONE.to_f32() - 1.0).abs() < 0.01);
1621
1764
  assert!((f16::NEG_ONE.to_f32() + 1.0).abs() < 0.01);
1622
-
1765
+
1623
1766
  // Test comparisons
1624
1767
  assert!(a > b);
1625
1768
  assert!(!(a < b));
1626
1769
  assert!(a == a);
1627
-
1770
+
1628
1771
  // Test utility methods
1629
1772
  assert!((-a).abs().to_f32() - 3.5 < 0.01);
1630
1773
  assert!(a.is_finite());
@@ -1633,31 +1776,46 @@ mod tests {
1633
1776
  }
1634
1777
 
1635
1778
  #[test]
1636
- fn test_bf16_arithmetic() {
1779
+ fn bf16_arithmetic() {
1637
1780
  let a = bf16::from_f32(3.5);
1638
1781
  let b = bf16::from_f32(2.0);
1639
-
1782
+
1640
1783
  // Test basic arithmetic
1641
1784
  assert!((a + b).to_f32() - 5.5 < 0.1);
1642
1785
  assert!((a - b).to_f32() - 1.5 < 0.1);
1643
1786
  assert!((a * b).to_f32() - 7.0 < 0.1);
1644
1787
  assert!((a / b).to_f32() - 1.75 < 0.1);
1645
1788
  assert!((-a).to_f32() + 3.5 < 0.1);
1646
-
1789
+
1647
1790
  // Test constants
1648
1791
  assert!(bf16::ZERO.to_f32() == 0.0);
1649
1792
  assert!((bf16::ONE.to_f32() - 1.0).abs() < 0.01);
1650
1793
  assert!((bf16::NEG_ONE.to_f32() + 1.0).abs() < 0.01);
1651
-
1794
+
1652
1795
  // Test comparisons
1653
1796
  assert!(a > b);
1654
1797
  assert!(!(a < b));
1655
1798
  assert!(a == a);
1656
-
1799
+
1657
1800
  // Test utility methods
1658
1801
  assert!((-a).abs().to_f32() - 3.5 < 0.1);
1659
1802
  assert!(a.is_finite());
1660
1803
  assert!(!a.is_nan());
1661
1804
  assert!(!a.is_infinite());
1662
1805
  }
1806
+
1807
+ #[test]
1808
+ fn bf16_dot() {
1809
+ let brain_a: Vec<bf16> = vec![1.0, 2.0, 3.0, 1.0, 2.0]
1810
+ .iter()
1811
+ .map(|&x| bf16::from_f32(x))
1812
+ .collect();
1813
+ let brain_b: Vec<bf16> = vec![4.0, 5.0, 6.0, 4.0, 5.0]
1814
+ .iter()
1815
+ .map(|&x| bf16::from_f32(x))
1816
+ .collect();
1817
+ if let Some(result) = <bf16 as SpatialSimilarity>::dot(&brain_a, &brain_b) {
1818
+ assert_eq!(46.0, result);
1819
+ }
1820
+ }
1663
1821
  }