pgvector 0.3.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pgvector/asyncpg/__init__.py +9 -0
- pgvector/asyncpg/register.py +31 -0
- pgvector/django/__init__.py +26 -0
- pgvector/django/bit.py +32 -0
- pgvector/django/extensions.py +6 -0
- pgvector/django/functions.py +55 -0
- pgvector/django/halfvec.py +60 -0
- pgvector/django/indexes.py +46 -0
- pgvector/django/sparsevec.py +55 -0
- pgvector/django/vector.py +73 -0
- pgvector/peewee/__init__.py +14 -0
- pgvector/peewee/bit.py +21 -0
- pgvector/peewee/halfvec.py +34 -0
- pgvector/peewee/sparsevec.py +34 -0
- pgvector/peewee/vector.py +34 -0
- pgvector/psycopg/__init__.py +11 -0
- pgvector/psycopg/bit.py +31 -0
- pgvector/psycopg/halfvec.py +53 -0
- pgvector/psycopg/register.py +37 -0
- pgvector/psycopg/sparsevec.py +53 -0
- pgvector/psycopg/vector.py +58 -0
- pgvector/psycopg2/__init__.py +8 -0
- pgvector/psycopg2/halfvec.py +20 -0
- pgvector/psycopg2/register.py +28 -0
- pgvector/psycopg2/sparsevec.py +20 -0
- pgvector/psycopg2/vector.py +21 -0
- pgvector/sqlalchemy/__init__.py +19 -0
- pgvector/sqlalchemy/bit.py +26 -0
- pgvector/sqlalchemy/functions.py +14 -0
- pgvector/sqlalchemy/halfvec.py +51 -0
- pgvector/sqlalchemy/sparsevec.py +51 -0
- pgvector/sqlalchemy/vector.py +51 -0
- pgvector/utils/__init__.py +11 -0
- pgvector/utils/bit.py +61 -0
- pgvector/utils/halfvec.py +78 -0
- pgvector/utils/sparsevec.py +156 -0
- pgvector/utils/vector.py +78 -0
- pgvector-0.3.5.dist-info/LICENSE.txt +21 -0
- pgvector-0.3.5.dist-info/METADATA +554 -0
- pgvector-0.3.5.dist-info/RECORD +42 -0
- pgvector-0.3.5.dist-info/WHEEL +5 -0
- pgvector-0.3.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from psycopg.adapt import Loader, Dumper
|
|
2
|
+
from psycopg.pq import Format
|
|
3
|
+
from ..utils import SparseVector
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SparseVectorDumper(Dumper):
|
|
7
|
+
|
|
8
|
+
format = Format.TEXT
|
|
9
|
+
|
|
10
|
+
def dump(self, obj):
|
|
11
|
+
return SparseVector._to_db(obj).encode('utf8')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SparseVectorBinaryDumper(SparseVectorDumper):
|
|
15
|
+
|
|
16
|
+
format = Format.BINARY
|
|
17
|
+
|
|
18
|
+
def dump(self, obj):
|
|
19
|
+
return SparseVector._to_db_binary(obj)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SparseVectorLoader(Loader):
|
|
23
|
+
|
|
24
|
+
format = Format.TEXT
|
|
25
|
+
|
|
26
|
+
def load(self, data):
|
|
27
|
+
if isinstance(data, memoryview):
|
|
28
|
+
data = bytes(data)
|
|
29
|
+
return SparseVector._from_db(data.decode('utf8'))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SparseVectorBinaryLoader(SparseVectorLoader):
|
|
33
|
+
|
|
34
|
+
format = Format.BINARY
|
|
35
|
+
|
|
36
|
+
def load(self, data):
|
|
37
|
+
if isinstance(data, memoryview):
|
|
38
|
+
data = bytes(data)
|
|
39
|
+
return SparseVector._from_db_binary(data)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def register_sparsevec_info(context, info):
|
|
43
|
+
info.register(context)
|
|
44
|
+
|
|
45
|
+
# add oid to anonymous class for set_types
|
|
46
|
+
text_dumper = type('', (SparseVectorDumper,), {'oid': info.oid})
|
|
47
|
+
binary_dumper = type('', (SparseVectorBinaryDumper,), {'oid': info.oid})
|
|
48
|
+
|
|
49
|
+
adapters = context.adapters
|
|
50
|
+
adapters.register_dumper(SparseVector, text_dumper)
|
|
51
|
+
adapters.register_dumper(SparseVector, binary_dumper)
|
|
52
|
+
adapters.register_loader(info.oid, SparseVectorLoader)
|
|
53
|
+
adapters.register_loader(info.oid, SparseVectorBinaryLoader)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import psycopg
|
|
2
|
+
from psycopg.adapt import Loader, Dumper
|
|
3
|
+
from psycopg.pq import Format
|
|
4
|
+
from ..utils import Vector
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class VectorDumper(Dumper):
|
|
8
|
+
|
|
9
|
+
format = Format.TEXT
|
|
10
|
+
|
|
11
|
+
def dump(self, obj):
|
|
12
|
+
return Vector._to_db(obj).encode('utf8')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class VectorBinaryDumper(VectorDumper):
|
|
16
|
+
|
|
17
|
+
format = Format.BINARY
|
|
18
|
+
|
|
19
|
+
def dump(self, obj):
|
|
20
|
+
return Vector._to_db_binary(obj)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VectorLoader(Loader):
|
|
24
|
+
|
|
25
|
+
format = Format.TEXT
|
|
26
|
+
|
|
27
|
+
def load(self, data):
|
|
28
|
+
if isinstance(data, memoryview):
|
|
29
|
+
data = bytes(data)
|
|
30
|
+
return Vector._from_db(data.decode('utf8'))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class VectorBinaryLoader(VectorLoader):
|
|
34
|
+
|
|
35
|
+
format = Format.BINARY
|
|
36
|
+
|
|
37
|
+
def load(self, data):
|
|
38
|
+
if isinstance(data, memoryview):
|
|
39
|
+
data = bytes(data)
|
|
40
|
+
return Vector._from_db_binary(data)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def register_vector_info(context, info):
|
|
44
|
+
if info is None:
|
|
45
|
+
raise psycopg.ProgrammingError('vector type not found in the database')
|
|
46
|
+
info.register(context)
|
|
47
|
+
|
|
48
|
+
# add oid to anonymous class for set_types
|
|
49
|
+
text_dumper = type('', (VectorDumper,), {'oid': info.oid})
|
|
50
|
+
binary_dumper = type('', (VectorBinaryDumper,), {'oid': info.oid})
|
|
51
|
+
|
|
52
|
+
adapters = context.adapters
|
|
53
|
+
adapters.register_dumper('numpy.ndarray', text_dumper)
|
|
54
|
+
adapters.register_dumper('numpy.ndarray', binary_dumper)
|
|
55
|
+
adapters.register_dumper(Vector, text_dumper)
|
|
56
|
+
adapters.register_dumper(Vector, binary_dumper)
|
|
57
|
+
adapters.register_loader(info.oid, VectorLoader)
|
|
58
|
+
adapters.register_loader(info.oid, VectorBinaryLoader)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from psycopg2.extensions import adapt, new_type, register_adapter, register_type
|
|
2
|
+
from ..utils import HalfVector
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HalfvecAdapter:
|
|
6
|
+
def __init__(self, value):
|
|
7
|
+
self._value = value
|
|
8
|
+
|
|
9
|
+
def getquoted(self):
|
|
10
|
+
return adapt(HalfVector._to_db(self._value)).getquoted()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def cast_halfvec(value, cur):
|
|
14
|
+
return HalfVector._from_db(value)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register_halfvec_info(oid, scope):
|
|
18
|
+
halfvec = new_type((oid,), 'HALFVEC', cast_halfvec)
|
|
19
|
+
register_type(halfvec, scope)
|
|
20
|
+
register_adapter(HalfVector, HalfvecAdapter)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import psycopg2
|
|
2
|
+
from psycopg2.extensions import cursor
|
|
3
|
+
from .halfvec import register_halfvec_info
|
|
4
|
+
from .sparsevec import register_sparsevec_info
|
|
5
|
+
from .vector import register_vector_info
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# TODO make globally False by default in 0.4.0
|
|
9
|
+
# note: register_adapter is always global
|
|
10
|
+
def register_vector(conn_or_curs=None, globally=True):
|
|
11
|
+
conn = conn_or_curs if hasattr(conn_or_curs, 'cursor') else conn_or_curs.connection
|
|
12
|
+
cur = conn.cursor(cursor_factory=cursor)
|
|
13
|
+
scope = None if globally else conn_or_curs
|
|
14
|
+
|
|
15
|
+
# use to_regtype to get first matching type in search path
|
|
16
|
+
cur.execute("SELECT typname, oid FROM pg_type WHERE oid IN (to_regtype('vector'), to_regtype('halfvec'), to_regtype('sparsevec'))")
|
|
17
|
+
type_info = dict(cur.fetchall())
|
|
18
|
+
|
|
19
|
+
if 'vector' not in type_info:
|
|
20
|
+
raise psycopg2.ProgrammingError('vector type not found in the database')
|
|
21
|
+
|
|
22
|
+
register_vector_info(type_info['vector'], scope)
|
|
23
|
+
|
|
24
|
+
if 'halfvec' in type_info:
|
|
25
|
+
register_halfvec_info(type_info['halfvec'], scope)
|
|
26
|
+
|
|
27
|
+
if 'sparsevec' in type_info:
|
|
28
|
+
register_sparsevec_info(type_info['sparsevec'], scope)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from psycopg2.extensions import adapt, new_type, register_adapter, register_type
|
|
2
|
+
from ..utils import SparseVector
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SparsevecAdapter:
|
|
6
|
+
def __init__(self, value):
|
|
7
|
+
self._value = value
|
|
8
|
+
|
|
9
|
+
def getquoted(self):
|
|
10
|
+
return adapt(SparseVector._to_db(self._value)).getquoted()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def cast_sparsevec(value, cur):
|
|
14
|
+
return SparseVector._from_db(value)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register_sparsevec_info(oid, scope):
|
|
18
|
+
sparsevec = new_type((oid,), 'SPARSEVEC', cast_sparsevec)
|
|
19
|
+
register_type(sparsevec, scope)
|
|
20
|
+
register_adapter(SparseVector, SparsevecAdapter)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from psycopg2.extensions import adapt, new_type, register_adapter, register_type
|
|
3
|
+
from ..utils import Vector
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class VectorAdapter:
|
|
7
|
+
def __init__(self, value):
|
|
8
|
+
self._value = value
|
|
9
|
+
|
|
10
|
+
def getquoted(self):
|
|
11
|
+
return adapt(Vector._to_db(self._value)).getquoted()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def cast_vector(value, cur):
|
|
15
|
+
return Vector._from_db(value)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register_vector_info(oid, scope):
|
|
19
|
+
vector = new_type((oid,), 'VECTOR', cast_vector)
|
|
20
|
+
register_type(vector, scope)
|
|
21
|
+
register_adapter(np.ndarray, VectorAdapter)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .bit import BIT
|
|
2
|
+
from .functions import avg, sum
|
|
3
|
+
from .halfvec import HALFVEC
|
|
4
|
+
from .sparsevec import SPARSEVEC
|
|
5
|
+
from .vector import VECTOR
|
|
6
|
+
from .vector import VECTOR as Vector
|
|
7
|
+
from ..utils import HalfVector, SparseVector
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'Vector',
|
|
11
|
+
'VECTOR',
|
|
12
|
+
'HALFVEC',
|
|
13
|
+
'BIT',
|
|
14
|
+
'SPARSEVEC',
|
|
15
|
+
'HalfVector',
|
|
16
|
+
'SparseVector',
|
|
17
|
+
'avg',
|
|
18
|
+
'sum'
|
|
19
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from sqlalchemy.dialects.postgresql.base import ischema_names
|
|
2
|
+
from sqlalchemy.types import UserDefinedType, Float
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BIT(UserDefinedType):
|
|
6
|
+
cache_ok = True
|
|
7
|
+
|
|
8
|
+
def __init__(self, length=None):
|
|
9
|
+
super(UserDefinedType, self).__init__()
|
|
10
|
+
self.length = length
|
|
11
|
+
|
|
12
|
+
def get_col_spec(self, **kw):
|
|
13
|
+
if self.length is None:
|
|
14
|
+
return 'BIT'
|
|
15
|
+
return 'BIT(%d)' % self.length
|
|
16
|
+
|
|
17
|
+
class comparator_factory(UserDefinedType.Comparator):
|
|
18
|
+
def hamming_distance(self, other):
|
|
19
|
+
return self.op('<~>', return_type=Float)(other)
|
|
20
|
+
|
|
21
|
+
def jaccard_distance(self, other):
|
|
22
|
+
return self.op('<%>', return_type=Float)(other)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# for reflection
|
|
26
|
+
ischema_names['bit'] = BIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# https://docs.sqlalchemy.org/en/20/core/functions.html
|
|
2
|
+
# include sum for a consistent API
|
|
3
|
+
from sqlalchemy.sql.functions import ReturnTypeFromArgs, sum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class avg(ReturnTypeFromArgs):
|
|
7
|
+
inherit_cache = True
|
|
8
|
+
package = 'pgvector'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'avg',
|
|
13
|
+
'sum'
|
|
14
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from sqlalchemy.dialects.postgresql.base import ischema_names
|
|
2
|
+
from sqlalchemy.types import UserDefinedType, Float, String
|
|
3
|
+
from ..utils import HalfVector
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HALFVEC(UserDefinedType):
|
|
7
|
+
cache_ok = True
|
|
8
|
+
_string = String()
|
|
9
|
+
|
|
10
|
+
def __init__(self, dim=None):
|
|
11
|
+
super(UserDefinedType, self).__init__()
|
|
12
|
+
self.dim = dim
|
|
13
|
+
|
|
14
|
+
def get_col_spec(self, **kw):
|
|
15
|
+
if self.dim is None:
|
|
16
|
+
return 'HALFVEC'
|
|
17
|
+
return 'HALFVEC(%d)' % self.dim
|
|
18
|
+
|
|
19
|
+
def bind_processor(self, dialect):
|
|
20
|
+
def process(value):
|
|
21
|
+
return HalfVector._to_db(value, self.dim)
|
|
22
|
+
return process
|
|
23
|
+
|
|
24
|
+
def literal_processor(self, dialect):
|
|
25
|
+
string_literal_processor = self._string._cached_literal_processor(dialect)
|
|
26
|
+
|
|
27
|
+
def process(value):
|
|
28
|
+
return string_literal_processor(HalfVector._to_db(value, self.dim))
|
|
29
|
+
return process
|
|
30
|
+
|
|
31
|
+
def result_processor(self, dialect, coltype):
|
|
32
|
+
def process(value):
|
|
33
|
+
return HalfVector._from_db(value)
|
|
34
|
+
return process
|
|
35
|
+
|
|
36
|
+
class comparator_factory(UserDefinedType.Comparator):
|
|
37
|
+
def l2_distance(self, other):
|
|
38
|
+
return self.op('<->', return_type=Float)(other)
|
|
39
|
+
|
|
40
|
+
def max_inner_product(self, other):
|
|
41
|
+
return self.op('<#>', return_type=Float)(other)
|
|
42
|
+
|
|
43
|
+
def cosine_distance(self, other):
|
|
44
|
+
return self.op('<=>', return_type=Float)(other)
|
|
45
|
+
|
|
46
|
+
def l1_distance(self, other):
|
|
47
|
+
return self.op('<+>', return_type=Float)(other)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# for reflection
|
|
51
|
+
ischema_names['halfvec'] = HALFVEC
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from sqlalchemy.dialects.postgresql.base import ischema_names
|
|
2
|
+
from sqlalchemy.types import UserDefinedType, Float, String
|
|
3
|
+
from ..utils import SparseVector
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SPARSEVEC(UserDefinedType):
|
|
7
|
+
cache_ok = True
|
|
8
|
+
_string = String()
|
|
9
|
+
|
|
10
|
+
def __init__(self, dim=None):
|
|
11
|
+
super(UserDefinedType, self).__init__()
|
|
12
|
+
self.dim = dim
|
|
13
|
+
|
|
14
|
+
def get_col_spec(self, **kw):
|
|
15
|
+
if self.dim is None:
|
|
16
|
+
return 'SPARSEVEC'
|
|
17
|
+
return 'SPARSEVEC(%d)' % self.dim
|
|
18
|
+
|
|
19
|
+
def bind_processor(self, dialect):
|
|
20
|
+
def process(value):
|
|
21
|
+
return SparseVector._to_db(value, self.dim)
|
|
22
|
+
return process
|
|
23
|
+
|
|
24
|
+
def literal_processor(self, dialect):
|
|
25
|
+
string_literal_processor = self._string._cached_literal_processor(dialect)
|
|
26
|
+
|
|
27
|
+
def process(value):
|
|
28
|
+
return string_literal_processor(SparseVector._to_db(value, self.dim))
|
|
29
|
+
return process
|
|
30
|
+
|
|
31
|
+
def result_processor(self, dialect, coltype):
|
|
32
|
+
def process(value):
|
|
33
|
+
return SparseVector._from_db(value)
|
|
34
|
+
return process
|
|
35
|
+
|
|
36
|
+
class comparator_factory(UserDefinedType.Comparator):
|
|
37
|
+
def l2_distance(self, other):
|
|
38
|
+
return self.op('<->', return_type=Float)(other)
|
|
39
|
+
|
|
40
|
+
def max_inner_product(self, other):
|
|
41
|
+
return self.op('<#>', return_type=Float)(other)
|
|
42
|
+
|
|
43
|
+
def cosine_distance(self, other):
|
|
44
|
+
return self.op('<=>', return_type=Float)(other)
|
|
45
|
+
|
|
46
|
+
def l1_distance(self, other):
|
|
47
|
+
return self.op('<+>', return_type=Float)(other)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# for reflection
|
|
51
|
+
ischema_names['sparsevec'] = SPARSEVEC
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from sqlalchemy.dialects.postgresql.base import ischema_names
|
|
2
|
+
from sqlalchemy.types import UserDefinedType, Float, String
|
|
3
|
+
from ..utils import Vector
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class VECTOR(UserDefinedType):
|
|
7
|
+
cache_ok = True
|
|
8
|
+
_string = String()
|
|
9
|
+
|
|
10
|
+
def __init__(self, dim=None):
|
|
11
|
+
super(UserDefinedType, self).__init__()
|
|
12
|
+
self.dim = dim
|
|
13
|
+
|
|
14
|
+
def get_col_spec(self, **kw):
|
|
15
|
+
if self.dim is None:
|
|
16
|
+
return 'VECTOR'
|
|
17
|
+
return 'VECTOR(%d)' % self.dim
|
|
18
|
+
|
|
19
|
+
def bind_processor(self, dialect):
|
|
20
|
+
def process(value):
|
|
21
|
+
return Vector._to_db(value, self.dim)
|
|
22
|
+
return process
|
|
23
|
+
|
|
24
|
+
def literal_processor(self, dialect):
|
|
25
|
+
string_literal_processor = self._string._cached_literal_processor(dialect)
|
|
26
|
+
|
|
27
|
+
def process(value):
|
|
28
|
+
return string_literal_processor(Vector._to_db(value, self.dim))
|
|
29
|
+
return process
|
|
30
|
+
|
|
31
|
+
def result_processor(self, dialect, coltype):
|
|
32
|
+
def process(value):
|
|
33
|
+
return Vector._from_db(value)
|
|
34
|
+
return process
|
|
35
|
+
|
|
36
|
+
class comparator_factory(UserDefinedType.Comparator):
|
|
37
|
+
def l2_distance(self, other):
|
|
38
|
+
return self.op('<->', return_type=Float)(other)
|
|
39
|
+
|
|
40
|
+
def max_inner_product(self, other):
|
|
41
|
+
return self.op('<#>', return_type=Float)(other)
|
|
42
|
+
|
|
43
|
+
def cosine_distance(self, other):
|
|
44
|
+
return self.op('<=>', return_type=Float)(other)
|
|
45
|
+
|
|
46
|
+
def l1_distance(self, other):
|
|
47
|
+
return self.op('<+>', return_type=Float)(other)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# for reflection
|
|
51
|
+
ischema_names['vector'] = VECTOR
|
pgvector/utils/bit.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from struct import pack, unpack_from
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Bit:
|
|
6
|
+
def __init__(self, value):
|
|
7
|
+
if isinstance(value, str):
|
|
8
|
+
self._value = self.from_text(value)._value
|
|
9
|
+
else:
|
|
10
|
+
# TODO change in 0.4.0
|
|
11
|
+
# TODO raise if dtype not bool or uint8
|
|
12
|
+
# if isinstance(value, np.ndarray) and value.dtype == np.uint8:
|
|
13
|
+
# value = np.unpackbits(value)
|
|
14
|
+
# else:
|
|
15
|
+
# value = np.asarray(value, dtype=bool)
|
|
16
|
+
|
|
17
|
+
value = np.asarray(value, dtype=bool)
|
|
18
|
+
|
|
19
|
+
if value.ndim != 1:
|
|
20
|
+
raise ValueError('expected ndim to be 1')
|
|
21
|
+
|
|
22
|
+
self._value = value
|
|
23
|
+
|
|
24
|
+
def __repr__(self):
|
|
25
|
+
return f'Bit({self.to_text()})'
|
|
26
|
+
|
|
27
|
+
def to_list(self):
|
|
28
|
+
return self._value.tolist()
|
|
29
|
+
|
|
30
|
+
def to_numpy(self):
|
|
31
|
+
return self._value
|
|
32
|
+
|
|
33
|
+
def to_text(self):
|
|
34
|
+
return ''.join(self._value.astype(np.uint8).astype(str))
|
|
35
|
+
|
|
36
|
+
def to_binary(self):
|
|
37
|
+
return pack('>i', len(self._value)) + np.packbits(self._value).tobytes()
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_text(cls, value):
|
|
41
|
+
return cls(np.asarray([v != '0' for v in value], dtype=bool))
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def from_binary(cls, value):
|
|
45
|
+
count = unpack_from('>i', value)[0]
|
|
46
|
+
buf = np.frombuffer(value, dtype=np.uint8, offset=4)
|
|
47
|
+
return cls(np.unpackbits(buf, count=count).astype(bool))
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def _to_db(cls, value):
|
|
51
|
+
if not isinstance(value, cls):
|
|
52
|
+
raise ValueError('expected bit')
|
|
53
|
+
|
|
54
|
+
return value.to_text()
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def _to_db_binary(cls, value):
|
|
58
|
+
if not isinstance(value, cls):
|
|
59
|
+
raise ValueError('expected bit')
|
|
60
|
+
|
|
61
|
+
return value.to_binary()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from struct import pack, unpack_from
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HalfVector:
|
|
6
|
+
def __init__(self, value):
|
|
7
|
+
# asarray still copies if same dtype
|
|
8
|
+
if not isinstance(value, np.ndarray) or value.dtype != '>f2':
|
|
9
|
+
value = np.asarray(value, dtype='>f2')
|
|
10
|
+
|
|
11
|
+
if value.ndim != 1:
|
|
12
|
+
raise ValueError('expected ndim to be 1')
|
|
13
|
+
|
|
14
|
+
self._value = value
|
|
15
|
+
|
|
16
|
+
def __repr__(self):
|
|
17
|
+
return f'HalfVector({self.to_list()})'
|
|
18
|
+
|
|
19
|
+
def dimensions(self):
|
|
20
|
+
return len(self._value)
|
|
21
|
+
|
|
22
|
+
def to_list(self):
|
|
23
|
+
return self._value.tolist()
|
|
24
|
+
|
|
25
|
+
def to_numpy(self):
|
|
26
|
+
return self._value
|
|
27
|
+
|
|
28
|
+
def to_text(self):
|
|
29
|
+
return '[' + ','.join([str(float(v)) for v in self._value]) + ']'
|
|
30
|
+
|
|
31
|
+
def to_binary(self):
|
|
32
|
+
return pack('>HH', self.dimensions(), 0) + self._value.tobytes()
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_text(cls, value):
|
|
36
|
+
return cls([float(v) for v in value[1:-1].split(',')])
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def from_binary(cls, value):
|
|
40
|
+
dim, unused = unpack_from('>HH', value)
|
|
41
|
+
return cls(np.frombuffer(value, dtype='>f2', count=dim, offset=4))
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def _to_db(cls, value, dim=None):
|
|
45
|
+
if value is None:
|
|
46
|
+
return value
|
|
47
|
+
|
|
48
|
+
if not isinstance(value, cls):
|
|
49
|
+
value = cls(value)
|
|
50
|
+
|
|
51
|
+
if dim is not None and value.dimensions() != dim:
|
|
52
|
+
raise ValueError('expected %d dimensions, not %d' % (dim, value.dimensions()))
|
|
53
|
+
|
|
54
|
+
return value.to_text()
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def _to_db_binary(cls, value):
|
|
58
|
+
if value is None:
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
if not isinstance(value, cls):
|
|
62
|
+
value = cls(value)
|
|
63
|
+
|
|
64
|
+
return value.to_binary()
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def _from_db(cls, value):
|
|
68
|
+
if value is None or isinstance(value, cls):
|
|
69
|
+
return value
|
|
70
|
+
|
|
71
|
+
return cls.from_text(value)
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def _from_db_binary(cls, value):
|
|
75
|
+
if value is None or isinstance(value, cls):
|
|
76
|
+
return value
|
|
77
|
+
|
|
78
|
+
return cls.from_binary(value)
|