@gmod/gbz-base 1.1.0 → 2.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 +136 -26
- package/dist/cli.js +12 -11
- package/dist/cli.js.map +1 -1
- package/dist/db.d.ts +18 -9
- package/dist/db.js +100 -4
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +2 -1
- 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 +8 -10
- package/dist/query.js +7 -19
- package/dist/query.js.map +1 -1
- package/dist/sqlite/btree.d.ts +2 -0
- package/dist/sqlite/btree.js +48 -18
- package/dist/sqlite/btree.js.map +1 -1
- package/dist/subgraph.d.ts +28 -13
- package/dist/subgraph.js +91 -37
- package/dist/subgraph.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +19 -13
- package/src/db.ts +160 -13
- package/src/index.ts +12 -10
- package/src/pathName.ts +54 -0
- package/src/query.ts +17 -32
- package/src/sqlite/btree.ts +61 -17
- package/src/subgraph.ts +117 -52
- package/tools/haplotype-index/src/main.rs +33 -16
package/src/query.ts
CHANGED
|
@@ -1,29 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pathNameFor } from './pathName.ts'
|
|
2
2
|
import { Subgraph } from './subgraph.ts'
|
|
3
3
|
|
|
4
|
-
import type { GBZBase
|
|
4
|
+
import type { GBZBase } from './db.ts'
|
|
5
|
+
import type { PathQuery } from './pathName.ts'
|
|
5
6
|
import type { HaplotypeOutput, SnarlOutput } from './subgraph.ts'
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
-
context?: number
|
|
9
|
-
haplotypes?: HaplotypeOutput
|
|
10
|
-
snarls?: SnarlOutput
|
|
11
|
-
limit?: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface PathQuery {
|
|
15
|
-
sample?: string
|
|
16
|
-
contig: string
|
|
17
|
-
haplotype?: number
|
|
18
|
-
}
|
|
8
|
+
export type { PathQuery } from './pathName.ts'
|
|
19
9
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
10
|
+
export interface QueryOptions {
|
|
11
|
+
context?: number | undefined
|
|
12
|
+
haplotypes?: HaplotypeOutput | undefined
|
|
13
|
+
snarls?: SnarlOutput | undefined
|
|
14
|
+
limit?: number | undefined
|
|
15
|
+
signal?: AbortSignal | undefined
|
|
27
16
|
}
|
|
28
17
|
|
|
29
18
|
export async function subgraphAtOffset(
|
|
@@ -32,9 +21,8 @@ export async function subgraphAtOffset(
|
|
|
32
21
|
offset: number,
|
|
33
22
|
opts: QueryOptions = {},
|
|
34
23
|
) {
|
|
35
|
-
const subgraph = new Subgraph(db)
|
|
36
|
-
|
|
37
|
-
const reference = await subgraph.pathPosition(pathName(query, offset))
|
|
24
|
+
const subgraph = new Subgraph(db, opts)
|
|
25
|
+
const reference = await subgraph.pathPosition(pathNameFor(query, offset))
|
|
38
26
|
await subgraph.aroundPosition(
|
|
39
27
|
reference.position.handle,
|
|
40
28
|
reference.position.nodeOffset,
|
|
@@ -52,9 +40,8 @@ export async function subgraphInInterval(
|
|
|
52
40
|
end: number,
|
|
53
41
|
opts: QueryOptions = {},
|
|
54
42
|
) {
|
|
55
|
-
const subgraph = new Subgraph(db)
|
|
56
|
-
|
|
57
|
-
const reference = await subgraph.pathPosition(pathName(query, start))
|
|
43
|
+
const subgraph = new Subgraph(db, opts)
|
|
44
|
+
const reference = await subgraph.pathPosition(pathNameFor(query, start))
|
|
58
45
|
await subgraph.aroundInterval(
|
|
59
46
|
reference.position,
|
|
60
47
|
end - start,
|
|
@@ -80,8 +67,7 @@ export async function subgraphAroundNodes(
|
|
|
80
67
|
'Overlapping snarls cannot be extracted for a node-based query with multiple nodes',
|
|
81
68
|
)
|
|
82
69
|
}
|
|
83
|
-
const subgraph = new Subgraph(db)
|
|
84
|
-
subgraph.limit = opts.limit
|
|
70
|
+
const subgraph = new Subgraph(db, opts)
|
|
85
71
|
await subgraph.aroundNodes(nodes, opts.context ?? 100)
|
|
86
72
|
await subgraph.extractSnarls(snarls)
|
|
87
73
|
subgraph.extractPaths(undefined, haplotypes)
|
|
@@ -92,14 +78,13 @@ export async function subgraphBetween(
|
|
|
92
78
|
db: GBZBase,
|
|
93
79
|
start: number,
|
|
94
80
|
end: number,
|
|
95
|
-
opts: Pick<QueryOptions, 'haplotypes' | 'limit'> = {},
|
|
81
|
+
opts: Pick<QueryOptions, 'haplotypes' | 'limit' | 'signal'> = {},
|
|
96
82
|
) {
|
|
97
83
|
const haplotypes = opts.haplotypes ?? 'all'
|
|
98
84
|
if (haplotypes === 'reference-only') {
|
|
99
85
|
throw new Error('Cannot output a reference path in a node-based query')
|
|
100
86
|
}
|
|
101
|
-
const subgraph = new Subgraph(db)
|
|
102
|
-
subgraph.limit = opts.limit
|
|
87
|
+
const subgraph = new Subgraph(db, opts)
|
|
103
88
|
await subgraph.betweenNodes(start, end)
|
|
104
89
|
subgraph.extractPaths(undefined, haplotypes)
|
|
105
90
|
return subgraph
|
package/src/sqlite/btree.ts
CHANGED
|
@@ -15,8 +15,20 @@ interface PageHeader {
|
|
|
15
15
|
cellPointers: number
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function readUint16(page: Uint8Array, offset: number) {
|
|
19
|
+
return page[offset]! * 256 + page[offset + 1]!
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readUint32(page: Uint8Array, offset: number) {
|
|
23
|
+
return (
|
|
24
|
+
page[offset]! * 16777216 +
|
|
25
|
+
page[offset + 1]! * 65536 +
|
|
26
|
+
page[offset + 2]! * 256 +
|
|
27
|
+
page[offset + 3]!
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
function readHeader(page: Uint8Array, start: number): PageHeader {
|
|
19
|
-
const view = new DataView(page.buffer, page.byteOffset, page.byteLength)
|
|
20
32
|
const type = page[start]
|
|
21
33
|
if (
|
|
22
34
|
type !== INTERIOR_INDEX &&
|
|
@@ -29,25 +41,27 @@ function readHeader(page: Uint8Array, start: number): PageHeader {
|
|
|
29
41
|
const interior = type === INTERIOR_INDEX || type === INTERIOR_TABLE
|
|
30
42
|
return {
|
|
31
43
|
type,
|
|
32
|
-
cellCount:
|
|
33
|
-
rightChild: interior ?
|
|
44
|
+
cellCount: readUint16(page, start + 3),
|
|
45
|
+
rightChild: interior ? readUint32(page, start + 8) : 0,
|
|
34
46
|
cellPointers: start + (interior ? 12 : 8),
|
|
35
47
|
}
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
function cellOffset(page: Uint8Array, header: PageHeader, index: number) {
|
|
39
|
-
|
|
40
|
-
return view.getUint16(header.cellPointers + 2 * index)
|
|
51
|
+
return readUint16(page, header.cellPointers + 2 * index)
|
|
41
52
|
}
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
interface IndexCell {
|
|
55
|
+
values: SqlValue[]
|
|
56
|
+
leftChild: number
|
|
46
57
|
}
|
|
47
58
|
|
|
59
|
+
const MAX_DECODED_INDEX_PAGES = 4096
|
|
60
|
+
|
|
48
61
|
export class BTree {
|
|
49
62
|
private readonly usable: number
|
|
50
63
|
private pager: Pager
|
|
64
|
+
private decodedIndexPages = new Map<number, (IndexCell | undefined)[]>()
|
|
51
65
|
|
|
52
66
|
constructor(pager: Pager, reservedBytes: number) {
|
|
53
67
|
this.pager = pager
|
|
@@ -179,12 +193,42 @@ export class BTree {
|
|
|
179
193
|
}
|
|
180
194
|
}
|
|
181
195
|
|
|
182
|
-
private
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
196
|
+
private decodedCells(pageNumber: number, header: PageHeader) {
|
|
197
|
+
let cells = this.decodedIndexPages.get(pageNumber)
|
|
198
|
+
if (cells) {
|
|
199
|
+
this.decodedIndexPages.delete(pageNumber)
|
|
200
|
+
} else {
|
|
201
|
+
cells = new Array<IndexCell | undefined>(header.cellCount)
|
|
202
|
+
if (this.decodedIndexPages.size >= MAX_DECODED_INDEX_PAGES) {
|
|
203
|
+
const oldest = this.decodedIndexPages.keys().next().value
|
|
204
|
+
if (oldest !== undefined) {
|
|
205
|
+
this.decodedIndexPages.delete(oldest)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this.decodedIndexPages.set(pageNumber, cells)
|
|
210
|
+
return cells
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private async indexCell(
|
|
214
|
+
pageNumber: number,
|
|
215
|
+
page: Uint8Array,
|
|
216
|
+
header: PageHeader,
|
|
217
|
+
index: number,
|
|
218
|
+
): Promise<IndexCell> {
|
|
219
|
+
const cells = this.decodedCells(pageNumber, header)
|
|
220
|
+
let cell = cells[index]
|
|
221
|
+
if (!cell) {
|
|
222
|
+
const offset = cellOffset(page, header, index)
|
|
223
|
+
const interior = header.type === INTERIOR_INDEX
|
|
224
|
+
const [size, afterSize] = readVarint(page, interior ? offset + 4 : offset)
|
|
225
|
+
cell = {
|
|
226
|
+
values: decodeRecord(await this.payload(page, afterSize, size, true)),
|
|
227
|
+
leftChild: interior ? readUint32(page, offset) : 0,
|
|
228
|
+
}
|
|
229
|
+
cells[index] = cell
|
|
230
|
+
}
|
|
231
|
+
return cell
|
|
188
232
|
}
|
|
189
233
|
|
|
190
234
|
async *indexScanFrom(
|
|
@@ -196,7 +240,7 @@ export class BTree {
|
|
|
196
240
|
let high = header.cellCount
|
|
197
241
|
while (first < high) {
|
|
198
242
|
const mid = (first + high) >> 1
|
|
199
|
-
const cell = await this.indexCell(page, header, mid)
|
|
243
|
+
const cell = await this.indexCell(root, page, header, mid)
|
|
200
244
|
if (compareKey(cell.values, low) < 0) {
|
|
201
245
|
first = mid + 1
|
|
202
246
|
} else {
|
|
@@ -204,7 +248,7 @@ export class BTree {
|
|
|
204
248
|
}
|
|
205
249
|
}
|
|
206
250
|
for (let i = first; i < header.cellCount; i++) {
|
|
207
|
-
const cell = await this.indexCell(page, header, i)
|
|
251
|
+
const cell = await this.indexCell(root, page, header, i)
|
|
208
252
|
if (header.type === INTERIOR_INDEX) {
|
|
209
253
|
yield* this.indexScanFrom(cell.leftChild, low)
|
|
210
254
|
}
|
|
@@ -228,7 +272,7 @@ export class BTree {
|
|
|
228
272
|
let child = 0
|
|
229
273
|
while (low < high) {
|
|
230
274
|
const mid = (low + high) >> 1
|
|
231
|
-
const cell = await this.indexCell(page, header, mid)
|
|
275
|
+
const cell = await this.indexCell(pageNumber, page, header, mid)
|
|
232
276
|
if (compareKey(cell.values, key) <= 0) {
|
|
233
277
|
best = cell.values
|
|
234
278
|
low = mid + 1
|
package/src/subgraph.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { formatPathName } from './db.ts'
|
|
2
1
|
import {
|
|
3
2
|
ENDMARKER,
|
|
4
3
|
edgeIsCanonical,
|
|
@@ -16,16 +15,12 @@ import {
|
|
|
16
15
|
} from './gbwt/node.ts'
|
|
17
16
|
import { gfaHeaderLines, sha256Hex, subgraphName } from './graphName.ts'
|
|
18
17
|
import { weightedLcs } from './lcs.ts'
|
|
18
|
+
import { formatPathName } from './pathName.ts'
|
|
19
19
|
|
|
20
|
-
import type {
|
|
21
|
-
GBZBase,
|
|
22
|
-
GbzPath,
|
|
23
|
-
GbzRecord,
|
|
24
|
-
HaplotypeSample,
|
|
25
|
-
PathName,
|
|
26
|
-
} from './db.ts'
|
|
20
|
+
import type { GBZBase, GbzPath, GbzRecord, HaplotypeSample } from './db.ts'
|
|
27
21
|
import type { NodeSide, Orientation } from './gbwt/node.ts'
|
|
28
22
|
import type { Pos } from './gbwt/record.ts'
|
|
23
|
+
import type { PathName } from './pathName.ts'
|
|
29
24
|
|
|
30
25
|
export type HaplotypeOutput = 'all' | 'distinct' | 'reference-only' | 'none'
|
|
31
26
|
|
|
@@ -86,12 +81,8 @@ export interface SubgraphJson {
|
|
|
86
81
|
paths: SubgraphPath[]
|
|
87
82
|
}
|
|
88
83
|
|
|
89
|
-
export interface
|
|
90
|
-
pathHandle: number | undefined
|
|
91
|
-
name: PathName | undefined
|
|
84
|
+
export interface AlignmentSpan {
|
|
92
85
|
strand: '+' | '-'
|
|
93
|
-
hapStart: number | undefined
|
|
94
|
-
hapEnd: number | undefined
|
|
95
86
|
refStart: number
|
|
96
87
|
refEnd: number
|
|
97
88
|
cigar: string
|
|
@@ -100,8 +91,27 @@ export interface HaplotypeAlignment {
|
|
|
100
91
|
start: Pos
|
|
101
92
|
}
|
|
102
93
|
|
|
103
|
-
export
|
|
104
|
-
|
|
94
|
+
export type HaplotypeAlignment = AlignmentSpan &
|
|
95
|
+
(
|
|
96
|
+
| {
|
|
97
|
+
resolved: true
|
|
98
|
+
name: PathName
|
|
99
|
+
label: string
|
|
100
|
+
pathHandle: number
|
|
101
|
+
hapStart: number
|
|
102
|
+
hapEnd: number
|
|
103
|
+
}
|
|
104
|
+
| { resolved: false }
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
export interface SubgraphOutputOptions {
|
|
108
|
+
cigar?: boolean | undefined
|
|
109
|
+
names?: 'anonymous' | 'resolved' | undefined
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface SubgraphOptions {
|
|
113
|
+
limit?: number | undefined
|
|
114
|
+
signal?: AbortSignal | undefined
|
|
105
115
|
}
|
|
106
116
|
|
|
107
117
|
function sideBefore(
|
|
@@ -183,7 +193,6 @@ export class Subgraph {
|
|
|
183
193
|
private refInterval: [number, number] | undefined
|
|
184
194
|
private refIndexCache: Map<number, number[]> | undefined
|
|
185
195
|
private refPrefixCache: number[] | undefined
|
|
186
|
-
limit: number | undefined
|
|
187
196
|
readonly stats = {
|
|
188
197
|
orderedAlignments: 0,
|
|
189
198
|
lcsAlignments: 0,
|
|
@@ -192,9 +201,13 @@ export class Subgraph {
|
|
|
192
201
|
}
|
|
193
202
|
|
|
194
203
|
private db: GBZBase
|
|
204
|
+
private readonly limit: number | undefined
|
|
205
|
+
private readonly signal: AbortSignal | undefined
|
|
195
206
|
|
|
196
|
-
constructor(db: GBZBase) {
|
|
207
|
+
constructor(db: GBZBase, opts: SubgraphOptions = {}) {
|
|
197
208
|
this.db = db
|
|
209
|
+
this.limit = opts.limit
|
|
210
|
+
this.signal = opts.signal
|
|
198
211
|
}
|
|
199
212
|
|
|
200
213
|
get nodeCount() {
|
|
@@ -236,6 +249,7 @@ export class Subgraph {
|
|
|
236
249
|
}
|
|
237
250
|
|
|
238
251
|
private async addNode(id: number) {
|
|
252
|
+
this.signal?.throwIfAborted()
|
|
239
253
|
if (this.limit !== undefined && this.nodeCount >= this.limit) {
|
|
240
254
|
throw new Error(`Subgraph size limit of ${this.limit} nodes exceeded`)
|
|
241
255
|
}
|
|
@@ -800,6 +814,7 @@ export class Subgraph {
|
|
|
800
814
|
let current: number | undefined = start
|
|
801
815
|
let pos: Pos | undefined
|
|
802
816
|
while (anchor === undefined) {
|
|
817
|
+
this.signal?.throwIfAborted()
|
|
803
818
|
if (current !== undefined) {
|
|
804
819
|
if (visited.has(current)) {
|
|
805
820
|
break
|
|
@@ -1030,13 +1045,19 @@ export class Subgraph {
|
|
|
1030
1045
|
appendEdit(edits, 'M', suffix)
|
|
1031
1046
|
}
|
|
1032
1047
|
|
|
1033
|
-
private
|
|
1034
|
-
const
|
|
1035
|
-
|
|
1036
|
-
|
|
1048
|
+
private sharedWeight(path: number[], ref: number[]) {
|
|
1049
|
+
const index = this.refIndex(ref)
|
|
1050
|
+
let weight = 0
|
|
1051
|
+
for (const handle of path) {
|
|
1052
|
+
if (index.has(handle)) {
|
|
1053
|
+
weight += this.record(handle).sequenceLen
|
|
1054
|
+
}
|
|
1037
1055
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1056
|
+
return weight
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
private editsAgainst(path: number[], ref: number[]) {
|
|
1060
|
+
const ordered = this.orderedMatches(path, ref)
|
|
1040
1061
|
if (ordered) {
|
|
1041
1062
|
this.stats.orderedAlignments += 1
|
|
1042
1063
|
} else {
|
|
@@ -1044,27 +1065,63 @@ export class Subgraph {
|
|
|
1044
1065
|
}
|
|
1045
1066
|
const lcs =
|
|
1046
1067
|
ordered ??
|
|
1047
|
-
weightedLcs(
|
|
1068
|
+
weightedLcs(path, ref, handle => this.record(handle).sequenceLen)[0]
|
|
1048
1069
|
const edits: Edit[] = []
|
|
1070
|
+
let matched = 0
|
|
1049
1071
|
let pathOffset = 0
|
|
1050
1072
|
let refOffset = 0
|
|
1051
1073
|
for (const [nextPath, nextRef] of lcs) {
|
|
1052
1074
|
this.align(
|
|
1053
|
-
|
|
1075
|
+
path.slice(pathOffset, nextPath),
|
|
1054
1076
|
ref.slice(refOffset, nextRef),
|
|
1055
1077
|
edits,
|
|
1056
1078
|
)
|
|
1057
|
-
|
|
1079
|
+
const nodeLen = this.record(path[nextPath]!).sequenceLen
|
|
1080
|
+
appendEdit(edits, 'M', nodeLen)
|
|
1081
|
+
matched += nodeLen
|
|
1058
1082
|
pathOffset = nextPath + 1
|
|
1059
1083
|
refOffset = nextRef + 1
|
|
1060
1084
|
}
|
|
1061
|
-
this.align(
|
|
1062
|
-
return edits
|
|
1085
|
+
this.align(path.slice(pathOffset), ref.slice(refOffset), edits)
|
|
1086
|
+
return { edits, matched }
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
private alignment(pathIndex: number) {
|
|
1090
|
+
const info = this.paths[pathIndex]
|
|
1091
|
+
if (this.refId === undefined || pathIndex === this.refId || !info) {
|
|
1092
|
+
return undefined
|
|
1093
|
+
}
|
|
1094
|
+
const ref = this.paths[this.refId]!.path
|
|
1095
|
+
const flippedPath = pathIsCanonical(ref)
|
|
1096
|
+
? []
|
|
1097
|
+
: info.path.map(handle => flipNode(handle)).reverse()
|
|
1098
|
+
const forwardBound = this.sharedWeight(info.path, ref)
|
|
1099
|
+
const flippedBound = this.sharedWeight(flippedPath, ref)
|
|
1100
|
+
let result: { edits: Edit[]; flipped: boolean }
|
|
1101
|
+
if (flippedBound === 0) {
|
|
1102
|
+
result = {
|
|
1103
|
+
edits: this.editsAgainst(info.path, ref).edits,
|
|
1104
|
+
flipped: false,
|
|
1105
|
+
}
|
|
1106
|
+
} else if (forwardBound === 0) {
|
|
1107
|
+
result = {
|
|
1108
|
+
edits: this.editsAgainst(flippedPath, ref).edits,
|
|
1109
|
+
flipped: true,
|
|
1110
|
+
}
|
|
1111
|
+
} else {
|
|
1112
|
+
const forward = this.editsAgainst(info.path, ref)
|
|
1113
|
+
const flipped = this.editsAgainst(flippedPath, ref)
|
|
1114
|
+
result =
|
|
1115
|
+
flipped.matched > forward.matched
|
|
1116
|
+
? { edits: flipped.edits, flipped: true }
|
|
1117
|
+
: { edits: forward.edits, flipped: false }
|
|
1118
|
+
}
|
|
1119
|
+
return result
|
|
1063
1120
|
}
|
|
1064
1121
|
|
|
1065
1122
|
alignToRef(pathIndex: number) {
|
|
1066
|
-
return this.
|
|
1067
|
-
?.map(([op, len]) => `${len}${op}`)
|
|
1123
|
+
return this.alignment(pathIndex)
|
|
1124
|
+
?.edits.map(([op, len]) => `${len}${op}`)
|
|
1068
1125
|
.join('')
|
|
1069
1126
|
}
|
|
1070
1127
|
|
|
@@ -1080,7 +1137,7 @@ export class Subgraph {
|
|
|
1080
1137
|
if (index === this.refId) {
|
|
1081
1138
|
return
|
|
1082
1139
|
}
|
|
1083
|
-
const edits = this.
|
|
1140
|
+
const { edits, flipped } = this.alignment(index)!
|
|
1084
1141
|
let first = 0
|
|
1085
1142
|
let leading = 0
|
|
1086
1143
|
while (first < edits.length && edits[first]![0] === 'D') {
|
|
@@ -1094,24 +1151,11 @@ export class Subgraph {
|
|
|
1094
1151
|
last -= 1
|
|
1095
1152
|
}
|
|
1096
1153
|
const identity = info.identity
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
result.push({
|
|
1103
|
-
pathHandle: identity?.pathHandle,
|
|
1104
|
-
name: identity?.name,
|
|
1105
|
-
strand: identity
|
|
1106
|
-
? identity.orientation === 'forward'
|
|
1107
|
-
? '+'
|
|
1108
|
-
: '-'
|
|
1109
|
-
: strand,
|
|
1110
|
-
hapStart: identity
|
|
1111
|
-
? identity.name.fragment + identity.hapStart
|
|
1112
|
-
: undefined,
|
|
1113
|
-
hapEnd: identity ? identity.name.fragment + identity.hapEnd : undefined,
|
|
1114
|
-
start: info.positions[0]!,
|
|
1154
|
+
const alongReference = identity
|
|
1155
|
+
? (identity.orientation === 'forward') !== flipped
|
|
1156
|
+
: !flipped
|
|
1157
|
+
const span: AlignmentSpan = {
|
|
1158
|
+
strand: alongReference ? '+' : '-',
|
|
1115
1159
|
refStart: reference.start + leading,
|
|
1116
1160
|
refEnd: reference.start + refTotal - trailing,
|
|
1117
1161
|
cigar: edits
|
|
@@ -1120,7 +1164,26 @@ export class Subgraph {
|
|
|
1120
1164
|
.join(''),
|
|
1121
1165
|
weight: info.weight,
|
|
1122
1166
|
path: info.path,
|
|
1123
|
-
|
|
1167
|
+
start: info.positions[0]!,
|
|
1168
|
+
}
|
|
1169
|
+
if (identity) {
|
|
1170
|
+
const hapStart = identity.name.fragment + identity.hapStart
|
|
1171
|
+
const hapEnd = identity.name.fragment + identity.hapEnd
|
|
1172
|
+
result.push({
|
|
1173
|
+
...span,
|
|
1174
|
+
resolved: true,
|
|
1175
|
+
name: identity.name,
|
|
1176
|
+
label: formatPathName(
|
|
1177
|
+
{ ...identity.name, fragment: hapStart },
|
|
1178
|
+
hapEnd,
|
|
1179
|
+
),
|
|
1180
|
+
pathHandle: identity.pathHandle,
|
|
1181
|
+
hapStart,
|
|
1182
|
+
hapEnd,
|
|
1183
|
+
})
|
|
1184
|
+
} else {
|
|
1185
|
+
result.push({ ...span, resolved: false })
|
|
1186
|
+
}
|
|
1124
1187
|
})
|
|
1125
1188
|
return result
|
|
1126
1189
|
}
|
|
@@ -1165,7 +1228,8 @@ export class Subgraph {
|
|
|
1165
1228
|
return sha256Hex(chunks)
|
|
1166
1229
|
}
|
|
1167
1230
|
|
|
1168
|
-
async toGFA(
|
|
1231
|
+
async toGFA(opts: SubgraphOutputOptions = {}) {
|
|
1232
|
+
const cigar = opts.cigar ?? false
|
|
1169
1233
|
const lines = [
|
|
1170
1234
|
this.refPath ? `H\tVN:Z:1.1\tRS:Z:${this.refPath.sample}` : 'H\tVN:Z:1.1',
|
|
1171
1235
|
...gfaHeaderLines(
|
|
@@ -1244,7 +1308,8 @@ export class Subgraph {
|
|
|
1244
1308
|
return `${lines.join('\n')}\n`
|
|
1245
1309
|
}
|
|
1246
1310
|
|
|
1247
|
-
|
|
1311
|
+
toSubgraphJson(opts: SubgraphOutputOptions = {}): SubgraphJson {
|
|
1312
|
+
const cigar = opts.cigar ?? false
|
|
1248
1313
|
const handles = this.sortedHandles()
|
|
1249
1314
|
const nodes = handles
|
|
1250
1315
|
.filter(handle => !isReverse(handle))
|
|
@@ -21,7 +21,8 @@ HaplotypeLengths. The path start and end are always sampled.
|
|
|
21
21
|
By default the tables are written into the database itself, replacing any
|
|
22
22
|
existing ones. With --output FILE they are written into FILE as a standalone
|
|
23
23
|
companion database that the reader opens beside the graph database; the
|
|
24
|
-
companion records the graph's path
|
|
24
|
+
companion records the graph's path and node counts so a mismatch is caught at
|
|
25
|
+
open.
|
|
25
26
|
|
|
26
27
|
With --from-db the walk reads node records from the database itself, so the
|
|
27
28
|
GBZ is not needed. Walking a GBZ uses --threads (default: all cores).
|
|
@@ -208,15 +209,22 @@ fn orientations(args: &Args) -> Vec<Orientation> {
|
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
|
|
211
|
-
fn walk_paths(source: &dyn PathSource, handles: std::ops::Range<usize>, args: &Args) -> (Vec<Sample>, Vec<(usize, usize)>) {
|
|
212
|
+
fn walk_paths(source: &dyn PathSource, handles: std::ops::Range<usize>, args: &Args, label: &str) -> (Vec<Sample>, Vec<(usize, usize)>) {
|
|
212
213
|
let mut samples = Vec::new();
|
|
213
214
|
let mut lengths = Vec::new();
|
|
214
|
-
|
|
215
|
+
let started = Instant::now();
|
|
216
|
+
let total = handles.len();
|
|
217
|
+
let mut walked_bp: usize = 0;
|
|
218
|
+
for (done, path_handle) in handles.enumerate() {
|
|
215
219
|
let mut length = 0;
|
|
216
220
|
for &orientation in orientations(args).iter() {
|
|
217
221
|
length = walk(source, path_handle, orientation, args.interval, &mut samples);
|
|
218
222
|
}
|
|
223
|
+
walked_bp += length;
|
|
219
224
|
lengths.push((path_handle, length));
|
|
225
|
+
if (done + 1) % 500 == 0 || done + 1 == total {
|
|
226
|
+
eprintln!("{}: {} / {} paths, {:.2} Gbp, {} samples, {:.0} s", label, done + 1, total, walked_bp as f64 / 1e9, samples.len(), started.elapsed().as_secs_f64());
|
|
227
|
+
}
|
|
220
228
|
}
|
|
221
229
|
(samples, lengths)
|
|
222
230
|
}
|
|
@@ -230,8 +238,9 @@ fn walk_gbz(graph: &GBZ, paths: usize, args: &Args) -> (Vec<Sample>, Vec<(usize,
|
|
|
230
238
|
let range = (t * chunk).min(paths)..((t + 1) * chunk).min(paths);
|
|
231
239
|
scope.spawn(move || {
|
|
232
240
|
let source = GbzSource { graph };
|
|
233
|
-
let
|
|
234
|
-
|
|
241
|
+
let label = format!("thread {} (paths {}..{})", t, range.start, range.end);
|
|
242
|
+
let result = walk_paths(&source, range.clone(), args, &label);
|
|
243
|
+
eprintln!("{} done in {:.0} s", label, started.elapsed().as_secs_f64());
|
|
235
244
|
result
|
|
236
245
|
})
|
|
237
246
|
})
|
|
@@ -261,7 +270,7 @@ CREATE TABLE HaplotypeLengths (
|
|
|
261
270
|
length INTEGER NOT NULL
|
|
262
271
|
) STRICT;";
|
|
263
272
|
|
|
264
|
-
fn write(target: &str, standalone: bool, mut samples: Vec<Sample>, lengths: &[(usize, usize)], paths: usize, args: &Args) {
|
|
273
|
+
fn write(target: &str, standalone: bool, mut samples: Vec<Sample>, lengths: &[(usize, usize)], paths: usize, nodes: usize, args: &Args) {
|
|
265
274
|
let started = Instant::now();
|
|
266
275
|
samples.sort_unstable_by_key(|s| (s.node_handle, s.node_offset));
|
|
267
276
|
eprintln!("Sorted {} samples in {:.0} s", samples.len(), started.elapsed().as_secs_f64());
|
|
@@ -294,22 +303,23 @@ fn write(target: &str, standalone: bool, mut samples: Vec<Sample>, lengths: &[(u
|
|
|
294
303
|
write_tag.execute(params!["haplotype_index_interval", args.interval.to_string()]).unwrap();
|
|
295
304
|
write_tag.execute(params!["haplotype_index_orientations", if args.forward_only { "forward" } else { "both" }]).unwrap();
|
|
296
305
|
write_tag.execute(params!["haplotype_index_paths", paths.to_string()]).unwrap();
|
|
306
|
+
write_tag.execute(params!["haplotype_index_nodes", nodes.to_string()]).unwrap();
|
|
297
307
|
}
|
|
298
308
|
transaction.commit().unwrap();
|
|
299
309
|
eprintln!("Wrote {} samples for {} paths to {} in {:.0} s", samples.len(), paths, target, started.elapsed().as_secs_f64());
|
|
300
310
|
}
|
|
301
311
|
|
|
302
|
-
fn
|
|
312
|
+
fn count_from_db(db: &str, key: &str) -> usize {
|
|
303
313
|
let connection = Connection::open(db).unwrap();
|
|
304
314
|
let value: String = connection
|
|
305
|
-
.query_row("SELECT value FROM Tags WHERE key =
|
|
315
|
+
.query_row("SELECT value FROM Tags WHERE key = ?1", params![key], |row| row.get(0))
|
|
306
316
|
.unwrap_or_else(|_| "0".to_string());
|
|
307
317
|
value.parse().unwrap_or(0)
|
|
308
318
|
}
|
|
309
319
|
|
|
310
320
|
fn main() {
|
|
311
321
|
let args = parse_args();
|
|
312
|
-
let (samples, lengths, paths) = match &args.gbz {
|
|
322
|
+
let (samples, lengths, paths, nodes) = match &args.gbz {
|
|
313
323
|
Some(gbz) => {
|
|
314
324
|
let started = Instant::now();
|
|
315
325
|
let graph: GBZ = serialize::load_from(gbz).unwrap_or_else(|e| {
|
|
@@ -322,32 +332,39 @@ fn main() {
|
|
|
322
332
|
process::exit(1);
|
|
323
333
|
}
|
|
324
334
|
eprintln!("Loaded {} with {} paths in {:.0} s", gbz, paths, started.elapsed().as_secs_f64());
|
|
335
|
+
let nodes = graph.nodes();
|
|
325
336
|
if let Some(db) = &args.db {
|
|
326
|
-
let db_paths =
|
|
337
|
+
let db_paths = count_from_db(db, "paths");
|
|
327
338
|
if db_paths != paths {
|
|
328
339
|
eprintln!("{} has {} paths but {} has {}", gbz, paths, db, db_paths);
|
|
329
340
|
process::exit(1);
|
|
330
341
|
}
|
|
342
|
+
let db_nodes = count_from_db(db, "nodes");
|
|
343
|
+
if db_nodes != nodes {
|
|
344
|
+
eprintln!("{} has {} nodes but {} has {}", gbz, nodes, db, db_nodes);
|
|
345
|
+
process::exit(1);
|
|
346
|
+
}
|
|
331
347
|
}
|
|
332
348
|
let (samples, lengths) = walk_gbz(&graph, paths, &args);
|
|
333
|
-
(samples, lengths, paths)
|
|
349
|
+
(samples, lengths, paths, nodes)
|
|
334
350
|
}
|
|
335
351
|
None => {
|
|
336
352
|
let db = args.db.as_ref().unwrap();
|
|
337
|
-
let paths =
|
|
353
|
+
let paths = count_from_db(db, "paths");
|
|
354
|
+
let nodes = count_from_db(db, "nodes");
|
|
338
355
|
let database = GBZBase::open(db).unwrap_or_else(|e| {
|
|
339
356
|
eprintln!("Cannot open {} as a GBZ-base: {}", db, e);
|
|
340
357
|
process::exit(1);
|
|
341
358
|
});
|
|
342
359
|
let interface = GraphInterface::new(&database).unwrap();
|
|
343
360
|
let source = DbSource { interface: std::cell::RefCell::new(interface) };
|
|
344
|
-
let (samples, lengths) = walk_paths(&source, 0..paths, &args);
|
|
345
|
-
(samples, lengths, paths)
|
|
361
|
+
let (samples, lengths) = walk_paths(&source, 0..paths, &args, "database walk");
|
|
362
|
+
(samples, lengths, paths, nodes)
|
|
346
363
|
}
|
|
347
364
|
};
|
|
348
365
|
match (&args.output, &args.db) {
|
|
349
|
-
(Some(output), _) => write(output, true, samples, &lengths, paths, &args),
|
|
350
|
-
(None, Some(db)) => write(db, false, samples, &lengths, paths, &args),
|
|
366
|
+
(Some(output), _) => write(output, true, samples, &lengths, paths, nodes, &args),
|
|
367
|
+
(None, Some(db)) => write(db, false, samples, &lengths, paths, nodes, &args),
|
|
351
368
|
(None, None) => unreachable!(),
|
|
352
369
|
}
|
|
353
370
|
}
|