faiss 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (226) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/README.md +103 -3
  4. data/ext/faiss/ext.cpp +99 -32
  5. data/ext/faiss/extconf.rb +12 -2
  6. data/lib/faiss/ext.bundle +0 -0
  7. data/lib/faiss/index.rb +3 -3
  8. data/lib/faiss/index_binary.rb +3 -3
  9. data/lib/faiss/kmeans.rb +1 -1
  10. data/lib/faiss/pca_matrix.rb +2 -2
  11. data/lib/faiss/product_quantizer.rb +3 -3
  12. data/lib/faiss/version.rb +1 -1
  13. data/vendor/faiss/AutoTune.cpp +719 -0
  14. data/vendor/faiss/AutoTune.h +212 -0
  15. data/vendor/faiss/Clustering.cpp +261 -0
  16. data/vendor/faiss/Clustering.h +101 -0
  17. data/vendor/faiss/IVFlib.cpp +339 -0
  18. data/vendor/faiss/IVFlib.h +132 -0
  19. data/vendor/faiss/Index.cpp +171 -0
  20. data/vendor/faiss/Index.h +261 -0
  21. data/vendor/faiss/Index2Layer.cpp +437 -0
  22. data/vendor/faiss/Index2Layer.h +85 -0
  23. data/vendor/faiss/IndexBinary.cpp +77 -0
  24. data/vendor/faiss/IndexBinary.h +163 -0
  25. data/vendor/faiss/IndexBinaryFlat.cpp +83 -0
  26. data/vendor/faiss/IndexBinaryFlat.h +54 -0
  27. data/vendor/faiss/IndexBinaryFromFloat.cpp +78 -0
  28. data/vendor/faiss/IndexBinaryFromFloat.h +52 -0
  29. data/vendor/faiss/IndexBinaryHNSW.cpp +325 -0
  30. data/vendor/faiss/IndexBinaryHNSW.h +56 -0
  31. data/vendor/faiss/IndexBinaryIVF.cpp +671 -0
  32. data/vendor/faiss/IndexBinaryIVF.h +211 -0
  33. data/vendor/faiss/IndexFlat.cpp +508 -0
  34. data/vendor/faiss/IndexFlat.h +175 -0
  35. data/vendor/faiss/IndexHNSW.cpp +1090 -0
  36. data/vendor/faiss/IndexHNSW.h +170 -0
  37. data/vendor/faiss/IndexIVF.cpp +909 -0
  38. data/vendor/faiss/IndexIVF.h +353 -0
  39. data/vendor/faiss/IndexIVFFlat.cpp +502 -0
  40. data/vendor/faiss/IndexIVFFlat.h +118 -0
  41. data/vendor/faiss/IndexIVFPQ.cpp +1207 -0
  42. data/vendor/faiss/IndexIVFPQ.h +161 -0
  43. data/vendor/faiss/IndexIVFPQR.cpp +219 -0
  44. data/vendor/faiss/IndexIVFPQR.h +65 -0
  45. data/vendor/faiss/IndexIVFSpectralHash.cpp +331 -0
  46. data/vendor/faiss/IndexIVFSpectralHash.h +75 -0
  47. data/vendor/faiss/IndexLSH.cpp +225 -0
  48. data/vendor/faiss/IndexLSH.h +87 -0
  49. data/vendor/faiss/IndexLattice.cpp +143 -0
  50. data/vendor/faiss/IndexLattice.h +68 -0
  51. data/vendor/faiss/IndexPQ.cpp +1188 -0
  52. data/vendor/faiss/IndexPQ.h +199 -0
  53. data/vendor/faiss/IndexPreTransform.cpp +288 -0
  54. data/vendor/faiss/IndexPreTransform.h +91 -0
  55. data/vendor/faiss/IndexReplicas.cpp +123 -0
  56. data/vendor/faiss/IndexReplicas.h +76 -0
  57. data/vendor/faiss/IndexScalarQuantizer.cpp +317 -0
  58. data/vendor/faiss/IndexScalarQuantizer.h +127 -0
  59. data/vendor/faiss/IndexShards.cpp +317 -0
  60. data/vendor/faiss/IndexShards.h +100 -0
  61. data/vendor/faiss/InvertedLists.cpp +623 -0
  62. data/vendor/faiss/InvertedLists.h +334 -0
  63. data/vendor/faiss/LICENSE +21 -0
  64. data/vendor/faiss/MatrixStats.cpp +252 -0
  65. data/vendor/faiss/MatrixStats.h +62 -0
  66. data/vendor/faiss/MetaIndexes.cpp +351 -0
  67. data/vendor/faiss/MetaIndexes.h +126 -0
  68. data/vendor/faiss/OnDiskInvertedLists.cpp +674 -0
  69. data/vendor/faiss/OnDiskInvertedLists.h +127 -0
  70. data/vendor/faiss/VectorTransform.cpp +1157 -0
  71. data/vendor/faiss/VectorTransform.h +322 -0
  72. data/vendor/faiss/c_api/AutoTune_c.cpp +83 -0
  73. data/vendor/faiss/c_api/AutoTune_c.h +64 -0
  74. data/vendor/faiss/c_api/Clustering_c.cpp +139 -0
  75. data/vendor/faiss/c_api/Clustering_c.h +117 -0
  76. data/vendor/faiss/c_api/IndexFlat_c.cpp +140 -0
  77. data/vendor/faiss/c_api/IndexFlat_c.h +115 -0
  78. data/vendor/faiss/c_api/IndexIVFFlat_c.cpp +64 -0
  79. data/vendor/faiss/c_api/IndexIVFFlat_c.h +58 -0
  80. data/vendor/faiss/c_api/IndexIVF_c.cpp +92 -0
  81. data/vendor/faiss/c_api/IndexIVF_c.h +135 -0
  82. data/vendor/faiss/c_api/IndexLSH_c.cpp +37 -0
  83. data/vendor/faiss/c_api/IndexLSH_c.h +40 -0
  84. data/vendor/faiss/c_api/IndexShards_c.cpp +44 -0
  85. data/vendor/faiss/c_api/IndexShards_c.h +42 -0
  86. data/vendor/faiss/c_api/Index_c.cpp +105 -0
  87. data/vendor/faiss/c_api/Index_c.h +183 -0
  88. data/vendor/faiss/c_api/MetaIndexes_c.cpp +49 -0
  89. data/vendor/faiss/c_api/MetaIndexes_c.h +49 -0
  90. data/vendor/faiss/c_api/clone_index_c.cpp +23 -0
  91. data/vendor/faiss/c_api/clone_index_c.h +32 -0
  92. data/vendor/faiss/c_api/error_c.h +42 -0
  93. data/vendor/faiss/c_api/error_impl.cpp +27 -0
  94. data/vendor/faiss/c_api/error_impl.h +16 -0
  95. data/vendor/faiss/c_api/faiss_c.h +58 -0
  96. data/vendor/faiss/c_api/gpu/GpuAutoTune_c.cpp +96 -0
  97. data/vendor/faiss/c_api/gpu/GpuAutoTune_c.h +56 -0
  98. data/vendor/faiss/c_api/gpu/GpuClonerOptions_c.cpp +52 -0
  99. data/vendor/faiss/c_api/gpu/GpuClonerOptions_c.h +68 -0
  100. data/vendor/faiss/c_api/gpu/GpuIndex_c.cpp +17 -0
  101. data/vendor/faiss/c_api/gpu/GpuIndex_c.h +30 -0
  102. data/vendor/faiss/c_api/gpu/GpuIndicesOptions_c.h +38 -0
  103. data/vendor/faiss/c_api/gpu/GpuResources_c.cpp +86 -0
  104. data/vendor/faiss/c_api/gpu/GpuResources_c.h +66 -0
  105. data/vendor/faiss/c_api/gpu/StandardGpuResources_c.cpp +54 -0
  106. data/vendor/faiss/c_api/gpu/StandardGpuResources_c.h +53 -0
  107. data/vendor/faiss/c_api/gpu/macros_impl.h +42 -0
  108. data/vendor/faiss/c_api/impl/AuxIndexStructures_c.cpp +220 -0
  109. data/vendor/faiss/c_api/impl/AuxIndexStructures_c.h +149 -0
  110. data/vendor/faiss/c_api/index_factory_c.cpp +26 -0
  111. data/vendor/faiss/c_api/index_factory_c.h +30 -0
  112. data/vendor/faiss/c_api/index_io_c.cpp +42 -0
  113. data/vendor/faiss/c_api/index_io_c.h +50 -0
  114. data/vendor/faiss/c_api/macros_impl.h +110 -0
  115. data/vendor/faiss/clone_index.cpp +147 -0
  116. data/vendor/faiss/clone_index.h +38 -0
  117. data/vendor/faiss/demos/demo_imi_flat.cpp +151 -0
  118. data/vendor/faiss/demos/demo_imi_pq.cpp +199 -0
  119. data/vendor/faiss/demos/demo_ivfpq_indexing.cpp +146 -0
  120. data/vendor/faiss/demos/demo_sift1M.cpp +252 -0
  121. data/vendor/faiss/gpu/GpuAutoTune.cpp +95 -0
  122. data/vendor/faiss/gpu/GpuAutoTune.h +27 -0
  123. data/vendor/faiss/gpu/GpuCloner.cpp +403 -0
  124. data/vendor/faiss/gpu/GpuCloner.h +82 -0
  125. data/vendor/faiss/gpu/GpuClonerOptions.cpp +28 -0
  126. data/vendor/faiss/gpu/GpuClonerOptions.h +53 -0
  127. data/vendor/faiss/gpu/GpuDistance.h +52 -0
  128. data/vendor/faiss/gpu/GpuFaissAssert.h +29 -0
  129. data/vendor/faiss/gpu/GpuIndex.h +148 -0
  130. data/vendor/faiss/gpu/GpuIndexBinaryFlat.h +89 -0
  131. data/vendor/faiss/gpu/GpuIndexFlat.h +190 -0
  132. data/vendor/faiss/gpu/GpuIndexIVF.h +89 -0
  133. data/vendor/faiss/gpu/GpuIndexIVFFlat.h +85 -0
  134. data/vendor/faiss/gpu/GpuIndexIVFPQ.h +143 -0
  135. data/vendor/faiss/gpu/GpuIndexIVFScalarQuantizer.h +100 -0
  136. data/vendor/faiss/gpu/GpuIndicesOptions.h +30 -0
  137. data/vendor/faiss/gpu/GpuResources.cpp +52 -0
  138. data/vendor/faiss/gpu/GpuResources.h +73 -0
  139. data/vendor/faiss/gpu/StandardGpuResources.cpp +295 -0
  140. data/vendor/faiss/gpu/StandardGpuResources.h +114 -0
  141. data/vendor/faiss/gpu/impl/RemapIndices.cpp +43 -0
  142. data/vendor/faiss/gpu/impl/RemapIndices.h +24 -0
  143. data/vendor/faiss/gpu/perf/IndexWrapper-inl.h +71 -0
  144. data/vendor/faiss/gpu/perf/IndexWrapper.h +39 -0
  145. data/vendor/faiss/gpu/perf/PerfClustering.cpp +115 -0
  146. data/vendor/faiss/gpu/perf/PerfIVFPQAdd.cpp +139 -0
  147. data/vendor/faiss/gpu/perf/WriteIndex.cpp +102 -0
  148. data/vendor/faiss/gpu/test/TestGpuIndexBinaryFlat.cpp +130 -0
  149. data/vendor/faiss/gpu/test/TestGpuIndexFlat.cpp +371 -0
  150. data/vendor/faiss/gpu/test/TestGpuIndexIVFFlat.cpp +550 -0
  151. data/vendor/faiss/gpu/test/TestGpuIndexIVFPQ.cpp +450 -0
  152. data/vendor/faiss/gpu/test/TestGpuMemoryException.cpp +84 -0
  153. data/vendor/faiss/gpu/test/TestUtils.cpp +315 -0
  154. data/vendor/faiss/gpu/test/TestUtils.h +93 -0
  155. data/vendor/faiss/gpu/test/demo_ivfpq_indexing_gpu.cpp +159 -0
  156. data/vendor/faiss/gpu/utils/DeviceMemory.cpp +77 -0
  157. data/vendor/faiss/gpu/utils/DeviceMemory.h +71 -0
  158. data/vendor/faiss/gpu/utils/DeviceUtils.h +185 -0
  159. data/vendor/faiss/gpu/utils/MemorySpace.cpp +89 -0
  160. data/vendor/faiss/gpu/utils/MemorySpace.h +44 -0
  161. data/vendor/faiss/gpu/utils/StackDeviceMemory.cpp +239 -0
  162. data/vendor/faiss/gpu/utils/StackDeviceMemory.h +129 -0
  163. data/vendor/faiss/gpu/utils/StaticUtils.h +83 -0
  164. data/vendor/faiss/gpu/utils/Timer.cpp +60 -0
  165. data/vendor/faiss/gpu/utils/Timer.h +52 -0
  166. data/vendor/faiss/impl/AuxIndexStructures.cpp +305 -0
  167. data/vendor/faiss/impl/AuxIndexStructures.h +246 -0
  168. data/vendor/faiss/impl/FaissAssert.h +95 -0
  169. data/vendor/faiss/impl/FaissException.cpp +66 -0
  170. data/vendor/faiss/impl/FaissException.h +71 -0
  171. data/vendor/faiss/impl/HNSW.cpp +818 -0
  172. data/vendor/faiss/impl/HNSW.h +275 -0
  173. data/vendor/faiss/impl/PolysemousTraining.cpp +953 -0
  174. data/vendor/faiss/impl/PolysemousTraining.h +158 -0
  175. data/vendor/faiss/impl/ProductQuantizer.cpp +876 -0
  176. data/vendor/faiss/impl/ProductQuantizer.h +242 -0
  177. data/vendor/faiss/impl/ScalarQuantizer.cpp +1628 -0
  178. data/vendor/faiss/impl/ScalarQuantizer.h +120 -0
  179. data/vendor/faiss/impl/ThreadedIndex-inl.h +192 -0
  180. data/vendor/faiss/impl/ThreadedIndex.h +80 -0
  181. data/vendor/faiss/impl/index_read.cpp +793 -0
  182. data/vendor/faiss/impl/index_write.cpp +558 -0
  183. data/vendor/faiss/impl/io.cpp +142 -0
  184. data/vendor/faiss/impl/io.h +98 -0
  185. data/vendor/faiss/impl/lattice_Zn.cpp +712 -0
  186. data/vendor/faiss/impl/lattice_Zn.h +199 -0
  187. data/vendor/faiss/index_factory.cpp +392 -0
  188. data/vendor/faiss/index_factory.h +25 -0
  189. data/vendor/faiss/index_io.h +75 -0
  190. data/vendor/faiss/misc/test_blas.cpp +84 -0
  191. data/vendor/faiss/tests/test_binary_flat.cpp +64 -0
  192. data/vendor/faiss/tests/test_dealloc_invlists.cpp +183 -0
  193. data/vendor/faiss/tests/test_ivfpq_codec.cpp +67 -0
  194. data/vendor/faiss/tests/test_ivfpq_indexing.cpp +98 -0
  195. data/vendor/faiss/tests/test_lowlevel_ivf.cpp +566 -0
  196. data/vendor/faiss/tests/test_merge.cpp +258 -0
  197. data/vendor/faiss/tests/test_omp_threads.cpp +14 -0
  198. data/vendor/faiss/tests/test_ondisk_ivf.cpp +220 -0
  199. data/vendor/faiss/tests/test_pairs_decoding.cpp +189 -0
  200. data/vendor/faiss/tests/test_params_override.cpp +231 -0
  201. data/vendor/faiss/tests/test_pq_encoding.cpp +98 -0
  202. data/vendor/faiss/tests/test_sliding_ivf.cpp +240 -0
  203. data/vendor/faiss/tests/test_threaded_index.cpp +253 -0
  204. data/vendor/faiss/tests/test_transfer_invlists.cpp +159 -0
  205. data/vendor/faiss/tutorial/cpp/1-Flat.cpp +98 -0
  206. data/vendor/faiss/tutorial/cpp/2-IVFFlat.cpp +81 -0
  207. data/vendor/faiss/tutorial/cpp/3-IVFPQ.cpp +93 -0
  208. data/vendor/faiss/tutorial/cpp/4-GPU.cpp +119 -0
  209. data/vendor/faiss/tutorial/cpp/5-Multiple-GPUs.cpp +99 -0
  210. data/vendor/faiss/utils/Heap.cpp +122 -0
  211. data/vendor/faiss/utils/Heap.h +495 -0
  212. data/vendor/faiss/utils/WorkerThread.cpp +126 -0
  213. data/vendor/faiss/utils/WorkerThread.h +61 -0
  214. data/vendor/faiss/utils/distances.cpp +765 -0
  215. data/vendor/faiss/utils/distances.h +243 -0
  216. data/vendor/faiss/utils/distances_simd.cpp +809 -0
  217. data/vendor/faiss/utils/extra_distances.cpp +336 -0
  218. data/vendor/faiss/utils/extra_distances.h +54 -0
  219. data/vendor/faiss/utils/hamming-inl.h +472 -0
  220. data/vendor/faiss/utils/hamming.cpp +792 -0
  221. data/vendor/faiss/utils/hamming.h +220 -0
  222. data/vendor/faiss/utils/random.cpp +192 -0
  223. data/vendor/faiss/utils/random.h +60 -0
  224. data/vendor/faiss/utils/utils.cpp +783 -0
  225. data/vendor/faiss/utils/utils.h +181 -0
  226. metadata +216 -2
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+
9
+
10
+ #include <cmath>
11
+ #include <cstdio>
12
+ #include <cstdlib>
13
+ #include <cassert>
14
+ #include <cstring>
15
+
16
+ #include <sys/types.h>
17
+ #include <sys/stat.h>
18
+ #include <unistd.h>
19
+
20
+ #include <sys/time.h>
21
+
22
+ #include <faiss/AutoTune.h>
23
+ #include <faiss/index_factory.h>
24
+
25
+ /**
26
+ * To run this demo, please download the ANN_SIFT1M dataset from
27
+ *
28
+ * http://corpus-texmex.irisa.fr/
29
+ *
30
+ * and unzip it to the sudirectory sift1M.
31
+ **/
32
+
33
+ /*****************************************************
34
+ * I/O functions for fvecs and ivecs
35
+ *****************************************************/
36
+
37
+
38
+ float * fvecs_read (const char *fname,
39
+ size_t *d_out, size_t *n_out)
40
+ {
41
+ FILE *f = fopen(fname, "r");
42
+ if(!f) {
43
+ fprintf(stderr, "could not open %s\n", fname);
44
+ perror("");
45
+ abort();
46
+ }
47
+ int d;
48
+ fread(&d, 1, sizeof(int), f);
49
+ assert((d > 0 && d < 1000000) || !"unreasonable dimension");
50
+ fseek(f, 0, SEEK_SET);
51
+ struct stat st;
52
+ fstat(fileno(f), &st);
53
+ size_t sz = st.st_size;
54
+ assert(sz % ((d + 1) * 4) == 0 || !"weird file size");
55
+ size_t n = sz / ((d + 1) * 4);
56
+
57
+ *d_out = d; *n_out = n;
58
+ float *x = new float[n * (d + 1)];
59
+ size_t nr = fread(x, sizeof(float), n * (d + 1), f);
60
+ assert(nr == n * (d + 1) || !"could not read whole file");
61
+
62
+ // shift array to remove row headers
63
+ for(size_t i = 0; i < n; i++)
64
+ memmove(x + i * d, x + 1 + i * (d + 1), d * sizeof(*x));
65
+
66
+ fclose(f);
67
+ return x;
68
+ }
69
+
70
+ // not very clean, but works as long as sizeof(int) == sizeof(float)
71
+ int *ivecs_read(const char *fname, size_t *d_out, size_t *n_out)
72
+ {
73
+ return (int*)fvecs_read(fname, d_out, n_out);
74
+ }
75
+
76
+ double elapsed ()
77
+ {
78
+ struct timeval tv;
79
+ gettimeofday (&tv, nullptr);
80
+ return tv.tv_sec + tv.tv_usec * 1e-6;
81
+ }
82
+
83
+
84
+
85
+ int main()
86
+ {
87
+ double t0 = elapsed();
88
+
89
+ // this is typically the fastest one.
90
+ const char *index_key = "IVF4096,Flat";
91
+
92
+ // these ones have better memory usage
93
+ // const char *index_key = "Flat";
94
+ // const char *index_key = "PQ32";
95
+ // const char *index_key = "PCA80,Flat";
96
+ // const char *index_key = "IVF4096,PQ8+16";
97
+ // const char *index_key = "IVF4096,PQ32";
98
+ // const char *index_key = "IMI2x8,PQ32";
99
+ // const char *index_key = "IMI2x8,PQ8+16";
100
+ // const char *index_key = "OPQ16_64,IMI2x8,PQ8+16";
101
+
102
+ faiss::Index * index;
103
+
104
+ size_t d;
105
+
106
+ {
107
+ printf ("[%.3f s] Loading train set\n", elapsed() - t0);
108
+
109
+ size_t nt;
110
+ float *xt = fvecs_read("sift1M/sift_learn.fvecs", &d, &nt);
111
+
112
+ printf ("[%.3f s] Preparing index \"%s\" d=%ld\n",
113
+ elapsed() - t0, index_key, d);
114
+ index = faiss::index_factory(d, index_key);
115
+
116
+ printf ("[%.3f s] Training on %ld vectors\n", elapsed() - t0, nt);
117
+
118
+ index->train(nt, xt);
119
+ delete [] xt;
120
+ }
121
+
122
+
123
+ {
124
+ printf ("[%.3f s] Loading database\n", elapsed() - t0);
125
+
126
+ size_t nb, d2;
127
+ float *xb = fvecs_read("sift1M/sift_base.fvecs", &d2, &nb);
128
+ assert(d == d2 || !"dataset does not have same dimension as train set");
129
+
130
+ printf ("[%.3f s] Indexing database, size %ld*%ld\n",
131
+ elapsed() - t0, nb, d);
132
+
133
+ index->add(nb, xb);
134
+
135
+ delete [] xb;
136
+ }
137
+
138
+ size_t nq;
139
+ float *xq;
140
+
141
+ {
142
+ printf ("[%.3f s] Loading queries\n", elapsed() - t0);
143
+
144
+ size_t d2;
145
+ xq = fvecs_read("sift1M/sift_query.fvecs", &d2, &nq);
146
+ assert(d == d2 || !"query does not have same dimension as train set");
147
+
148
+ }
149
+
150
+ size_t k; // nb of results per query in the GT
151
+ faiss::Index::idx_t *gt; // nq * k matrix of ground-truth nearest-neighbors
152
+
153
+ {
154
+ printf ("[%.3f s] Loading ground truth for %ld queries\n",
155
+ elapsed() - t0, nq);
156
+
157
+ // load ground-truth and convert int to long
158
+ size_t nq2;
159
+ int *gt_int = ivecs_read("sift1M/sift_groundtruth.ivecs", &k, &nq2);
160
+ assert(nq2 == nq || !"incorrect nb of ground truth entries");
161
+
162
+ gt = new faiss::Index::idx_t[k * nq];
163
+ for(int i = 0; i < k * nq; i++) {
164
+ gt[i] = gt_int[i];
165
+ }
166
+ delete [] gt_int;
167
+ }
168
+
169
+ // Result of the auto-tuning
170
+ std::string selected_params;
171
+
172
+ { // run auto-tuning
173
+
174
+ printf ("[%.3f s] Preparing auto-tune criterion 1-recall at 1 "
175
+ "criterion, with k=%ld nq=%ld\n", elapsed() - t0, k, nq);
176
+
177
+ faiss::OneRecallAtRCriterion crit(nq, 1);
178
+ crit.set_groundtruth (k, nullptr, gt);
179
+ crit.nnn = k; // by default, the criterion will request only 1 NN
180
+
181
+ printf ("[%.3f s] Preparing auto-tune parameters\n", elapsed() - t0);
182
+
183
+ faiss::ParameterSpace params;
184
+ params.initialize(index);
185
+
186
+ printf ("[%.3f s] Auto-tuning over %ld parameters (%ld combinations)\n",
187
+ elapsed() - t0, params.parameter_ranges.size(),
188
+ params.n_combinations());
189
+
190
+ faiss::OperatingPoints ops;
191
+ params.explore (index, nq, xq, crit, &ops);
192
+
193
+ printf ("[%.3f s] Found the following operating points: \n",
194
+ elapsed() - t0);
195
+
196
+ ops.display ();
197
+
198
+ // keep the first parameter that obtains > 0.5 1-recall@1
199
+ for (int i = 0; i < ops.optimal_pts.size(); i++) {
200
+ if (ops.optimal_pts[i].perf > 0.5) {
201
+ selected_params = ops.optimal_pts[i].key;
202
+ break;
203
+ }
204
+ }
205
+ assert (selected_params.size() >= 0 ||
206
+ !"could not find good enough op point");
207
+ }
208
+
209
+
210
+ { // Use the found configuration to perform a search
211
+
212
+ faiss::ParameterSpace params;
213
+
214
+ printf ("[%.3f s] Setting parameter configuration \"%s\" on index\n",
215
+ elapsed() - t0, selected_params.c_str());
216
+
217
+ params.set_index_parameters (index, selected_params.c_str());
218
+
219
+ printf ("[%.3f s] Perform a search on %ld queries\n",
220
+ elapsed() - t0, nq);
221
+
222
+ // output buffers
223
+ faiss::Index::idx_t *I = new faiss::Index::idx_t[nq * k];
224
+ float *D = new float[nq * k];
225
+
226
+ index->search(nq, xq, k, D, I);
227
+
228
+ printf ("[%.3f s] Compute recalls\n", elapsed() - t0);
229
+
230
+ // evaluate result by hand.
231
+ int n_1 = 0, n_10 = 0, n_100 = 0;
232
+ for(int i = 0; i < nq; i++) {
233
+ int gt_nn = gt[i * k];
234
+ for(int j = 0; j < k; j++) {
235
+ if (I[i * k + j] == gt_nn) {
236
+ if(j < 1) n_1++;
237
+ if(j < 10) n_10++;
238
+ if(j < 100) n_100++;
239
+ }
240
+ }
241
+ }
242
+ printf("R@1 = %.4f\n", n_1 / float(nq));
243
+ printf("R@10 = %.4f\n", n_10 / float(nq));
244
+ printf("R@100 = %.4f\n", n_100 / float(nq));
245
+
246
+ }
247
+
248
+ delete [] xq;
249
+ delete [] gt;
250
+ delete index;
251
+ return 0;
252
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ #include <faiss/gpu/GpuAutoTune.h>
9
+ #include <typeinfo>
10
+
11
+ #include <faiss/gpu/GpuIndex.h>
12
+ #include <faiss/IndexReplicas.h>
13
+ #include <faiss/IndexShards.h>
14
+ #include <faiss/IndexPreTransform.h>
15
+ #include <faiss/gpu/GpuIndexFlat.h>
16
+ #include <faiss/gpu/GpuIndexIVFFlat.h>
17
+ #include <faiss/gpu/GpuIndexIVFPQ.h>
18
+ #include <faiss/gpu/GpuIndexIVFScalarQuantizer.h>
19
+ #include <faiss/impl/FaissAssert.h>
20
+ #include <faiss/gpu/utils/DeviceUtils.h>
21
+
22
+ namespace faiss { namespace gpu {
23
+
24
+
25
+ using namespace ::faiss;
26
+
27
+ /**********************************************************
28
+ * Parameters to auto-tune on GpuIndex'es
29
+ **********************************************************/
30
+
31
+ #define DC(classname) auto ix = dynamic_cast<const classname *>(index)
32
+
33
+
34
+ void GpuParameterSpace::initialize (const Index * index)
35
+ {
36
+ if (DC (IndexPreTransform)) {
37
+ index = ix->index;
38
+ }
39
+ if (DC (IndexReplicas)) {
40
+ if (ix->count() == 0) return;
41
+ index = ix->at(0);
42
+ }
43
+ if (DC (IndexShards)) {
44
+ if (ix->count() == 0) return;
45
+ index = ix->at(0);
46
+ }
47
+ if (DC (GpuIndexIVF)) {
48
+ ParameterRange & pr = add_range("nprobe");
49
+ for (int i = 0; i < 12; i++) {
50
+ size_t nprobe = 1 << i;
51
+ if (nprobe >= ix->getNumLists() ||
52
+ nprobe > getMaxKSelection()) break;
53
+ pr.values.push_back (nprobe);
54
+ }
55
+ }
56
+ // not sure we should call the parent initializer
57
+ }
58
+
59
+
60
+
61
+ #undef DC
62
+ // non-const version
63
+ #define DC(classname) auto *ix = dynamic_cast<classname *>(index)
64
+
65
+
66
+
67
+ void GpuParameterSpace::set_index_parameter (
68
+ Index * index, const std::string & name, double val) const
69
+ {
70
+ if (DC (IndexReplicas)) {
71
+ for (int i = 0; i < ix->count(); i++)
72
+ set_index_parameter (ix->at(i), name, val);
73
+ return;
74
+ }
75
+ if (name == "nprobe") {
76
+ if (DC (GpuIndexIVF)) {
77
+ ix->setNumProbes (int (val));
78
+ return;
79
+ }
80
+ }
81
+ if (name == "use_precomputed_table") {
82
+ if (DC (GpuIndexIVFPQ)) {
83
+ ix->setPrecomputedCodes(bool (val));
84
+ return;
85
+ }
86
+ }
87
+
88
+ // maybe normal index parameters apply?
89
+ ParameterSpace::set_index_parameter (index, name, val);
90
+ }
91
+
92
+
93
+
94
+
95
+ } } // namespace
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ #pragma once
9
+
10
+ #include <faiss/Index.h>
11
+ #include <faiss/AutoTune.h>
12
+
13
+ namespace faiss { namespace gpu {
14
+
15
+
16
+ /// parameter space and setters for GPU indexes
17
+ struct GpuParameterSpace: faiss::ParameterSpace {
18
+ /// initialize with reasonable parameters for the index
19
+ void initialize (const faiss::Index * index) override;
20
+
21
+ /// set a combination of parameters on an index
22
+ void set_index_parameter (
23
+ faiss::Index * index, const std::string & name,
24
+ double val) const override;
25
+ };
26
+
27
+ } } // namespace
@@ -0,0 +1,403 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+
9
+ #include <faiss/gpu/GpuCloner.h>
10
+ #include <typeinfo>
11
+
12
+ #include <faiss/gpu/GpuIndex.h>
13
+ #include <faiss/impl/FaissAssert.h>
14
+ #include <faiss/index_io.h>
15
+ #include <faiss/IndexFlat.h>
16
+ #include <faiss/IndexIVF.h>
17
+ #include <faiss/IndexIVFFlat.h>
18
+ #include <faiss/IndexScalarQuantizer.h>
19
+ #include <faiss/IndexIVFPQ.h>
20
+ #include <faiss/IndexReplicas.h>
21
+ #include <faiss/IndexPreTransform.h>
22
+ #include <faiss/MetaIndexes.h>
23
+ #include <faiss/gpu/GpuIndexFlat.h>
24
+ #include <faiss/gpu/GpuIndexIVFFlat.h>
25
+ #include <faiss/gpu/GpuIndexIVFPQ.h>
26
+ #include <faiss/gpu/GpuIndexIVFScalarQuantizer.h>
27
+ #include <faiss/gpu/utils/DeviceUtils.h>
28
+
29
+ namespace faiss { namespace gpu {
30
+
31
+
32
+ /**********************************************************
33
+ * Cloning to CPU
34
+ **********************************************************/
35
+
36
+ void ToCPUCloner::merge_index(Index *dst, Index *src, bool successive_ids)
37
+ {
38
+ if (auto ifl = dynamic_cast<IndexFlat *>(dst)) {
39
+ auto ifl2 = dynamic_cast<const IndexFlat *>(src);
40
+ FAISS_ASSERT(ifl2);
41
+ FAISS_ASSERT(successive_ids);
42
+ ifl->add(ifl2->ntotal, ifl2->xb.data());
43
+ } else if(auto ifl = dynamic_cast<IndexIVFFlat *>(dst)) {
44
+ auto ifl2 = dynamic_cast<IndexIVFFlat *>(src);
45
+ FAISS_ASSERT(ifl2);
46
+ ifl->merge_from(*ifl2, successive_ids ? ifl->ntotal : 0);
47
+ } else if(auto ifl = dynamic_cast<IndexIVFScalarQuantizer *>(dst)) {
48
+ auto ifl2 = dynamic_cast<IndexIVFScalarQuantizer *>(src);
49
+ FAISS_ASSERT(ifl2);
50
+ ifl->merge_from(*ifl2, successive_ids ? ifl->ntotal : 0);
51
+ } else if(auto ifl = dynamic_cast<IndexIVFPQ *>(dst)) {
52
+ auto ifl2 = dynamic_cast<IndexIVFPQ *>(src);
53
+ FAISS_ASSERT(ifl2);
54
+ ifl->merge_from(*ifl2, successive_ids ? ifl->ntotal : 0);
55
+ } else {
56
+ FAISS_ASSERT(!"merging not implemented for this type of class");
57
+ }
58
+ }
59
+
60
+
61
+ Index *ToCPUCloner::clone_Index(const Index *index)
62
+ {
63
+ if(auto ifl = dynamic_cast<const GpuIndexFlat *>(index)) {
64
+ IndexFlat *res = new IndexFlat();
65
+ ifl->copyTo(res);
66
+ return res;
67
+ } else if(auto ifl = dynamic_cast<const GpuIndexIVFFlat *>(index)) {
68
+ IndexIVFFlat *res = new IndexIVFFlat();
69
+ ifl->copyTo(res);
70
+ return res;
71
+ } else if(auto ifl =
72
+ dynamic_cast<const GpuIndexIVFScalarQuantizer *>(index)) {
73
+ IndexIVFScalarQuantizer *res = new IndexIVFScalarQuantizer();
74
+ ifl->copyTo(res);
75
+ return res;
76
+ } else if(auto ipq = dynamic_cast<const GpuIndexIVFPQ *>(index)) {
77
+ IndexIVFPQ *res = new IndexIVFPQ();
78
+ ipq->copyTo(res);
79
+ return res;
80
+
81
+ // for IndexShards and IndexReplicas we assume that the
82
+ // objective is to make a single component out of them
83
+ // (inverse op of ToGpuClonerMultiple)
84
+
85
+ } else if(auto ish = dynamic_cast<const IndexShards *>(index)) {
86
+ int nshard = ish->count();
87
+ FAISS_ASSERT(nshard > 0);
88
+ Index *res = clone_Index(ish->at(0));
89
+ for(int i = 1; i < ish->count(); i++) {
90
+ Index *res_i = clone_Index(ish->at(i));
91
+ merge_index(res, res_i, ish->successive_ids);
92
+ delete res_i;
93
+ }
94
+ return res;
95
+ } else if(auto ipr = dynamic_cast<const IndexReplicas *>(index)) {
96
+ // just clone one of the replicas
97
+ FAISS_ASSERT(ipr->count() > 0);
98
+ return clone_Index(ipr->at(0));
99
+ } else {
100
+ return Cloner::clone_Index(index);
101
+ }
102
+ }
103
+
104
+ faiss::Index * index_gpu_to_cpu(const faiss::Index *gpu_index)
105
+ {
106
+ ToCPUCloner cl;
107
+ return cl.clone_Index(gpu_index);
108
+ }
109
+
110
+
111
+
112
+
113
+ /**********************************************************
114
+ * Cloning to 1 GPU
115
+ **********************************************************/
116
+
117
+ ToGpuCloner::ToGpuCloner(GpuResources *resources, int device,
118
+ const GpuClonerOptions &options):
119
+ GpuClonerOptions(options), resources(resources), device(device)
120
+ {}
121
+
122
+ Index *ToGpuCloner::clone_Index(const Index *index)
123
+ {
124
+ if(auto ifl = dynamic_cast<const IndexFlat *>(index)) {
125
+ GpuIndexFlatConfig config;
126
+ config.device = device;
127
+ config.useFloat16 = useFloat16;
128
+ config.storeTransposed = storeTransposed;
129
+
130
+ return new GpuIndexFlat(resources, ifl, config);
131
+ } else if(auto ifl = dynamic_cast<const faiss::IndexIVFFlat *>(index)) {
132
+ GpuIndexIVFFlatConfig config;
133
+ config.device = device;
134
+ config.indicesOptions = indicesOptions;
135
+ config.flatConfig.useFloat16 = useFloat16CoarseQuantizer;
136
+ config.flatConfig.storeTransposed = storeTransposed;
137
+
138
+ GpuIndexIVFFlat *res =
139
+ new GpuIndexIVFFlat(resources,
140
+ ifl->d,
141
+ ifl->nlist,
142
+ ifl->metric_type,
143
+ config);
144
+ if(reserveVecs > 0 && ifl->ntotal == 0) {
145
+ res->reserveMemory(reserveVecs);
146
+ }
147
+
148
+ res->copyFrom(ifl);
149
+ return res;
150
+ } else if(auto ifl =
151
+ dynamic_cast<const faiss::IndexIVFScalarQuantizer *>(index)) {
152
+ GpuIndexIVFScalarQuantizerConfig config;
153
+ config.device = device;
154
+ config.indicesOptions = indicesOptions;
155
+ config.flatConfig.useFloat16 = useFloat16CoarseQuantizer;
156
+ config.flatConfig.storeTransposed = storeTransposed;
157
+
158
+ GpuIndexIVFScalarQuantizer *res =
159
+ new GpuIndexIVFScalarQuantizer(resources,
160
+ ifl->d,
161
+ ifl->nlist,
162
+ ifl->sq.qtype,
163
+ ifl->metric_type,
164
+ ifl->by_residual,
165
+ config);
166
+ if(reserveVecs > 0 && ifl->ntotal == 0) {
167
+ res->reserveMemory(reserveVecs);
168
+ }
169
+
170
+ res->copyFrom(ifl);
171
+ return res;
172
+ } else if(auto ipq = dynamic_cast<const faiss::IndexIVFPQ *>(index)) {
173
+ if(verbose)
174
+ printf(" IndexIVFPQ size %ld -> GpuIndexIVFPQ "
175
+ "indicesOptions=%d "
176
+ "usePrecomputed=%d useFloat16=%d reserveVecs=%ld\n",
177
+ ipq->ntotal, indicesOptions, usePrecomputed,
178
+ useFloat16, reserveVecs);
179
+ GpuIndexIVFPQConfig config;
180
+ config.device = device;
181
+ config.indicesOptions = indicesOptions;
182
+ config.flatConfig.useFloat16 = useFloat16CoarseQuantizer;
183
+ config.flatConfig.storeTransposed = storeTransposed;
184
+ config.useFloat16LookupTables = useFloat16;
185
+ config.usePrecomputedTables = usePrecomputed;
186
+
187
+ GpuIndexIVFPQ *res = new GpuIndexIVFPQ(resources, ipq, config);
188
+
189
+ if(reserveVecs > 0 && ipq->ntotal == 0) {
190
+ res->reserveMemory(reserveVecs);
191
+ }
192
+
193
+ return res;
194
+ } else {
195
+ return Cloner::clone_Index(index);
196
+ }
197
+ }
198
+
199
+
200
+ faiss::Index * index_cpu_to_gpu(
201
+ GpuResources* resources, int device,
202
+ const faiss::Index *index,
203
+ const GpuClonerOptions *options)
204
+ {
205
+ GpuClonerOptions defaults;
206
+ ToGpuCloner cl(resources, device, options ? *options : defaults);
207
+ return cl.clone_Index(index);
208
+ }
209
+
210
+
211
+ /**********************************************************
212
+ * Cloning to multiple GPUs
213
+ **********************************************************/
214
+
215
+ ToGpuClonerMultiple::ToGpuClonerMultiple(
216
+ std::vector<GpuResources *> & resources,
217
+ std::vector<int>& devices,
218
+ const GpuMultipleClonerOptions &options):
219
+ GpuMultipleClonerOptions(options)
220
+ {
221
+ FAISS_ASSERT(resources.size() == devices.size());
222
+ for(int i = 0; i < resources.size(); i++) {
223
+ sub_cloners.push_back(ToGpuCloner(resources[i], devices[i], options));
224
+ }
225
+ }
226
+
227
+
228
+ ToGpuClonerMultiple::ToGpuClonerMultiple(
229
+ const std::vector<ToGpuCloner> & sub_cloners,
230
+ const GpuMultipleClonerOptions &options):
231
+ GpuMultipleClonerOptions(options),
232
+ sub_cloners(sub_cloners)
233
+ {}
234
+
235
+
236
+ void ToGpuClonerMultiple::copy_ivf_shard (
237
+ const IndexIVF *index_ivf, IndexIVF *idx2,
238
+ long n, long i)
239
+ {
240
+ if (shard_type == 2) {
241
+ long i0 = i * index_ivf->ntotal / n;
242
+ long i1 = (i + 1) * index_ivf->ntotal / n;
243
+
244
+ if(verbose)
245
+ printf("IndexShards shard %ld indices %ld:%ld\n",
246
+ i, i0, i1);
247
+ index_ivf->copy_subset_to(*idx2, 2, i0, i1);
248
+ FAISS_ASSERT(idx2->ntotal == i1 - i0);
249
+ } else if (shard_type == 1) {
250
+ if(verbose)
251
+ printf("IndexShards shard %ld select modulo %ld = %ld\n",
252
+ i, n, i);
253
+ index_ivf->copy_subset_to(*idx2, 1, n, i);
254
+ } else {
255
+ FAISS_THROW_FMT ("shard_type %d not implemented", shard_type);
256
+ }
257
+
258
+ }
259
+
260
+ Index * ToGpuClonerMultiple::clone_Index_to_shards (const Index *index)
261
+ {
262
+ long n = sub_cloners.size();
263
+
264
+ auto index_ivfpq =
265
+ dynamic_cast<const faiss::IndexIVFPQ *>(index);
266
+ auto index_ivfflat =
267
+ dynamic_cast<const faiss::IndexIVFFlat *>(index);
268
+ auto index_ivfsq =
269
+ dynamic_cast<const faiss::IndexIVFScalarQuantizer *>(index);
270
+ auto index_flat =
271
+ dynamic_cast<const faiss::IndexFlat *>(index);
272
+ FAISS_THROW_IF_NOT_MSG (
273
+ index_ivfpq || index_ivfflat || index_flat || index_ivfsq,
274
+ "IndexShards implemented only for "
275
+ "IndexIVFFlat, IndexIVFScalarQuantizer, "
276
+ "IndexFlat and IndexIVFPQ");
277
+
278
+ std::vector<faiss::Index*> shards(n);
279
+
280
+ for(long i = 0; i < n; i++) {
281
+ // make a shallow copy
282
+ if(reserveVecs)
283
+ sub_cloners[i].reserveVecs =
284
+ (reserveVecs + n - 1) / n;
285
+
286
+ if (index_ivfpq) {
287
+ faiss::IndexIVFPQ idx2(
288
+ index_ivfpq->quantizer, index_ivfpq->d,
289
+ index_ivfpq->nlist, index_ivfpq->code_size,
290
+ index_ivfpq->pq.nbits);
291
+ idx2.metric_type = index_ivfpq->metric_type;
292
+ idx2.pq = index_ivfpq->pq;
293
+ idx2.nprobe = index_ivfpq->nprobe;
294
+ idx2.use_precomputed_table = 0;
295
+ idx2.is_trained = index->is_trained;
296
+ copy_ivf_shard (index_ivfpq, &idx2, n, i);
297
+ shards[i] = sub_cloners[i].clone_Index(&idx2);
298
+ } else if (index_ivfflat) {
299
+ faiss::IndexIVFFlat idx2(
300
+ index_ivfflat->quantizer, index->d,
301
+ index_ivfflat->nlist, index_ivfflat->metric_type);
302
+ idx2.nprobe = index_ivfflat->nprobe;
303
+ copy_ivf_shard (index_ivfflat, &idx2, n, i);
304
+ shards[i] = sub_cloners[i].clone_Index(&idx2);
305
+ } else if (index_ivfsq) {
306
+ faiss::IndexIVFScalarQuantizer idx2(
307
+ index_ivfsq->quantizer, index->d, index_ivfsq->nlist,
308
+ index_ivfsq->sq.qtype,
309
+ index_ivfsq->metric_type,
310
+ index_ivfsq->by_residual);
311
+ idx2.nprobe = index_ivfsq->nprobe;
312
+ copy_ivf_shard (index_ivfsq, &idx2, n, i);
313
+ shards[i] = sub_cloners[i].clone_Index(&idx2);
314
+ } else if (index_flat) {
315
+ faiss::IndexFlat idx2 (
316
+ index->d, index->metric_type);
317
+ shards[i] = sub_cloners[i].clone_Index(&idx2);
318
+ if (index->ntotal > 0) {
319
+ long i0 = index->ntotal * i / n;
320
+ long i1 = index->ntotal * (i + 1) / n;
321
+ shards[i]->add (i1 - i0,
322
+ index_flat->xb.data() + i0 * index->d);
323
+ }
324
+ }
325
+ }
326
+
327
+ bool successive_ids = index_flat != nullptr;
328
+ faiss::IndexShards *res =
329
+ new faiss::IndexShards(index->d, true,
330
+ successive_ids);
331
+
332
+ for (int i = 0; i < n; i++) {
333
+ res->add_shard(shards[i]);
334
+ }
335
+ res->own_fields = true;
336
+ FAISS_ASSERT(index->ntotal == res->ntotal);
337
+ return res;
338
+ }
339
+
340
+ Index *ToGpuClonerMultiple::clone_Index(const Index *index)
341
+ {
342
+ long n = sub_cloners.size();
343
+ if (n == 1)
344
+ return sub_cloners[0].clone_Index(index);
345
+
346
+ if(dynamic_cast<const IndexFlat *>(index) ||
347
+ dynamic_cast<const faiss::IndexIVFFlat *>(index) ||
348
+ dynamic_cast<const faiss::IndexIVFScalarQuantizer *>(index) ||
349
+ dynamic_cast<const faiss::IndexIVFPQ *>(index)) {
350
+ if(!shard) {
351
+ IndexReplicas * res = new IndexReplicas();
352
+ for(auto & sub_cloner: sub_cloners) {
353
+ res->addIndex(sub_cloner.clone_Index(index));
354
+ }
355
+ res->own_fields = true;
356
+ return res;
357
+ } else {
358
+ return clone_Index_to_shards (index);
359
+ }
360
+ } else if(auto miq = dynamic_cast<const MultiIndexQuantizer *>(index)) {
361
+ if (verbose) {
362
+ printf("cloning MultiIndexQuantizer: "
363
+ "will be valid only for search k=1\n");
364
+ }
365
+ const ProductQuantizer & pq = miq->pq;
366
+ IndexSplitVectors *splitv = new IndexSplitVectors(pq.d, true);
367
+ splitv->own_fields = true;
368
+
369
+ for (int m = 0; m < pq.M; m++) {
370
+ // which GPU(s) will be assigned to this sub-quantizer
371
+
372
+ long i0 = m * n / pq.M;
373
+ long i1 = pq.M <= n ? (m + 1) * n / pq.M : i0 + 1;
374
+ std::vector<ToGpuCloner> sub_cloners_2;
375
+ sub_cloners_2.insert(
376
+ sub_cloners_2.begin(), sub_cloners.begin() + i0,
377
+ sub_cloners.begin() + i1);
378
+ ToGpuClonerMultiple cm(sub_cloners_2, *this);
379
+ IndexFlatL2 idxc (pq.dsub);
380
+ idxc.add (pq.ksub, pq.centroids.data() + m * pq.d * pq.ksub);
381
+ Index *idx2 = cm.clone_Index(&idxc);
382
+ splitv->add_sub_index(idx2);
383
+ }
384
+ return splitv;
385
+ } else {
386
+ return Cloner::clone_Index(index);
387
+ }
388
+ }
389
+
390
+
391
+
392
+ faiss::Index * index_cpu_to_gpu_multiple(
393
+ std::vector<GpuResources*> & resources,
394
+ std::vector<int> &devices,
395
+ const faiss::Index *index,
396
+ const GpuMultipleClonerOptions *options)
397
+ {
398
+ GpuMultipleClonerOptions defaults;
399
+ ToGpuClonerMultiple cl(resources, devices, options ? *options : defaults);
400
+ return cl.clone_Index(index);
401
+ }
402
+
403
+ } } // namespace