syncoder 0.0.2a1__tar.gz → 0.0.3a1__tar.gz
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.
- {syncoder-0.0.2a1 → syncoder-0.0.3a1}/.gitignore +1 -0
- {syncoder-0.0.2a1 → syncoder-0.0.3a1}/PKG-INFO +1 -1
- {syncoder-0.0.2a1 → syncoder-0.0.3a1}/src/syncoder/__init__.py +1 -1
- {syncoder-0.0.2a1 → syncoder-0.0.3a1}/src/syncoder/codec.py +35 -15
- {syncoder-0.0.2a1 → syncoder-0.0.3a1}/tests/test_codec_pytest.py +19 -2
- {syncoder-0.0.2a1 → syncoder-0.0.3a1}/.vscode/.gitignore +0 -0
- {syncoder-0.0.2a1 → syncoder-0.0.3a1}/LICENSE +0 -0
- {syncoder-0.0.2a1 → syncoder-0.0.3a1}/README.md +0 -0
- {syncoder-0.0.2a1 → syncoder-0.0.3a1}/environments/conda_environment.yml +0 -0
- {syncoder-0.0.2a1 → syncoder-0.0.3a1}/environments/conda_environment_dev.yml +0 -0
- {syncoder-0.0.2a1 → syncoder-0.0.3a1}/environments/conda_environment_test.yml +0 -0
- {syncoder-0.0.2a1 → syncoder-0.0.3a1}/pyproject.toml +0 -0
|
@@ -62,15 +62,39 @@ def _baseN_to_int(digits: list[int], base: int) -> int:
|
|
|
62
62
|
n = n*base + d
|
|
63
63
|
return n
|
|
64
64
|
|
|
65
|
-
def
|
|
65
|
+
def compute_num_strands(nbytes:int,
|
|
66
66
|
inner_alphabet_size:int=32,
|
|
67
67
|
inner_d:int=5,
|
|
68
68
|
inner_n:Optional[int]=None,
|
|
69
69
|
n_redundant_strands:int=5)->int:
|
|
70
|
-
|
|
70
|
+
"""
|
|
71
|
+
Computes the number of strands needed to encode a given number of bytes.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
nbytes (int): The number of bytes to encode.
|
|
75
|
+
inner_alphabet_size (int, optional): Size of the inner alphabet (must be a prime power). Defaults to 32.
|
|
76
|
+
inner_d (int, optional): Inner codeword distance. Defaults to 5.
|
|
77
|
+
inner_n (Optional[int], optional): Number of symbols in inner block (n < inner_alphabet_size). Defaults to inner_alphabet_size-1.
|
|
78
|
+
n_redundant_strands (int, optional): Number of redundant strands. Defaults to 5.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
int: The minimum number of strands required to encode the given number of bytes.
|
|
82
|
+
"""
|
|
71
83
|
#TODO: do some algebra to solve for n_strands.
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
if inner_n is None:
|
|
85
|
+
inner_n = inner_alphabet_size - 1
|
|
86
|
+
|
|
87
|
+
k = inner_n - (inner_d - 1)
|
|
88
|
+
bits_per_strand_max = np.ceil(np.log2(inner_alphabet_size))*k
|
|
89
|
+
bits_per_strand_min = np.floor(np.log2(inner_alphabet_size))*k-16 #16 bits for largest possible index.
|
|
90
|
+
#min_m=1
|
|
91
|
+
min_m = int( np.floor(nbytes*8 / bits_per_strand_max + n_redundant_strands) )
|
|
92
|
+
#max_m=2**16-1
|
|
93
|
+
max_m = int( np.ceil(nbytes*8 / bits_per_strand_min + n_redundant_strands) )
|
|
94
|
+
|
|
95
|
+
_logger = logging.getLogger()
|
|
96
|
+
_t = _logger.getEffectiveLevel()
|
|
97
|
+
_logger.setLevel(logging.ERROR) #suppress debug messages
|
|
74
98
|
while min_m<max_m:
|
|
75
99
|
mid = (min_m+max_m)//2
|
|
76
100
|
codec = BaseNBlockCodec(inner_alphabet_size=inner_alphabet_size,
|
|
@@ -82,14 +106,8 @@ def computeNStrands(nbytes:int,
|
|
|
82
106
|
min_m = mid+1
|
|
83
107
|
else:
|
|
84
108
|
max_m = mid
|
|
109
|
+
_logger.setLevel(_t) #restore logger level
|
|
85
110
|
return min_m
|
|
86
|
-
#index_bits = np.ceil(np.log2(n_strands)).astype(int)
|
|
87
|
-
#k = inner_n - (inner_d - 1)
|
|
88
|
-
#data_chunk_size = int(k*np.log2(inner_alphabet_size)-index_bits) // 8
|
|
89
|
-
#block_capacity_bytes = data_chunk_size*(n_strands - n_redundant_strands)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
111
|
|
|
94
112
|
class BaseNBlockCodec:
|
|
95
113
|
def __init__(self,
|
|
@@ -110,8 +128,6 @@ class BaseNBlockCodec:
|
|
|
110
128
|
n_strands (int): Number of "strands" stored by this codec including redundant strands.
|
|
111
129
|
max_strand_index (Optional[int]): Maximum guaranteed index. Default: n_strands.
|
|
112
130
|
"""
|
|
113
|
-
#TODO: handle more than 255 total strands.
|
|
114
|
-
#this was formally a parameter but probably better to keep it fixed or automatically calculated.
|
|
115
131
|
if n_strands<=255:
|
|
116
132
|
self.outer_alphabet_size_bytes = 1
|
|
117
133
|
else:
|
|
@@ -119,6 +135,10 @@ class BaseNBlockCodec:
|
|
|
119
135
|
if n_strands>(2**(8*self.outer_alphabet_size_bytes)-1):
|
|
120
136
|
raise ValueError("Too many strands for outer code.")
|
|
121
137
|
self.n_strands = n_strands
|
|
138
|
+
if max_strand_index is None:
|
|
139
|
+
max_strand_index = n_strands
|
|
140
|
+
if max_strand_index<n_strands:
|
|
141
|
+
raise ValueError("max_strand_index must be greater than or equal to n_strands.")
|
|
122
142
|
|
|
123
143
|
self.n_message_strands = n_strands - n_redundant_strands
|
|
124
144
|
|
|
@@ -146,7 +166,7 @@ class BaseNBlockCodec:
|
|
|
146
166
|
q = self.inner_coder.field.order
|
|
147
167
|
|
|
148
168
|
#number of bytes needed to store the index
|
|
149
|
-
self.index_bits = np.ceil(np.log2(
|
|
169
|
+
self.index_bits = np.ceil(np.log2(max_strand_index)).astype(int)
|
|
150
170
|
logging.debug("bits per strand used for indexing: {}".format(self.index_bits))
|
|
151
171
|
|
|
152
172
|
#message length in bytes (without index)
|
|
@@ -286,7 +306,7 @@ class BaseNBlockCodec:
|
|
|
286
306
|
chunk_errors:list[int] = [-1] * self.n_strands
|
|
287
307
|
for chunk in chunked_data:
|
|
288
308
|
chunk_int = _baseN_to_int(chunk[0].tolist(), self.inner_coder.field.order)
|
|
289
|
-
chunk_index = chunk_int >> (self.data_chunk_size * 8) - index_start
|
|
309
|
+
chunk_index = (chunk_int >> (self.data_chunk_size * 8)) - index_start
|
|
290
310
|
_mask = (2 ** (self.data_chunk_size * 8)) - 1
|
|
291
311
|
chunk_int = chunk_int & _mask
|
|
292
312
|
chunk_bytes:bytes = chunk_int.to_bytes(self.data_chunk_size, 'little')
|
|
@@ -4,7 +4,8 @@ import random
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
from itertools import product
|
|
6
6
|
|
|
7
|
-
import syncoder
|
|
7
|
+
import syncoder
|
|
8
|
+
codec = syncoder
|
|
8
9
|
|
|
9
10
|
#logging.basicConfig(level=logging.DEBUG,stream=sys.stdout)
|
|
10
11
|
#numba_logger = logging.getLogger('numba')
|
|
@@ -91,7 +92,6 @@ def test_inserts():
|
|
|
91
92
|
total_ibytes = len(insert_bytes)*n_inserts
|
|
92
93
|
data_size = coder.block_capacity_bytes - total_ibytes
|
|
93
94
|
|
|
94
|
-
|
|
95
95
|
test_data = np.random.randint(0,255,data_size,dtype=np.uint8).tobytes()
|
|
96
96
|
|
|
97
97
|
inserted_data = codec.insert_bytes(test_data,insert_bytes,coder.data_chunk_size,n_inserts)
|
|
@@ -107,3 +107,20 @@ def test_inserts():
|
|
|
107
107
|
assert strand.decode().startswith(insert)
|
|
108
108
|
|
|
109
109
|
assert codec.dna_to_bN(test_dna,codec._default_b32_alphabet,codec._default_b32_alphabet_alt) == enc_d
|
|
110
|
+
|
|
111
|
+
def test_compute_n_strands():
|
|
112
|
+
nbytes = 26600
|
|
113
|
+
ns = syncoder.compute_num_strands(nbytes,32,5,29,100)
|
|
114
|
+
data = np.random.randint(0,255,nbytes,dtype=np.uint8).tobytes()
|
|
115
|
+
coder = codec.BaseNBlockCodec(inner_alphabet_size=32,inner_d=5,inner_n=29,n_strands=ns,n_redundant_strands=100)
|
|
116
|
+
enc_d = coder.encode(data)
|
|
117
|
+
assert len(enc_d) == ns
|
|
118
|
+
|
|
119
|
+
def test_nonzero_index():
|
|
120
|
+
coder = syncoder.BaseNBlockCodec(inner_alphabet_size=32,inner_d=5,inner_n=31,n_strands=10,n_redundant_strands=3,max_strand_index=3000)
|
|
121
|
+
data_size = coder.block_capacity_bytes
|
|
122
|
+
|
|
123
|
+
test_data = np.random.randint(0,255,data_size,dtype=np.uint8).tobytes()
|
|
124
|
+
enc_d = coder.encode(test_data,index_start=2500)
|
|
125
|
+
ded_d = coder.decode(enc_d,index_start=2500)
|
|
126
|
+
assert ded_d[0] == test_data
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|