faiss-napi
Version:
JavaScript NAPI bindings for faiss
270 lines (226 loc) • 11.6 kB
JavaScript
const { IndexFlatL2 } = require('..');
describe('IndexFlatL2', () => {
describe('#read', () => {
it('throws an error if file does not existed', () => {
const fname = 'not_existed_file'
expect(() => { IndexFlatL2.read(fname) }).toThrow(new RegExp(`^Error.*could not open ${fname} for reading: No such file or directory$`));
});
it('read saved file.', () => {
const dimension = 2;
const index = new IndexFlatL2(dimension);
index.add([1, 0]);
const fname = '_tmp.read.index';
index.write(fname);
const index_loaded = IndexFlatL2.read(fname);
expect(index_loaded.dims).toBe(2);
expect(index_loaded.ntotal).toBe(1);
})
});
describe('#ntotal', () => {
const index = new IndexFlatL2(1);
it('returns 0 if the index is just initialized', () => {
expect(index.ntotal).toBe(0);
});
it('returns total count', () => {
index.add([1]);
expect(index.ntotal).toBe(1);
index.add([1, 2, 3]);
expect(index.ntotal).toBe(4);
});
});
describe('#dims', () => {
const index = new IndexFlatL2(128);
it('returns dimension', () => {
expect(index.dims).toBe(128);
});
});
describe('#isTrained', () => {
const index = new IndexFlatL2(1);
it('returns true fixed', () => {
expect(index.isTrained).toBe(true);
});
});
describe('#add', () => {
const index = new IndexFlatL2(2);
it('throws an error if the count of given param is not 1', () => {
expect(() => { index.add() }).toThrow('Expected 1 argument, but got 0.');
expect(() => { index.add([], 1) }).toThrow('Expected 1 argument, but got 2.');
});
it('throws an error if given a non-Array object', () => {
expect(() => { index.add('[1, 2, 3]') }).toThrow('Invalid the first argument type, must be an Array.');
});
it('throws an error if the length of given array is not adhere to the dimension of the index', () => {
expect(() => { index.add([1, 2, 3]) }).toThrow('Invalid the given array length.');
expect(() => { index.add([1, 2, 3, 4, 5]) }).toThrow('Invalid the given array length.');
expect(() => { index.add([1]) }).toThrow('Invalid the given array length.');
});
});
describe('#search', () => {
const index = new IndexFlatL2(2);
beforeAll(() => {
index.add([1, 0]);
index.add([1, 2]);
index.add([1, 3]);
index.add([1, 1]);
});
it('throws an error if the count of given param is not 1 or 2', () => {
expect(() => { index.search() }).toThrow('Invalid the first argument type, must be an Array.');
});
it('throws an error if given a non-Array object to first argument', () => {
expect(() => { index.search('[1, 2, 3]', 2) }).toThrow('Invalid the first argument type, must be an Array.');
});
it('throws an error if given a non-Number object to second argument', () => {
expect(() => { index.search([1, 2, 3], '2') }).toThrow('Invalid the second argument type, must be a Number.');
});
it('returns ntotal results if 2nd argument not provided', () => {
expect(index.search([1, 1])).toMatchObject({ distances: [0, 1, 1, 4], labels: [3n, 0n, 1n, 2n] });
});
it('never returns more than ntotal results', () => {
expect(index.search([1, 1], 6)).toMatchObject({ distances: [0, 1, 1, 4], labels: [3n, 0n, 1n, 2n] });
});
it('throws an error if the length of given array is not adhere to the dimension of the index', () => {
expect(() => { index.search([1, 2, 3], 2) }).toThrow('Invalid the given array length.');
expect(() => { index.search([1, 2, 3, 4, 5], 2) }).toThrow('Invalid the given array length.');
expect(() => { index.search([1], 2) }).toThrow('Invalid the given array length.');
});
it('returns search results', () => {
expect(index.search([1, 0], 1)).toMatchObject({ distances: [0], labels: [0n] });
expect(index.search([1, 0], 4)).toMatchObject({ distances: [0, 1, 4, 9], labels: [0n, 3n, 1n, 2n] });
expect(index.search([1, 1], 4)).toMatchObject({ distances: [0, 1, 1, 4], labels: [3n, 0n, 1n, 2n] });
});
});
describe("#merge", () => {
const index1 = new IndexFlatL2(2);
beforeAll(() => {
index1.add([1, 0]);
index1.add([1, 2]);
index1.add([1, 3]);
index1.add([1, 1]);
});
const index2 = new IndexFlatL2(2);
beforeAll(() => {
index2.mergeFrom(index1);
});
it("throws an error if the number of arguments is not 1", () => {
expect(() => { index2.mergeFrom() }).toThrow('Expected 1 argument, but got 0.');
expect(() => { index2.mergeFrom(index1, 2) }).toThrow('Expected 1 argument, but got 2.');
});
it("throws an error if argument is not an object", () => {
expect(() => { index2.mergeFrom(1) }).toThrow('Invalid argument type, must be an object.');
expect(() => { index2.mergeFrom("string") }).toThrow('Invalid argument type, must be an object.');
});
it("throws an error if merging index has different dimensions", () => {
const index3 = new IndexFlatL2(3);
expect(() => { index2.mergeFrom(index3) }).toThrow('The merging index must have the same dimension.');
});
it("returns search results on merged index", () => {
expect(index2.search([1, 0], 1)).toMatchObject({
distances: [0],
labels: [0n],
});
expect(index2.search([1, 0], 4)).toMatchObject({
distances: [0, 1, 4, 9],
labels: [0n, 3n, 1n, 2n],
});
expect(index2.search([1, 1], 4)).toMatchObject({
distances: [0, 1, 1, 4],
labels: [3n, 0n, 1n, 2n],
});
});
});
describe("#removeIds", () => {
let index;
beforeEach(() => {
index = new IndexFlatL2(2);
index.add([1, 0]);
index.add([1, 1]);
index.add([1, 2]);
index.add([1, 3]);
});
it('throws an error if the count of given param is not 1', () => {
expect(() => { index.removeIds() }).toThrow('Expected 1 argument, but got 0.');
expect(() => { index.removeIds([], 1) }).toThrow('Expected 1 argument, but got 2.');
});
it('throws an error if given a non-Array object', () => {
expect(() => { index.removeIds('[1, 2, 3]') }).toThrow('Invalid the first argument type, must be an Array.');
});
it("returns number of IDs removed", () => {
expect(index.removeIds([])).toBe(0);
expect(index.removeIds([0])).toBe(1);
expect(index.removeIds([0, 1])).toBe(2);
expect(index.removeIds([2])).toBe(0);
expect(index.removeIds([0, 1, 2])).toBe(1);
});
it("correctly removed", () => {
expect(index.search([1, 1], 1)).toMatchObject({ distances: [0], labels: [1n] });
expect(index.removeIds([0])).toBe(1);
expect(index.search([1, 1], 1)).toMatchObject({ distances: [0], labels: [0n] });
});
it("correctly removed multiple elements", () => {
expect(index.search([1, 3], 1)).toMatchObject({ distances: [0], labels: [3n] });
expect(index.removeIds([0, 1])).toBe(2);
expect(index.search([1, 3], 1)).toMatchObject({ distances: [0], labels: [1n] });
});
it("correctly removed partal elements", () => {
expect(index.search([1, 3], 1)).toMatchObject({ distances: [0], labels: [3n] });
expect(index.removeIds([0, 1, 2, 4, 5])).toBe(3);
expect(index.search([1, 3], 1)).toMatchObject({ distances: [0], labels: [0n] });
});
});
describe('#codes', () => {
it("returns codeSize", () => {
const index = new IndexFlatL2(2);
expect(index.codeSize).toBe(8);
});
it("returns codes", () => {
const index = new IndexFlatL2(2);
const arr = [1, 1, 255, 255];
index.add(arr.slice(0, 2));
index.add(arr.slice(2, 4));
expect(index.codes).toStrictEqual(Buffer.from(Float32Array.from(arr).buffer));
index.add([99, 99]);
expect(index.codes).toStrictEqual(Buffer.from(Float32Array.from(arr.concat([99, 99])).buffer));
});
it("getCodesByRange defaults is same as codes", () => {
const index = new IndexFlatL2(2);
const arr = [1, 1, 255, 255];
index.add(arr.slice(0, 2));
index.add(arr.slice(2, 4));
expect(index.getCodesByRange()).toStrictEqual(Buffer.from(Float32Array.from(arr).buffer));
index.add([99, 99]);
expect(index.getCodesByRange()).toStrictEqual(Buffer.from(Float32Array.from(arr.concat([99, 99])).buffer));
});
it("getCodesByRange to only return 1st vector codes", () => {
const index = new IndexFlatL2(2);
const arr = [1, 1, 255, 255];
index.add(arr.slice(0, 2));
index.add(arr.slice(2, 4));
expect(index.getCodesByRange(0, 2 * 4)).toStrictEqual(Buffer.from(Float32Array.from(arr.slice(0, 2)).buffer));
});
it("getCodesByRange to only return 2nd vector codes", () => {
const index = new IndexFlatL2(2);
const arr = [1, 1, 255, 255];
index.add(arr.slice(0, 2));
index.add(arr.slice(2, 4));
expect(index.getCodesByRange(2 * 4)).toStrictEqual(Buffer.from(Float32Array.from(arr.slice(2, 4)).buffer));
});
it("setCodesByRange to replace codes on 1st vector only", () => {
const index = new IndexFlatL2(2);
const arr = [1, 1, 255, 255];
index.add(arr.slice(0, 2));
index.add(arr.slice(2, 4));
expect(index.getCodesByRange(0, 2 * 4)).toStrictEqual(Buffer.from(Float32Array.from(arr.slice(0, 2)).buffer));
index.setCodesByRange(Buffer.from(Float32Array.from(arr.slice(2, 4)).buffer), 0);
expect(index.getCodesByRange(0, 2 * 4)).toStrictEqual(Buffer.from(Float32Array.from(arr.slice(2, 4)).buffer));
});
it("setCodesByRange to replace codes on 2nd vector only", () => {
const index = new IndexFlatL2(2);
const arr = [1, 1, 255, 255];
index.add(arr.slice(0, 2));
index.add(arr.slice(2, 4));
expect(index.getCodesByRange(2 * 4)).toStrictEqual(Buffer.from(Float32Array.from(arr.slice(2, 4)).buffer));
index.setCodesByRange(Buffer.from(Float32Array.from(arr.slice(0, 2)).buffer), 2 * 4);
expect(index.getCodesByRange(2 * 4)).toStrictEqual(Buffer.from(Float32Array.from(arr.slice(0, 2)).buffer));
});
});
});