bioforge 2.3.0__tar.gz → 3.1.0__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.
- {bioforge-2.3.0 → bioforge-3.1.0}/PKG-INFO +44 -1
- {bioforge-2.3.0 → bioforge-3.1.0}/README.md +43 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge/__init__.py +5 -1
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge/aligner.py +9 -4
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge/engine/_loader.py +40 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge/engine/engine.c +43 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge/engine/engine.dll +0 -0
- bioforge-3.1.0/bioforge/genomemap.py +365 -0
- bioforge-3.1.0/bioforge/minimizers.py +148 -0
- bioforge-3.1.0/bioforge/refindex.py +111 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge.egg-info/PKG-INFO +44 -1
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge.egg-info/SOURCES.txt +6 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/pyproject.toml +7 -3
- {bioforge-2.3.0 → bioforge-3.1.0}/tests/test_aligner.py +17 -0
- bioforge-3.1.0/tests/test_genomemap.py +144 -0
- bioforge-3.1.0/tests/test_minimizers.py +133 -0
- bioforge-3.1.0/tests/test_refindex.py +98 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/LICENSE +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge/analyze.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge/bgzf.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge/biocore.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge/engine/__init__.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge/engine/build.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge/qcreport.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge/smart_translator.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge.egg-info/dependency_links.txt +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge.egg-info/entry_points.txt +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge.egg-info/requires.txt +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/bioforge.egg-info/top_level.txt +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/setup.cfg +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/setup.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/tests/test_analyze.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/tests/test_bgzf.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/tests/test_biocore.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/tests/test_errors.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/tests/test_qcreport.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/tests/test_streaming.py +0 -0
- {bioforge-2.3.0 → bioforge-3.1.0}/tests/test_translator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bioforge
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: High-performance bioinformatics engine for Edge Computing — 5-bit encoding, vectorised NumPy core, optional C backend
|
|
5
5
|
Author-email: Aarón Aranda Torrijos <noe9601@gmail.com>
|
|
6
6
|
License: # PolyForm Noncommercial License 1.0.0
|
|
@@ -554,6 +554,26 @@ for mut in result.mutations:
|
|
|
554
554
|
# Mutation(kind='substitution', pos_a=18, pos_b=18, sym_a='A', sym_b='T')
|
|
555
555
|
```
|
|
556
556
|
|
|
557
|
+
### Map long reads to a genome (Level 4 — seed-chain-align)
|
|
558
|
+
|
|
559
|
+
Locate reads in a reference far beyond what the O(m·n) aligner can handle,
|
|
560
|
+
minimap2-style: minimizer seeding → chaining (C DP) → banded extension.
|
|
561
|
+
|
|
562
|
+
```python
|
|
563
|
+
from bioforge import GenomeAligner
|
|
564
|
+
|
|
565
|
+
mapper = GenomeAligner(reference_sequence, k=15, w=10) # builds the index once
|
|
566
|
+
|
|
567
|
+
for m in mapper.map(read):
|
|
568
|
+
print(m.to_paf()) # standard PAF, one line per mapping
|
|
569
|
+
# read1 1000 0 1000 + chr1 4600000 733120 734118 980 998 60 ...
|
|
570
|
+
print(m.strand, m.target_start, f"{m.identity:.1%}")
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
Handles both strands, tolerates mismatches/indels, and reports a mapping
|
|
574
|
+
quality. The chaining dynamic program runs in the C engine (with a NumPy
|
|
575
|
+
fallback).
|
|
576
|
+
|
|
557
577
|
### Full mutation analysis pipeline (DNA + protein)
|
|
558
578
|
|
|
559
579
|
```python
|
|
@@ -722,6 +742,29 @@ python check.py
|
|
|
722
742
|
|
|
723
743
|
---
|
|
724
744
|
|
|
745
|
+
## References & inspiration
|
|
746
|
+
|
|
747
|
+
BioForge's genome mapper (Level 4) is an **independent, from-scratch
|
|
748
|
+
implementation** of well-established, published algorithms. No third-party
|
|
749
|
+
source code is included or copied — only the *ideas* from the scientific
|
|
750
|
+
literature, which is what publishing them is for. With gratitude to:
|
|
751
|
+
|
|
752
|
+
- **Minimap2** — Li, H. (2018). *Minimap2: pairwise alignment for nucleotide
|
|
753
|
+
sequences.* Bioinformatics, 34(18), 3094–3100. The seed-chain-align strategy
|
|
754
|
+
and the chaining dynamic program that inspired `genomemap.py`.
|
|
755
|
+
([paper](https://doi.org/10.1093/bioinformatics/bty191) ·
|
|
756
|
+
[MIT-licensed source](https://github.com/lh3/minimap2))
|
|
757
|
+
- **Minimizers** — Roberts, M., Hayes, W., Hunt, B. R., Mount, S. M., &
|
|
758
|
+
Yorke, J. A. (2004). *Reducing storage requirements for biological sequence
|
|
759
|
+
comparison.* Bioinformatics, 20(18), 3363–3369. The (w, k) minimizer sampling
|
|
760
|
+
behind `minimizers.py`.
|
|
761
|
+
- **Needleman–Wunsch** (1970) and **Smith–Waterman** (1981) — the classic
|
|
762
|
+
dynamic-programming alignments behind Level 3.
|
|
763
|
+
|
|
764
|
+
BioForge is not affiliated with or endorsed by the authors of the above.
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
725
768
|
## Author
|
|
726
769
|
|
|
727
770
|
**Aarón Aranda Torrijos** — [github.com/erlanders177](https://github.com/erlanders177)
|
|
@@ -234,6 +234,26 @@ for mut in result.mutations:
|
|
|
234
234
|
# Mutation(kind='substitution', pos_a=18, pos_b=18, sym_a='A', sym_b='T')
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
+
### Map long reads to a genome (Level 4 — seed-chain-align)
|
|
238
|
+
|
|
239
|
+
Locate reads in a reference far beyond what the O(m·n) aligner can handle,
|
|
240
|
+
minimap2-style: minimizer seeding → chaining (C DP) → banded extension.
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from bioforge import GenomeAligner
|
|
244
|
+
|
|
245
|
+
mapper = GenomeAligner(reference_sequence, k=15, w=10) # builds the index once
|
|
246
|
+
|
|
247
|
+
for m in mapper.map(read):
|
|
248
|
+
print(m.to_paf()) # standard PAF, one line per mapping
|
|
249
|
+
# read1 1000 0 1000 + chr1 4600000 733120 734118 980 998 60 ...
|
|
250
|
+
print(m.strand, m.target_start, f"{m.identity:.1%}")
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Handles both strands, tolerates mismatches/indels, and reports a mapping
|
|
254
|
+
quality. The chaining dynamic program runs in the C engine (with a NumPy
|
|
255
|
+
fallback).
|
|
256
|
+
|
|
237
257
|
### Full mutation analysis pipeline (DNA + protein)
|
|
238
258
|
|
|
239
259
|
```python
|
|
@@ -402,6 +422,29 @@ python check.py
|
|
|
402
422
|
|
|
403
423
|
---
|
|
404
424
|
|
|
425
|
+
## References & inspiration
|
|
426
|
+
|
|
427
|
+
BioForge's genome mapper (Level 4) is an **independent, from-scratch
|
|
428
|
+
implementation** of well-established, published algorithms. No third-party
|
|
429
|
+
source code is included or copied — only the *ideas* from the scientific
|
|
430
|
+
literature, which is what publishing them is for. With gratitude to:
|
|
431
|
+
|
|
432
|
+
- **Minimap2** — Li, H. (2018). *Minimap2: pairwise alignment for nucleotide
|
|
433
|
+
sequences.* Bioinformatics, 34(18), 3094–3100. The seed-chain-align strategy
|
|
434
|
+
and the chaining dynamic program that inspired `genomemap.py`.
|
|
435
|
+
([paper](https://doi.org/10.1093/bioinformatics/bty191) ·
|
|
436
|
+
[MIT-licensed source](https://github.com/lh3/minimap2))
|
|
437
|
+
- **Minimizers** — Roberts, M., Hayes, W., Hunt, B. R., Mount, S. M., &
|
|
438
|
+
Yorke, J. A. (2004). *Reducing storage requirements for biological sequence
|
|
439
|
+
comparison.* Bioinformatics, 20(18), 3363–3369. The (w, k) minimizer sampling
|
|
440
|
+
behind `minimizers.py`.
|
|
441
|
+
- **Needleman–Wunsch** (1970) and **Smith–Waterman** (1981) — the classic
|
|
442
|
+
dynamic-programming alignments behind Level 3.
|
|
443
|
+
|
|
444
|
+
BioForge is not affiliated with or endorsed by the authors of the above.
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
405
448
|
## Author
|
|
406
449
|
|
|
407
450
|
**Aarón Aranda Torrijos** — [github.com/erlanders177](https://github.com/erlanders177)
|
|
@@ -33,9 +33,10 @@ from .biocore import (
|
|
|
33
33
|
TranslationError,
|
|
34
34
|
compute_stats,
|
|
35
35
|
)
|
|
36
|
+
from .genomemap import GenomeAligner, Mapping
|
|
36
37
|
from .smart_translator import SmartTranslator
|
|
37
38
|
|
|
38
|
-
__version__ = "
|
|
39
|
+
__version__ = "3.1.0"
|
|
39
40
|
__author__ = "Aarón Aranda Torrijos"
|
|
40
41
|
|
|
41
42
|
__all__ = [
|
|
@@ -67,6 +68,9 @@ __all__ = [
|
|
|
67
68
|
"format_alignment",
|
|
68
69
|
"Mutation",
|
|
69
70
|
"AlignmentResult",
|
|
71
|
+
# Genome mapper (v3.0)
|
|
72
|
+
"GenomeAligner",
|
|
73
|
+
"Mapping",
|
|
70
74
|
# Pipeline
|
|
71
75
|
"run",
|
|
72
76
|
"build_report",
|
|
@@ -179,6 +179,7 @@ class SequenceAligner:
|
|
|
179
179
|
seq_b: PackedSequence,
|
|
180
180
|
mode: Literal['global', 'semi-global'] = 'global',
|
|
181
181
|
band: int | None = None,
|
|
182
|
+
detect_mutations: bool = True,
|
|
182
183
|
) -> AlignmentResult:
|
|
183
184
|
"""
|
|
184
185
|
Align seq_a (reference) against seq_b (query) — Needleman-Wunsch.
|
|
@@ -238,7 +239,8 @@ class SequenceAligner:
|
|
|
238
239
|
raise AlignmentError(f"band debe ser ≥ 1, se recibió {band}.")
|
|
239
240
|
if C_AVAILABLE:
|
|
240
241
|
return cls._align_banded_c(
|
|
241
|
-
codes_a, codes_b, m, n, band, mode, seq_a.seq_type
|
|
242
|
+
codes_a, codes_b, m, n, band, mode, seq_a.seq_type,
|
|
243
|
+
detect_mutations,
|
|
242
244
|
)
|
|
243
245
|
# NumPy fallback: banded fill sobre matriz completa
|
|
244
246
|
if m > cls._MAX_SAFE_LEN or n > cls._MAX_SAFE_LEN:
|
|
@@ -261,7 +263,8 @@ class SequenceAligner:
|
|
|
261
263
|
)
|
|
262
264
|
|
|
263
265
|
if C_AVAILABLE:
|
|
264
|
-
return cls._align_c(codes_a, codes_b, m, n, mode, seq_a.seq_type
|
|
266
|
+
return cls._align_c(codes_a, codes_b, m, n, mode, seq_a.seq_type,
|
|
267
|
+
detect_mutations)
|
|
265
268
|
|
|
266
269
|
H = cls._fill_matrix(codes_a, codes_b, m, n, mode)
|
|
267
270
|
return cls._traceback(H, codes_a, codes_b, m, n, mode, seq_a.seq_type)
|
|
@@ -361,6 +364,7 @@ class SequenceAligner:
|
|
|
361
364
|
band: int,
|
|
362
365
|
mode: str,
|
|
363
366
|
seq_type: SeqType,
|
|
367
|
+
detect_mutations: bool = True,
|
|
364
368
|
) -> AlignmentResult:
|
|
365
369
|
decode_bytes = (
|
|
366
370
|
_NUC_DECODE_BYTES if seq_type == SeqType.NUCLEOTIDE else _AA_DECODE_BYTES
|
|
@@ -370,7 +374,7 @@ class SequenceAligner:
|
|
|
370
374
|
int(cls.MATCH), int(cls.MISMATCH), int(cls.GAP),
|
|
371
375
|
band, mode,
|
|
372
376
|
)
|
|
373
|
-
mutations = cls._detect_mutations(aligned_a, aligned_b)
|
|
377
|
+
mutations = cls._detect_mutations(aligned_a, aligned_b) if detect_mutations else []
|
|
374
378
|
aln_len = n_matches + n_mismatches + n_gaps
|
|
375
379
|
identity = n_matches / aln_len if aln_len else 0.0
|
|
376
380
|
return AlignmentResult(
|
|
@@ -389,6 +393,7 @@ class SequenceAligner:
|
|
|
389
393
|
n: int,
|
|
390
394
|
mode: str,
|
|
391
395
|
seq_type: SeqType,
|
|
396
|
+
detect_mutations: bool = True,
|
|
392
397
|
) -> AlignmentResult:
|
|
393
398
|
decode_bytes = (
|
|
394
399
|
_NUC_DECODE_BYTES if seq_type == SeqType.NUCLEOTIDE else _AA_DECODE_BYTES
|
|
@@ -398,7 +403,7 @@ class SequenceAligner:
|
|
|
398
403
|
int(cls.MATCH), int(cls.MISMATCH), int(cls.GAP),
|
|
399
404
|
mode,
|
|
400
405
|
)
|
|
401
|
-
mutations = cls._detect_mutations(aligned_a, aligned_b)
|
|
406
|
+
mutations = cls._detect_mutations(aligned_a, aligned_b) if detect_mutations else []
|
|
402
407
|
aln_len = n_matches + n_mismatches + n_gaps
|
|
403
408
|
identity = n_matches / aln_len if aln_len else 0.0
|
|
404
409
|
return AlignmentResult(
|
|
@@ -17,8 +17,11 @@ _DLL_PATH = _ENGINE_DIR / ("engine.dll" if sys.platform == "win32" else "engin
|
|
|
17
17
|
# ── Tipos ctypes frecuentes ────────────────────────────────────────────────────
|
|
18
18
|
_U8P = ctypes.POINTER(ctypes.c_uint8)
|
|
19
19
|
_I32P = ctypes.POINTER(ctypes.c_int32)
|
|
20
|
+
_I64P = ctypes.POINTER(ctypes.c_int64)
|
|
21
|
+
_F64P = ctypes.POINTER(ctypes.c_double)
|
|
20
22
|
_I32 = ctypes.c_int32
|
|
21
23
|
_I64 = ctypes.c_int64
|
|
24
|
+
_F64 = ctypes.c_double
|
|
22
25
|
_CHARP = ctypes.c_char_p
|
|
23
26
|
|
|
24
27
|
# ── Carga del DLL ──────────────────────────────────────────────────────────────
|
|
@@ -200,10 +203,30 @@ def _check_libdeflate() -> None:
|
|
|
200
203
|
pass
|
|
201
204
|
|
|
202
205
|
|
|
206
|
+
C_CHAIN_AVAILABLE: bool = False
|
|
207
|
+
|
|
208
|
+
def _check_chain() -> None:
|
|
209
|
+
"""DP de chaining del alineador de genomas (v3). Opcional."""
|
|
210
|
+
global C_CHAIN_AVAILABLE
|
|
211
|
+
if not C_AVAILABLE or _lib is None:
|
|
212
|
+
return
|
|
213
|
+
try:
|
|
214
|
+
_lib.bio_chain_dp.restype = None
|
|
215
|
+
_lib.bio_chain_dp.argtypes = [
|
|
216
|
+
_I64P, _I64P, _I32, # x, y, n
|
|
217
|
+
_I32, _I64, _I32, _F64, # k, max_gap, window, gap_w
|
|
218
|
+
_F64P, _I32P, # f (out), prev (out)
|
|
219
|
+
]
|
|
220
|
+
C_CHAIN_AVAILABLE = True
|
|
221
|
+
except (AttributeError, OSError):
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
|
|
203
225
|
_check_parser()
|
|
204
226
|
_check_batch()
|
|
205
227
|
_check_parallel()
|
|
206
228
|
_check_libdeflate()
|
|
229
|
+
_check_chain()
|
|
207
230
|
|
|
208
231
|
|
|
209
232
|
# ── Wrappers Python ────────────────────────────────────────────────────────────
|
|
@@ -324,6 +347,23 @@ def c_nw_banded(
|
|
|
324
347
|
)
|
|
325
348
|
|
|
326
349
|
|
|
350
|
+
def c_chain_dp(xs: np.ndarray, ys: np.ndarray, k: int,
|
|
351
|
+
max_gap: int, window: int, gap_w: float):
|
|
352
|
+
"""DP de chaining en C. Rellena y devuelve (f, prev).
|
|
353
|
+
|
|
354
|
+
``xs``/``ys`` deben ser int64 contiguos y estar ordenados por (x, y).
|
|
355
|
+
"""
|
|
356
|
+
n = int(xs.size)
|
|
357
|
+
f = np.empty(n, dtype=np.float64)
|
|
358
|
+
prev = np.empty(n, dtype=np.int32)
|
|
359
|
+
_lib.bio_chain_dp(
|
|
360
|
+
xs.ctypes.data_as(_I64P), ys.ctypes.data_as(_I64P), _I32(n),
|
|
361
|
+
_I32(int(k)), _I64(int(max_gap)), _I32(int(window)), _F64(float(gap_w)),
|
|
362
|
+
f.ctypes.data_as(_F64P), prev.ctypes.data_as(_I32P),
|
|
363
|
+
)
|
|
364
|
+
return f, prev
|
|
365
|
+
|
|
366
|
+
|
|
327
367
|
def c_parser_open(path: str) -> int:
|
|
328
368
|
"""Abre un archivo FASTA/FASTQ y devuelve un handle opaco (c_void_p)."""
|
|
329
369
|
raw = path.encode("utf-8") if isinstance(path, str) else path
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
#include <stdlib.h>
|
|
22
22
|
#include <string.h>
|
|
23
23
|
#include <stdint.h>
|
|
24
|
+
#include <math.h>
|
|
24
25
|
|
|
25
26
|
/* ── Lectura de archivos: gzip transparente si se compila con -DBIO_USE_ZLIB ──
|
|
26
27
|
zlib gzopen/gzread leen tanto archivos planos como .gz (autodetección del
|
|
@@ -1536,3 +1537,45 @@ EXPORT int32_t bio_parse_mem_parallel(
|
|
|
1536
1537
|
free(outs);
|
|
1537
1538
|
return (rc != 0) ? rc : total_rec;
|
|
1538
1539
|
}
|
|
1540
|
+
|
|
1541
|
+
/* ── Chaining DP (alineador v3) ───────────────────────────────────────────────
|
|
1542
|
+
Rellena f[] (mejor score de cadena que TERMINA en i) y prev[] (predecesor)
|
|
1543
|
+
para anclas ordenadas ascendentemente por (x, y). El backtracking y la
|
|
1544
|
+
supresión de solapamientos se hacen en Python (baratos, O(n)).
|
|
1545
|
+
|
|
1546
|
+
x, y : posiciones de las anclas (ref y coord. de chaining), int64.
|
|
1547
|
+
n : nº de anclas. k : peso del ancla (longitud del k-mer).
|
|
1548
|
+
max_gap : distancia máxima entre anclas encadenables.
|
|
1549
|
+
window : nº de predecesores examinados por ancla.
|
|
1550
|
+
gap_w : peso lineal de la penalización por hueco.
|
|
1551
|
+
|
|
1552
|
+
Réplica exacta del DP vectorizado de genomemap.py (misma fórmula y mismo
|
|
1553
|
+
desempate: al iterar j de i-1 hacia abajo con `>` estricto, en empate gana
|
|
1554
|
+
el predecesor MÁS CERCANO). Así el resultado C == fallback NumPy. */
|
|
1555
|
+
EXPORT void bio_chain_dp(const int64_t* x, const int64_t* y, int32_t n,
|
|
1556
|
+
int32_t k, int64_t max_gap, int32_t window,
|
|
1557
|
+
double gap_w, double* f, int32_t* prev) {
|
|
1558
|
+
for (int32_t i = 0; i < n; i++) {
|
|
1559
|
+
double best = (double)k;
|
|
1560
|
+
int32_t bp = -1;
|
|
1561
|
+
int32_t lo = i - window;
|
|
1562
|
+
if (lo < 0) lo = 0;
|
|
1563
|
+
for (int32_t j = i - 1; j >= lo; j--) {
|
|
1564
|
+
int64_t dx = x[i] - x[j];
|
|
1565
|
+
if (dx > max_gap) break; /* ordenado → resto aún más lejos */
|
|
1566
|
+
int64_t dy = y[i] - y[j];
|
|
1567
|
+
if (dy <= 0 || dx <= 0 || dy > max_gap) continue;
|
|
1568
|
+
int64_t gap = dx - dy;
|
|
1569
|
+
if (gap < 0) gap = -gap;
|
|
1570
|
+
if (gap > max_gap) continue;
|
|
1571
|
+
int64_t mn = (dx < dy) ? dx : dy;
|
|
1572
|
+
double match = (double)((mn < (int64_t)k) ? mn : (int64_t)k);
|
|
1573
|
+
double cost = gap_w * (double)gap;
|
|
1574
|
+
if (gap > 0) cost += log2((double)gap + 1.0);
|
|
1575
|
+
double sc = f[j] + match - cost;
|
|
1576
|
+
if (sc > best) { best = sc; bp = j; }
|
|
1577
|
+
}
|
|
1578
|
+
f[i] = best;
|
|
1579
|
+
prev[i] = bp;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
Binary file
|