18 #include "FaissAssert.h"
20 #include "IndexFlat.h"
21 #include "VectorTransform.h"
25 #include "IndexIVFPQ.h"
26 #include "MetaIndexes.h"
27 #include "IndexScalarQuantizer.h"
55 static uint32_t fourcc (
const char sx[4]) {
56 const unsigned char *x = (
unsigned char*)sx;
57 return x[0] | x[1] << 8 | x[2] << 16 | x[3] << 24;
68 #define WRITEANDCHECK(ptr, n) { \
69 size_t ret = fwrite (ptr, sizeof (* (ptr)), n, f); \
70 FAISS_THROW_IF_NOT_MSG (ret == (n), "write error"); \
73 #define READANDCHECK(ptr, n) { \
74 size_t ret = fread (ptr, sizeof (* (ptr)), n, f); \
75 FAISS_THROW_IF_NOT_MSG (ret == (n), "read error"); \
78 #define WRITE1(x) WRITEANDCHECK(&(x), 1)
79 #define READ1(x) READANDCHECK(&(x), 1)
81 #define WRITEVECTOR(vec) { \
82 size_t size = (vec).size (); \
83 WRITEANDCHECK (&size, 1); \
84 WRITEANDCHECK ((vec).data (), size); \
87 #define READVECTOR(vec) { \
89 READANDCHECK (&size, 1); \
90 FAISS_THROW_IF_NOT (size >= 0 && size < (1L << 40)); \
91 (vec).resize (size); \
92 READANDCHECK ((vec).data (), size); \
104 #define WRITETABPAD16(tab, size_in) { \
105 size_t size = (size_in); \
106 WRITEANDCHECK (&size, 1); \
107 uint8_t padding[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; \
108 int idx = ftell(f) % 16; \
109 padding [idx] = 15 - idx; \
110 WRITEANDCHECK (padding + idx, 16 - idx); \
111 WRITEANDCHECK ((tab), size); \
114 #define READTABPAD16(tab, basetype, expected_size) { \
116 READANDCHECK (&size, 1); \
117 FAISS_THROW_IF_NOT ((expected_size) == size); \
118 uint8_t padding[16], npad; \
120 FAISS_THROW_IF_NOT (npad < 16); \
121 READANDCHECK (padding, npad); \
122 (tab) = new basetype [size]; \
123 READANDCHECK ((tab), size); \
127 #define TABOFFSETPAD16(taboffset, basetype, expected_size) { \
129 READANDCHECK (&size, 1); \
130 FAISS_THROW_IF_NOT ((expected_size) == size); \
131 uint8_t padding[16], npad; \
133 FAISS_THROW_IF_NOT (npad < 16); \
134 READANDCHECK (padding, npad); \
135 taboffset = ftell(f); \
136 fseek (f, sizeof(basetype) * size, SEEK_CUR); \
146 static void write_index_header (
const Index *idx, FILE *f) {
158 void write_VectorTransform (
const VectorTransform *vt, FILE *f) {
159 if (
const LinearTransform * lt =
160 dynamic_cast < const LinearTransform *> (vt)) {
161 if (dynamic_cast<const RandomRotationMatrix *>(lt)) {
162 uint32_t h = fourcc (
"rrot");
164 }
else if (
const PCAMatrix * pca =
165 dynamic_cast<const PCAMatrix *>(lt)) {
166 uint32_t h = fourcc (
"PcAm");
168 WRITE1 (pca->eigen_power);
169 WRITE1 (pca->random_rotation);
170 WRITE1 (pca->balanced_bins);
171 WRITEVECTOR (pca->mean);
172 WRITEVECTOR (pca->eigenvalues);
173 WRITEVECTOR (pca->PCAMat);
176 uint32_t h = fourcc (
"LTra");
179 WRITE1 (lt->have_bias);
182 }
else if (
const RemapDimensionsTransform *rdt =
183 dynamic_cast<const RemapDimensionsTransform *>(vt)) {
184 uint32_t h = fourcc (
"RmDT");
186 WRITEVECTOR (rdt->map);
187 }
else if (
const NormalizationTransform *nt =
188 dynamic_cast<const NormalizationTransform *>(vt)) {
189 uint32_t h = fourcc (
"VNrm");
193 FAISS_THROW_MSG (
"cannot serialize this");
198 WRITE1 (vt->is_trained);
201 static void write_ProductQuantizer (
const ProductQuantizer *pq, FILE *f) {
205 WRITEVECTOR (pq->centroids);
208 static void write_ScalarQuantizer (
const ScalarQuantizer *ivsc, FILE *f) {
209 WRITE1 (ivsc->qtype);
210 WRITE1 (ivsc->rangestat);
211 WRITE1 (ivsc->rangestat_arg);
213 WRITE1 (ivsc->code_size);
214 WRITEVECTOR (ivsc->trained);
217 void write_ProductQuantizer (
const ProductQuantizer*pq,
const char *fname) {
218 FILE *f = fopen (fname,
"w");
219 FAISS_THROW_IF_NOT_FMT (f,
"cannot open %s for writing", fname);
220 ScopeFileCloser closer(f);
221 write_ProductQuantizer (pq, f);
226 static void write_ivf_header (
const IndexIVF * ivf, FILE *f,
227 bool include_ids =
true) {
228 write_index_header (ivf, f);
230 WRITE1 (ivf->nprobe);
231 write_index (ivf->quantizer, f);
233 for (
size_t i = 0; i < ivf->nlist; i++)
234 WRITEVECTOR (ivf->ids[i]);
236 WRITE1 (ivf->maintain_direct_map);
237 WRITEVECTOR (ivf->direct_map);
240 void write_index (
const Index *idx, FILE *f) {
241 if (
const IndexFlat * idxf = dynamic_cast<const IndexFlat *> (idx)) {
242 uint32_t h = fourcc (
243 idxf->metric_type == METRIC_INNER_PRODUCT ?
"IxFI" :
244 idxf->metric_type == METRIC_L2 ?
"IxF2" :
nullptr);
246 write_index_header (idx, f);
247 WRITEVECTOR (idxf->xb);
248 }
else if(
const IndexLSH * idxl = dynamic_cast<const IndexLSH *> (idx)) {
249 uint32_t h = fourcc (
"IxHe");
251 write_index_header (idx, f);
252 WRITE1 (idxl->nbits);
253 WRITE1 (idxl->rotate_data);
254 WRITE1 (idxl->train_thresholds);
255 WRITEVECTOR (idxl->thresholds);
256 WRITE1 (idxl->bytes_per_vec);
257 write_VectorTransform (&idxl->rrot, f);
258 WRITEVECTOR (idxl->codes);
259 }
else if(
const IndexPQ * idxp = dynamic_cast<const IndexPQ *> (idx)) {
260 uint32_t h = fourcc (
"IxPq");
262 write_index_header (idx, f);
263 write_ProductQuantizer (&idxp->pq, f);
264 WRITEVECTOR (idxp->codes);
266 WRITE1 (idxp->search_type);
267 WRITE1 (idxp->encode_signs);
268 WRITE1 (idxp->polysemous_ht);
269 }
else if(
const IndexScalarQuantizer * idxs =
270 dynamic_cast<const IndexScalarQuantizer *> (idx)) {
271 uint32_t h = fourcc (
"IxSQ");
273 write_index_header (idx, f);
274 write_ScalarQuantizer (&idxs->sq, f);
275 WRITEVECTOR (idxs->codes);
276 }
else if(
const IndexIVFFlat * ivfl =
277 dynamic_cast<const IndexIVFFlat *> (idx)) {
278 uint32_t h = fourcc (
"IvFl");
280 write_ivf_header (ivfl, f);
281 for(
int i = 0; i < ivfl->nlist; i++)
282 WRITEVECTOR (ivfl->vecs[i]);
283 }
else if(
const IndexIVFScalarQuantizer * ivsc =
284 dynamic_cast<const IndexIVFScalarQuantizer *> (idx)) {
285 uint32_t h = fourcc (
"IvSQ");
287 write_ivf_header (ivsc, f);
288 write_ScalarQuantizer (&ivsc->sq, f);
289 WRITE1 (ivsc->code_size);
290 for(
int i = 0; i < ivsc->nlist; i++)
291 WRITEVECTOR (ivsc->codes[i]);
292 }
else if(
const IndexIVFPQ * ivpq =
293 dynamic_cast<const IndexIVFPQ *> (idx)) {
294 const IndexIVFPQR * ivfpqr =
dynamic_cast<const IndexIVFPQR *
> (idx);
295 const IndexIVFPQCompact * ivfpqc =
296 dynamic_cast<const IndexIVFPQCompact *
> (idx);
297 uint32_t h = fourcc (ivfpqr ?
"IvQR" : ivfpqc ?
"IvPC" :
"IvPQ");
299 write_ivf_header (ivpq, f, !ivfpqc);
300 WRITE1 (ivpq->by_residual);
301 WRITE1 (ivpq->code_size);
302 write_ProductQuantizer (&ivpq->pq, f);
304 for(
int i = 0; i < ivpq->codes.size(); i++)
305 WRITEVECTOR (ivpq->codes[i]);
308 write_ProductQuantizer (&ivfpqr->refine_pq, f);
309 WRITEVECTOR (ivfpqr->refine_codes);
310 WRITE1 (ivfpqr->k_factor);
313 WRITETABPAD16 (ivfpqc->limits, ivfpqc->nlist + 1);
314 WRITETABPAD16 (ivfpqc->compact_ids, ivfpqc->ntotal);
315 WRITETABPAD16 (ivfpqc->compact_codes,
316 ivfpqc->ntotal * ivfpqc->code_size);
318 }
else if(
const IndexPreTransform * ixpt =
319 dynamic_cast<const IndexPreTransform *> (idx)) {
320 uint32_t h = fourcc (
"IxPT");
322 write_index_header (ixpt, f);
323 int nt = ixpt->chain.size();
325 for (
int i = 0; i < nt; i++)
326 write_VectorTransform (ixpt->chain[i], f);
327 write_index (ixpt->index, f);
328 }
else if(
const MultiIndexQuantizer * imiq =
329 dynamic_cast<const MultiIndexQuantizer *> (idx)) {
330 uint32_t h = fourcc (
"Imiq");
332 write_index_header (imiq, f);
333 write_ProductQuantizer (&imiq->pq, f);
334 }
else if(
const IndexRefineFlat * idxrf =
335 dynamic_cast<const IndexRefineFlat *> (idx)) {
336 uint32_t h = fourcc (
"IxRF");
338 write_index_header (idxrf, f);
339 write_index (idxrf->base_index, f);
340 write_index (&idxrf->refine_index, f);
341 WRITE1 (idxrf->k_factor);
342 }
else if(
const IndexIDMap * idxmap =
343 dynamic_cast<const IndexIDMap *> (idx)) {
345 dynamic_cast<const IndexIDMap2 *
> (idx) ? fourcc (
"IxM2") :
349 write_index_header (idxmap, f);
350 write_index (idxmap->index, f);
351 WRITEVECTOR (idxmap->id_map);
353 FAISS_THROW_MSG (
"don't know how to serialize this type of index");
357 void write_index (
const Index *idx,
const char *fname) {
358 FILE *f = fopen (fname,
"w");
359 FAISS_THROW_IF_NOT_FMT (f,
"cannot open %s for writing", fname);
360 ScopeFileCloser closer(f);
361 write_index (idx, f);
364 void write_VectorTransform (
const VectorTransform *vt,
const char *fname) {
365 FILE *f = fopen (fname,
"w");
366 FAISS_THROW_IF_NOT_FMT (f,
"cannot open %s for writing", fname);
367 ScopeFileCloser closer(f);
368 write_VectorTransform (vt, f);
375 static void read_index_header (Index *idx, FILE *f) {
381 READ1 (idx->is_trained);
382 READ1 (idx->metric_type);
383 idx->verbose =
false;
386 VectorTransform* read_VectorTransform (FILE *f) {
389 VectorTransform *vt =
nullptr;
391 if (h == fourcc (
"rrot") || h == fourcc (
"PCAm") ||
392 h == fourcc (
"LTra") || h == fourcc (
"PcAm")) {
393 LinearTransform *lt =
nullptr;
394 if (h == fourcc (
"rrot")) {
395 lt =
new RandomRotationMatrix ();
396 }
else if (h == fourcc (
"PCAm") ||
397 h == fourcc (
"PcAm")) {
398 PCAMatrix * pca =
new PCAMatrix ();
399 READ1 (pca->eigen_power);
400 READ1 (pca->random_rotation);
401 if (h == fourcc (
"PcAm"))
402 READ1 (pca->balanced_bins);
403 READVECTOR (pca->mean);
404 READVECTOR (pca->eigenvalues);
405 READVECTOR (pca->PCAMat);
407 }
else if (h == fourcc (
"LTra")) {
408 lt =
new LinearTransform ();
410 READ1 (lt->have_bias);
414 }
else if (h == fourcc (
"RmDT")) {
415 RemapDimensionsTransform *rdt =
new RemapDimensionsTransform ();
416 READVECTOR (rdt->map);
418 }
else if (h == fourcc (
"VNrm")) {
419 NormalizationTransform *nt =
new NormalizationTransform ();
423 FAISS_THROW_MSG(
"fourcc not recognized");
427 READ1 (vt->is_trained);
431 static void read_ProductQuantizer (ProductQuantizer *pq, FILE *f) {
435 pq->set_derived_values ();
436 READVECTOR (pq->centroids);
439 static void read_ScalarQuantizer (ScalarQuantizer *ivsc, FILE *f) {
441 READ1 (ivsc->rangestat);
442 READ1 (ivsc->rangestat_arg);
444 READ1 (ivsc->code_size);
445 READVECTOR (ivsc->trained);
448 ProductQuantizer * read_ProductQuantizer (
const char*fname) {
449 FILE *f = fopen (fname,
"r");
450 FAISS_THROW_IF_NOT_FMT (f,
"cannot open %s for writing", fname);
451 ScopeFileCloser closer(f);
452 ProductQuantizer *pq =
new ProductQuantizer();
453 ScopeDeleter1<ProductQuantizer> del (pq);
454 read_ProductQuantizer(pq, f);
459 static void read_ivf_header (IndexIVF * ivf, FILE *f,
460 bool include_ids =
true) {
461 read_index_header (ivf, f);
465 ivf->own_fields =
true;
467 ivf->ids.resize (ivf->nlist);
468 for (
size_t i = 0; i < ivf->nlist; i++)
469 READVECTOR (ivf->ids[i]);
471 READ1 (ivf->maintain_direct_map);
472 READVECTOR (ivf->direct_map);
475 static IndexIVFPQ *read_ivfpq (FILE *f, uint32_t h,
bool try_mmap)
478 IndexIVFPQR *ivfpqr =
479 h == fourcc (
"IvQR") ?
new IndexIVFPQR () : nullptr;
480 IndexIVFPQCompact *ivfpqc =
481 h == fourcc (
"IvPC") ?
new IndexIVFPQCompact () : nullptr;
482 IndexIVFPQ * ivpq = ivfpqr ? ivfpqr : ivfpqc ? ivfpqc :
new IndexIVFPQ ();
483 read_ivf_header (ivpq, f, !ivfpqc);
484 READ1 (ivpq->by_residual);
485 READ1 (ivpq->code_size);
486 read_ProductQuantizer (&ivpq->pq, f);
488 ivpq->codes.resize (ivpq->nlist);
489 for (
size_t i = 0; i < ivpq->nlist; i++)
490 READVECTOR (ivpq->codes[i]);
493 ivpq->use_precomputed_table = 0;
494 if (ivpq->by_residual)
495 ivpq->precompute_table ();
497 read_ProductQuantizer (&ivfpqr->refine_pq, f);
498 READVECTOR (ivfpqr->refine_codes);
499 READ1 (ivfpqr->k_factor);
503 READTABPAD16 (ivfpqc->limits, uint32_t, ivfpqc->nlist + 1);
504 READTABPAD16 (ivfpqc->compact_ids, uint32_t, ivfpqc->ntotal);
505 READTABPAD16 (ivfpqc->compact_codes, uint8_t,
506 ivfpqc->ntotal * ivfpqc->code_size);
508 long offset_limits, offset_compact_ids, offset_compact_codes;
509 TABOFFSETPAD16 (offset_limits, uint32_t, ivfpqc->nlist + 1);
510 TABOFFSETPAD16 (offset_compact_ids, uint32_t, ivfpqc->ntotal);
511 TABOFFSETPAD16 (offset_compact_codes, uint8_t,
512 ivfpqc->ntotal * ivfpqc->code_size);
513 ivfpqc->mmap_length = ftell (f);
515 ivfpqc->mmap_buffer = (
char*)mmap (
516 nullptr, ivfpqc->mmap_length,
517 PROT_READ, MAP_SHARED, fileno (f), 0);
518 if (!ivfpqc->mmap_buffer) {
519 perror (
"mmap failed");
524 ivfpqc->limits = (uint32_t*)(ivfpqc->mmap_buffer + offset_limits);
525 ivfpqc->compact_ids = (uint32_t*)(ivfpqc->mmap_buffer +
527 ivfpqc->compact_codes = (uint8_t*)(ivfpqc->mmap_buffer +
528 offset_compact_codes);
534 int read_old_fmt_hack = 0;
537 Index * idx =
nullptr;
540 if (h == fourcc (
"IxFI") || h == fourcc (
"IxF2")) {
542 if (h == fourcc (
"IxFI")) idxf =
new IndexFlatIP ();
544 read_index_header (idxf, f);
545 READVECTOR (idxf->
xb);
546 FAISS_THROW_IF_NOT (idxf->
xb.size() == idxf->
ntotal * idxf->
d);
549 }
else if (h == fourcc(
"IxHE") || h == fourcc(
"IxHe")) {
551 read_index_header (idxl, f);
557 if (h == fourcc(
"IxHE")) {
558 FAISS_THROW_IF_NOT_FMT (idxl->
nbits % 64 == 0,
559 "can only read old format IndexLSH with "
560 "nbits multiple of 64 (got %d)",
567 (read_VectorTransform (f));
568 FAISS_THROW_IF_NOT_MSG(rrot,
"expected a random rotation");
572 READVECTOR (idxl->
codes);
573 FAISS_THROW_IF_NOT (idxl->
rrot.d_in == idxl->
d &&
578 }
else if (h == fourcc (
"IxPQ") || h == fourcc (
"IxPo") ||
579 h == fourcc (
"IxPq")) {
582 read_index_header (idxp, f);
583 read_ProductQuantizer (&idxp->
pq, f);
584 READVECTOR (idxp->
codes);
585 if (h == fourcc (
"IxPo") || h == fourcc (
"IxPq")) {
586 READ1 (idxp->search_type);
587 READ1 (idxp->encode_signs);
593 if (h == fourcc (
"IxPQ") || h == fourcc (
"IxPo")) {
597 }
else if(h == fourcc (
"IvFl")) {
599 read_ivf_header (ivfl, f);
601 for (
size_t i = 0; i < ivfl->
nlist; i++)
602 READVECTOR (ivfl->
vecs[i]);
604 }
else if (h == fourcc (
"IxSQ")) {
606 read_index_header (idxs, f);
607 read_ScalarQuantizer (&idxs->
sq, f);
608 READVECTOR (idxs->
codes);
611 }
else if(h == fourcc (
"IvSQ")) {
613 read_ivf_header (ivsc, f);
615 read_ScalarQuantizer (&ivsc->sq, f);
616 READ1 (ivsc->code_size);
617 for(
int i = 0; i < ivsc->
nlist; i++)
618 READVECTOR (ivsc->
codes[i]);
620 }
else if(h == fourcc (
"IvPQ") || h == fourcc (
"IvQR") ||
621 h == fourcc (
"IvPC")) {
623 idx = read_ivfpq (f, h, try_mmap);
625 }
else if(h == fourcc (
"IxPT")) {
628 read_index_header (ixpt, f);
630 if (read_old_fmt_hack == 2) {
635 for (
int i = 0; i < nt; i++) {
636 ixpt->chain.push_back (read_VectorTransform (f));
640 }
else if(h == fourcc (
"Imiq")) {
642 read_index_header (imiq, f);
643 read_ProductQuantizer (&imiq->pq, f);
645 }
else if(h == fourcc (
"IxRF")) {
647 read_index_header (idxrf, f);
655 }
else if(h == fourcc (
"IxMp") || h == fourcc (
"IxM2")) {
656 bool is_map2 = h == fourcc (
"IxM2");
658 read_index_header (idxmap, f);
661 READVECTOR (idxmap->
id_map);
663 static_cast<IndexIDMap2*
>(idxmap)->construct_rev_map ();
667 fprintf (stderr,
"Index type 0x%08x not supported\n", h);
675 Index *
read_index (
const char *fname,
bool try_mmap) {
676 FILE *f = fopen (fname,
"r");
677 FAISS_THROW_IF_NOT_FMT (f,
"cannot open %s for reading:", fname);
683 VectorTransform *read_VectorTransform (
const char *fname) {
684 FILE *f = fopen (fname,
"r");
686 fprintf (stderr,
"cannot open %s for reading:", fname);
690 VectorTransform *vt = read_VectorTransform (f);
701 Index * clone_index (
const Index *index)
704 return cl.clone_Index (index);
709 #define TRYCLONE(classname, obj) \
710 if (const classname *clo = dynamic_cast<const classname *>(obj)) { \
711 return new classname(*clo); \
714 VectorTransform *Cloner::clone_VectorTransform (
const VectorTransform *vt)
716 TRYCLONE (RemapDimensionsTransform, vt)
717 TRYCLONE (OPQMatrix, vt)
718 TRYCLONE (PCAMatrix, vt)
719 TRYCLONE (RandomRotationMatrix, vt)
720 TRYCLONE (LinearTransform, vt)
722 FAISS_THROW_MSG(
"clone not supported for this type of VectorTransform");
727 IndexIVF * Cloner::clone_IndexIVF (
const IndexIVF *ivf)
729 TRYCLONE (IndexIVFPQR, ivf)
730 TRYCLONE (IndexIVFPQ, ivf)
731 TRYCLONE (IndexIVFFlat, ivf)
732 TRYCLONE (IndexIVFScalarQuantizer, ivf)
734 FAISS_THROW_MSG(
"clone not supported for this type of IndexIVF");
739 Index *Cloner::clone_Index (
const Index *index)
741 TRYCLONE (IndexPQ, index)
742 TRYCLONE (IndexLSH, index)
743 TRYCLONE (IndexFlatL2, index)
744 TRYCLONE (IndexFlatIP, index)
745 TRYCLONE (IndexFlat, index)
746 TRYCLONE (IndexScalarQuantizer, index)
747 TRYCLONE (MultiIndexQuantizer, index)
748 if (const IndexIVF * ivf = dynamic_cast<const IndexIVF*>(index)) {
749 IndexIVF *res = clone_IndexIVF (ivf);
750 res->own_fields =
true;
751 res->quantizer = clone_Index (ivf->quantizer);
753 }
else if (
const IndexPreTransform * ipt =
754 dynamic_cast<const IndexPreTransform*> (index)) {
755 IndexPreTransform *res =
new IndexPreTransform ();
757 res->index = clone_Index (ipt->index);
758 for (
int i = 0; i < ipt->chain.size(); i++)
759 res->chain.push_back (clone_VectorTransform (ipt->chain[i]));
760 res->own_fields =
true;
763 FAISS_THROW_MSG(
"clone not supported for this type of Index");
std::vector< uint8_t > codes
Codes. Size ntotal * pq.code_size.
size_t code_size
bytes per vector
Randomly rotate a set of vectors.
Index * read_index(FILE *f, bool try_mmap)
int bytes_per_vec
nb of 8-bits per encoded vector
std::vector< float > thresholds
thresholds to compare with
bool train_thresholds
whether we train thresholds or use 0
Index * base_index
faster index to pre-select the vectors that should be filtered
std::vector< std::vector< uint8_t > > codes
inverted list codes.
IndexFlat refine_index
storage for full vectors
bool own_fields
should the base index be deallocated?
std::vector< long > id_map
! whether pointers are deleted in destructo
std::vector< uint8_t > codes
Codes. Size ntotal * pq.code_size.
RandomRotationMatrix rrot
optional random rotation
ScalarQuantizer sq
Used to encode the vectors.
long idx_t
all indices are this type
ProductQuantizer pq
The product quantizer used to encode the vectors.
idx_t ntotal
total nb of indexed vectors
MetricType metric_type
type of metric this index uses for search
size_t nlist
number of possible key values
int nbits
nb of bits per vector
bool is_trained
set if the Index does not require training, or if training is done already
std::vector< float > xb
database vectors, size ntotal * d
int polysemous_ht
Hamming threshold used for polysemy.
bool rotate_data
whether to apply a random rotation to input
std::vector< uint8_t > codes
encoded dataset
std::vector< std::vector< float > > vecs
bool own_fields
! the sub-index