syncoder 0.0.4a2__tar.gz → 0.0.5a0__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.4a2 → syncoder-0.0.5a0}/PKG-INFO +1 -1
- syncoder-0.0.5a0/environments/conda_environment.yml +10 -0
- syncoder-0.0.5a0/environments/conda_environment_dev.yml +21 -0
- syncoder-0.0.5a0/environments/conda_environment_test.yml +15 -0
- {syncoder-0.0.4a2 → syncoder-0.0.5a0}/src/syncoder/__init__.py +1 -1
- {syncoder-0.0.4a2 → syncoder-0.0.5a0}/src/syncoder/codec.py +125 -41
- {syncoder-0.0.4a2 → syncoder-0.0.5a0}/tests/test_codec_pytest.py +19 -1
- syncoder-0.0.4a2/environments/conda_environment.yml +0 -12
- syncoder-0.0.4a2/environments/conda_environment_dev.yml +0 -20
- syncoder-0.0.4a2/environments/conda_environment_test.yml +0 -14
- {syncoder-0.0.4a2 → syncoder-0.0.5a0}/.gitignore +0 -0
- {syncoder-0.0.4a2 → syncoder-0.0.5a0}/.vscode/.gitignore +0 -0
- {syncoder-0.0.4a2 → syncoder-0.0.5a0}/LICENSE +0 -0
- {syncoder-0.0.4a2 → syncoder-0.0.5a0}/README.md +0 -0
- {syncoder-0.0.4a2 → syncoder-0.0.5a0}/pyproject.toml +0 -0
- {syncoder-0.0.4a2 → syncoder-0.0.5a0}/src/syncoder/reedsoloMulti.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: codec-dev
|
|
2
|
+
channels:
|
|
3
|
+
- conda-forge
|
|
4
|
+
dependencies:
|
|
5
|
+
- python>=3.13
|
|
6
|
+
- pip
|
|
7
|
+
- "libblas=*=*mkl"
|
|
8
|
+
- numpy=2.2.6 # version pinned for galois 0.4.6
|
|
9
|
+
- matplotlib=3.*
|
|
10
|
+
- numba=0.61.* # version pinned for galois 0.4.6
|
|
11
|
+
- pytest
|
|
12
|
+
- sphinx=7.*
|
|
13
|
+
- sphinx_rtd_theme
|
|
14
|
+
- biopython
|
|
15
|
+
- jupyter
|
|
16
|
+
- ipykernel
|
|
17
|
+
- twine
|
|
18
|
+
- pip:
|
|
19
|
+
- galois>=0.4.6
|
|
20
|
+
- build
|
|
21
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name: codec-test
|
|
2
|
+
channels:
|
|
3
|
+
- conda-forge
|
|
4
|
+
dependencies:
|
|
5
|
+
- python>=3.13
|
|
6
|
+
- pip
|
|
7
|
+
- numpy=2.2.6 # version pinned for galois 0.4.6
|
|
8
|
+
- numba=0.61.* # version pinned for galois 0.4.6
|
|
9
|
+
- pytest
|
|
10
|
+
- sphinx=7.*
|
|
11
|
+
- sphinx_rtd_theme
|
|
12
|
+
- twine
|
|
13
|
+
- pip:
|
|
14
|
+
- galois>=0.4.6
|
|
15
|
+
- build
|
|
@@ -124,7 +124,9 @@ class BaseNBlockCodec:
|
|
|
124
124
|
inner_n:Optional[int]=None,
|
|
125
125
|
n_redundant_strands:int=5,
|
|
126
126
|
n_strands:int=100,
|
|
127
|
-
max_strand_index:Optional[int]=None
|
|
127
|
+
max_strand_index:Optional[int]=None,
|
|
128
|
+
index_type:Literal["binary", "inner symbol", "inner"]="binary",
|
|
129
|
+
index_location:Literal["middle", "beginning", "end"]="middle"):
|
|
128
130
|
"""
|
|
129
131
|
Initialize the Codec object.
|
|
130
132
|
|
|
@@ -135,6 +137,16 @@ class BaseNBlockCodec:
|
|
|
135
137
|
n_redundant_strands (int): Number of redundant strands.
|
|
136
138
|
n_strands (int): Number of "strands" stored by this codec including redundant strands.
|
|
137
139
|
max_strand_index (Optional[int]): Maximum guaranteed index. Default: n_strands.
|
|
140
|
+
index_type (str): Type of index to use. Options: "binary", "inner symbol", "inner". Default: "binary".
|
|
141
|
+
index_location (str): Location of the index within the inner code. Options: "middle", "beginning", "end". Default: "middle".
|
|
142
|
+
|
|
143
|
+
index_type "binary" integrates the index into the data stream as a binary number.
|
|
144
|
+
This may help packing efficiency.
|
|
145
|
+
index_type "inner symbol"/"inner" integrates the index inner code symbol symbols.
|
|
146
|
+
This may help packing efficiency and always simplifies index extraction.
|
|
147
|
+
index_location "middle" (default) places the index after the data symbols but before the redundancy symbols.
|
|
148
|
+
index_location "beginning" places the index before the data symbols.
|
|
149
|
+
index_location "end" places the index after the redundancy symbols. (not implemented)
|
|
138
150
|
"""
|
|
139
151
|
if n_strands<=255:
|
|
140
152
|
self.outer_alphabet_size_bytes = 1
|
|
@@ -147,6 +159,20 @@ class BaseNBlockCodec:
|
|
|
147
159
|
max_strand_index = n_strands
|
|
148
160
|
if max_strand_index<n_strands:
|
|
149
161
|
raise ValueError("max_strand_index must be greater than or equal to n_strands.")
|
|
162
|
+
|
|
163
|
+
if index_type not in {"binary","inner symbol","inner"}:
|
|
164
|
+
raise ValueError("index_type must be 'binary' or 'inner symbol'.")
|
|
165
|
+
#"inner" and "inner symbol" are synomymous.
|
|
166
|
+
self.index_type = "inner" if index_type == "inner symbol" else index_type
|
|
167
|
+
|
|
168
|
+
if index_location in {"middle","beginning","end"}:
|
|
169
|
+
self.index_location = index_location
|
|
170
|
+
if index_location == "end":
|
|
171
|
+
raise NotImplementedError("index_location 'end' is not implemented.")
|
|
172
|
+
if index_location != "middle" and index_type == "binary":
|
|
173
|
+
raise ValueError("index_location 'beginning' or 'end' not supported for index_type 'binary'.")
|
|
174
|
+
else:
|
|
175
|
+
raise ValueError("index_location must be 'middle', 'beginning', or 'end'.")
|
|
150
176
|
|
|
151
177
|
self.n_message_strands = n_strands - n_redundant_strands
|
|
152
178
|
|
|
@@ -178,25 +204,45 @@ class BaseNBlockCodec:
|
|
|
178
204
|
logging.debug("bits per strand used for indexing: {}".format(self.index_bits))
|
|
179
205
|
|
|
180
206
|
#message length in bytes (without index)
|
|
181
|
-
|
|
207
|
+
if self.index_type == "binary":
|
|
208
|
+
self.data_chunk_size = int(k*np.log2(q)-self.index_bits) // 8
|
|
209
|
+
elif self.index_type == "inner":
|
|
210
|
+
self.index_symbols = len(_int_to_baseN(max_strand_index,q))
|
|
211
|
+
self.data_symbols = k-self.index_symbols
|
|
212
|
+
self.data_chunk_size = int(self.data_symbols*np.log2(q)) // 8
|
|
213
|
+
assert k == self.data_symbols + self.index_symbols
|
|
214
|
+
logging.debug("data chunk size in symbols: {}".format(self.data_symbols))
|
|
215
|
+
logging.debug(f"number of inner symbols: {k}; index symbols: {self.index_symbols}")
|
|
216
|
+
|
|
182
217
|
logging.debug( "strand capacity in bytes: {}".format(self.data_chunk_size) )
|
|
218
|
+
#todo: only execute if logging level is debug or warning
|
|
219
|
+
self.__compute_waste()
|
|
183
220
|
|
|
184
|
-
_wasted_bits = k*np.log2(q) - (self.data_chunk_size*8+self.index_bits )
|
|
185
|
-
logging.debug( "{} bits wasted per strand".format( _wasted_bits ) )
|
|
186
|
-
logging.debug( "Maximum strand index (using wasted bits): {}".format(2**(int(self.index_bits+_wasted_bits))))
|
|
187
|
-
_wasted_symbols = _wasted_bits/np.log2(q)
|
|
188
|
-
if _wasted_symbols>=1:
|
|
189
|
-
logging.warning( "{} symbols wasted per strand. Consider decreasing inner_n.".format( _wasted_symbols) )
|
|
190
|
-
else:
|
|
191
|
-
logging.debug("{} symbols wasted per strand".format( _wasted_symbols))
|
|
192
|
-
|
|
193
|
-
|
|
194
221
|
self.block_capacity_bytes = self.data_chunk_size*(self.n_strands - n_redundant_strands)
|
|
195
222
|
logging.debug( "codec message size: {} bytes".format(self.block_capacity_bytes) )
|
|
196
223
|
|
|
197
224
|
#init the outer coder
|
|
198
225
|
self.outer_coder = reedsolo.RSCodec(n_redundant_strands, c_exp=8*self.outer_alphabet_size_bytes)
|
|
199
226
|
|
|
227
|
+
def __compute_waste(self):
|
|
228
|
+
k = self.inner_k
|
|
229
|
+
q = self.inner_coder.field.order
|
|
230
|
+
if self.index_type == "binary":
|
|
231
|
+
_wasted_bits = k*np.log2(q) - (self.data_chunk_size*8+self.index_bits )
|
|
232
|
+
logging.debug( "{} bits wasted per strand".format( _wasted_bits ) )
|
|
233
|
+
logging.debug( "Maximum strand index (using wasted bits): {}".format(2**(int(self.index_bits+_wasted_bits))))
|
|
234
|
+
_wasted_symbols = _wasted_bits/np.log2(q)
|
|
235
|
+
elif self.index_type == "inner":
|
|
236
|
+
data_chunk_symbols_used = self.data_chunk_size*8/np.log2(q)
|
|
237
|
+
_wasted_symbols = self.data_symbols - data_chunk_symbols_used
|
|
238
|
+
logging.debug( f"Maximum strand index:{q**self.index_symbols}")
|
|
239
|
+
else:
|
|
240
|
+
raise ValueError("Unknown index type: {}".format(self.index_type)) #should be impossible to get here.
|
|
241
|
+
if _wasted_symbols>=1:
|
|
242
|
+
logging.warning( "{} symbols wasted per strand. Consider decreasing inner_n.".format( _wasted_symbols) )
|
|
243
|
+
else:
|
|
244
|
+
logging.debug("{} symbols wasted per strand".format( _wasted_symbols))
|
|
245
|
+
|
|
200
246
|
def encode(self,data:bytes,index_start:int=0)->list[list[int]]:
|
|
201
247
|
""" Encodes the given data using the outer and inner codes.
|
|
202
248
|
|
|
@@ -231,14 +277,18 @@ class BaseNBlockCodec:
|
|
|
231
277
|
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size rrrrrrrrrrrrr>|
|
|
232
278
|
# ...
|
|
233
279
|
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size rrrrrrrrrrrrr>|
|
|
234
|
-
# 3. inner code (R= inner code redundancy applied to rows
|
|
235
|
-
#
|
|
236
|
-
#
|
|
280
|
+
# 3. inner code (R= inner code redundancy applied to rows)
|
|
281
|
+
# (* = inner code symbol)
|
|
282
|
+
# (@= binary index concatenated to data bits)
|
|
283
|
+
# (a= binary index concatenated to outer code redundancy bits)
|
|
284
|
+
# When index type is inner code, @ and a are full symbols.
|
|
285
|
+
# |<***************** self.data_chunk_size *************@@@RRRRRRRR>|
|
|
286
|
+
# |<***************** self.data_chunk_size *************@@@RRRRRRRR>|
|
|
237
287
|
# ...
|
|
238
|
-
# |<***************** self.data_chunk_size
|
|
239
|
-
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size
|
|
288
|
+
# |<***************** self.data_chunk_size *************@@@RRRRRRRR>|
|
|
289
|
+
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size rrrrrrrrrrrrraaaRRRRRRRR>|
|
|
240
290
|
# ...
|
|
241
|
-
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size
|
|
291
|
+
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size rrrrrrrrrrrrraaaRRRRRRRR>|
|
|
242
292
|
|
|
243
293
|
#1. Reshape data
|
|
244
294
|
logging.debug("applying outer code to {} bytes".format(len(data)))
|
|
@@ -273,11 +323,23 @@ class BaseNBlockCodec:
|
|
|
273
323
|
d = chunk.tobytes()
|
|
274
324
|
#use little endian to match x86/x64 byte order and avoid needing to pad byte string.
|
|
275
325
|
chunk = int.from_bytes(d,'little')
|
|
276
|
-
|
|
277
|
-
|
|
326
|
+
if self.index_type == "binary":
|
|
327
|
+
#add index to end (MS bits)
|
|
328
|
+
chunk += index<<(self.data_chunk_size*8)
|
|
329
|
+
|
|
330
|
+
#but.. we still need to pad out the symbol stream.
|
|
331
|
+
chunk = _int_to_baseN(chunk,self.inner_coder.field.order,length=self.inner_k)
|
|
332
|
+
elif self.index_type == "inner":
|
|
333
|
+
chunk = _int_to_baseN(chunk,self.inner_coder.field.order,length=self.data_symbols)
|
|
334
|
+
index_symbols = _int_to_baseN(index,self.inner_coder.field.order,length=self.index_symbols)
|
|
335
|
+
if self.index_location == "middle":
|
|
336
|
+
chunk += index_symbols
|
|
337
|
+
elif self.index_location == "beginning":
|
|
338
|
+
chunk = index_symbols + chunk
|
|
339
|
+
else:
|
|
340
|
+
raise NotImplementedError("index_location 'end' is not implemented.")
|
|
341
|
+
|
|
278
342
|
index += 1
|
|
279
|
-
#but.. we still need to pad out the symbol stream.
|
|
280
|
-
chunk = _int_to_baseN(chunk,self.inner_coder.field.order,length=self.inner_k)
|
|
281
343
|
chunks.append(chunk)
|
|
282
344
|
|
|
283
345
|
#apply inner code
|
|
@@ -313,12 +375,23 @@ class BaseNBlockCodec:
|
|
|
313
375
|
ordered_chunks:list[bytes|None] = [None] * self.n_strands
|
|
314
376
|
chunk_errors:list[int] = [-1] * self.n_strands
|
|
315
377
|
for chunk in chunked_data:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
378
|
+
if self.index_type == "binary":
|
|
379
|
+
chunk_int = _baseN_to_int(chunk[0].tolist(), self.inner_coder.field.order)
|
|
380
|
+
chunk_index = (chunk_int >> (self.data_chunk_size * 8)) - index_start
|
|
381
|
+
_mask = (2 ** (self.data_chunk_size * 8)) - 1
|
|
382
|
+
chunk_int = chunk_int & _mask
|
|
383
|
+
elif self.index_type == "inner":
|
|
384
|
+
if self.index_location == "middle":
|
|
385
|
+
chunk_int = _baseN_to_int(chunk[0][:self.data_symbols].tolist(),self.inner_coder.field.order)
|
|
386
|
+
chunk_index = _baseN_to_int(chunk[0][self.data_symbols:].tolist(),self.inner_coder.field.order) - index_start
|
|
387
|
+
elif self.index_location == "beginning":
|
|
388
|
+
chunk_index = _baseN_to_int(chunk[0][:-self.data_symbols].tolist(),self.inner_coder.field.order) - index_start
|
|
389
|
+
chunk_int = _baseN_to_int(chunk[0][-self.data_symbols:].tolist(),self.inner_coder.field.order)
|
|
390
|
+
else:
|
|
391
|
+
raise NotImplementedError("index_location 'end' is not implemented.")
|
|
392
|
+
else:
|
|
393
|
+
raise ValueError("Unknown index type: {}".format(self.index_type)) #should be impossible to get here.
|
|
320
394
|
chunk_bytes:bytes = chunk_int.to_bytes(self.data_chunk_size, 'little')
|
|
321
|
-
#print(chunk_index)
|
|
322
395
|
try:
|
|
323
396
|
ordered_chunks[chunk_index] = chunk_bytes
|
|
324
397
|
chunk_errors[chunk_index] = chunk[1]
|
|
@@ -381,27 +454,26 @@ class BaseNBlockCodec:
|
|
|
381
454
|
Returns:
|
|
382
455
|
int: The index. or -1 if error_check==True and unrecoverable error found.
|
|
383
456
|
"""
|
|
384
|
-
|
|
457
|
+
symbol_len:int = len(words[0])
|
|
385
458
|
if isinstance(dna,bytes):
|
|
386
459
|
dna= dna.decode()
|
|
387
460
|
dna_s:str = cast(str,dna)
|
|
388
|
-
|
|
389
|
-
if padding == "right":
|
|
390
|
-
_p = self.inner_k*word_len - len(dna_s)
|
|
391
|
-
if _p>0: #if i have less than a full codeword of bases
|
|
392
|
-
dna_s = dna_s + "A"*_p
|
|
393
|
-
elif padding == "left":
|
|
394
|
-
_p = self.inner_k*word_len - len(dna_s)
|
|
395
|
-
if _p>0: #if i have less than a full codeword of bases
|
|
396
|
-
dna_s = "A"*_p + dna_s
|
|
397
461
|
|
|
462
|
+
num_missing = self.inner_k*symbol_len - len(dna_s) # in DNA bases
|
|
463
|
+
if num_missing>0: #if i have less than a full codeword of bases
|
|
464
|
+
if padding == "right":
|
|
465
|
+
dna_s = dna_s + "A"*num_missing
|
|
466
|
+
elif padding == "left":
|
|
467
|
+
dna_s = "A"*num_missing + dna_s
|
|
468
|
+
|
|
469
|
+
#This could fail if padding makes a symbol not in words or alternate_words.
|
|
398
470
|
data = dna_to_bN([dna_s],words,alternate_words)[0]
|
|
399
471
|
|
|
400
472
|
return self.extract_index(data,error_check)
|
|
401
473
|
|
|
402
474
|
def extract_index(self, data: list[int], error_check: bool = False) -> int:
|
|
403
475
|
"""
|
|
404
|
-
Extracts the index from the given
|
|
476
|
+
Extracts the index from the given inner code sequence.
|
|
405
477
|
|
|
406
478
|
Args:
|
|
407
479
|
data list[int]: The input data from which to extract the index.
|
|
@@ -415,11 +487,23 @@ class BaseNBlockCodec:
|
|
|
415
487
|
if error_check == False:
|
|
416
488
|
data = data[0:self.inner_k] #extract message
|
|
417
489
|
else:
|
|
418
|
-
|
|
490
|
+
data_gf, n_corrected = self.inner_coder.decode(data,errors=True)
|
|
491
|
+
data = data_gf.tolist()
|
|
419
492
|
if n_corrected < 0:
|
|
420
493
|
return -1
|
|
421
|
-
|
|
422
|
-
|
|
494
|
+
if self.index_type == "binary":
|
|
495
|
+
chunk_int = _baseN_to_int(data, self.inner_coder.field.order)
|
|
496
|
+
chunk_index = (chunk_int >> (self.data_chunk_size * 8))
|
|
497
|
+
elif self.index_type == "inner":
|
|
498
|
+
if self.index_location == "middle":
|
|
499
|
+
chunk_index_baseN = data[self.data_symbols:]
|
|
500
|
+
elif self.index_location == "beginning":
|
|
501
|
+
chunk_index_baseN = data[:-self.data_symbols]
|
|
502
|
+
else:
|
|
503
|
+
raise NotImplementedError("index_location 'end' is not implemented.")
|
|
504
|
+
chunk_index = _baseN_to_int(chunk_index_baseN, self.inner_coder.field.order)
|
|
505
|
+
else:
|
|
506
|
+
raise ValueError("Unknown index type: {}".format(self.index_type)) #should be impossible to get here.
|
|
423
507
|
return chunk_index
|
|
424
508
|
|
|
425
509
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import random
|
|
4
4
|
import numpy as np
|
|
5
5
|
from itertools import product
|
|
6
|
+
from typing import Literal
|
|
6
7
|
|
|
7
8
|
import syncoder
|
|
8
9
|
codec = syncoder
|
|
@@ -12,11 +13,27 @@ codec = syncoder
|
|
|
12
13
|
#numba_logger.setLevel(logging.WARNING)
|
|
13
14
|
|
|
14
15
|
def test_encode_decode():
|
|
15
|
-
|
|
16
|
+
encode_decode(index_type="binary")
|
|
17
|
+
encode_decode(index_type="inner")
|
|
18
|
+
encode_decode(index_type="inner", index_loc="beginning")
|
|
19
|
+
|
|
20
|
+
def encode_decode(index_type,index_loc:Literal["beginning","middle"]="middle"):
|
|
21
|
+
c = codec.BaseNBlockCodec(inner_alphabet_size=32,
|
|
22
|
+
inner_d=5,
|
|
23
|
+
inner_n=30,
|
|
24
|
+
index_type=index_type,
|
|
25
|
+
index_location=index_loc)
|
|
16
26
|
|
|
17
27
|
in_text = (codec.lipsum + codec.lipsum)[0:c.block_capacity_bytes]
|
|
18
28
|
|
|
19
29
|
coded = c.encode( in_text )
|
|
30
|
+
assert c.extract_index(coded[27]) == 27 #test extract index while we're at it
|
|
31
|
+
dnadata = syncoder.b32_to_DNA(coded,syncoder._default_b32_alphabet,syncoder._default_b32_alphabet_alt)
|
|
32
|
+
assert c.extract_index_dna(dnadata[27],
|
|
33
|
+
words = syncoder._default_b32_alphabet,
|
|
34
|
+
alternate_words= syncoder._default_b32_alphabet_alt,
|
|
35
|
+
error_check=True) == 27
|
|
36
|
+
|
|
20
37
|
random.shuffle(coded)
|
|
21
38
|
#coded = coded[:-4] #erase a random strands
|
|
22
39
|
corrupt_index = random.randint(0,len(coded[0]))
|
|
@@ -76,6 +93,7 @@ def test_dna_to_bytes():
|
|
|
76
93
|
dna_bytes, mask = codec.dna_to_bytes(DNA,alphabet,alphabet_alt)
|
|
77
94
|
|
|
78
95
|
dna_baseN = _int_to_baseN(int.from_bytes(dna_bytes,"little"),len(alphabet))
|
|
96
|
+
#TODO: this can fail but only for some inputs randomly selected lookinto why.
|
|
79
97
|
assert dna_baseN == answer32
|
|
80
98
|
|
|
81
99
|
_t = np.array(dna_baseN)
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
name: codec-dev
|
|
2
|
-
channels:
|
|
3
|
-
- conda-forge
|
|
4
|
-
dependencies:
|
|
5
|
-
- python>=3.12
|
|
6
|
-
- numpy=2.*
|
|
7
|
-
- matplotlib=3.*
|
|
8
|
-
- pytest
|
|
9
|
-
- reedsolo=1.7.*
|
|
10
|
-
- pip=24.*
|
|
11
|
-
- sphinx=7.*
|
|
12
|
-
- sphinx_rtd_theme
|
|
13
|
-
- biopython
|
|
14
|
-
- jupyter
|
|
15
|
-
- ipykernel
|
|
16
|
-
- pip:
|
|
17
|
-
- galois==0.4.*
|
|
18
|
-
- build
|
|
19
|
-
- twine
|
|
20
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|