syncoder 0.0.4a1__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.4a1 → 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.4a1 → syncoder-0.0.5a0}/src/syncoder/__init__.py +1 -1
- {syncoder-0.0.4a1 → syncoder-0.0.5a0}/src/syncoder/codec.py +134 -42
- syncoder-0.0.5a0/src/syncoder/reedsoloMulti.py +1152 -0
- {syncoder-0.0.4a1 → syncoder-0.0.5a0}/tests/test_codec_pytest.py +19 -1
- syncoder-0.0.4a1/environments/conda_environment.yml +0 -12
- syncoder-0.0.4a1/environments/conda_environment_dev.yml +0 -20
- syncoder-0.0.4a1/environments/conda_environment_test.yml +0 -14
- {syncoder-0.0.4a1 → syncoder-0.0.5a0}/.gitignore +0 -0
- {syncoder-0.0.4a1 → syncoder-0.0.5a0}/.vscode/.gitignore +0 -0
- {syncoder-0.0.4a1 → syncoder-0.0.5a0}/LICENSE +0 -0
- {syncoder-0.0.4a1 → syncoder-0.0.5a0}/README.md +0 -0
- {syncoder-0.0.4a1 → syncoder-0.0.5a0}/pyproject.toml +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
|
|
@@ -10,7 +10,15 @@ import numpy as np
|
|
|
10
10
|
from numpy.typing import ArrayLike
|
|
11
11
|
|
|
12
12
|
import galois
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
#import a version of reedsolo that supports multi instance
|
|
15
|
+
import importlib.metadata
|
|
16
|
+
import packaging.version
|
|
17
|
+
rsvers = importlib.metadata.version('reedsolo')
|
|
18
|
+
if packaging.version.Version(rsvers) <= packaging.version.Version("2.1.3b1"):
|
|
19
|
+
from . import reedsoloMulti as reedsolo
|
|
20
|
+
else:
|
|
21
|
+
import reedsolo
|
|
14
22
|
|
|
15
23
|
#### ROADMAP ####
|
|
16
24
|
# - integrate the baseN to DNA function here. (generalize from b32fn)
|
|
@@ -116,7 +124,9 @@ class BaseNBlockCodec:
|
|
|
116
124
|
inner_n:Optional[int]=None,
|
|
117
125
|
n_redundant_strands:int=5,
|
|
118
126
|
n_strands:int=100,
|
|
119
|
-
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"):
|
|
120
130
|
"""
|
|
121
131
|
Initialize the Codec object.
|
|
122
132
|
|
|
@@ -127,6 +137,16 @@ class BaseNBlockCodec:
|
|
|
127
137
|
n_redundant_strands (int): Number of redundant strands.
|
|
128
138
|
n_strands (int): Number of "strands" stored by this codec including redundant strands.
|
|
129
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)
|
|
130
150
|
"""
|
|
131
151
|
if n_strands<=255:
|
|
132
152
|
self.outer_alphabet_size_bytes = 1
|
|
@@ -139,6 +159,20 @@ class BaseNBlockCodec:
|
|
|
139
159
|
max_strand_index = n_strands
|
|
140
160
|
if max_strand_index<n_strands:
|
|
141
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'.")
|
|
142
176
|
|
|
143
177
|
self.n_message_strands = n_strands - n_redundant_strands
|
|
144
178
|
|
|
@@ -170,25 +204,45 @@ class BaseNBlockCodec:
|
|
|
170
204
|
logging.debug("bits per strand used for indexing: {}".format(self.index_bits))
|
|
171
205
|
|
|
172
206
|
#message length in bytes (without index)
|
|
173
|
-
|
|
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
|
+
|
|
174
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()
|
|
175
220
|
|
|
176
|
-
_wasted_bits = k*np.log2(q) - (self.data_chunk_size*8+self.index_bits )
|
|
177
|
-
logging.debug( "{} bits wasted per strand".format( _wasted_bits ) )
|
|
178
|
-
logging.debug( "Maximum strand index (using wasted bits): {}".format(2**(int(self.index_bits+_wasted_bits))))
|
|
179
|
-
_wasted_symbols = _wasted_bits/np.log2(q)
|
|
180
|
-
if _wasted_symbols>=1:
|
|
181
|
-
logging.warning( "{} symbols wasted per strand. Consider decreasing inner_n.".format( _wasted_symbols) )
|
|
182
|
-
else:
|
|
183
|
-
logging.debug("{} symbols wasted per strand".format( _wasted_symbols))
|
|
184
|
-
|
|
185
|
-
|
|
186
221
|
self.block_capacity_bytes = self.data_chunk_size*(self.n_strands - n_redundant_strands)
|
|
187
222
|
logging.debug( "codec message size: {} bytes".format(self.block_capacity_bytes) )
|
|
188
223
|
|
|
189
224
|
#init the outer coder
|
|
190
225
|
self.outer_coder = reedsolo.RSCodec(n_redundant_strands, c_exp=8*self.outer_alphabet_size_bytes)
|
|
191
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
|
+
|
|
192
246
|
def encode(self,data:bytes,index_start:int=0)->list[list[int]]:
|
|
193
247
|
""" Encodes the given data using the outer and inner codes.
|
|
194
248
|
|
|
@@ -223,14 +277,18 @@ class BaseNBlockCodec:
|
|
|
223
277
|
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size rrrrrrrrrrrrr>|
|
|
224
278
|
# ...
|
|
225
279
|
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size rrrrrrrrrrrrr>|
|
|
226
|
-
# 3. inner code (R= inner code redundancy applied to rows
|
|
227
|
-
#
|
|
228
|
-
#
|
|
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>|
|
|
229
287
|
# ...
|
|
230
|
-
# |<***************** self.data_chunk_size
|
|
231
|
-
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size
|
|
288
|
+
# |<***************** self.data_chunk_size *************@@@RRRRRRRR>|
|
|
289
|
+
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size rrrrrrrrrrrrraaaRRRRRRRR>|
|
|
232
290
|
# ...
|
|
233
|
-
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size
|
|
291
|
+
# |<rrrrrrrrrrrrrrrrr self.data_chunk_size rrrrrrrrrrrrraaaRRRRRRRR>|
|
|
234
292
|
|
|
235
293
|
#1. Reshape data
|
|
236
294
|
logging.debug("applying outer code to {} bytes".format(len(data)))
|
|
@@ -265,11 +323,23 @@ class BaseNBlockCodec:
|
|
|
265
323
|
d = chunk.tobytes()
|
|
266
324
|
#use little endian to match x86/x64 byte order and avoid needing to pad byte string.
|
|
267
325
|
chunk = int.from_bytes(d,'little')
|
|
268
|
-
|
|
269
|
-
|
|
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
|
+
|
|
270
342
|
index += 1
|
|
271
|
-
#but.. we still need to pad out the symbol stream.
|
|
272
|
-
chunk = _int_to_baseN(chunk,self.inner_coder.field.order,length=self.inner_k)
|
|
273
343
|
chunks.append(chunk)
|
|
274
344
|
|
|
275
345
|
#apply inner code
|
|
@@ -305,12 +375,23 @@ class BaseNBlockCodec:
|
|
|
305
375
|
ordered_chunks:list[bytes|None] = [None] * self.n_strands
|
|
306
376
|
chunk_errors:list[int] = [-1] * self.n_strands
|
|
307
377
|
for chunk in chunked_data:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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.
|
|
312
394
|
chunk_bytes:bytes = chunk_int.to_bytes(self.data_chunk_size, 'little')
|
|
313
|
-
#print(chunk_index)
|
|
314
395
|
try:
|
|
315
396
|
ordered_chunks[chunk_index] = chunk_bytes
|
|
316
397
|
chunk_errors[chunk_index] = chunk[1]
|
|
@@ -373,27 +454,26 @@ class BaseNBlockCodec:
|
|
|
373
454
|
Returns:
|
|
374
455
|
int: The index. or -1 if error_check==True and unrecoverable error found.
|
|
375
456
|
"""
|
|
376
|
-
|
|
457
|
+
symbol_len:int = len(words[0])
|
|
377
458
|
if isinstance(dna,bytes):
|
|
378
459
|
dna= dna.decode()
|
|
379
460
|
dna_s:str = cast(str,dna)
|
|
380
|
-
|
|
381
|
-
if padding == "right":
|
|
382
|
-
_p = self.inner_k*word_len - len(dna_s)
|
|
383
|
-
if _p>0: #if i have less than a full codeword of bases
|
|
384
|
-
dna_s = dna_s + "A"*_p
|
|
385
|
-
elif padding == "left":
|
|
386
|
-
_p = self.inner_k*word_len - len(dna_s)
|
|
387
|
-
if _p>0: #if i have less than a full codeword of bases
|
|
388
|
-
dna_s = "A"*_p + dna_s
|
|
389
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.
|
|
390
470
|
data = dna_to_bN([dna_s],words,alternate_words)[0]
|
|
391
471
|
|
|
392
472
|
return self.extract_index(data,error_check)
|
|
393
473
|
|
|
394
474
|
def extract_index(self, data: list[int], error_check: bool = False) -> int:
|
|
395
475
|
"""
|
|
396
|
-
Extracts the index from the given
|
|
476
|
+
Extracts the index from the given inner code sequence.
|
|
397
477
|
|
|
398
478
|
Args:
|
|
399
479
|
data list[int]: The input data from which to extract the index.
|
|
@@ -407,11 +487,23 @@ class BaseNBlockCodec:
|
|
|
407
487
|
if error_check == False:
|
|
408
488
|
data = data[0:self.inner_k] #extract message
|
|
409
489
|
else:
|
|
410
|
-
|
|
490
|
+
data_gf, n_corrected = self.inner_coder.decode(data,errors=True)
|
|
491
|
+
data = data_gf.tolist()
|
|
411
492
|
if n_corrected < 0:
|
|
412
493
|
return -1
|
|
413
|
-
|
|
414
|
-
|
|
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.
|
|
415
507
|
return chunk_index
|
|
416
508
|
|
|
417
509
|
|