We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[MISC] The cosine method is slow to compute.
The Cosine method is slow in computation because it involves data transformation for the vector matrix every time, which results in significant time consumption.

```python
def cosine(x, y):
    '''
    Cosine similarity function for vector search
    '''
    x = x.astype(float)
    y = y.astype(float)
    x = x / numpy.linalg.norm(x, axis=1)[:, None]
    y = y / numpy.linalg.norm(y, axis=1)[:, None]
    return dot(x, y)

```


we need to preprocess all the incoming matrices of cosine.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `superduperdb/vector_search/in_memory.py`
Content:
```
1 import typing as t
2 
3 import numpy
4 
5 from superduperdb import logging
6 from superduperdb.vector_search.base import BaseVectorSearcher, VectorItem, measures
7 
8 
9 class InMemoryVectorSearcher(BaseVectorSearcher):
10     """
11     Simple hash-set for looking up with vector similarity.
12 
13     :param identifier: Unique string identifier of index
14     :param h: array/ tensor of vectors
15     :param index: list of IDs
16     :param measure: measure to assess similarity
17     """
18 
19     name = 'vanilla'
20 
21     def __init__(
22         self,
23         identifier: str,
24         dimensions: int,
25         h: t.Optional[numpy.ndarray] = None,
26         index: t.Optional[t.List[str]] = None,
27         measure: t.Union[str, t.Callable] = 'cosine',
28     ):
29         self.identifier = identifier
30         self.dimensions = dimensions
31         self._cache: t.Sequence[VectorItem] = []
32         self._CACHE_SIZE = 10000
33 
34         if h is not None:
35             assert index is not None
36             self._setup(h, index)
37         else:
38             self.h = None
39             self.index = None
40             self.lookup = None
41 
42         self.measure = measure
43         if isinstance(measure, str):
44             self.measure = measures[measure]
45 
46         self.identifier = identifier
47 
48     def __len__(self):
49         return self.h.shape[0]
50 
51     def _setup(self, h, index):
52         self.h = numpy.array(h) if not isinstance(h, numpy.ndarray) else h
53         self.index = index
54         self.lookup = dict(zip(index, range(len(index))))
55 
56     def find_nearest_from_id(self, _id, n=100):
57         self.post_create()
58         return self.find_nearest_from_array(self.h[self.lookup[_id]], n=n)
59 
60     def find_nearest_from_array(self, h, n=100, within_ids=None):
61         self.post_create()
62         h = self.to_numpy(h)[None, :]
63         if within_ids:
64             ix = list(map(self.lookup.__getitem__, within_ids))
65             similarities = self.measure(h, self.h[ix, :])  # mypy: ignore
66         else:
67             similarities = self.measure(h, self.h)  # mypy: ignore
68         similarities = similarities[0, :]
69         logging.debug(similarities)
70         scores = -numpy.sort(-similarities)
71         ## different ways of handling
72         if within_ids:
73             top_n_idxs = numpy.argsort(-similarities)[:n]
74             ix = [ix[i] for i in top_n_idxs]
75         else:
76             ix = numpy.argsort(-similarities)[:n]
77             ix = ix.tolist()
78         scores = scores.tolist()
79         _ids = [self.index[i] for i in ix]
80         return _ids, scores
81 
82     def add(self, items: t.Sequence[VectorItem]) -> None:
83         if len(self._cache) < self._CACHE_SIZE:
84             for item in items:
85                 self._cache.append(item)
86         else:
87             self._add(self._cache)
88             self._cache = []
89 
90     def post_create(self):
91         if self._cache:
92             self._add(self._cache)
93             self._cache = []
94 
95     def _add(self, items: t.Sequence[VectorItem]) -> None:
96         index = [item.id for item in items]
97         h = numpy.stack([item.vector for item in items])
98 
99         if self.h is not None:
100             old_not_in_new = list(set(self.index) - set(index))
101             ix_old = [self.lookup[_id] for _id in old_not_in_new]
102             h = numpy.concatenate((self.h[ix_old], h), axis=0)
103             index = [self.index[i] for i in ix_old] + index
104 
105         return self._setup(h, index)
106 
107     def delete(self, ids):
108         self.post_create()
109         ix = list(map(self.lookup.__getitem__, ids))
110         h = numpy.delete(self.h, ix, axis=0)
111         index = [_id for _id in self.index if _id not in set(ids)]
112         self._setup(h, index)
113
```

