@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
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const DECODE = ['', 'A', 'C', 'G', 'T', 'N']
|
|
2
|
+
|
|
3
|
+
export function encodedSequenceLength(encoded: Uint8Array) {
|
|
4
|
+
const last = encoded[encoded.length - 1]
|
|
5
|
+
if (last === undefined) {
|
|
6
|
+
return 0
|
|
7
|
+
}
|
|
8
|
+
let value = last
|
|
9
|
+
let inLast = 0
|
|
10
|
+
for (let i = 0; i < 3; i++) {
|
|
11
|
+
if (value % 6 === 0) {
|
|
12
|
+
break
|
|
13
|
+
}
|
|
14
|
+
value = Math.floor(value / 6)
|
|
15
|
+
inLast += 1
|
|
16
|
+
}
|
|
17
|
+
return 3 * (encoded.length - 1) + inLast
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function decodeSequence(encoded: Uint8Array) {
|
|
21
|
+
let result = ''
|
|
22
|
+
for (const byte of encoded) {
|
|
23
|
+
let value = byte
|
|
24
|
+
for (let i = 0; i < 3; i++) {
|
|
25
|
+
const base = DECODE[value % 6]!
|
|
26
|
+
if (base === '') {
|
|
27
|
+
return result
|
|
28
|
+
}
|
|
29
|
+
value = Math.floor(value / 6)
|
|
30
|
+
result += base
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return result
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const COMPLEMENT: Record<string, string> = {
|
|
37
|
+
A: 'T',
|
|
38
|
+
C: 'G',
|
|
39
|
+
G: 'C',
|
|
40
|
+
T: 'A',
|
|
41
|
+
N: 'N',
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function reverseComplement(sequence: string) {
|
|
45
|
+
let result = ''
|
|
46
|
+
for (let i = sequence.length - 1; i >= 0; i--) {
|
|
47
|
+
result += COMPLEMENT[sequence[i]!] ?? 'N'
|
|
48
|
+
}
|
|
49
|
+
return result
|
|
50
|
+
}
|
package/src/graphName.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export interface GraphName {
|
|
2
|
+
name: string | undefined
|
|
3
|
+
subgraph: Map<string, Set<string>>
|
|
4
|
+
translation: Map<string, Set<string>>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function parseRelationships(field: string | undefined, what: string) {
|
|
8
|
+
const relationships = new Map<string, Set<string>>()
|
|
9
|
+
if (field !== undefined) {
|
|
10
|
+
for (const rel of field.split(';')) {
|
|
11
|
+
const parts = rel.split(',')
|
|
12
|
+
const [from, to] = parts
|
|
13
|
+
if (parts.length !== 2 || !from || !to) {
|
|
14
|
+
throw new Error(`Invalid ${what} relationship: ${rel}`)
|
|
15
|
+
}
|
|
16
|
+
let targets = relationships.get(from)
|
|
17
|
+
if (!targets) {
|
|
18
|
+
targets = new Set()
|
|
19
|
+
relationships.set(from, targets)
|
|
20
|
+
}
|
|
21
|
+
targets.add(to)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return relationships
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function graphNameFromTags(tags: Map<string, string>): GraphName {
|
|
28
|
+
return {
|
|
29
|
+
name: tags.get('pggname'),
|
|
30
|
+
subgraph: parseRelationships(tags.get('subgraph'), 'subgraph'),
|
|
31
|
+
translation: parseRelationships(tags.get('translation'), 'translation'),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function merge(into: Map<string, Set<string>>, from: Map<string, Set<string>>) {
|
|
36
|
+
for (const [key, values] of from) {
|
|
37
|
+
let targets = into.get(key)
|
|
38
|
+
if (!targets) {
|
|
39
|
+
targets = new Set()
|
|
40
|
+
into.set(key, targets)
|
|
41
|
+
}
|
|
42
|
+
for (const value of values) {
|
|
43
|
+
targets.add(value)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function subgraphName(name: string, parent: GraphName): GraphName {
|
|
49
|
+
const result: GraphName = {
|
|
50
|
+
name,
|
|
51
|
+
subgraph: new Map(),
|
|
52
|
+
translation: new Map(),
|
|
53
|
+
}
|
|
54
|
+
if (parent.name !== undefined) {
|
|
55
|
+
result.subgraph.set(name, new Set([parent.name]))
|
|
56
|
+
merge(result.subgraph, parent.subgraph)
|
|
57
|
+
merge(result.translation, parent.translation)
|
|
58
|
+
}
|
|
59
|
+
return result
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function sortedEntries(relationships: Map<string, Set<string>>) {
|
|
63
|
+
return [...relationships.entries()]
|
|
64
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
65
|
+
.flatMap(([from, tos]) => [...tos].sort().map(to => [from, to] as const))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function gfaHeaderLines(graphName: GraphName) {
|
|
69
|
+
const lines: string[] = []
|
|
70
|
+
if (graphName.name !== undefined) {
|
|
71
|
+
lines.push(`H\tNM:Z:${graphName.name}`)
|
|
72
|
+
}
|
|
73
|
+
for (const [subgraph, supergraph] of sortedEntries(graphName.subgraph)) {
|
|
74
|
+
lines.push(`H\tSG:Z:${subgraph},${supergraph}`)
|
|
75
|
+
}
|
|
76
|
+
for (const [from, to] of sortedEntries(graphName.translation)) {
|
|
77
|
+
lines.push(`H\tTL:Z:${from},${to}`)
|
|
78
|
+
}
|
|
79
|
+
return lines
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function sha256Hex(chunks: Uint8Array[]) {
|
|
83
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
|
|
84
|
+
const bytes = new Uint8Array(total)
|
|
85
|
+
let offset = 0
|
|
86
|
+
for (const chunk of chunks) {
|
|
87
|
+
bytes.set(chunk, offset)
|
|
88
|
+
offset += chunk.length
|
|
89
|
+
}
|
|
90
|
+
const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes)
|
|
91
|
+
return [...new Uint8Array(digest)]
|
|
92
|
+
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
93
|
+
.join('')
|
|
94
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export {
|
|
2
|
+
GBZBase,
|
|
3
|
+
GENERIC_SAMPLE,
|
|
4
|
+
GbzRecord,
|
|
5
|
+
SCHEMA_VERSION,
|
|
6
|
+
SchemaVersionError,
|
|
7
|
+
formatPathName,
|
|
8
|
+
} from './db.ts'
|
|
9
|
+
export type { GbzPath, HaplotypeSample, OpenOptions, PathName } from './db.ts'
|
|
10
|
+
export type { ByteSource } from './filehandle.ts'
|
|
11
|
+
export type { Pos } from './gbwt/record.ts'
|
|
12
|
+
export { Subgraph } from './subgraph.ts'
|
|
13
|
+
export type {
|
|
14
|
+
HaplotypeAlignment,
|
|
15
|
+
HaplotypeOutput,
|
|
16
|
+
PathIdentity,
|
|
17
|
+
PathPosition,
|
|
18
|
+
ReferencePath,
|
|
19
|
+
SnarlOutput,
|
|
20
|
+
SubgraphJson,
|
|
21
|
+
SubgraphPath,
|
|
22
|
+
ToJsonOptions,
|
|
23
|
+
} from './subgraph.ts'
|
|
24
|
+
export {
|
|
25
|
+
subgraphAroundNodes,
|
|
26
|
+
subgraphAtOffset,
|
|
27
|
+
subgraphBetween,
|
|
28
|
+
subgraphInInterval,
|
|
29
|
+
} from './query.ts'
|
|
30
|
+
export type { PathQuery, QueryOptions } from './query.ts'
|
|
31
|
+
export { SqliteDatabase } from './sqlite/database.ts'
|
|
32
|
+
export type { GraphName } from './graphName.ts'
|
|
33
|
+
export { weightedLcs } from './lcs.ts'
|
|
34
|
+
export * as nodes from './gbwt/node.ts'
|
package/src/lcs.ts
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
interface Point {
|
|
2
|
+
weight: number
|
|
3
|
+
a: number
|
|
4
|
+
b: number
|
|
5
|
+
matches: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function prefixSums(sequence: number[], weight: (x: number) => number) {
|
|
9
|
+
const sums = [0]
|
|
10
|
+
for (let i = 0; i < sequence.length; i++) {
|
|
11
|
+
sums.push(sums[i]! + weight(sequence[i]!))
|
|
12
|
+
}
|
|
13
|
+
return sums
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class MinHeap {
|
|
17
|
+
private items: number[] = []
|
|
18
|
+
|
|
19
|
+
push(value: number) {
|
|
20
|
+
const items = this.items
|
|
21
|
+
items.push(value)
|
|
22
|
+
let i = items.length - 1
|
|
23
|
+
while (i > 0) {
|
|
24
|
+
const parent = (i - 1) >> 1
|
|
25
|
+
if (items[parent]! <= value) {
|
|
26
|
+
break
|
|
27
|
+
}
|
|
28
|
+
items[i] = items[parent]!
|
|
29
|
+
i = parent
|
|
30
|
+
}
|
|
31
|
+
items[i] = value
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
peek() {
|
|
35
|
+
return this.items[0]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pop() {
|
|
39
|
+
const items = this.items
|
|
40
|
+
const top = items[0]
|
|
41
|
+
const last = items.pop()
|
|
42
|
+
if (items.length > 0 && last !== undefined) {
|
|
43
|
+
let i = 0
|
|
44
|
+
for (;;) {
|
|
45
|
+
const left = 2 * i + 1
|
|
46
|
+
const right = left + 1
|
|
47
|
+
let smallest = i
|
|
48
|
+
let value = last
|
|
49
|
+
if (left < items.length && items[left]! < value) {
|
|
50
|
+
smallest = left
|
|
51
|
+
value = items[left]!
|
|
52
|
+
}
|
|
53
|
+
if (right < items.length && items[right]! < value) {
|
|
54
|
+
smallest = right
|
|
55
|
+
}
|
|
56
|
+
if (smallest === i) {
|
|
57
|
+
break
|
|
58
|
+
}
|
|
59
|
+
items[i] = items[smallest]!
|
|
60
|
+
i = smallest
|
|
61
|
+
}
|
|
62
|
+
items[i] = last
|
|
63
|
+
}
|
|
64
|
+
return top
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class Matrix {
|
|
69
|
+
readonly aSums: number[]
|
|
70
|
+
readonly bSums: number[]
|
|
71
|
+
readonly a: number[]
|
|
72
|
+
readonly b: number[]
|
|
73
|
+
readonly points = new Map<number, Map<number, Point>>()
|
|
74
|
+
private pendingEdits = new MinHeap()
|
|
75
|
+
|
|
76
|
+
constructor(a: number[], b: number[], weight: (x: number) => number) {
|
|
77
|
+
this.a = a
|
|
78
|
+
this.b = b
|
|
79
|
+
this.aSums = prefixSums(a, weight)
|
|
80
|
+
this.bSums = prefixSums(b, weight)
|
|
81
|
+
this.set(0, 0, { weight: 0, a: 0, b: 0, matches: 0 })
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private set(edits: number, diagonal: number, point: Point) {
|
|
85
|
+
let row = this.points.get(edits)
|
|
86
|
+
if (!row) {
|
|
87
|
+
row = new Map()
|
|
88
|
+
this.points.set(edits, row)
|
|
89
|
+
this.pendingEdits.push(edits)
|
|
90
|
+
}
|
|
91
|
+
row.set(diagonal, point)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get(edits: number, diagonal: number) {
|
|
95
|
+
return this.points.get(edits)?.get(diagonal)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private tryInsert(edits: number, diagonal: number, point: Point) {
|
|
99
|
+
const existing = this.get(edits, diagonal)
|
|
100
|
+
if (!existing || point.weight > existing.weight) {
|
|
101
|
+
this.set(edits, diagonal, point)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
aWeight(offset: number) {
|
|
106
|
+
return this.aSums[offset + 1]! - this.aSums[offset]!
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
bWeight(offset: number) {
|
|
110
|
+
return this.bSums[offset + 1]! - this.bSums[offset]!
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
extend(edits: number): Point | undefined {
|
|
114
|
+
const row = this.points.get(edits)
|
|
115
|
+
if (!row) {
|
|
116
|
+
return undefined
|
|
117
|
+
}
|
|
118
|
+
const diagonals = [...row.keys()].sort((x, y) => x - y)
|
|
119
|
+
for (const diagonal of diagonals) {
|
|
120
|
+
const found = row.get(diagonal)
|
|
121
|
+
if (!found) {
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
const point = { ...found }
|
|
125
|
+
while (
|
|
126
|
+
point.a < this.a.length &&
|
|
127
|
+
point.b < this.b.length &&
|
|
128
|
+
this.a[point.a] === this.b[point.b]
|
|
129
|
+
) {
|
|
130
|
+
point.weight += 2 * this.aWeight(point.a)
|
|
131
|
+
point.a += 1
|
|
132
|
+
point.b += 1
|
|
133
|
+
point.matches += 1
|
|
134
|
+
}
|
|
135
|
+
if (point.matches > 0) {
|
|
136
|
+
row.set(diagonal, point)
|
|
137
|
+
}
|
|
138
|
+
if (point.a === this.a.length && point.b === this.b.length) {
|
|
139
|
+
return point
|
|
140
|
+
}
|
|
141
|
+
if (point.a < this.a.length) {
|
|
142
|
+
const w = this.aWeight(point.a)
|
|
143
|
+
this.tryInsert(edits + w, diagonal + w, {
|
|
144
|
+
weight: point.weight,
|
|
145
|
+
a: point.a + 1,
|
|
146
|
+
b: point.b,
|
|
147
|
+
matches: 0,
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
if (point.b < this.b.length) {
|
|
151
|
+
const w = this.bWeight(point.b)
|
|
152
|
+
this.tryInsert(edits + w, diagonal - w, {
|
|
153
|
+
weight: point.weight,
|
|
154
|
+
a: point.a,
|
|
155
|
+
b: point.b + 1,
|
|
156
|
+
matches: 0,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return undefined
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
nextEdits(edits: number) {
|
|
164
|
+
while (
|
|
165
|
+
this.pendingEdits.peek() !== undefined &&
|
|
166
|
+
this.pendingEdits.peek()! <= edits
|
|
167
|
+
) {
|
|
168
|
+
this.pendingEdits.pop()
|
|
169
|
+
}
|
|
170
|
+
return this.pendingEdits.peek()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
predecessor(
|
|
174
|
+
a: number,
|
|
175
|
+
b: number,
|
|
176
|
+
edits: number,
|
|
177
|
+
): [Point, number] | undefined {
|
|
178
|
+
const diagonal = this.aSums[a]! - this.bSums[b]!
|
|
179
|
+
const prev =
|
|
180
|
+
a > 0 && this.aWeight(a - 1) <= edits
|
|
181
|
+
? this.get(edits - this.aWeight(a - 1), diagonal - this.aWeight(a - 1))
|
|
182
|
+
: undefined
|
|
183
|
+
const next =
|
|
184
|
+
b > 0 && this.bWeight(b - 1) <= edits
|
|
185
|
+
? this.get(edits - this.bWeight(b - 1), diagonal + this.bWeight(b - 1))
|
|
186
|
+
: undefined
|
|
187
|
+
if (prev && next) {
|
|
188
|
+
return prev.weight > next.weight
|
|
189
|
+
? [prev, edits - this.aWeight(a - 1)]
|
|
190
|
+
: [next, edits - this.bWeight(b - 1)]
|
|
191
|
+
}
|
|
192
|
+
if (prev) {
|
|
193
|
+
return [prev, edits - this.aWeight(a - 1)]
|
|
194
|
+
}
|
|
195
|
+
if (next) {
|
|
196
|
+
return [next, edits - this.bWeight(b - 1)]
|
|
197
|
+
}
|
|
198
|
+
return undefined
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function weightedLcs(
|
|
203
|
+
a: number[],
|
|
204
|
+
b: number[],
|
|
205
|
+
weight: (x: number) => number,
|
|
206
|
+
): [pairs: [number, number][], weight: number] {
|
|
207
|
+
let prefix = 0
|
|
208
|
+
while (prefix < a.length && prefix < b.length && a[prefix] === b[prefix]) {
|
|
209
|
+
prefix += 1
|
|
210
|
+
}
|
|
211
|
+
let suffix = 0
|
|
212
|
+
while (
|
|
213
|
+
suffix < a.length - prefix &&
|
|
214
|
+
suffix < b.length - prefix &&
|
|
215
|
+
a[a.length - 1 - suffix] === b[b.length - 1 - suffix]
|
|
216
|
+
) {
|
|
217
|
+
suffix += 1
|
|
218
|
+
}
|
|
219
|
+
const pairs: [number, number][] = []
|
|
220
|
+
let total = 0
|
|
221
|
+
for (let i = 0; i < prefix; i++) {
|
|
222
|
+
pairs.push([i, i])
|
|
223
|
+
total += weight(a[i]!)
|
|
224
|
+
}
|
|
225
|
+
const [middle, middleWeight] = weightedLcsCore(
|
|
226
|
+
a.slice(prefix, a.length - suffix),
|
|
227
|
+
b.slice(prefix, b.length - suffix),
|
|
228
|
+
weight,
|
|
229
|
+
)
|
|
230
|
+
for (const [i, j] of middle) {
|
|
231
|
+
pairs.push([i + prefix, j + prefix])
|
|
232
|
+
}
|
|
233
|
+
total += middleWeight
|
|
234
|
+
for (let i = suffix; i > 0; i--) {
|
|
235
|
+
pairs.push([a.length - i, b.length - i])
|
|
236
|
+
total += weight(a[a.length - i]!)
|
|
237
|
+
}
|
|
238
|
+
return [pairs, total]
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function weightedLcsCore(
|
|
242
|
+
a: number[],
|
|
243
|
+
b: number[],
|
|
244
|
+
weight: (x: number) => number,
|
|
245
|
+
): [pairs: [number, number][], weight: number] {
|
|
246
|
+
if (a.length === 0 || b.length === 0) {
|
|
247
|
+
return [[], 0]
|
|
248
|
+
}
|
|
249
|
+
const matrix = new Matrix(a, b, weight)
|
|
250
|
+
let edits = 0
|
|
251
|
+
let point: Point = { weight: 0, a: 0, b: 0, matches: 0 }
|
|
252
|
+
for (;;) {
|
|
253
|
+
const end = matrix.extend(edits)
|
|
254
|
+
if (end) {
|
|
255
|
+
point = end
|
|
256
|
+
break
|
|
257
|
+
}
|
|
258
|
+
const next = matrix.nextEdits(edits)
|
|
259
|
+
if (next === undefined) {
|
|
260
|
+
break
|
|
261
|
+
}
|
|
262
|
+
edits = next
|
|
263
|
+
}
|
|
264
|
+
const result: [number, number][] = []
|
|
265
|
+
const finalWeight = point.weight / 2
|
|
266
|
+
point = { ...point }
|
|
267
|
+
for (;;) {
|
|
268
|
+
for (let i = 0; i < point.matches; i++) {
|
|
269
|
+
point.a -= 1
|
|
270
|
+
point.b -= 1
|
|
271
|
+
result.push([point.a, point.b])
|
|
272
|
+
}
|
|
273
|
+
const pred = matrix.predecessor(point.a, point.b, edits)
|
|
274
|
+
if (!pred) {
|
|
275
|
+
break
|
|
276
|
+
}
|
|
277
|
+
point = { ...pred[0] }
|
|
278
|
+
edits = pred[1]
|
|
279
|
+
}
|
|
280
|
+
result.reverse()
|
|
281
|
+
return [result, finalWeight]
|
|
282
|
+
}
|
package/src/query.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { GENERIC_SAMPLE } from './db.ts'
|
|
2
|
+
import { Subgraph } from './subgraph.ts'
|
|
3
|
+
|
|
4
|
+
import type { GBZBase, PathName } from './db.ts'
|
|
5
|
+
import type { HaplotypeOutput, SnarlOutput } from './subgraph.ts'
|
|
6
|
+
|
|
7
|
+
export interface QueryOptions {
|
|
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
|
+
}
|
|
19
|
+
|
|
20
|
+
function pathName(query: PathQuery, fragment: number): PathName {
|
|
21
|
+
return {
|
|
22
|
+
sample: query.sample ?? GENERIC_SAMPLE,
|
|
23
|
+
contig: query.contig,
|
|
24
|
+
haplotype: query.haplotype ?? 0,
|
|
25
|
+
fragment,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function subgraphAtOffset(
|
|
30
|
+
db: GBZBase,
|
|
31
|
+
query: PathQuery,
|
|
32
|
+
offset: number,
|
|
33
|
+
opts: QueryOptions = {},
|
|
34
|
+
) {
|
|
35
|
+
const subgraph = new Subgraph(db)
|
|
36
|
+
subgraph.limit = opts.limit
|
|
37
|
+
const reference = await subgraph.pathPosition(pathName(query, offset))
|
|
38
|
+
await subgraph.aroundPosition(
|
|
39
|
+
reference.position.handle,
|
|
40
|
+
reference.position.nodeOffset,
|
|
41
|
+
opts.context ?? 100,
|
|
42
|
+
)
|
|
43
|
+
await subgraph.extractSnarls(opts.snarls ?? 'none')
|
|
44
|
+
subgraph.extractPaths(reference, opts.haplotypes ?? 'all')
|
|
45
|
+
return subgraph
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function subgraphInInterval(
|
|
49
|
+
db: GBZBase,
|
|
50
|
+
query: PathQuery,
|
|
51
|
+
start: number,
|
|
52
|
+
end: number,
|
|
53
|
+
opts: QueryOptions = {},
|
|
54
|
+
) {
|
|
55
|
+
const subgraph = new Subgraph(db)
|
|
56
|
+
subgraph.limit = opts.limit
|
|
57
|
+
const reference = await subgraph.pathPosition(pathName(query, start))
|
|
58
|
+
await subgraph.aroundInterval(
|
|
59
|
+
reference.position,
|
|
60
|
+
end - start,
|
|
61
|
+
opts.context ?? 100,
|
|
62
|
+
)
|
|
63
|
+
await subgraph.extractSnarls(opts.snarls ?? 'none')
|
|
64
|
+
subgraph.extractPaths(reference, opts.haplotypes ?? 'all')
|
|
65
|
+
return subgraph
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function subgraphAroundNodes(
|
|
69
|
+
db: GBZBase,
|
|
70
|
+
nodes: number[],
|
|
71
|
+
opts: QueryOptions = {},
|
|
72
|
+
) {
|
|
73
|
+
const haplotypes = opts.haplotypes ?? 'all'
|
|
74
|
+
const snarls = opts.snarls ?? 'none'
|
|
75
|
+
if (haplotypes === 'reference-only') {
|
|
76
|
+
throw new Error('Cannot output a reference path in a node-based query')
|
|
77
|
+
}
|
|
78
|
+
if (snarls === 'overlapping' && nodes.length > 1) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
'Overlapping snarls cannot be extracted for a node-based query with multiple nodes',
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
const subgraph = new Subgraph(db)
|
|
84
|
+
subgraph.limit = opts.limit
|
|
85
|
+
await subgraph.aroundNodes(nodes, opts.context ?? 100)
|
|
86
|
+
await subgraph.extractSnarls(snarls)
|
|
87
|
+
subgraph.extractPaths(undefined, haplotypes)
|
|
88
|
+
return subgraph
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function subgraphBetween(
|
|
92
|
+
db: GBZBase,
|
|
93
|
+
start: number,
|
|
94
|
+
end: number,
|
|
95
|
+
opts: Pick<QueryOptions, 'haplotypes' | 'limit'> = {},
|
|
96
|
+
) {
|
|
97
|
+
const haplotypes = opts.haplotypes ?? 'all'
|
|
98
|
+
if (haplotypes === 'reference-only') {
|
|
99
|
+
throw new Error('Cannot output a reference path in a node-based query')
|
|
100
|
+
}
|
|
101
|
+
const subgraph = new Subgraph(db)
|
|
102
|
+
subgraph.limit = opts.limit
|
|
103
|
+
await subgraph.betweenNodes(start, end)
|
|
104
|
+
subgraph.extractPaths(undefined, haplotypes)
|
|
105
|
+
return subgraph
|
|
106
|
+
}
|