@gmod/gbz-base 0.0.1 → 1.1.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 +45 -4
- package/bin/query.js +3 -1
- package/dist/cli.js +85 -19
- package/dist/cli.js.map +1 -0
- package/dist/db.d.ts +14 -6
- package/dist/db.js +92 -28
- package/dist/db.js.map +1 -0
- package/dist/filehandle.js +1 -0
- package/dist/filehandle.js.map +1 -0
- package/dist/gbwt/bytecode.d.ts +1 -1
- package/dist/gbwt/bytecode.js +7 -3
- package/dist/gbwt/bytecode.js.map +1 -0
- package/dist/gbwt/node.js +7 -2
- package/dist/gbwt/node.js.map +1 -0
- package/dist/gbwt/record.d.ts +4 -0
- package/dist/gbwt/record.js +30 -2
- package/dist/gbwt/record.js.map +1 -0
- package/dist/gbwt/sequence.js +8 -1
- package/dist/gbwt/sequence.js.map +1 -0
- 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 +5 -4
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -0
- package/dist/lcs.js +32 -10
- package/dist/lcs.js.map +1 -0
- package/dist/query.d.ts +4 -2
- package/dist/query.js +19 -0
- package/dist/query.js.map +1 -0
- package/dist/sqlite/btree.d.ts +1 -1
- package/dist/sqlite/btree.js +24 -7
- package/dist/sqlite/btree.js.map +1 -0
- package/dist/sqlite/database.d.ts +1 -1
- package/dist/sqlite/database.js +12 -2
- package/dist/sqlite/database.js.map +1 -0
- package/dist/sqlite/pager.d.ts +8 -3
- package/dist/sqlite/pager.js +71 -12
- package/dist/sqlite/pager.js.map +1 -0
- package/dist/sqlite/record.js +3 -1
- package/dist/sqlite/record.js.map +1 -0
- package/dist/subgraph.d.ts +12 -2
- package/dist/subgraph.js +413 -84
- package/dist/subgraph.js.map +1 -0
- package/package.json +36 -11
- package/src/cli.ts +258 -0
- package/src/db.ts +374 -0
- package/src/filehandle.ts +4 -0
- package/src/gbwt/bytecode.ts +87 -0
- package/src/gbwt/node.ts +64 -0
- package/src/gbwt/record.ts +177 -0
- package/src/gbwt/sequence.ts +50 -0
- package/src/graphName.ts +94 -0
- package/src/index.ts +34 -0
- package/src/lcs.ts +282 -0
- package/src/query.ts +106 -0
- package/src/sqlite/btree.ts +260 -0
- package/src/sqlite/database.ts +117 -0
- package/src/sqlite/pager.ts +148 -0
- package/src/sqlite/record.ts +100 -0
- package/src/subgraph.ts +1353 -0
- package/tools/haplotype-index/src/main.rs +197 -163
package/dist/subgraph.js
CHANGED
|
@@ -1,32 +1,64 @@
|
|
|
1
1
|
import { formatPathName } from "./db.js";
|
|
2
|
-
import { ENDMARKER, edgeIsCanonical, encodeNode, entryOrientation, entrySide, exitOrientation, exitSide, flipSide, isReverse, nodeId, nodeOrientation, pathIsCanonical, } from "./gbwt/node.js";
|
|
2
|
+
import { ENDMARKER, edgeIsCanonical, encodeNode, entryOrientation, entrySide, exitOrientation, exitSide, flipNode, flipSide, isReverse, nodeId, nodeOrientation, pathIsCanonical, } from "./gbwt/node.js";
|
|
3
|
+
import { gfaHeaderLines, sha256Hex, subgraphName } from "./graphName.js";
|
|
3
4
|
import { weightedLcs } from "./lcs.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
|
-
|
|
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;
|
|
16
50
|
}
|
|
17
51
|
}
|
|
18
|
-
|
|
19
|
-
return item;
|
|
52
|
+
return top;
|
|
20
53
|
}
|
|
21
54
|
get size() {
|
|
22
|
-
return this.
|
|
55
|
+
return this.heap.length;
|
|
23
56
|
}
|
|
24
57
|
}
|
|
25
58
|
function posKey(pos) {
|
|
26
59
|
return `${pos.node}:${pos.offset}`;
|
|
27
60
|
}
|
|
28
61
|
export class Subgraph {
|
|
29
|
-
db;
|
|
30
62
|
records = new Map();
|
|
31
63
|
paths = [];
|
|
32
64
|
refId;
|
|
@@ -36,7 +68,13 @@ export class Subgraph {
|
|
|
36
68
|
refIndexCache;
|
|
37
69
|
refPrefixCache;
|
|
38
70
|
limit;
|
|
39
|
-
stats = {
|
|
71
|
+
stats = {
|
|
72
|
+
orderedAlignments: 0,
|
|
73
|
+
lcsAlignments: 0,
|
|
74
|
+
identificationSteps: 0,
|
|
75
|
+
identificationFetches: 0,
|
|
76
|
+
};
|
|
77
|
+
db;
|
|
40
78
|
constructor(db) {
|
|
41
79
|
this.db = db;
|
|
42
80
|
}
|
|
@@ -48,7 +86,11 @@ export class Subgraph {
|
|
|
48
86
|
}
|
|
49
87
|
get referenceInterval() {
|
|
50
88
|
return this.refInterval && this.refPath
|
|
51
|
-
? {
|
|
89
|
+
? {
|
|
90
|
+
name: this.refPath,
|
|
91
|
+
start: this.refPath.fragment + this.refInterval[0],
|
|
92
|
+
end: this.refPath.fragment + this.refInterval[1],
|
|
93
|
+
}
|
|
52
94
|
: undefined;
|
|
53
95
|
}
|
|
54
96
|
hasNode(id) {
|
|
@@ -116,7 +158,12 @@ export class Subgraph {
|
|
|
116
158
|
const record = this.record(pos.node);
|
|
117
159
|
if (pathOffset + record.sequenceLen > queryOffset) {
|
|
118
160
|
return {
|
|
119
|
-
position: {
|
|
161
|
+
position: {
|
|
162
|
+
seqOffset: queryOffset,
|
|
163
|
+
handle: pos.node,
|
|
164
|
+
nodeOffset: queryOffset - pathOffset,
|
|
165
|
+
gbwtOffset: pos.offset,
|
|
166
|
+
},
|
|
120
167
|
name: path.name,
|
|
121
168
|
handle: path.handle,
|
|
122
169
|
};
|
|
@@ -227,6 +274,139 @@ export class Subgraph {
|
|
|
227
274
|
}
|
|
228
275
|
return { inserted, removed: toRemove.size };
|
|
229
276
|
}
|
|
277
|
+
async betweenNodes(start, end) {
|
|
278
|
+
this.clearPaths();
|
|
279
|
+
const active = [start, flipNode(end)];
|
|
280
|
+
const visited = new Set([nodeId(start), nodeId(end)]);
|
|
281
|
+
let inserted = 0;
|
|
282
|
+
while (active.length > 0) {
|
|
283
|
+
const curr = active.pop();
|
|
284
|
+
const id = nodeId(curr);
|
|
285
|
+
if (!this.hasNode(id)) {
|
|
286
|
+
await this.addNode(id);
|
|
287
|
+
inserted += 1;
|
|
288
|
+
}
|
|
289
|
+
for (const successor of this.record(curr).successors()) {
|
|
290
|
+
const successorId = nodeId(successor);
|
|
291
|
+
if (!visited.has(successorId)) {
|
|
292
|
+
active.push(successor, flipNode(successor));
|
|
293
|
+
visited.add(successorId);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return inserted;
|
|
298
|
+
}
|
|
299
|
+
async extractSnarls(snarls) {
|
|
300
|
+
let inserted = 0;
|
|
301
|
+
for (const [start, end] of await this.overlappingSnarls(snarls)) {
|
|
302
|
+
inserted += await this.betweenNodes(start, end);
|
|
303
|
+
}
|
|
304
|
+
return inserted;
|
|
305
|
+
}
|
|
306
|
+
async overlappingSnarls(snarls) {
|
|
307
|
+
const result = [];
|
|
308
|
+
if (snarls !== 'none') {
|
|
309
|
+
let foundLink = false;
|
|
310
|
+
for (const handle of this.sortedHandles()) {
|
|
311
|
+
const record = this.record(handle);
|
|
312
|
+
const next = record.next;
|
|
313
|
+
if (next !== undefined) {
|
|
314
|
+
foundLink = true;
|
|
315
|
+
if (this.hasHandle(next)) {
|
|
316
|
+
if (edgeIsCanonical(handle, next)) {
|
|
317
|
+
result.push([handle, next]);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else if (snarls === 'overlapping' &&
|
|
321
|
+
this.isSnarlEntryInSubgraph(record)) {
|
|
322
|
+
result.push([handle, next]);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (!foundLink &&
|
|
327
|
+
snarls === 'overlapping' &&
|
|
328
|
+
(await this.db.hasChainLinks())) {
|
|
329
|
+
const covering = await this.findCoveringSnarl();
|
|
330
|
+
if (covering) {
|
|
331
|
+
result.push(covering);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return result;
|
|
336
|
+
}
|
|
337
|
+
isSnarlEntryInSubgraph(record) {
|
|
338
|
+
const successors = record.successors();
|
|
339
|
+
const first = successors.find(handle => this.hasHandle(handle));
|
|
340
|
+
return first === undefined
|
|
341
|
+
? false
|
|
342
|
+
: successors.length > 1 ||
|
|
343
|
+
this.record(flipNode(first)).successors().length > 1;
|
|
344
|
+
}
|
|
345
|
+
recordReader(onFetch) {
|
|
346
|
+
const outside = new Map();
|
|
347
|
+
return async (handle) => {
|
|
348
|
+
const inside = this.records.get(handle);
|
|
349
|
+
if (inside) {
|
|
350
|
+
return inside;
|
|
351
|
+
}
|
|
352
|
+
let record = outside.get(handle);
|
|
353
|
+
if (!record) {
|
|
354
|
+
record = await this.db.getRecord(handle);
|
|
355
|
+
onFetch?.();
|
|
356
|
+
if (!record) {
|
|
357
|
+
throw new Error(`Node record ${handle} is missing from the database`);
|
|
358
|
+
}
|
|
359
|
+
outside.set(handle, record);
|
|
360
|
+
}
|
|
361
|
+
return record;
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
async findCoveringSnarl() {
|
|
365
|
+
const read = this.recordReader();
|
|
366
|
+
const isSnarlEntry = async (record) => {
|
|
367
|
+
const successors = record.successors();
|
|
368
|
+
const first = successors[0];
|
|
369
|
+
return first === undefined
|
|
370
|
+
? false
|
|
371
|
+
: successors.length > 1 ||
|
|
372
|
+
(await read(flipNode(first))).successors().length > 1;
|
|
373
|
+
};
|
|
374
|
+
const classify = async (handle) => {
|
|
375
|
+
const reverse = await read(flipNode(handle));
|
|
376
|
+
return reverse.next !== undefined
|
|
377
|
+
? (await isSnarlEntry(reverse))
|
|
378
|
+
? { kind: 'snarl-exit', snarl: [flipNode(handle), reverse.next] }
|
|
379
|
+
: { kind: 'chain' }
|
|
380
|
+
: (await read(handle)).next !== undefined
|
|
381
|
+
? { kind: 'chain' }
|
|
382
|
+
: { kind: 'regular' };
|
|
383
|
+
};
|
|
384
|
+
const visited = new Set();
|
|
385
|
+
const queue = this.sortedHandles().flatMap(handle => this.record(handle).successors());
|
|
386
|
+
let result;
|
|
387
|
+
let done = false;
|
|
388
|
+
while (!done && queue.length > 0) {
|
|
389
|
+
const handle = queue.shift();
|
|
390
|
+
const id = nodeId(handle);
|
|
391
|
+
if (!this.hasHandle(handle) && !visited.has(id)) {
|
|
392
|
+
visited.add(id);
|
|
393
|
+
const type = await classify(handle);
|
|
394
|
+
if (type.kind === 'snarl-exit') {
|
|
395
|
+
result = type.snarl;
|
|
396
|
+
done = true;
|
|
397
|
+
}
|
|
398
|
+
else if (type.kind === 'chain') {
|
|
399
|
+
done = true;
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
for (const orientation of ['forward', 'reverse']) {
|
|
403
|
+
queue.push(...(await read(encodeNode(id, orientation))).successors());
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
230
410
|
extractPaths(reference, output) {
|
|
231
411
|
this.clearPaths();
|
|
232
412
|
if (output === 'none') {
|
|
@@ -238,47 +418,65 @@ export class Subgraph {
|
|
|
238
418
|
const handles = this.sortedHandles();
|
|
239
419
|
const successors = new Map();
|
|
240
420
|
for (const handle of handles) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
421
|
+
const { nodes, offsets } = this.record(handle).gbwt().decompressArrays();
|
|
422
|
+
successors.set(handle, {
|
|
423
|
+
nodes,
|
|
424
|
+
offsets,
|
|
425
|
+
hasPredecessor: new Uint8Array(nodes.length),
|
|
426
|
+
});
|
|
245
427
|
}
|
|
246
428
|
for (const handle of handles) {
|
|
247
|
-
|
|
248
|
-
|
|
429
|
+
const { nodes, offsets } = successors.get(handle);
|
|
430
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
431
|
+
const entry = successors.get(nodes[i]);
|
|
249
432
|
if (entry) {
|
|
250
|
-
entry.hasPredecessor =
|
|
433
|
+
entry.hasPredecessor[offsets[i]] = 1;
|
|
251
434
|
}
|
|
252
435
|
}
|
|
253
436
|
}
|
|
254
437
|
let refOffset;
|
|
255
438
|
for (const handle of handles) {
|
|
256
439
|
const entries = successors.get(handle);
|
|
257
|
-
entries.
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
440
|
+
for (let offset = 0; offset < entries.nodes.length; offset++) {
|
|
441
|
+
if (entries.hasPredecessor[offset] === 0) {
|
|
442
|
+
let currNode = handle;
|
|
443
|
+
let currOffset = offset;
|
|
444
|
+
let isRef = false;
|
|
445
|
+
const path = [];
|
|
446
|
+
const positions = [];
|
|
447
|
+
let len = 0;
|
|
448
|
+
while (currNode !== undefined) {
|
|
449
|
+
if (currNode === refPos?.handle &&
|
|
450
|
+
currOffset === refPos.gbwtOffset) {
|
|
451
|
+
this.refId = this.paths.length;
|
|
452
|
+
refOffset = path.length;
|
|
453
|
+
isRef = true;
|
|
454
|
+
}
|
|
455
|
+
path.push(currNode);
|
|
456
|
+
positions.push({ node: currNode, offset: currOffset });
|
|
457
|
+
len += this.record(currNode).sequenceLen;
|
|
458
|
+
const step = successors.get(currNode);
|
|
459
|
+
const nextNode = step.nodes[currOffset];
|
|
460
|
+
const nextOffset = step.offsets[currOffset];
|
|
461
|
+
if (nextNode !== ENDMARKER && successors.has(nextNode)) {
|
|
462
|
+
currNode = nextNode;
|
|
463
|
+
currOffset = nextOffset;
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
currNode = undefined;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (isRef || pathIsCanonical(path)) {
|
|
470
|
+
this.paths.push({
|
|
471
|
+
path,
|
|
472
|
+
positions,
|
|
473
|
+
len,
|
|
474
|
+
weight: undefined,
|
|
475
|
+
identity: undefined,
|
|
476
|
+
});
|
|
271
477
|
}
|
|
272
|
-
path.push(curr.node);
|
|
273
|
-
positions.push(curr);
|
|
274
|
-
len += this.record(curr.node).sequenceLen;
|
|
275
|
-
const step = successors.get(curr.node)?.[curr.offset];
|
|
276
|
-
curr = step && step.next.node !== ENDMARKER && successors.has(step.next.node) ? step.next : undefined;
|
|
277
|
-
}
|
|
278
|
-
if (isRef || pathIsCanonical(path)) {
|
|
279
|
-
this.paths.push({ path, positions, len, weight: undefined, identity: undefined });
|
|
280
478
|
}
|
|
281
|
-
}
|
|
479
|
+
}
|
|
282
480
|
}
|
|
283
481
|
if (refPos) {
|
|
284
482
|
if (refOffset === undefined || this.refId === undefined) {
|
|
@@ -292,9 +490,13 @@ export class Subgraph {
|
|
|
292
490
|
}
|
|
293
491
|
const start = refPos.seqOffset - before;
|
|
294
492
|
this.refInterval = [start, start + info.len];
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
493
|
+
info.identity = {
|
|
494
|
+
pathHandle: reference.handle,
|
|
495
|
+
name: reference.name,
|
|
496
|
+
orientation: 'forward',
|
|
497
|
+
hapStart: start,
|
|
498
|
+
hapEnd: start + info.len,
|
|
499
|
+
};
|
|
298
500
|
}
|
|
299
501
|
if (output === 'distinct') {
|
|
300
502
|
this.distinctPaths();
|
|
@@ -349,23 +551,9 @@ export class Subgraph {
|
|
|
349
551
|
starts.set(posKey(first), index);
|
|
350
552
|
}
|
|
351
553
|
});
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (inside) {
|
|
356
|
-
return inside;
|
|
357
|
-
}
|
|
358
|
-
let record = outside.get(handle);
|
|
359
|
-
if (!record) {
|
|
360
|
-
record = await this.db.getRecord(handle);
|
|
361
|
-
this.stats.identificationFetches += 1;
|
|
362
|
-
if (!record) {
|
|
363
|
-
throw new Error(`Node record ${handle} is missing from the database`);
|
|
364
|
-
}
|
|
365
|
-
outside.set(handle, record);
|
|
366
|
-
}
|
|
367
|
-
return record;
|
|
368
|
-
};
|
|
554
|
+
const recordAt = this.recordReader(() => {
|
|
555
|
+
this.stats.identificationFetches += 1;
|
|
556
|
+
});
|
|
369
557
|
const sampleAt = async (pos) => {
|
|
370
558
|
if (pos.node >= minHandle && pos.node <= maxHandle) {
|
|
371
559
|
return samples.get(posKey(pos));
|
|
@@ -387,11 +575,27 @@ export class Subgraph {
|
|
|
387
575
|
return name;
|
|
388
576
|
};
|
|
389
577
|
const anchorFromSample = (sample, counter, nodeLen) => sample.orientation === 'forward'
|
|
390
|
-
? {
|
|
391
|
-
|
|
578
|
+
? {
|
|
579
|
+
pathHandle: sample.pathHandle,
|
|
580
|
+
orientation: 'forward',
|
|
581
|
+
base: sample.pathOffset - counter,
|
|
582
|
+
}
|
|
583
|
+
: {
|
|
584
|
+
pathHandle: sample.pathHandle,
|
|
585
|
+
orientation: 'reverse',
|
|
586
|
+
base: sample.pathOffset + counter + nodeLen,
|
|
587
|
+
};
|
|
392
588
|
const anchorFromIdentity = (identity, counter) => identity.orientation === 'forward'
|
|
393
|
-
? {
|
|
394
|
-
|
|
589
|
+
? {
|
|
590
|
+
pathHandle: identity.pathHandle,
|
|
591
|
+
orientation: 'forward',
|
|
592
|
+
base: identity.hapStart - counter,
|
|
593
|
+
}
|
|
594
|
+
: {
|
|
595
|
+
pathHandle: identity.pathHandle,
|
|
596
|
+
orientation: 'reverse',
|
|
597
|
+
base: identity.hapEnd + counter,
|
|
598
|
+
};
|
|
395
599
|
for (let start = 0; start < this.paths.length; start++) {
|
|
396
600
|
const startInfo = this.paths[start];
|
|
397
601
|
if (start === this.refId || startInfo.identity) {
|
|
@@ -449,7 +653,8 @@ export class Subgraph {
|
|
|
449
653
|
break;
|
|
450
654
|
}
|
|
451
655
|
this.stats.identificationSteps += 1;
|
|
452
|
-
if (counter - chain[chain.length - 1].startBp >
|
|
656
|
+
if (counter - chain[chain.length - 1].startBp >
|
|
657
|
+
4 * interval + 4 * record.sequenceLen) {
|
|
453
658
|
break;
|
|
454
659
|
}
|
|
455
660
|
counter += record.sequenceLen;
|
|
@@ -461,8 +666,20 @@ export class Subgraph {
|
|
|
461
666
|
const info = this.paths[index];
|
|
462
667
|
info.identity =
|
|
463
668
|
anchor.orientation === 'forward'
|
|
464
|
-
? {
|
|
465
|
-
|
|
669
|
+
? {
|
|
670
|
+
pathHandle: anchor.pathHandle,
|
|
671
|
+
name,
|
|
672
|
+
orientation: 'forward',
|
|
673
|
+
hapStart: anchor.base + startBp,
|
|
674
|
+
hapEnd: anchor.base + startBp + info.len,
|
|
675
|
+
}
|
|
676
|
+
: {
|
|
677
|
+
pathHandle: anchor.pathHandle,
|
|
678
|
+
name,
|
|
679
|
+
orientation: 'reverse',
|
|
680
|
+
hapStart: anchor.base - startBp - info.len,
|
|
681
|
+
hapEnd: anchor.base - startBp,
|
|
682
|
+
};
|
|
466
683
|
}
|
|
467
684
|
}
|
|
468
685
|
}
|
|
@@ -595,7 +812,9 @@ export class Subgraph {
|
|
|
595
812
|
}
|
|
596
813
|
else {
|
|
597
814
|
const mismatch = Math.min(pathMiddle, refMiddle);
|
|
598
|
-
const mismatchIndel = 4 * mismatch +
|
|
815
|
+
const mismatchIndel = 4 * mismatch +
|
|
816
|
+
gapPenalty(pathMiddle - mismatch) +
|
|
817
|
+
gapPenalty(refMiddle - mismatch);
|
|
599
818
|
const insertionDeletion = gapPenalty(pathMiddle) + gapPenalty(refMiddle);
|
|
600
819
|
if (mismatchIndel <= insertionDeletion) {
|
|
601
820
|
appendEdit(edits, 'M', mismatch);
|
|
@@ -622,7 +841,8 @@ export class Subgraph {
|
|
|
622
841
|
else {
|
|
623
842
|
this.stats.lcsAlignments += 1;
|
|
624
843
|
}
|
|
625
|
-
const lcs = ordered ??
|
|
844
|
+
const lcs = ordered ??
|
|
845
|
+
weightedLcs(info.path, ref, handle => this.record(handle).sequenceLen)[0];
|
|
626
846
|
const edits = [];
|
|
627
847
|
let pathOffset = 0;
|
|
628
848
|
let refOffset = 0;
|
|
@@ -636,7 +856,9 @@ export class Subgraph {
|
|
|
636
856
|
return edits;
|
|
637
857
|
}
|
|
638
858
|
alignToRef(pathIndex) {
|
|
639
|
-
return this.edits(pathIndex)
|
|
859
|
+
return this.edits(pathIndex)
|
|
860
|
+
?.map(([op, len]) => `${len}${op}`)
|
|
861
|
+
.join('');
|
|
640
862
|
}
|
|
641
863
|
alignments() {
|
|
642
864
|
const reference = this.referenceInterval;
|
|
@@ -664,12 +886,21 @@ export class Subgraph {
|
|
|
664
886
|
last -= 1;
|
|
665
887
|
}
|
|
666
888
|
const identity = info.identity;
|
|
667
|
-
const strand = info.path.some(handle => isReverse(handle)) &&
|
|
889
|
+
const strand = info.path.some(handle => isReverse(handle)) &&
|
|
890
|
+
!info.path.some(handle => !isReverse(handle))
|
|
891
|
+
? '-'
|
|
892
|
+
: '+';
|
|
668
893
|
result.push({
|
|
669
894
|
pathHandle: identity?.pathHandle,
|
|
670
895
|
name: identity?.name,
|
|
671
|
-
strand: identity
|
|
672
|
-
|
|
896
|
+
strand: identity
|
|
897
|
+
? identity.orientation === 'forward'
|
|
898
|
+
? '+'
|
|
899
|
+
: '-'
|
|
900
|
+
: strand,
|
|
901
|
+
hapStart: identity
|
|
902
|
+
? identity.name.fragment + identity.hapStart
|
|
903
|
+
: undefined,
|
|
673
904
|
hapEnd: identity ? identity.name.fragment + identity.hapEnd : undefined,
|
|
674
905
|
start: info.positions[0],
|
|
675
906
|
refStart: reference.start + leading,
|
|
@@ -684,11 +915,99 @@ export class Subgraph {
|
|
|
684
915
|
});
|
|
685
916
|
return result;
|
|
686
917
|
}
|
|
918
|
+
canonicalEdges(id) {
|
|
919
|
+
const edges = [];
|
|
920
|
+
for (const orientation of ['forward', 'reverse']) {
|
|
921
|
+
const handle = encodeNode(id, orientation);
|
|
922
|
+
for (const successor of this.record(handle).successors()) {
|
|
923
|
+
if (this.hasHandle(successor) && edgeIsCanonical(handle, successor)) {
|
|
924
|
+
edges.push([
|
|
925
|
+
orientation === 'reverse' ? 1 : 0,
|
|
926
|
+
nodeId(successor),
|
|
927
|
+
isReverse(successor) ? 1 : 0,
|
|
928
|
+
]);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
edges.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
|
|
933
|
+
return edges.filter((edge, i) => i === 0 ||
|
|
934
|
+
edge[0] !== edges[i - 1][0] ||
|
|
935
|
+
edge[1] !== edges[i - 1][1] ||
|
|
936
|
+
edge[2] !== edges[i - 1][2]);
|
|
937
|
+
}
|
|
938
|
+
async stableName() {
|
|
939
|
+
const encoder = new TextEncoder();
|
|
940
|
+
const chunks = [];
|
|
941
|
+
for (const handle of this.sortedHandles()) {
|
|
942
|
+
if (!isReverse(handle)) {
|
|
943
|
+
const id = nodeId(handle);
|
|
944
|
+
let text = `S\t${id}\t${this.record(handle).sequence}\n`;
|
|
945
|
+
for (const [fromReverse, toId, toReverse] of this.canonicalEdges(id)) {
|
|
946
|
+
text += `L\t${id}\t${fromReverse ? '-' : '+'}\t${toId}\t${toReverse ? '-' : '+'}\n`;
|
|
947
|
+
}
|
|
948
|
+
chunks.push(encoder.encode(text));
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return sha256Hex(chunks);
|
|
952
|
+
}
|
|
953
|
+
async toGFA(cigar, opts = {}) {
|
|
954
|
+
const lines = [
|
|
955
|
+
this.refPath ? `H\tVN:Z:1.1\tRS:Z:${this.refPath.sample}` : 'H\tVN:Z:1.1',
|
|
956
|
+
...gfaHeaderLines(subgraphName(await this.stableName(), await this.db.graphName())),
|
|
957
|
+
];
|
|
958
|
+
const handles = this.sortedHandles();
|
|
959
|
+
for (const handle of handles) {
|
|
960
|
+
if (!isReverse(handle)) {
|
|
961
|
+
lines.push(`S\t${nodeId(handle)}\t${this.record(handle).sequence}`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
const sign = (handle) => (isReverse(handle) ? '-' : '+');
|
|
965
|
+
for (const handle of handles) {
|
|
966
|
+
for (const successor of this.record(handle).successors()) {
|
|
967
|
+
if (this.hasHandle(successor) && edgeIsCanonical(handle, successor)) {
|
|
968
|
+
lines.push(`L\t${nodeId(handle)}\t${sign(handle)}\t${nodeId(successor)}\t${sign(successor)}\t0M`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
const walk = (info, name, end, cigarString) => {
|
|
973
|
+
const steps = info.path
|
|
974
|
+
.map(handle => `${isReverse(handle) ? '<' : '>'}${nodeId(handle)}`)
|
|
975
|
+
.join('');
|
|
976
|
+
const weight = info.weight === undefined ? '' : `\tWT:i:${info.weight}`;
|
|
977
|
+
const cg = cigarString === undefined ? '' : `\tCG:Z:${cigarString}`;
|
|
978
|
+
return `W\t${name.sample}\t${name.haplotype}\t${name.contig}\t${name.fragment}\t${end}\t${steps}${weight}${cg}`;
|
|
979
|
+
};
|
|
980
|
+
const contig = this.refPath?.contig ?? 'unknown';
|
|
981
|
+
if (this.refId !== undefined && this.refPath && this.refInterval) {
|
|
982
|
+
lines.push(walk(this.paths[this.refId], {
|
|
983
|
+
...this.refPath,
|
|
984
|
+
fragment: this.refPath.fragment + this.refInterval[0],
|
|
985
|
+
}, this.refPath.fragment + this.refInterval[1], undefined));
|
|
986
|
+
}
|
|
987
|
+
let haplotype = 1;
|
|
988
|
+
this.paths.forEach((info, index) => {
|
|
989
|
+
if (index !== this.refId) {
|
|
990
|
+
const resolved = opts.names === 'resolved' ? info.identity : undefined;
|
|
991
|
+
const cigarString = cigar ? this.alignToRef(index) : undefined;
|
|
992
|
+
lines.push(resolved
|
|
993
|
+
? walk(info, {
|
|
994
|
+
...resolved.name,
|
|
995
|
+
fragment: resolved.name.fragment + resolved.hapStart,
|
|
996
|
+
}, resolved.name.fragment + resolved.hapEnd, cigarString)
|
|
997
|
+
: walk(info, { sample: 'unknown', contig, haplotype, fragment: 0 }, info.len, cigarString));
|
|
998
|
+
haplotype += 1;
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
return `${lines.join('\n')}\n`;
|
|
1002
|
+
}
|
|
687
1003
|
toJSON(cigar, opts = {}) {
|
|
688
1004
|
const handles = this.sortedHandles();
|
|
689
1005
|
const nodes = handles
|
|
690
1006
|
.filter(handle => !isReverse(handle))
|
|
691
|
-
.map(handle => ({
|
|
1007
|
+
.map(handle => ({
|
|
1008
|
+
id: String(nodeId(handle)),
|
|
1009
|
+
sequence: this.record(handle).sequence,
|
|
1010
|
+
}));
|
|
692
1011
|
const edges = [];
|
|
693
1012
|
for (const handle of handles) {
|
|
694
1013
|
for (const successor of this.record(handle).successors()) {
|
|
@@ -706,7 +1025,10 @@ export class Subgraph {
|
|
|
706
1025
|
const contig = this.refPath?.contig ?? 'unknown';
|
|
707
1026
|
if (this.refId !== undefined && this.refPath && this.refInterval) {
|
|
708
1027
|
const info = this.paths[this.refId];
|
|
709
|
-
const name = {
|
|
1028
|
+
const name = {
|
|
1029
|
+
...this.refPath,
|
|
1030
|
+
fragment: this.refPath.fragment + this.refInterval[0],
|
|
1031
|
+
};
|
|
710
1032
|
paths.push(jsonPath(info, formatPathName(name, this.refPath.fragment + this.refInterval[1]), undefined));
|
|
711
1033
|
}
|
|
712
1034
|
let haplotype = 1;
|
|
@@ -716,7 +1038,10 @@ export class Subgraph {
|
|
|
716
1038
|
}
|
|
717
1039
|
const resolved = opts.names === 'resolved' ? info.identity : undefined;
|
|
718
1040
|
const name = resolved
|
|
719
|
-
? formatPathName({
|
|
1041
|
+
? formatPathName({
|
|
1042
|
+
...resolved.name,
|
|
1043
|
+
fragment: resolved.name.fragment + resolved.hapStart,
|
|
1044
|
+
}, resolved.name.fragment + resolved.hapEnd)
|
|
720
1045
|
: formatPathName({ sample: 'unknown', contig, haplotype, fragment: 0 }, info.len);
|
|
721
1046
|
paths.push(jsonPath(info, name, cigar ? this.alignToRef(index) : undefined));
|
|
722
1047
|
haplotype += 1;
|
|
@@ -729,7 +1054,10 @@ function jsonPath(info, name, cigar) {
|
|
|
729
1054
|
name,
|
|
730
1055
|
...(info.weight === undefined ? {} : { weight: info.weight }),
|
|
731
1056
|
...(cigar === undefined ? {} : { cigar }),
|
|
732
|
-
path: info.path.map(handle => ({
|
|
1057
|
+
path: info.path.map(handle => ({
|
|
1058
|
+
id: String(nodeId(handle)),
|
|
1059
|
+
is_reverse: isReverse(handle),
|
|
1060
|
+
})),
|
|
733
1061
|
};
|
|
734
1062
|
}
|
|
735
1063
|
function comparePaths(a, b) {
|
|
@@ -748,7 +1076,7 @@ function appendEdit(edits, op, len) {
|
|
|
748
1076
|
return;
|
|
749
1077
|
}
|
|
750
1078
|
const last = edits[edits.length - 1];
|
|
751
|
-
if (last
|
|
1079
|
+
if (last?.[0] === op) {
|
|
752
1080
|
last[1] += len;
|
|
753
1081
|
}
|
|
754
1082
|
else {
|
|
@@ -758,3 +1086,4 @@ function appendEdit(edits, op, len) {
|
|
|
758
1086
|
function gapPenalty(len) {
|
|
759
1087
|
return len === 0 ? 0 : 6 + (len - 1);
|
|
760
1088
|
}
|
|
1089
|
+
//# sourceMappingURL=subgraph.js.map
|