Path: `superduperdb/vector_search/base.py`
Content:
```
1 from __future__ import annotations
2 
3 import enum
4 import typing as t
5 from abc import ABC, abstractmethod
6 from dataclasses import dataclass, field
7 
8 import numpy
9 import numpy.typing
10 
11 if t.TYPE_CHECKING:
12     from superduperdb.components.vector_index import VectorIndex
13 
14 
15 class BaseVectorSearcher(ABC):
16     @classmethod
17     def from_component(cls, vi: 'VectorIndex'):
18         return cls(
19             identifier=vi.identifier, dimensions=vi.dimensions, measure=vi.measure
20         )
21 
22     @abstractmethod
23     def __init__(
24         self,
25         identifier: str,
26         dimensions: int,
27         h: t.Optional[numpy.ndarray] = None,
28         index: t.Optional[t.List[str]] = None,
29         measure: t.Optional[str] = None,
30     ):
31         pass
32 
33     @abstractmethod
34     def __len__(self):
35         pass
36 
37     @staticmethod
38     def to_numpy(h):
39         if isinstance(h, numpy.ndarray):
40             return h
41         if hasattr(h, 'numpy'):
42             return h.numpy()
43         if isinstance(h, list):
44             return numpy.array(h)
45         raise ValueError(str(h))
46 
47     @staticmethod
48     def to_list(h):
49         if hasattr(h, 'tolist'):
50             return h.tolist()
51         if isinstance(h, list):
52             return h
53         raise ValueError(str(h))
54 
55     @abstractmethod
56     def add(self, items: t.Sequence[VectorItem]) -> None:
57         """
58         Add items to the index.
59 
60         :param items: t.Sequence of VectorItems
61         """
62 
63     @abstractmethod
64     def delete(self, ids: t.Sequence[str]) -> None:
65         """
66         Remove items from the index
67 
68         :param ids: t.Sequence of ids of vectors.
69         """
70 
71     @abstractmethod
72     def find_nearest_from_id(
73         self,
74         _id,
75         n: int = 100,
76         within_ids: t.Sequence[str] = (),
77     ) -> t.Tuple[t.List[str], t.List[float]]:
78         """
79         Find the nearest vectors to the vector with the given id.
80 
81         :param _id: id of the vector
82         :param n: number of nearest vectors to return
83         """
84 
85     @abstractmethod
86     def find_nearest_from_array(
87         self,
88         h: numpy.typing.ArrayLike,
89         n: int = 100,
90         within_ids: t.Sequence[str] = (),
91     ) -> t.Tuple[t.List[str], t.List[float]]:
92         """
93         Find the nearest vectors to the given vector.
94 
95         :param h: vector
96         :param n: number of nearest vectors to return
97         """
98 
99     def post_create(self):
100         """
101         This method is used for searchers which requires
102         to perform a task after all vectors have been added
103         """
104 
105 
106 class VectorIndexMeasureType(str, enum.Enum):
107     cosine = 'cosine'
108     css = 'css'
109     dot = 'dot'
110     l2 = 'l2'
111 
112 
113 @dataclass(frozen=True)
114 class VectorSearchConfig:
115     '''
116     Represents search config which helps initiate a vector
117     searcher class.
118     '''
119 
120     id: str
121     dimensions: int
122     measure: VectorIndexMeasureType = VectorIndexMeasureType.l2
123     parameters: t.Mapping[str, t.Any] = field(default_factory=dict)
124 
125 
126 @dataclass(frozen=True)
127 class VectorItem:
128     '''
129     Class for representing a vector in vector search with
130     id and vector.
131 
132     '''
133 
134     id: str
135     vector: numpy.ndarray
136 
137     @classmethod
138     def create(
139         cls,
140         *,
141         id: str,
142         vector: numpy.typing.ArrayLike,
143     ) -> VectorItem:
144         return VectorItem(id=id, vector=BaseVectorSearcher.to_numpy(vector))
145 
146     def to_dict(self) -> t.Dict:
147         return {'id': self.id, 'vector': self.vector}
148 
149 
150 @dataclass(frozen=True)
151 class VectorSearchResult:
152     '''
153     Dataclass for representing vector search results with
154     `id` and `score`.
155     '''
156 
157     id: str
158     score: float
159 
160 
161 def l2(x, y):
162     '''
163     L2 function for vector similarity search
164     '''
165     return numpy.array([-numpy.linalg.norm(x - y, axis=1)])
166 
167 
168 def dot(x, y):
169     '''
170     Dot function for vector similarity search
171     '''
172     return numpy.dot(x, y.T)
173 
174 
175 def cosine(x, y):
176     '''
177     Cosine similarity function for vector search
178     '''
179     x = x.astype(float)
180     y = y.astype(float)
181     x = x / numpy.linalg.norm(x, axis=1)[:, None]
182     y = y / numpy.linalg.norm(y, axis=1)[:, None]
183     return dot(x, y)
184 
185 
186 measures = {'cosine': cosine, 'dot': dot, 'l2': l2}
187
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/superduperdb/vector_search/base.py b/superduperdb/vector_search/base.py
--- a/superduperdb/vector_search/base.py
+++ b/superduperdb/vector_search/base.py
@@ -176,10 +176,9 @@
     '''
     Cosine similarity function for vector search
     '''
-    x = x.astype(float)
-    y = y.astype(float)
     x = x / numpy.linalg.norm(x, axis=1)[:, None]
-    y = y / numpy.linalg.norm(y, axis=1)[:, None]
+    # y which implies all vectors in vectordatabase
+    # has normalized vectors.
     return dot(x, y)
 
 
diff --git a/superduperdb/vector_search/in_memory.py b/superduperdb/vector_search/in_memory.py
--- a/superduperdb/vector_search/in_memory.py
+++ b/superduperdb/vector_search/in_memory.py
@@ -31,6 +31,10 @@
         self._cache: t.Sequence[VectorItem] = []
         self._CACHE_SIZE = 10000
 
+        self.measure = measure
+        if isinstance(measure, str):
+            self.measure = measures[measure]
+
         if h is not None:
             assert index is not None
             self._setup(h, index)
@@ -39,17 +43,19 @@
             self.index = None
             self.lookup = None
 
-        self.measure = measure
-        if isinstance(measure, str):
-            self.measure = measures[measure]
-
         self.identifier = identifier
 
     def __len__(self):
         return self.h.shape[0]
 
     def _setup(self, h, index):
-        self.h = numpy.array(h) if not isinstance(h, numpy.ndarray) else h
+        h = numpy.array(h) if not isinstance(h, numpy.ndarray) else h
+
+        if self.measure == 'cosine':
+            # Normalization is required for cosine, hence preparing
+            # all vectors in advance.
+            h = h / numpy.linalg.norm(h, axis=1)[:, None]
+        self.h = h
         self.index = index
         self.lookup = dict(zip(index, range(len(index))))

{"golden_diff": "diff --git a/superduperdb/vector_search/base.py b/superduperdb/vector_search/base.py\n--- a/superduperdb/vector_search/base.py\n+++ b/superduperdb/vector_search/base.py\n@@ -176,10 +176,9 @@\n     '''\n     Cosine similarity function for vector search\n     '''\n-    x = x.astype(float)\n-    y = y.astype(float)\n     x = x / numpy.linalg.norm(x, axis=1)[:, None]\n-    y = y / numpy.linalg.norm(y, axis=1)[:, None]\n+    # y which implies all vectors in vectordatabase\n+    # has normalized vectors.\n     return dot(x, y)\n \n \ndiff --git a/superduperdb/vector_search/in_memory.py b/superduperdb/vector_search/in_memory.py\n--- a/superduperdb/vector_search/in_memory.py\n+++ b/superduperdb/vector_search/in_memory.py\n@@ -31,6 +31,10 @@\n         self._cache: t.Sequence[VectorItem] = []\n         self._CACHE_SIZE = 10000\n \n+        self.measure = measure\n+        if isinstance(measure, str):\n+            self.measure = measures[measure]\n+\n         if h is not None:\n             assert index is not None\n             self._setup(h, index)\n@@ -39,17 +43,19 @@\n             self.index = None\n             self.lookup = None\n \n-        self.measure = measure\n-        if isinstance(measure, str):\n-            self.measure = measures[measure]\n-\n         self.identifier = identifier\n \n     def __len__(self):\n         return self.h.shape[0]\n \n     def _setup(self, h, index):\n-        self.h = numpy.array(h) if not isinstance(h, numpy.ndarray) else h\n+        h = numpy.array(h) if not isinstance(h, numpy.ndarray) else h\n+\n+        if self.measure == 'cosine':\n+            # Normalization is required for cosine, hence preparing\n+            # all vectors in advance.\n+            h = h / numpy.linalg.norm(h, axis=1)[:, None]\n+        self.h = h\n         self.index = index\n         self.lookup = dict(zip(index, range(len(index))))\n", "issue": "[MISC] The cosine method is slow to compute.\nThe Cosine method is slow in computation because it involves data transformation for the vector matrix every time, which results in significant time consumption.\r\n\r\n```python\r\ndef cosine(x, y):\r\n    '''\r\n    Cosine similarity function for vector search\r\n    '''\r\n    x = x.astype(float)\r\n    y = y.astype(float)\r\n    x = x / numpy.linalg.norm(x, axis=1)[:, None]\r\n    y = y / numpy.linalg.norm(y, axis=1)[:, None]\r\n    return dot(x, y)\r\n\r\n```\r\n\r\n\r\nwe need to preprocess all the incoming matrices of cosine.\n", "before_files": [{"content": "import typing as t\n\nimport numpy\n\nfrom superduperdb import logging\nfrom superduperdb.vector_search.base import BaseVectorSearcher, VectorItem, measures\n\n\nclass InMemoryVectorSearcher(BaseVectorSearcher):\n    \"\"\"\n    Simple hash-set for looking up with vector similarity.\n\n    :param identifier: Unique string identifier of index\n    :param h: array/ tensor of vectors\n    :param index: list of IDs\n    :param measure: measure to assess similarity\n    \"\"\"\n\n    name = 'vanilla'\n\n    def __init__(\n        self,\n        identifier: str,\n        dimensions: int,\n        h: t.Optional[numpy.ndarray] = None,\n        index: t.Optional[t.List[str]] = None,\n        measure: t.Union[str, t.Callable] = 'cosine',\n    ):\n        self.identifier = identifier\n        self.dimensions = dimensions\n        self._cache: t.Sequence[VectorItem] = []\n        self._CACHE_SIZE = 10000\n\n        if h is not None:\n            assert index is not None\n            self._setup(h, index)\n        else:\n            self.h = None\n            self.index = None\n            self.lookup = None\n\n        self.measure = measure\n        if isinstance(measure, str):\n            self.measure = measures[measure]\n\n        self.identifier = identifier\n\n    def __len__(self):\n        return self.h.shape[0]\n\n    def _setup(self, h, index):\n        self.h = numpy.array(h) if not isinstance(h, numpy.ndarray) else h\n        self.index = index\n        self.lookup = dict(zip(index, range(len(index))))\n\n    def find_nearest_from_id(self, _id, n=100):\n        self.post_create()\n        return self.find_nearest_from_array(self.h[self.lookup[_id]], n=n)\n\n    def find_nearest_from_array(self, h, n=100, within_ids=None):\n        self.post_create()\n        h = self.to_numpy(h)[None, :]\n        if within_ids:\n            ix = list(map(self.lookup.__getitem__, within_ids))\n            similarities = self.measure(h, self.h[ix, :])  # mypy: ignore\n        else:\n            similarities = self.measure(h, self.h)  # mypy: ignore\n        similarities = similarities[0, :]\n        logging.debug(similarities)\n        scores = -numpy.sort(-similarities)\n        ## different ways of handling\n        if within_ids:\n            top_n_idxs = numpy.argsort(-similarities)[:n]\n            ix = [ix[i] for i in top_n_idxs]\n        else:\n            ix = numpy.argsort(-similarities)[:n]\n            ix = ix.tolist()\n        scores = scores.tolist()\n        _ids = [self.index[i] for i in ix]\n        return _ids, scores\n\n    def add(self, items: t.Sequence[VectorItem]) -> None:\n        if len(self._cache) < self._CACHE_SIZE:\n            for item in items:\n                self._cache.append(item)\n        else:\n            self._add(self._cache)\n            self._cache = []\n\n    def post_create(self):\n        if self._cache:\n            self._add(self._cache)\n            self._cache = []\n\n    def _add(self, items: t.Sequence[VectorItem]) -> None:\n        index = [item.id for item in items]\n        h = numpy.stack([item.vector for item in items])\n\n        if self.h is not None:\n            old_not_in_new = list(set(self.index) - set(index))\n            ix_old = [self.lookup[_id] for _id in old_not_in_new]\n            h = numpy.concatenate((self.h[ix_old], h), axis=0)\n            index = [self.index[i] for i in ix_old] + index\n\n        return self._setup(h, index)\n\n    def delete(self, ids):\n        self.post_create()\n        ix = list(map(self.lookup.__getitem__, ids))\n        h = numpy.delete(self.h, ix, axis=0)\n        index = [_id for _id in self.index if _id not in set(ids)]\n        self._setup(h, index)\n", "path": "superduperdb/vector_search/in_memory.py"}, {"content": "from __future__ import annotations\n\nimport enum\nimport typing as t\nfrom abc import ABC, abstractmethod\nfrom dataclasses import dataclass, field\n\nimport numpy\nimport numpy.typing\n\nif t.TYPE_CHECKING:\n    from superduperdb.components.vector_index import VectorIndex\n\n\nclass BaseVectorSearcher(ABC):\n    @classmethod\n    def from_component(cls, vi: 'VectorIndex'):\n        return cls(\n            identifier=vi.identifier, dimensions=vi.dimensions, measure=vi.measure\n        )\n\n    @abstractmethod\n    def __init__(\n        self,\n        identifier: str,\n        dimensions: int,\n        h: t.Optional[numpy.ndarray] = None,\n        index: t.Optional[t.List[str]] = None,\n        measure: t.Optional[str] = None,\n    ):\n        pass\n\n    @abstractmethod\n    def __len__(self):\n        pass\n\n    @staticmethod\n    def to_numpy(h):\n        if isinstance(h, numpy.ndarray):\n            return h\n        if hasattr(h, 'numpy'):\n            return h.numpy()\n        if isinstance(h, list):\n            return numpy.array(h)\n        raise ValueError(str(h))\n\n    @staticmethod\n    def to_list(h):\n        if hasattr(h, 'tolist'):\n            return h.tolist()\n        if isinstance(h, list):\n            return h\n        raise ValueError(str(h))\n\n    @abstractmethod\n    def add(self, items: t.Sequence[VectorItem]) -> None:\n        \"\"\"\n        Add items to the index.\n\n        :param items: t.Sequence of VectorItems\n        \"\"\"\n\n    @abstractmethod\n    def delete(self, ids: t.Sequence[str]) -> None:\n        \"\"\"\n        Remove items from the index\n\n        :param ids: t.Sequence of ids of vectors.\n        \"\"\"\n\n    @abstractmethod\n    def find_nearest_from_id(\n        self,\n        _id,\n        n: int = 100,\n        within_ids: t.Sequence[str] = (),\n    ) -> t.Tuple[t.List[str], t.List[float]]:\n        \"\"\"\n        Find the nearest vectors to the vector with the given id.\n\n        :param _id: id of the vector\n        :param n: number of nearest vectors to return\n        \"\"\"\n\n    @abstractmethod\n    def find_nearest_from_array(\n        self,\n        h: numpy.typing.ArrayLike,\n        n: int = 100,\n        within_ids: t.Sequence[str] = (),\n    ) -> t.Tuple[t.List[str], t.List[float]]:\n        \"\"\"\n        Find the nearest vectors to the given vector.\n\n        :param h: vector\n        :param n: number of nearest vectors to return\n        \"\"\"\n\n    def post_create(self):\n        \"\"\"\n        This method is used for searchers which requires\n        to perform a task after all vectors have been added\n        \"\"\"\n\n\nclass VectorIndexMeasureType(str, enum.Enum):\n    cosine = 'cosine'\n    css = 'css'\n    dot = 'dot'\n    l2 = 'l2'\n\n\n@dataclass(frozen=True)\nclass VectorSearchConfig:\n    '''\n    Represents search config which helps initiate a vector\n    searcher class.\n    '''\n\n    id: str\n    dimensions: int\n    measure: VectorIndexMeasureType = VectorIndexMeasureType.l2\n    parameters: t.Mapping[str, t.Any] = field(default_factory=dict)\n\n\n@dataclass(frozen=True)\nclass VectorItem:\n    '''\n    Class for representing a vector in vector search with\n    id and vector.\n\n    '''\n\n    id: str\n    vector: numpy.ndarray\n\n    @classmethod\n    def create(\n        cls,\n        *,\n        id: str,\n        vector: numpy.typing.ArrayLike,\n    ) -> VectorItem:\n        return VectorItem(id=id, vector=BaseVectorSearcher.to_numpy(vector))\n\n    def to_dict(self) -> t.Dict:\n        return {'id': self.id, 'vector': self.vector}\n\n\n@dataclass(frozen=True)\nclass VectorSearchResult:\n    '''\n    Dataclass for representing vector search results with\n    `id` and `score`.\n    '''\n\n    id: str\n    score: float\n\n\ndef l2(x, y):\n    '''\n    L2 function for vector similarity search\n    '''\n    return numpy.array([-numpy.linalg.norm(x - y, axis=1)])\n\n\ndef dot(x, y):\n    '''\n    Dot function for vector similarity search\n    '''\n    return numpy.dot(x, y.T)\n\n\ndef cosine(x, y):\n    '''\n    Cosine similarity function for vector search\n    '''\n    x = x.astype(float)\n    y = y.astype(float)\n    x = x / numpy.linalg.norm(x, axis=1)[:, None]\n    y = y / numpy.linalg.norm(y, axis=1)[:, None]\n    return dot(x, y)\n\n\nmeasures = {'cosine': cosine, 'dot': dot, 'l2': l2}\n", "path": "superduperdb/vector_search/base.py"}], "after_files": [{"content": "import typing as t\n\nimport numpy\n\nfrom superduperdb import logging\nfrom superduperdb.vector_search.base import BaseVectorSearcher, VectorItem, measures\n\n\nclass InMemoryVectorSearcher(BaseVectorSearcher):\n    \"\"\"\n    Simple hash-set for looking up with vector similarity.\n\n    :param identifier: Unique string identifier of index\n    :param h: array/ tensor of vectors\n    :param index: list of IDs\n    :param measure: measure to assess similarity\n    \"\"\"\n\n    name = 'vanilla'\n\n    def __init__(\n        self,\n        identifier: str,\n        dimensions: int,\n        h: t.Optional[numpy.ndarray] = None,\n        index: t.Optional[t.List[str]] = None,\n        measure: t.Union[str, t.Callable] = 'cosine',\n    ):\n        self.identifier = identifier\n        self.dimensions = dimensions\n        self._cache: t.Sequence[VectorItem] = []\n        self._CACHE_SIZE = 10000\n\n        self.measure = measure\n        if isinstance(measure, str):\n            self.measure = measures[measure]\n\n        if h is not None:\n            assert index is not None\n            self._setup(h, index)\n        else:\n            self.h = None\n            self.index = None\n            self.lookup = None\n\n        self.identifier = identifier\n\n    def __len__(self):\n        return self.h.shape[0]\n\n    def _setup(self, h, index):\n        h = numpy.array(h) if not isinstance(h, numpy.ndarray) else h\n\n        if self.measure == 'cosine':\n            # Normalization is required for cosine, hence preparing\n            # all vectors in advance.\n            h = h / numpy.linalg.norm(h, axis=1)[:, None]\n        self.h = h\n        self.index = index\n        self.lookup = dict(zip(index, range(len(index))))\n\n    def find_nearest_from_id(self, _id, n=100):\n        self.post_create()\n        return self.find_nearest_from_array(self.h[self.lookup[_id]], n=n)\n\n    def find_nearest_from_array(self, h, n=100, within_ids=None):\n        self.post_create()\n        h = self.to_numpy(h)[None, :]\n        if within_ids:\n            ix = list(map(self.lookup.__getitem__, within_ids))\n            similarities = self.measure(h, self.h[ix, :])  # mypy: ignore\n        else:\n            similarities = self.measure(h, self.h)  # mypy: ignore\n        similarities = similarities[0, :]\n        logging.debug(similarities)\n        scores = -numpy.sort(-similarities)\n        ## different ways of handling\n        if within_ids:\n            top_n_idxs = numpy.argsort(-similarities)[:n]\n            ix = [ix[i] for i in top_n_idxs]\n        else:\n            ix = numpy.argsort(-similarities)[:n]\n            ix = ix.tolist()\n        scores = scores.tolist()\n        _ids = [self.index[i] for i in ix]\n        return _ids, scores\n\n    def add(self, items: t.Sequence[VectorItem]) -> None:\n        if len(self._cache) < self._CACHE_SIZE:\n            for item in items:\n                self._cache.append(item)\n        else:\n            self._add(self._cache)\n            self._cache = []\n\n    def post_create(self):\n        if self._cache:\n            self._add(self._cache)\n            self._cache = []\n\n    def _add(self, items: t.Sequence[VectorItem]) -> None:\n        index = [item.id for item in items]\n        h = numpy.stack([item.vector for item in items])\n\n        if self.h is not None:\n            old_not_in_new = list(set(self.index) - set(index))\n            ix_old = [self.lookup[_id] for _id in old_not_in_new]\n            h = numpy.concatenate((self.h[ix_old], h), axis=0)\n            index = [self.index[i] for i in ix_old] + index\n\n        return self._setup(h, index)\n\n    def delete(self, ids):\n        self.post_create()\n        ix = list(map(self.lookup.__getitem__, ids))\n        h = numpy.delete(self.h, ix, axis=0)\n        index = [_id for _id in self.index if _id not in set(ids)]\n        self._setup(h, index)\n", "path": "superduperdb/vector_search/in_memory.py"}, {"content": "from __future__ import annotations\n\nimport enum\nimport typing as t\nfrom abc import ABC, abstractmethod\nfrom dataclasses import dataclass, field\n\nimport numpy\nimport numpy.typing\n\nif t.TYPE_CHECKING:\n    from superduperdb.components.vector_index import VectorIndex\n\n\nclass BaseVectorSearcher(ABC):\n    @classmethod\n    def from_component(cls, vi: 'VectorIndex'):\n        return cls(\n            identifier=vi.identifier, dimensions=vi.dimensions, measure=vi.measure\n        )\n\n    @abstractmethod\n    def __init__(\n        self,\n        identifier: str,\n        dimensions: int,\n        h: t.Optional[numpy.ndarray] = None,\n        index: t.Optional[t.List[str]] = None,\n        measure: t.Optional[str] = None,\n    ):\n        pass\n\n    @abstractmethod\n    def __len__(self):\n        pass\n\n    @staticmethod\n    def to_numpy(h):\n        if isinstance(h, numpy.ndarray):\n            return h\n        if hasattr(h, 'numpy'):\n            return h.numpy()\n        if isinstance(h, list):\n            return numpy.array(h)\n        raise ValueError(str(h))\n\n    @staticmethod\n    def to_list(h):\n        if hasattr(h, 'tolist'):\n            return h.tolist()\n        if isinstance(h, list):\n            return h\n        raise ValueError(str(h))\n\n    @abstractmethod\n    def add(self, items: t.Sequence[VectorItem]) -> None:\n        \"\"\"\n        Add items to the index.\n\n        :param items: t.Sequence of VectorItems\n        \"\"\"\n\n    @abstractmethod\n    def delete(self, ids: t.Sequence[str]) -> None:\n        \"\"\"\n        Remove items from the index\n\n        :param ids: t.Sequence of ids of vectors.\n        \"\"\"\n\n    @abstractmethod\n    def find_nearest_from_id(\n        self,\n        _id,\n        n: int = 100,\n        within_ids: t.Sequence[str] = (),\n    ) -> t.Tuple[t.List[str], t.List[float]]:\n        \"\"\"\n        Find the nearest vectors to the vector with the given id.\n\n        :param _id: id of the vector\n        :param n: number of nearest vectors to return\n        \"\"\"\n\n    @abstractmethod\n    def find_nearest_from_array(\n        self,\n        h: numpy.typing.ArrayLike,\n        n: int = 100,\n        within_ids: t.Sequence[str] = (),\n    ) -> t.Tuple[t.List[str], t.List[float]]:\n        \"\"\"\n        Find the nearest vectors to the given vector.\n\n        :param h: vector\n        :param n: number of nearest vectors to return\n        \"\"\"\n\n    def post_create(self):\n        \"\"\"\n        This method is used for searchers which requires\n        to perform a task after all vectors have been added\n        \"\"\"\n\n\nclass VectorIndexMeasureType(str, enum.Enum):\n    cosine = 'cosine'\n    css = 'css'\n    dot = 'dot'\n    l2 = 'l2'\n\n\n@dataclass(frozen=True)\nclass VectorSearchConfig:\n    '''\n    Represents search config which helps initiate a vector\n    searcher class.\n    '''\n\n    id: str\n    dimensions: int\n    measure: VectorIndexMeasureType = VectorIndexMeasureType.l2\n    parameters: t.Mapping[str, t.Any] = field(default_factory=dict)\n\n\n@dataclass(frozen=True)\nclass VectorItem:\n    '''\n    Class for representing a vector in vector search with\n    id and vector.\n\n    '''\n\n    id: str\n    vector: numpy.ndarray\n\n    @classmethod\n    def create(\n        cls,\n        *,\n        id: str,\n        vector: numpy.typing.ArrayLike,\n    ) -> VectorItem:\n        return VectorItem(id=id, vector=BaseVectorSearcher.to_numpy(vector))\n\n    def to_dict(self) -> t.Dict:\n        return {'id': self.id, 'vector': self.vector}\n\n\n@dataclass(frozen=True)\nclass VectorSearchResult:\n    '''\n    Dataclass for representing vector search results with\n    `id` and `score`.\n    '''\n\n    id: str\n    score: float\n\n\ndef l2(x, y):\n    '''\n    L2 function for vector similarity search\n    '''\n    return numpy.array([-numpy.linalg.norm(x - y, axis=1)])\n\n\ndef dot(x, y):\n    '''\n    Dot function for vector similarity search\n    '''\n    return numpy.dot(x, y.T)\n\n\ndef cosine(x, y):\n    '''\n    Cosine similarity function for vector search\n    '''\n    x = x / numpy.linalg.norm(x, axis=1)[:, None]\n    # y which implies all vectors in vectordatabase\n    # has normalized vectors.\n    return dot(x, y)\n\n\nmeasures = {'cosine': cosine, 'dot': dot, 'l2': l2}\n", "path": "superduperdb/vector_search/base.py"}]}