@gmod/gbz-base 1.0.0 → 2.0.0
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.
- package/README.md +166 -26
- package/bin/query.js +0 -0
- package/dist/cli.js +76 -28
- package/dist/cli.js.map +1 -1
- package/dist/db.d.ts +26 -9
- package/dist/db.js +158 -24
- package/dist/db.js.map +1 -1
- package/dist/gbwt/record.d.ts +4 -0
- package/dist/gbwt/record.js +22 -0
- package/dist/gbwt/record.js.map +1 -1
- package/dist/graphName.d.ts +9 -0
- package/dist/graphName.js +83 -0
- package/dist/graphName.js.map +1 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/pathName.d.ts +17 -0
- package/dist/pathName.js +33 -0
- package/dist/pathName.js.map +1 -0
- package/dist/query.d.ts +9 -9
- package/dist/query.js +23 -17
- package/dist/query.js.map +1 -1
- package/dist/sqlite/btree.d.ts +2 -0
- package/dist/sqlite/btree.js +55 -20
- package/dist/sqlite/btree.js.map +1 -1
- package/dist/sqlite/pager.d.ts +5 -0
- package/dist/sqlite/pager.js +65 -8
- package/dist/sqlite/pager.js.map +1 -1
- package/dist/subgraph.d.ts +34 -11
- package/dist/subgraph.js +346 -87
- package/dist/subgraph.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +85 -24
- package/src/db.ts +225 -33
- package/src/gbwt/record.ts +23 -0
- package/src/graphName.ts +94 -0
- package/src/index.ts +15 -10
- package/src/pathName.ts +54 -0
- package/src/query.ts +41 -29
- package/src/sqlite/btree.ts +68 -19
- package/src/sqlite/pager.ts +78 -8
- package/src/subgraph.ts +430 -114
- package/tools/haplotype-index/src/main.rs +205 -163
package/dist/subgraph.js
CHANGED
|
@@ -1,26 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ENDMARKER, edgeIsCanonical, encodeNode, entryOrientation, entrySide, exitOrientation, exitSide, flipNode, flipSide, isReverse, nodeId, nodeOrientation, pathIsCanonical, } from "./gbwt/node.js";
|
|
2
|
+
import { gfaHeaderLines, sha256Hex, subgraphName } from "./graphName.js";
|
|
3
3
|
import { weightedLcs } from "./lcs.js";
|
|
4
|
+
import { formatPathName } from "./pathName.js";
|
|
5
|
+
function sideBefore(a, b) {
|
|
6
|
+
return (a[0] < b[0] ||
|
|
7
|
+
(a[0] === b[0] && (a[1] < b[1] || (a[1] === b[1] && a[2] < b[2]))));
|
|
8
|
+
}
|
|
4
9
|
class SideQueue {
|
|
5
|
-
|
|
10
|
+
heap = [];
|
|
6
11
|
push(distance, node, side) {
|
|
7
|
-
this.
|
|
12
|
+
const heap = this.heap;
|
|
13
|
+
heap.push([distance, node, side]);
|
|
14
|
+
let i = heap.length - 1;
|
|
15
|
+
while (i > 0) {
|
|
16
|
+
const parent = (i - 1) >> 1;
|
|
17
|
+
if (sideBefore(heap[i], heap[parent])) {
|
|
18
|
+
;
|
|
19
|
+
[heap[i], heap[parent]] = [heap[parent], heap[i]];
|
|
20
|
+
i = parent;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
8
26
|
}
|
|
9
27
|
pop() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
28
|
+
const heap = this.heap;
|
|
29
|
+
const top = heap[0];
|
|
30
|
+
const last = heap.pop();
|
|
31
|
+
if (heap.length > 0 && last !== undefined) {
|
|
32
|
+
heap[0] = last;
|
|
33
|
+
let i = 0;
|
|
34
|
+
for (;;) {
|
|
35
|
+
const left = 2 * i + 1;
|
|
36
|
+
const right = left + 1;
|
|
37
|
+
let smallest = i;
|
|
38
|
+
if (left < heap.length && sideBefore(heap[left], heap[smallest])) {
|
|
39
|
+
smallest = left;
|
|
40
|
+
}
|
|
41
|
+
if (right < heap.length && sideBefore(heap[right], heap[smallest])) {
|
|
42
|
+
smallest = right;
|
|
43
|
+
}
|
|
44
|
+
if (smallest === i) {
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
;
|
|
48
|
+
[heap[i], heap[smallest]] = [heap[smallest], heap[i]];
|
|
49
|
+
i = smallest;
|
|
17
50
|
}
|
|
18
51
|
}
|
|
19
|
-
|
|
20
|
-
return item;
|
|
52
|
+
return top;
|
|
21
53
|
}
|
|
22
54
|
get size() {
|
|
23
|
-
return this.
|
|
55
|
+
return this.heap.length;
|
|
24
56
|
}
|
|
25
57
|
}
|
|
26
58
|
function posKey(pos) {
|
|
@@ -35,7 +67,6 @@ export class Subgraph {
|
|
|
35
67
|
refInterval;
|
|
36
68
|
refIndexCache;
|
|
37
69
|
refPrefixCache;
|
|
38
|
-
limit;
|
|
39
70
|
stats = {
|
|
40
71
|
orderedAlignments: 0,
|
|
41
72
|
lcsAlignments: 0,
|
|
@@ -43,8 +74,12 @@ export class Subgraph {
|
|
|
43
74
|
identificationFetches: 0,
|
|
44
75
|
};
|
|
45
76
|
db;
|
|
46
|
-
|
|
77
|
+
limit;
|
|
78
|
+
signal;
|
|
79
|
+
constructor(db, opts = {}) {
|
|
47
80
|
this.db = db;
|
|
81
|
+
this.limit = opts.limit;
|
|
82
|
+
this.signal = opts.signal;
|
|
48
83
|
}
|
|
49
84
|
get nodeCount() {
|
|
50
85
|
return this.records.size / 2;
|
|
@@ -78,6 +113,7 @@ export class Subgraph {
|
|
|
78
113
|
return [...this.records.keys()].sort((a, b) => a - b);
|
|
79
114
|
}
|
|
80
115
|
async addNode(id) {
|
|
116
|
+
this.signal?.throwIfAborted();
|
|
81
117
|
if (this.limit !== undefined && this.nodeCount >= this.limit) {
|
|
82
118
|
throw new Error(`Subgraph size limit of ${this.limit} nodes exceeded`);
|
|
83
119
|
}
|
|
@@ -242,6 +278,139 @@ export class Subgraph {
|
|
|
242
278
|
}
|
|
243
279
|
return { inserted, removed: toRemove.size };
|
|
244
280
|
}
|
|
281
|
+
async betweenNodes(start, end) {
|
|
282
|
+
this.clearPaths();
|
|
283
|
+
const active = [start, flipNode(end)];
|
|
284
|
+
const visited = new Set([nodeId(start), nodeId(end)]);
|
|
285
|
+
let inserted = 0;
|
|
286
|
+
while (active.length > 0) {
|
|
287
|
+
const curr = active.pop();
|
|
288
|
+
const id = nodeId(curr);
|
|
289
|
+
if (!this.hasNode(id)) {
|
|
290
|
+
await this.addNode(id);
|
|
291
|
+
inserted += 1;
|
|
292
|
+
}
|
|
293
|
+
for (const successor of this.record(curr).successors()) {
|
|
294
|
+
const successorId = nodeId(successor);
|
|
295
|
+
if (!visited.has(successorId)) {
|
|
296
|
+
active.push(successor, flipNode(successor));
|
|
297
|
+
visited.add(successorId);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return inserted;
|
|
302
|
+
}
|
|
303
|
+
async extractSnarls(snarls) {
|
|
304
|
+
let inserted = 0;
|
|
305
|
+
for (const [start, end] of await this.overlappingSnarls(snarls)) {
|
|
306
|
+
inserted += await this.betweenNodes(start, end);
|
|
307
|
+
}
|
|
308
|
+
return inserted;
|
|
309
|
+
}
|
|
310
|
+
async overlappingSnarls(snarls) {
|
|
311
|
+
const result = [];
|
|
312
|
+
if (snarls !== 'none') {
|
|
313
|
+
let foundLink = false;
|
|
314
|
+
for (const handle of this.sortedHandles()) {
|
|
315
|
+
const record = this.record(handle);
|
|
316
|
+
const next = record.next;
|
|
317
|
+
if (next !== undefined) {
|
|
318
|
+
foundLink = true;
|
|
319
|
+
if (this.hasHandle(next)) {
|
|
320
|
+
if (edgeIsCanonical(handle, next)) {
|
|
321
|
+
result.push([handle, next]);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else if (snarls === 'overlapping' &&
|
|
325
|
+
this.isSnarlEntryInSubgraph(record)) {
|
|
326
|
+
result.push([handle, next]);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (!foundLink &&
|
|
331
|
+
snarls === 'overlapping' &&
|
|
332
|
+
(await this.db.hasChainLinks())) {
|
|
333
|
+
const covering = await this.findCoveringSnarl();
|
|
334
|
+
if (covering) {
|
|
335
|
+
result.push(covering);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
isSnarlEntryInSubgraph(record) {
|
|
342
|
+
const successors = record.successors();
|
|
343
|
+
const first = successors.find(handle => this.hasHandle(handle));
|
|
344
|
+
return first === undefined
|
|
345
|
+
? false
|
|
346
|
+
: successors.length > 1 ||
|
|
347
|
+
this.record(flipNode(first)).successors().length > 1;
|
|
348
|
+
}
|
|
349
|
+
recordReader(onFetch) {
|
|
350
|
+
const outside = new Map();
|
|
351
|
+
return async (handle) => {
|
|
352
|
+
const inside = this.records.get(handle);
|
|
353
|
+
if (inside) {
|
|
354
|
+
return inside;
|
|
355
|
+
}
|
|
356
|
+
let record = outside.get(handle);
|
|
357
|
+
if (!record) {
|
|
358
|
+
record = await this.db.getRecord(handle);
|
|
359
|
+
onFetch?.();
|
|
360
|
+
if (!record) {
|
|
361
|
+
throw new Error(`Node record ${handle} is missing from the database`);
|
|
362
|
+
}
|
|
363
|
+
outside.set(handle, record);
|
|
364
|
+
}
|
|
365
|
+
return record;
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async findCoveringSnarl() {
|
|
369
|
+
const read = this.recordReader();
|
|
370
|
+
const isSnarlEntry = async (record) => {
|
|
371
|
+
const successors = record.successors();
|
|
372
|
+
const first = successors[0];
|
|
373
|
+
return first === undefined
|
|
374
|
+
? false
|
|
375
|
+
: successors.length > 1 ||
|
|
376
|
+
(await read(flipNode(first))).successors().length > 1;
|
|
377
|
+
};
|
|
378
|
+
const classify = async (handle) => {
|
|
379
|
+
const reverse = await read(flipNode(handle));
|
|
380
|
+
return reverse.next !== undefined
|
|
381
|
+
? (await isSnarlEntry(reverse))
|
|
382
|
+
? { kind: 'snarl-exit', snarl: [flipNode(handle), reverse.next] }
|
|
383
|
+
: { kind: 'chain' }
|
|
384
|
+
: (await read(handle)).next !== undefined
|
|
385
|
+
? { kind: 'chain' }
|
|
386
|
+
: { kind: 'regular' };
|
|
387
|
+
};
|
|
388
|
+
const visited = new Set();
|
|
389
|
+
const queue = this.sortedHandles().flatMap(handle => this.record(handle).successors());
|
|
390
|
+
let result;
|
|
391
|
+
let done = false;
|
|
392
|
+
while (!done && queue.length > 0) {
|
|
393
|
+
const handle = queue.shift();
|
|
394
|
+
const id = nodeId(handle);
|
|
395
|
+
if (!this.hasHandle(handle) && !visited.has(id)) {
|
|
396
|
+
visited.add(id);
|
|
397
|
+
const type = await classify(handle);
|
|
398
|
+
if (type.kind === 'snarl-exit') {
|
|
399
|
+
result = type.snarl;
|
|
400
|
+
done = true;
|
|
401
|
+
}
|
|
402
|
+
else if (type.kind === 'chain') {
|
|
403
|
+
done = true;
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
for (const orientation of ['forward', 'reverse']) {
|
|
407
|
+
queue.push(...(await read(encodeNode(id, orientation))).successors());
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return result;
|
|
413
|
+
}
|
|
245
414
|
extractPaths(reference, output) {
|
|
246
415
|
this.clearPaths();
|
|
247
416
|
if (output === 'none') {
|
|
@@ -253,59 +422,65 @@ export class Subgraph {
|
|
|
253
422
|
const handles = this.sortedHandles();
|
|
254
423
|
const successors = new Map();
|
|
255
424
|
for (const handle of handles) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
425
|
+
const { nodes, offsets } = this.record(handle).gbwt().decompressArrays();
|
|
426
|
+
successors.set(handle, {
|
|
427
|
+
nodes,
|
|
428
|
+
offsets,
|
|
429
|
+
hasPredecessor: new Uint8Array(nodes.length),
|
|
430
|
+
});
|
|
260
431
|
}
|
|
261
432
|
for (const handle of handles) {
|
|
262
|
-
|
|
263
|
-
|
|
433
|
+
const { nodes, offsets } = successors.get(handle);
|
|
434
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
435
|
+
const entry = successors.get(nodes[i]);
|
|
264
436
|
if (entry) {
|
|
265
|
-
entry.hasPredecessor =
|
|
437
|
+
entry.hasPredecessor[offsets[i]] = 1;
|
|
266
438
|
}
|
|
267
439
|
}
|
|
268
440
|
}
|
|
269
441
|
let refOffset;
|
|
270
442
|
for (const handle of handles) {
|
|
271
443
|
const entries = successors.get(handle);
|
|
272
|
-
entries.
|
|
273
|
-
if (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
444
|
+
for (let offset = 0; offset < entries.nodes.length; offset++) {
|
|
445
|
+
if (entries.hasPredecessor[offset] === 0) {
|
|
446
|
+
let currNode = handle;
|
|
447
|
+
let currOffset = offset;
|
|
448
|
+
let isRef = false;
|
|
449
|
+
const path = [];
|
|
450
|
+
const positions = [];
|
|
451
|
+
let len = 0;
|
|
452
|
+
while (currNode !== undefined) {
|
|
453
|
+
if (currNode === refPos?.handle &&
|
|
454
|
+
currOffset === refPos.gbwtOffset) {
|
|
455
|
+
this.refId = this.paths.length;
|
|
456
|
+
refOffset = path.length;
|
|
457
|
+
isRef = true;
|
|
458
|
+
}
|
|
459
|
+
path.push(currNode);
|
|
460
|
+
positions.push({ node: currNode, offset: currOffset });
|
|
461
|
+
len += this.record(currNode).sequenceLen;
|
|
462
|
+
const step = successors.get(currNode);
|
|
463
|
+
const nextNode = step.nodes[currOffset];
|
|
464
|
+
const nextOffset = step.offsets[currOffset];
|
|
465
|
+
if (nextNode !== ENDMARKER && successors.has(nextNode)) {
|
|
466
|
+
currNode = nextNode;
|
|
467
|
+
currOffset = nextOffset;
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
currNode = undefined;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (isRef || pathIsCanonical(path)) {
|
|
474
|
+
this.paths.push({
|
|
475
|
+
path,
|
|
476
|
+
positions,
|
|
477
|
+
len,
|
|
478
|
+
weight: undefined,
|
|
479
|
+
identity: undefined,
|
|
480
|
+
});
|
|
287
481
|
}
|
|
288
|
-
path.push(curr.node);
|
|
289
|
-
positions.push(curr);
|
|
290
|
-
len += this.record(curr.node).sequenceLen;
|
|
291
|
-
const step = successors.get(curr.node)?.[curr.offset];
|
|
292
|
-
curr =
|
|
293
|
-
step &&
|
|
294
|
-
step.next.node !== ENDMARKER &&
|
|
295
|
-
successors.has(step.next.node)
|
|
296
|
-
? step.next
|
|
297
|
-
: undefined;
|
|
298
|
-
}
|
|
299
|
-
if (isRef || pathIsCanonical(path)) {
|
|
300
|
-
this.paths.push({
|
|
301
|
-
path,
|
|
302
|
-
positions,
|
|
303
|
-
len,
|
|
304
|
-
weight: undefined,
|
|
305
|
-
identity: undefined,
|
|
306
|
-
});
|
|
307
482
|
}
|
|
308
|
-
}
|
|
483
|
+
}
|
|
309
484
|
}
|
|
310
485
|
if (refPos) {
|
|
311
486
|
if (refOffset === undefined || this.refId === undefined) {
|
|
@@ -380,23 +555,9 @@ export class Subgraph {
|
|
|
380
555
|
starts.set(posKey(first), index);
|
|
381
556
|
}
|
|
382
557
|
});
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
if (inside) {
|
|
387
|
-
return inside;
|
|
388
|
-
}
|
|
389
|
-
let record = outside.get(handle);
|
|
390
|
-
if (!record) {
|
|
391
|
-
record = await this.db.getRecord(handle);
|
|
392
|
-
this.stats.identificationFetches += 1;
|
|
393
|
-
if (!record) {
|
|
394
|
-
throw new Error(`Node record ${handle} is missing from the database`);
|
|
395
|
-
}
|
|
396
|
-
outside.set(handle, record);
|
|
397
|
-
}
|
|
398
|
-
return record;
|
|
399
|
-
};
|
|
558
|
+
const recordAt = this.recordReader(() => {
|
|
559
|
+
this.stats.identificationFetches += 1;
|
|
560
|
+
});
|
|
400
561
|
const sampleAt = async (pos) => {
|
|
401
562
|
if (pos.node >= minHandle && pos.node <= maxHandle) {
|
|
402
563
|
return samples.get(posKey(pos));
|
|
@@ -451,6 +612,7 @@ export class Subgraph {
|
|
|
451
612
|
let current = start;
|
|
452
613
|
let pos;
|
|
453
614
|
while (anchor === undefined) {
|
|
615
|
+
this.signal?.throwIfAborted();
|
|
454
616
|
if (current !== undefined) {
|
|
455
617
|
if (visited.has(current)) {
|
|
456
618
|
break;
|
|
@@ -729,23 +891,16 @@ export class Subgraph {
|
|
|
729
891
|
last -= 1;
|
|
730
892
|
}
|
|
731
893
|
const identity = info.identity;
|
|
732
|
-
const
|
|
894
|
+
const walkStrand = info.path.some(handle => isReverse(handle)) &&
|
|
733
895
|
!info.path.some(handle => !isReverse(handle))
|
|
734
896
|
? '-'
|
|
735
897
|
: '+';
|
|
736
|
-
|
|
737
|
-
pathHandle: identity?.pathHandle,
|
|
738
|
-
name: identity?.name,
|
|
898
|
+
const span = {
|
|
739
899
|
strand: identity
|
|
740
900
|
? identity.orientation === 'forward'
|
|
741
901
|
? '+'
|
|
742
902
|
: '-'
|
|
743
|
-
:
|
|
744
|
-
hapStart: identity
|
|
745
|
-
? identity.name.fragment + identity.hapStart
|
|
746
|
-
: undefined,
|
|
747
|
-
hapEnd: identity ? identity.name.fragment + identity.hapEnd : undefined,
|
|
748
|
-
start: info.positions[0],
|
|
903
|
+
: walkStrand,
|
|
749
904
|
refStart: reference.start + leading,
|
|
750
905
|
refEnd: reference.start + refTotal - trailing,
|
|
751
906
|
cigar: edits
|
|
@@ -754,11 +909,115 @@ export class Subgraph {
|
|
|
754
909
|
.join(''),
|
|
755
910
|
weight: info.weight,
|
|
756
911
|
path: info.path,
|
|
757
|
-
|
|
912
|
+
start: info.positions[0],
|
|
913
|
+
};
|
|
914
|
+
if (identity) {
|
|
915
|
+
const hapStart = identity.name.fragment + identity.hapStart;
|
|
916
|
+
const hapEnd = identity.name.fragment + identity.hapEnd;
|
|
917
|
+
result.push({
|
|
918
|
+
...span,
|
|
919
|
+
resolved: true,
|
|
920
|
+
name: identity.name,
|
|
921
|
+
label: formatPathName({ ...identity.name, fragment: hapStart }, hapEnd),
|
|
922
|
+
pathHandle: identity.pathHandle,
|
|
923
|
+
hapStart,
|
|
924
|
+
hapEnd,
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
result.push({ ...span, resolved: false });
|
|
929
|
+
}
|
|
758
930
|
});
|
|
759
931
|
return result;
|
|
760
932
|
}
|
|
761
|
-
|
|
933
|
+
canonicalEdges(id) {
|
|
934
|
+
const edges = [];
|
|
935
|
+
for (const orientation of ['forward', 'reverse']) {
|
|
936
|
+
const handle = encodeNode(id, orientation);
|
|
937
|
+
for (const successor of this.record(handle).successors()) {
|
|
938
|
+
if (this.hasHandle(successor) && edgeIsCanonical(handle, successor)) {
|
|
939
|
+
edges.push([
|
|
940
|
+
orientation === 'reverse' ? 1 : 0,
|
|
941
|
+
nodeId(successor),
|
|
942
|
+
isReverse(successor) ? 1 : 0,
|
|
943
|
+
]);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
edges.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
|
|
948
|
+
return edges.filter((edge, i) => i === 0 ||
|
|
949
|
+
edge[0] !== edges[i - 1][0] ||
|
|
950
|
+
edge[1] !== edges[i - 1][1] ||
|
|
951
|
+
edge[2] !== edges[i - 1][2]);
|
|
952
|
+
}
|
|
953
|
+
async stableName() {
|
|
954
|
+
const encoder = new TextEncoder();
|
|
955
|
+
const chunks = [];
|
|
956
|
+
for (const handle of this.sortedHandles()) {
|
|
957
|
+
if (!isReverse(handle)) {
|
|
958
|
+
const id = nodeId(handle);
|
|
959
|
+
let text = `S\t${id}\t${this.record(handle).sequence}\n`;
|
|
960
|
+
for (const [fromReverse, toId, toReverse] of this.canonicalEdges(id)) {
|
|
961
|
+
text += `L\t${id}\t${fromReverse ? '-' : '+'}\t${toId}\t${toReverse ? '-' : '+'}\n`;
|
|
962
|
+
}
|
|
963
|
+
chunks.push(encoder.encode(text));
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return sha256Hex(chunks);
|
|
967
|
+
}
|
|
968
|
+
async toGFA(opts = {}) {
|
|
969
|
+
const cigar = opts.cigar ?? false;
|
|
970
|
+
const lines = [
|
|
971
|
+
this.refPath ? `H\tVN:Z:1.1\tRS:Z:${this.refPath.sample}` : 'H\tVN:Z:1.1',
|
|
972
|
+
...gfaHeaderLines(subgraphName(await this.stableName(), await this.db.graphName())),
|
|
973
|
+
];
|
|
974
|
+
const handles = this.sortedHandles();
|
|
975
|
+
for (const handle of handles) {
|
|
976
|
+
if (!isReverse(handle)) {
|
|
977
|
+
lines.push(`S\t${nodeId(handle)}\t${this.record(handle).sequence}`);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
const sign = (handle) => (isReverse(handle) ? '-' : '+');
|
|
981
|
+
for (const handle of handles) {
|
|
982
|
+
for (const successor of this.record(handle).successors()) {
|
|
983
|
+
if (this.hasHandle(successor) && edgeIsCanonical(handle, successor)) {
|
|
984
|
+
lines.push(`L\t${nodeId(handle)}\t${sign(handle)}\t${nodeId(successor)}\t${sign(successor)}\t0M`);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
const walk = (info, name, end, cigarString) => {
|
|
989
|
+
const steps = info.path
|
|
990
|
+
.map(handle => `${isReverse(handle) ? '<' : '>'}${nodeId(handle)}`)
|
|
991
|
+
.join('');
|
|
992
|
+
const weight = info.weight === undefined ? '' : `\tWT:i:${info.weight}`;
|
|
993
|
+
const cg = cigarString === undefined ? '' : `\tCG:Z:${cigarString}`;
|
|
994
|
+
return `W\t${name.sample}\t${name.haplotype}\t${name.contig}\t${name.fragment}\t${end}\t${steps}${weight}${cg}`;
|
|
995
|
+
};
|
|
996
|
+
const contig = this.refPath?.contig ?? 'unknown';
|
|
997
|
+
if (this.refId !== undefined && this.refPath && this.refInterval) {
|
|
998
|
+
lines.push(walk(this.paths[this.refId], {
|
|
999
|
+
...this.refPath,
|
|
1000
|
+
fragment: this.refPath.fragment + this.refInterval[0],
|
|
1001
|
+
}, this.refPath.fragment + this.refInterval[1], undefined));
|
|
1002
|
+
}
|
|
1003
|
+
let haplotype = 1;
|
|
1004
|
+
this.paths.forEach((info, index) => {
|
|
1005
|
+
if (index !== this.refId) {
|
|
1006
|
+
const resolved = opts.names === 'resolved' ? info.identity : undefined;
|
|
1007
|
+
const cigarString = cigar ? this.alignToRef(index) : undefined;
|
|
1008
|
+
lines.push(resolved
|
|
1009
|
+
? walk(info, {
|
|
1010
|
+
...resolved.name,
|
|
1011
|
+
fragment: resolved.name.fragment + resolved.hapStart,
|
|
1012
|
+
}, resolved.name.fragment + resolved.hapEnd, cigarString)
|
|
1013
|
+
: walk(info, { sample: 'unknown', contig, haplotype, fragment: 0 }, info.len, cigarString));
|
|
1014
|
+
haplotype += 1;
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
return `${lines.join('\n')}\n`;
|
|
1018
|
+
}
|
|
1019
|
+
toSubgraphJson(opts = {}) {
|
|
1020
|
+
const cigar = opts.cigar ?? false;
|
|
762
1021
|
const handles = this.sortedHandles();
|
|
763
1022
|
const nodes = handles
|
|
764
1023
|
.filter(handle => !isReverse(handle))
|