@gmod/gbz-base 0.0.1
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/LICENSE +25 -0
- package/README.md +125 -0
- package/bin/query.js +7 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +140 -0
- package/dist/db.d.ts +72 -0
- package/dist/db.js +208 -0
- package/dist/filehandle.d.ts +6 -0
- package/dist/filehandle.js +1 -0
- package/dist/gbwt/bytecode.d.ts +20 -0
- package/dist/gbwt/bytecode.js +70 -0
- package/dist/gbwt/node.d.ts +15 -0
- package/dist/gbwt/node.js +44 -0
- package/dist/gbwt/record.d.ts +18 -0
- package/dist/gbwt/record.js +133 -0
- package/dist/gbwt/sequence.d.ts +3 -0
- package/dist/gbwt/sequence.js +40 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +6 -0
- package/dist/lcs.d.ts +1 -0
- package/dist/lcs.js +214 -0
- package/dist/query.d.ts +16 -0
- package/dist/query.js +37 -0
- package/dist/sqlite/btree.d.ts +18 -0
- package/dist/sqlite/btree.js +201 -0
- package/dist/sqlite/database.d.ts +29 -0
- package/dist/sqlite/database.js +71 -0
- package/dist/sqlite/pager.d.ts +19 -0
- package/dist/sqlite/pager.js +55 -0
- package/dist/sqlite/record.d.ts +3 -0
- package/dist/sqlite/record.js +85 -0
- package/dist/subgraph.d.ts +122 -0
- package/dist/subgraph.js +760 -0
- package/package.json +53 -0
- package/tools/haplotype-index/Cargo.toml +15 -0
- package/tools/haplotype-index/src/main.rs +319 -0
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gmod/gbz-base",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Pure TypeScript reader for gbz-base pangenome databases (.gbz.db) with random access over HTTP range requests",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"gbz-base-query": "./bin/query.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"bin",
|
|
22
|
+
"tools/haplotype-index/Cargo.toml",
|
|
23
|
+
"tools/haplotype-index/src"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"generic-filehandle2": "^2.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^24.0.0",
|
|
30
|
+
"typescript": "^5.9.0",
|
|
31
|
+
"vitest": "^3.2.0"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/GMOD/gbz-base-js.git"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"pangenome",
|
|
39
|
+
"gbz",
|
|
40
|
+
"gbwt",
|
|
41
|
+
"sqlite",
|
|
42
|
+
"genomics",
|
|
43
|
+
"jbrowse"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc -p tsconfig.build.json",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "gbz-haplotype-index"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "Adds HaplotypeSamples and HaplotypeLengths tables to a gbz-base database so every haplotype path can be identified and placed"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
|
|
8
|
+
[dependencies]
|
|
9
|
+
gbz = "0.7"
|
|
10
|
+
simple-sds = "0.4"
|
|
11
|
+
gbz-base = "0.6.1"
|
|
12
|
+
rusqlite = { version = "0.38", features = ["bundled"] }
|
|
13
|
+
|
|
14
|
+
[profile.release]
|
|
15
|
+
opt-level = 3
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
use gbz::bwt::BWT;
|
|
2
|
+
use gbz::support;
|
|
3
|
+
use gbz::{GBWT, GBZ, Orientation, Pos, ENDMARKER};
|
|
4
|
+
use gbz_base::{GBZBase, GraphInterface};
|
|
5
|
+
use rusqlite::{params, Connection};
|
|
6
|
+
use simple_sds::serialize;
|
|
7
|
+
|
|
8
|
+
use std::env;
|
|
9
|
+
use std::process;
|
|
10
|
+
|
|
11
|
+
const USAGE: &str = "Usage: gbz-haplotype-index [--interval BP] [--forward-only] graph.gbz graph.gbz.db
|
|
12
|
+
gbz-haplotype-index [--interval BP] [--forward-only] --from-db graph.gbz.db
|
|
13
|
+
|
|
14
|
+
Walks every path in both orientations and writes a sample every --interval bp
|
|
15
|
+
(default 4096) into table HaplotypeSamples of the database, plus the path
|
|
16
|
+
lengths into HaplotypeLengths. The path start and end are always sampled.
|
|
17
|
+
Existing tables are replaced. With --from-db the walk reads node records from
|
|
18
|
+
the database itself, so the GBZ is not needed.
|
|
19
|
+
";
|
|
20
|
+
|
|
21
|
+
struct Args {
|
|
22
|
+
gbz: Option<String>,
|
|
23
|
+
db: String,
|
|
24
|
+
interval: usize,
|
|
25
|
+
forward_only: bool,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn parse_args() -> Args {
|
|
29
|
+
let mut positional = Vec::new();
|
|
30
|
+
let mut interval = 4096;
|
|
31
|
+
let mut forward_only = false;
|
|
32
|
+
let mut from_db = false;
|
|
33
|
+
let mut iter = env::args().skip(1);
|
|
34
|
+
while let Some(arg) = iter.next() {
|
|
35
|
+
match arg.as_str() {
|
|
36
|
+
"--interval" => {
|
|
37
|
+
let value = iter.next().unwrap_or_default();
|
|
38
|
+
interval = value.parse().unwrap_or_else(|_| {
|
|
39
|
+
eprintln!("Invalid --interval: {}", value);
|
|
40
|
+
process::exit(1);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
"--forward-only" => forward_only = true,
|
|
44
|
+
"--from-db" => from_db = true,
|
|
45
|
+
"-h" | "--help" => {
|
|
46
|
+
eprint!("{}", USAGE);
|
|
47
|
+
process::exit(0);
|
|
48
|
+
}
|
|
49
|
+
_ => positional.push(arg),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let expected = if from_db { 1 } else { 2 };
|
|
53
|
+
if positional.len() != expected || interval == 0 {
|
|
54
|
+
eprint!("{}", USAGE);
|
|
55
|
+
process::exit(1);
|
|
56
|
+
}
|
|
57
|
+
let db = positional.pop().unwrap();
|
|
58
|
+
let gbz = positional.pop();
|
|
59
|
+
Args { gbz, db, interval, forward_only }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
struct Sample {
|
|
63
|
+
node_handle: usize,
|
|
64
|
+
node_offset: usize,
|
|
65
|
+
path_handle: usize,
|
|
66
|
+
orientation: usize,
|
|
67
|
+
path_offset: usize,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
trait PathSource {
|
|
71
|
+
fn path_count(&mut self) -> usize;
|
|
72
|
+
fn start(&mut self, path_handle: usize, orientation: Orientation) -> Option<Pos>;
|
|
73
|
+
fn step(&mut self, pos: Pos) -> (usize, Option<Pos>);
|
|
74
|
+
fn node_len(&mut self, handle: usize) -> usize;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
struct GbzSource {
|
|
78
|
+
graph: GBZ,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
impl PathSource for GbzSource {
|
|
82
|
+
fn path_count(&mut self) -> usize {
|
|
83
|
+
self.graph.metadata().map(|m| m.paths()).unwrap_or(0)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fn start(&mut self, path_handle: usize, orientation: Orientation) -> Option<Pos> {
|
|
87
|
+
let index: &GBWT = self.graph.as_ref();
|
|
88
|
+
index.start(support::encode_path(path_handle, orientation))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fn step(&mut self, pos: Pos) -> (usize, Option<Pos>) {
|
|
92
|
+
let node_len = self.graph.sequence_len(support::node_id(pos.node)).unwrap();
|
|
93
|
+
let index: &GBWT = self.graph.as_ref();
|
|
94
|
+
let bwt: &BWT = index.as_ref();
|
|
95
|
+
let record = bwt.record(index.node_to_record(pos.node)).unwrap();
|
|
96
|
+
(node_len, record.lf(pos.offset))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fn node_len(&mut self, handle: usize) -> usize {
|
|
100
|
+
self.graph.sequence_len(support::node_id(handle)).unwrap()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
struct DbSource<'a> {
|
|
105
|
+
interface: GraphInterface<'a>,
|
|
106
|
+
paths: usize,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
impl<'a> PathSource for DbSource<'a> {
|
|
110
|
+
fn path_count(&mut self) -> usize {
|
|
111
|
+
self.paths
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn start(&mut self, path_handle: usize, orientation: Orientation) -> Option<Pos> {
|
|
115
|
+
let path = self.interface.get_path(path_handle).unwrap()?;
|
|
116
|
+
Some(if orientation == Orientation::Forward { path.fw_start } else { path.rev_start })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn step(&mut self, pos: Pos) -> (usize, Option<Pos>) {
|
|
120
|
+
let record = self.interface.get_record(pos.node).unwrap().unwrap();
|
|
121
|
+
(record.sequence_len(), record.to_gbwt_record().lf(pos.offset))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fn node_len(&mut self, handle: usize) -> usize {
|
|
125
|
+
self.interface.get_record(handle).unwrap().unwrap().sequence_len()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn walk(source: &mut dyn PathSource, path_handle: usize, orientation: Orientation, interval: usize) -> (Vec<Sample>, usize) {
|
|
130
|
+
let mut samples = Vec::new();
|
|
131
|
+
let mut pos = source.start(path_handle, orientation);
|
|
132
|
+
let mut offset = 0;
|
|
133
|
+
let mut next_sample = 0;
|
|
134
|
+
let mut last: Option<(Pos, usize)> = None;
|
|
135
|
+
while let Some(current) = pos {
|
|
136
|
+
if current.node == ENDMARKER {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
let (node_len, next) = source.step(current);
|
|
140
|
+
if offset >= next_sample {
|
|
141
|
+
samples.push(Sample {
|
|
142
|
+
node_handle: current.node,
|
|
143
|
+
node_offset: current.offset,
|
|
144
|
+
path_handle,
|
|
145
|
+
orientation: orientation as usize,
|
|
146
|
+
path_offset: offset,
|
|
147
|
+
});
|
|
148
|
+
next_sample = offset + interval;
|
|
149
|
+
last = None;
|
|
150
|
+
} else {
|
|
151
|
+
last = Some((current, offset));
|
|
152
|
+
}
|
|
153
|
+
offset += node_len;
|
|
154
|
+
pos = next;
|
|
155
|
+
}
|
|
156
|
+
if let Some((end, end_offset)) = last {
|
|
157
|
+
samples.push(Sample {
|
|
158
|
+
node_handle: end.node,
|
|
159
|
+
node_offset: end.offset,
|
|
160
|
+
path_handle,
|
|
161
|
+
orientation: orientation as usize,
|
|
162
|
+
path_offset: end_offset,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
let length = offset;
|
|
166
|
+
if orientation == Orientation::Reverse {
|
|
167
|
+
for sample in samples.iter_mut() {
|
|
168
|
+
let node_len = source.node_len(sample.node_handle);
|
|
169
|
+
sample.path_offset = length - sample.path_offset - node_len;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
(samples, length)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fn run(source: &mut dyn PathSource, connection: &mut Connection, args: &Args) {
|
|
176
|
+
let orientations: Vec<Orientation> = if args.forward_only {
|
|
177
|
+
vec![Orientation::Forward]
|
|
178
|
+
} else {
|
|
179
|
+
vec![Orientation::Forward, Orientation::Reverse]
|
|
180
|
+
};
|
|
181
|
+
let paths = source.path_count();
|
|
182
|
+
let mut inserted = 0;
|
|
183
|
+
let batch = 64;
|
|
184
|
+
let mut handle = 0;
|
|
185
|
+
while handle < paths {
|
|
186
|
+
let transaction = connection.transaction().unwrap();
|
|
187
|
+
{
|
|
188
|
+
let mut insert_sample = transaction
|
|
189
|
+
.prepare("INSERT INTO HaplotypeSamples(node_handle, node_offset, path_handle, orientation, path_offset) VALUES (?1, ?2, ?3, ?4, ?5)")
|
|
190
|
+
.unwrap();
|
|
191
|
+
let mut insert_length = transaction.prepare("INSERT INTO HaplotypeLengths(path_handle, length) VALUES (?1, ?2)").unwrap();
|
|
192
|
+
for path_handle in handle..(handle + batch).min(paths) {
|
|
193
|
+
let mut length = 0;
|
|
194
|
+
for &orientation in orientations.iter() {
|
|
195
|
+
let (samples, walked) = walk(source, path_handle, orientation, args.interval);
|
|
196
|
+
length = walked;
|
|
197
|
+
for sample in samples {
|
|
198
|
+
insert_sample
|
|
199
|
+
.execute(params![
|
|
200
|
+
sample.node_handle as i64,
|
|
201
|
+
sample.node_offset as i64,
|
|
202
|
+
sample.path_handle as i64,
|
|
203
|
+
sample.orientation as i64,
|
|
204
|
+
sample.path_offset as i64
|
|
205
|
+
])
|
|
206
|
+
.unwrap();
|
|
207
|
+
inserted += 1;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
insert_length.execute(params![path_handle as i64, length as i64]).unwrap();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
transaction.commit().unwrap();
|
|
214
|
+
handle += batch;
|
|
215
|
+
eprintln!("{} / {} paths, {} samples", handle.min(paths), paths, inserted);
|
|
216
|
+
}
|
|
217
|
+
eprintln!("Inserted {} samples for {} paths", inserted, paths);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
fn path_count_from_db(db: &str) -> usize {
|
|
221
|
+
let connection = Connection::open(db).unwrap();
|
|
222
|
+
let value: String = connection
|
|
223
|
+
.query_row("SELECT value FROM Tags WHERE key = 'paths'", [], |row| row.get(0))
|
|
224
|
+
.unwrap_or_else(|_| "0".to_string());
|
|
225
|
+
value.parse().unwrap_or(0)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const SCHEMA: &str = "CREATE TABLE HaplotypeSamples (
|
|
229
|
+
node_handle INTEGER NOT NULL,
|
|
230
|
+
node_offset INTEGER NOT NULL,
|
|
231
|
+
path_handle INTEGER NOT NULL,
|
|
232
|
+
orientation INTEGER NOT NULL,
|
|
233
|
+
path_offset INTEGER NOT NULL,
|
|
234
|
+
PRIMARY KEY (node_handle, node_offset)
|
|
235
|
+
) STRICT;
|
|
236
|
+
CREATE TABLE HaplotypeLengths (
|
|
237
|
+
path_handle INTEGER PRIMARY KEY,
|
|
238
|
+
length INTEGER NOT NULL
|
|
239
|
+
) STRICT;";
|
|
240
|
+
|
|
241
|
+
fn merge(db: &str, tmp: &str, args: &Args) {
|
|
242
|
+
let scratch = Connection::open(tmp).unwrap();
|
|
243
|
+
let mut connection = Connection::open(db).unwrap();
|
|
244
|
+
connection
|
|
245
|
+
.execute_batch(&format!("DROP TABLE IF EXISTS HaplotypeSamples; DROP TABLE IF EXISTS HaplotypeLengths; {}", SCHEMA))
|
|
246
|
+
.unwrap();
|
|
247
|
+
let transaction = connection.transaction().unwrap();
|
|
248
|
+
{
|
|
249
|
+
let mut read_samples = scratch
|
|
250
|
+
.prepare("SELECT node_handle, node_offset, path_handle, orientation, path_offset FROM HaplotypeSamples ORDER BY node_handle, node_offset")
|
|
251
|
+
.unwrap();
|
|
252
|
+
let mut write_sample = transaction
|
|
253
|
+
.prepare("INSERT INTO HaplotypeSamples(node_handle, node_offset, path_handle, orientation, path_offset) VALUES (?1, ?2, ?3, ?4, ?5)")
|
|
254
|
+
.unwrap();
|
|
255
|
+
let mut rows = read_samples.query([]).unwrap();
|
|
256
|
+
while let Some(row) = rows.next().unwrap() {
|
|
257
|
+
let values: [i64; 5] = [row.get(0).unwrap(), row.get(1).unwrap(), row.get(2).unwrap(), row.get(3).unwrap(), row.get(4).unwrap()];
|
|
258
|
+
write_sample.execute(params![values[0], values[1], values[2], values[3], values[4]]).unwrap();
|
|
259
|
+
}
|
|
260
|
+
let mut read_lengths = scratch.prepare("SELECT path_handle, length FROM HaplotypeLengths ORDER BY path_handle").unwrap();
|
|
261
|
+
let mut write_length = transaction.prepare("INSERT INTO HaplotypeLengths(path_handle, length) VALUES (?1, ?2)").unwrap();
|
|
262
|
+
let mut rows = read_lengths.query([]).unwrap();
|
|
263
|
+
while let Some(row) = rows.next().unwrap() {
|
|
264
|
+
let handle: i64 = row.get(0).unwrap();
|
|
265
|
+
let length: i64 = row.get(1).unwrap();
|
|
266
|
+
write_length.execute(params![handle, length]).unwrap();
|
|
267
|
+
}
|
|
268
|
+
transaction
|
|
269
|
+
.execute("INSERT OR REPLACE INTO Tags(key, value) VALUES ('haplotype_index_interval', ?1)", params![args.interval.to_string()])
|
|
270
|
+
.unwrap();
|
|
271
|
+
transaction
|
|
272
|
+
.execute(
|
|
273
|
+
"INSERT OR REPLACE INTO Tags(key, value) VALUES ('haplotype_index_orientations', ?1)",
|
|
274
|
+
params![if args.forward_only { "forward" } else { "both" }],
|
|
275
|
+
)
|
|
276
|
+
.unwrap();
|
|
277
|
+
}
|
|
278
|
+
transaction.commit().unwrap();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
fn main() {
|
|
282
|
+
let args = parse_args();
|
|
283
|
+
let tmp = format!("{}.haplotype-index.tmp", args.db);
|
|
284
|
+
let _ = std::fs::remove_file(&tmp);
|
|
285
|
+
{
|
|
286
|
+
let mut scratch = Connection::open(&tmp).unwrap_or_else(|e| {
|
|
287
|
+
eprintln!("Cannot create {}: {}", tmp, e);
|
|
288
|
+
process::exit(1);
|
|
289
|
+
});
|
|
290
|
+
scratch.execute_batch(SCHEMA).unwrap();
|
|
291
|
+
match &args.gbz {
|
|
292
|
+
Some(gbz) => {
|
|
293
|
+
let graph: GBZ = serialize::load_from(gbz).unwrap_or_else(|e| {
|
|
294
|
+
eprintln!("Cannot load {}: {}", gbz, e);
|
|
295
|
+
process::exit(1);
|
|
296
|
+
});
|
|
297
|
+
if graph.metadata().is_none() {
|
|
298
|
+
eprintln!("The GBZ has no path metadata");
|
|
299
|
+
process::exit(1);
|
|
300
|
+
}
|
|
301
|
+
let mut source = GbzSource { graph };
|
|
302
|
+
run(&mut source, &mut scratch, &args);
|
|
303
|
+
}
|
|
304
|
+
None => {
|
|
305
|
+
let paths = path_count_from_db(&args.db);
|
|
306
|
+
let database = GBZBase::open(&args.db).unwrap_or_else(|e| {
|
|
307
|
+
eprintln!("Cannot open {} as a GBZ-base: {}", args.db, e);
|
|
308
|
+
process::exit(1);
|
|
309
|
+
});
|
|
310
|
+
let interface = GraphInterface::new(&database).unwrap();
|
|
311
|
+
let mut source = DbSource { interface, paths };
|
|
312
|
+
run(&mut source, &mut scratch, &args);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
merge(&args.db, &tmp, &args);
|
|
317
|
+
let _ = std::fs::remove_file(&tmp);
|
|
318
|
+
eprintln!("Merged into {}", args.db);
|
|
319
|
+
}
|