mirror of
https://github.com/chhylp123/hifiasm.git
synced 2026-09-20 03:18:13 +08:00
499 lines
13 KiB
C++
499 lines
13 KiB
C++
#include <stdint.h>
|
|
#include "CommandLines.h"
|
|
#include "yak.h"
|
|
#include "khashl.h"
|
|
|
|
#define YAK_COUNTER_BITS 12
|
|
#define YAK_N_COUNTS (1<<YAK_COUNTER_BITS)
|
|
#define YAK_MAX_COUNT ((1<<YAK_COUNTER_BITS)-1)
|
|
|
|
#define yak_ch_eq(a, b) ((a)>>YAK_COUNTER_BITS == (b)>>YAK_COUNTER_BITS) // lower 8 bits for counts; higher bits for k-mer
|
|
#define yak_ch_hash(a) ((a)>>YAK_COUNTER_BITS)
|
|
KHASHL_SET_INIT(static klib_unused, yak_ht_t, yak_ht, uint64_t, yak_ch_hash, yak_ch_eq)
|
|
|
|
KHASHL_SET_INIT(static klib_unused, yak_hh_t, yak_hh, uint64_t, kh_hash_dummy, kh_eq_generic)
|
|
|
|
typedef struct {
|
|
int32_t bf_shift, bf_n_hash;
|
|
int32_t k, w, is_HPC;
|
|
int32_t pre;
|
|
int32_t n_thread;
|
|
int64_t chunk_size;
|
|
} yak_copt_t;
|
|
|
|
typedef struct {
|
|
yak_ht_t *h;
|
|
yak_bf_t *b;
|
|
} yak_ch1_t;
|
|
|
|
typedef struct {
|
|
int k, pre, n_hash, n_shift;
|
|
uint64_t tot;
|
|
yak_ch1_t *h;
|
|
} yak_ch_t;
|
|
|
|
const unsigned char seq_nt4_table[256] = { // translate ACGT to 0123
|
|
0, 1, 2, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 0, 4, 1, 4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 0, 4, 1, 4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
|
|
};
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include "kthread.h"
|
|
|
|
/*** hash table ***/
|
|
|
|
static yak_ch_t *yak_ch_init(int k, int pre, int n_hash, int n_shift)
|
|
{
|
|
yak_ch_t *h;
|
|
int i;
|
|
if (pre < YAK_COUNTER_BITS) return 0;
|
|
CALLOC(h, 1);
|
|
h->k = k, h->pre = pre;
|
|
CALLOC(h->h, 1<<h->pre);
|
|
for (i = 0; i < 1<<h->pre; ++i)
|
|
h->h[i].h = yak_ht_init();
|
|
if (n_hash > 0 && n_shift > h->pre) {
|
|
h->n_hash = n_hash, h->n_shift = n_shift;
|
|
for (i = 0; i < 1<<h->pre; ++i)
|
|
h->h[i].b = yak_bf_init(h->n_shift - h->pre, h->n_hash);
|
|
}
|
|
return h;
|
|
}
|
|
|
|
static void yak_ch_destroy_bf(yak_ch_t *h)
|
|
{
|
|
int i;
|
|
for (i = 0; i < 1<<h->pre; ++i) {
|
|
if (h->h[i].b)
|
|
yak_bf_destroy(h->h[i].b);
|
|
h->h[i].b = 0;
|
|
}
|
|
}
|
|
|
|
static void yak_ch_destroy(yak_ch_t *h)
|
|
{
|
|
int i;
|
|
if (h == 0) return;
|
|
yak_ch_destroy_bf(h);
|
|
for (i = 0; i < 1<<h->pre; ++i)
|
|
yak_ht_destroy(h->h[i].h);
|
|
free(h->h); free(h);
|
|
}
|
|
|
|
static int yak_ch_insert_list(yak_ch_t *h, int create_new, int n, const uint64_t *a)
|
|
{
|
|
int j, mask = (1<<h->pre) - 1, n_ins = 0;
|
|
yak_ch1_t *g;
|
|
if (n == 0) return 0;
|
|
g = &h->h[a[0]&mask];
|
|
for (j = 0; j < n; ++j) {
|
|
int ins = 1, absent;
|
|
uint64_t x = a[j] >> h->pre;
|
|
khint_t k;
|
|
if ((a[j]&mask) != (a[0]&mask)) continue;
|
|
if (create_new) {
|
|
if (g->b)
|
|
ins = (yak_bf_insert(g->b, x) == h->n_hash);
|
|
if (ins) {
|
|
k = yak_ht_put(g->h, x<<YAK_COUNTER_BITS, &absent);
|
|
if (absent) ++n_ins;
|
|
if ((kh_key(g->h, k)&YAK_MAX_COUNT) < YAK_MAX_COUNT)
|
|
++kh_key(g->h, k);
|
|
}
|
|
} else {
|
|
k = yak_ht_get(g->h, x<<YAK_COUNTER_BITS);
|
|
if (k != kh_end(g->h) && (kh_key(g->h, k)&YAK_MAX_COUNT) < YAK_MAX_COUNT)
|
|
++kh_key(g->h, k);
|
|
}
|
|
}
|
|
return n_ins;
|
|
}
|
|
|
|
static int yak_ch_get(const yak_ch_t *h, uint64_t x)
|
|
{
|
|
int mask = (1<<h->pre) - 1;
|
|
yak_ht_t *g = h->h[x&mask].h;
|
|
khint_t k;
|
|
k = yak_ht_get(g, x >> h->pre << YAK_COUNTER_BITS);
|
|
return k == kh_end(g)? -1 : kh_key(g, k)&YAK_MAX_COUNT;
|
|
}
|
|
|
|
/*** generate histogram ***/
|
|
|
|
typedef struct {
|
|
uint64_t c[YAK_N_COUNTS];
|
|
} buf_cnt_t;
|
|
|
|
typedef struct {
|
|
const yak_ch_t *h;
|
|
buf_cnt_t *cnt;
|
|
} hist_aux_t;
|
|
|
|
static void worker_hist(void *data, long i, int tid) // callback for kt_for()
|
|
{
|
|
hist_aux_t *a = (hist_aux_t*)data;
|
|
uint64_t *cnt = a->cnt[tid].c;
|
|
yak_ht_t *g = a->h->h[i].h;
|
|
khint_t k;
|
|
for (k = 0; k < kh_end(g); ++k)
|
|
if (kh_exist(g, k))
|
|
++cnt[kh_key(g, k)&YAK_MAX_COUNT];
|
|
}
|
|
|
|
static void yak_ch_hist(const yak_ch_t *h, int64_t cnt[YAK_N_COUNTS], int n_thread)
|
|
{
|
|
hist_aux_t a;
|
|
int i, j;
|
|
a.h = h;
|
|
memset(cnt, 0, YAK_N_COUNTS * sizeof(uint64_t));
|
|
CALLOC(a.cnt, n_thread);
|
|
kt_for(n_thread, worker_hist, &a, 1<<h->pre);
|
|
for (i = 0; i < YAK_N_COUNTS; ++i) cnt[i] = 0;
|
|
for (j = 0; j < n_thread; ++j)
|
|
for (i = 0; i < YAK_N_COUNTS; ++i)
|
|
cnt[i] += a.cnt[j].c[i];
|
|
free(a.cnt);
|
|
}
|
|
|
|
/*** shrink a hash table ***/
|
|
|
|
typedef struct {
|
|
int min, max;
|
|
yak_ch_t *h;
|
|
} shrink_aux_t;
|
|
|
|
static void worker_shrink(void *data, long i, int tid) // callback for kt_for()
|
|
{
|
|
shrink_aux_t *a = (shrink_aux_t*)data;
|
|
yak_ch_t *h = a->h;
|
|
yak_ht_t *g = h->h[i].h, *f;
|
|
khint_t k;
|
|
f = yak_ht_init();
|
|
yak_ht_resize(f, kh_size(g));
|
|
for (k = 0; k < kh_end(g); ++k) {
|
|
if (kh_exist(g, k)) {
|
|
int absent, c = kh_key(g, k) & YAK_MAX_COUNT;
|
|
if (c >= a->min && c <= a->max)
|
|
yak_ht_put(f, kh_key(g, k), &absent);
|
|
}
|
|
}
|
|
yak_ht_destroy(g);
|
|
h->h[i].h = f;
|
|
}
|
|
|
|
static void yak_ch_shrink(yak_ch_t *h, int min, int max, int n_thread)
|
|
{
|
|
int i;
|
|
shrink_aux_t a;
|
|
a.h = h, a.min = min, a.max = max;
|
|
kt_for(n_thread, worker_shrink, &a, 1<<h->pre);
|
|
for (i = 0, h->tot = 0; i < 1<<h->pre; ++i)
|
|
h->tot += kh_size(h->h[i].h);
|
|
}
|
|
|
|
#include <zlib.h>
|
|
#include <string.h>
|
|
#include "kseq.h" // FASTA/Q parser
|
|
KSEQ_INIT(gzFile, gzread)
|
|
|
|
void yak_copt_init(yak_copt_t *o)
|
|
{
|
|
memset(o, 0, sizeof(yak_copt_t));
|
|
o->bf_shift = 0;
|
|
o->bf_n_hash = 4;
|
|
o->k = 31;
|
|
o->w = 1;
|
|
o->pre = YAK_COUNTER_BITS;
|
|
o->n_thread = 4;
|
|
o->chunk_size = 10000000;
|
|
}
|
|
|
|
typedef struct {
|
|
int n, m;
|
|
uint64_t n_ins;
|
|
uint64_t *a;
|
|
} ch_buf_t;
|
|
|
|
static inline void ch_insert_buf(ch_buf_t *buf, int p, uint64_t y) // insert a k-mer $y to a linear buffer
|
|
{
|
|
int pre = y & ((1<<p) - 1);
|
|
ch_buf_t *b = &buf[pre];
|
|
if (b->n == b->m) {
|
|
b->m = b->m < 8? 8 : b->m + (b->m>>1);
|
|
REALLOC(b->a, b->m);
|
|
}
|
|
b->a[b->n++] = y;
|
|
}
|
|
|
|
static void count_seq_buf(ch_buf_t *buf, int k, int p, int len, const char *seq) // insert k-mers in $seq to linear buffer $buf
|
|
{
|
|
int i, l;
|
|
uint64_t x[4], mask = (1ULL<<k) - 1, shift = k - 1;
|
|
for (i = l = 0, x[0] = x[1] = x[2] = x[3] = 0; i < len; ++i) {
|
|
int c = seq_nt4_table[(uint8_t)seq[i]];
|
|
if (c < 4) { // not an "N" base
|
|
x[0] = (x[0] << 1 | (c&1)) & mask;
|
|
x[1] = (x[1] << 1 | (c>>1)) & mask;
|
|
x[2] = x[2] >> 1 | (uint64_t)(1 - (c&1)) << shift;
|
|
x[3] = x[3] >> 1 | (uint64_t)(1 - (c>>1)) << shift;
|
|
if (++l >= k)
|
|
ch_insert_buf(buf, p, yak_hash_long(x));
|
|
} else l = 0, x[0] = x[1] = x[2] = x[3] = 0; // if there is an "N", restart
|
|
}
|
|
}
|
|
|
|
static void count_seq_buf_HPC(ch_buf_t *buf, int k, int p, int len, const char *seq) // insert k-mers in $seq to linear buffer $buf
|
|
{
|
|
int i, l, last = -1;
|
|
uint64_t x[4], mask = (1ULL<<k) - 1, shift = k - 1;
|
|
for (i = l = 0, x[0] = x[1] = x[2] = x[3] = 0; i < len; ++i) {
|
|
int c = seq_nt4_table[(uint8_t)seq[i]];
|
|
if (c < 4) { // not an "N" base
|
|
if (c != last) {
|
|
x[0] = (x[0] << 1 | (c&1)) & mask;
|
|
x[1] = (x[1] << 1 | (c>>1)) & mask;
|
|
x[2] = x[2] >> 1 | (uint64_t)(1 - (c&1)) << shift;
|
|
x[3] = x[3] >> 1 | (uint64_t)(1 - (c>>1)) << shift;
|
|
if (++l >= k)
|
|
ch_insert_buf(buf, p, yak_hash_long(x));
|
|
last = c;
|
|
}
|
|
} else l = 0, last = -1, x[0] = x[1] = x[2] = x[3] = 0; // if there is an "N", restart
|
|
}
|
|
}
|
|
|
|
/******************
|
|
* K-mer counting *
|
|
******************/
|
|
|
|
typedef struct { // global data structure for kt_pipeline()
|
|
const yak_copt_t *opt;
|
|
const void *flt_tab;
|
|
int create_new, is_store;
|
|
kseq_t *ks;
|
|
yak_ch_t *h;
|
|
} pl_data_t;
|
|
|
|
typedef struct { // data structure for each step in kt_pipeline()
|
|
pl_data_t *p;
|
|
int n_seq, m_seq, sum_len, nk;
|
|
int *len;
|
|
char **seq;
|
|
ha_mz1_v *mz_buf;
|
|
ha_mz1_v *mz;
|
|
ch_buf_t *buf;
|
|
} st_data_t;
|
|
|
|
static void worker_for_insert(void *data, long i, int tid) // callback for kt_for()
|
|
{
|
|
st_data_t *s = (st_data_t*)data;
|
|
ch_buf_t *b = &s->buf[i];
|
|
yak_ch_t *h = s->p->h;
|
|
b->n_ins += yak_ch_insert_list(h, s->p->create_new, b->n, b->a);
|
|
}
|
|
|
|
static void worker_for_mz(void *data, long i, int tid)
|
|
{
|
|
st_data_t *s = (st_data_t*)data;
|
|
ha_mz1_v *b = &s->mz_buf[tid];
|
|
s->mz_buf[tid].n = 0;
|
|
ha_sketch(s->seq[i], s->len[i], s->p->opt->w, s->p->opt->k, 0, s->p->opt->is_HPC, b, s->p->flt_tab);
|
|
s->mz[i].n = s->mz[i].m = b->n;
|
|
MALLOC(s->mz[i].a, b->n);
|
|
memcpy(s->mz[i].a, b->a, b->n * sizeof(ha_mz1_t));
|
|
}
|
|
|
|
static void *worker_count(void *data, int step, void *in) // callback for kt_pipeline()
|
|
{
|
|
pl_data_t *p = (pl_data_t*)data;
|
|
if (step == 0) { // step 1: read a block of sequences
|
|
int ret;
|
|
st_data_t *s;
|
|
CALLOC(s, 1);
|
|
s->p = p;
|
|
while ((ret = kseq_read(p->ks)) >= 0) {
|
|
int l = p->ks->seq.l;
|
|
if (l < p->opt->k) continue;
|
|
if (s->n_seq == s->m_seq) {
|
|
s->m_seq = s->m_seq < 16? 16 : s->m_seq + (s->m_seq>>1);
|
|
REALLOC(s->len, s->m_seq);
|
|
REALLOC(s->seq, s->m_seq);
|
|
}
|
|
MALLOC(s->seq[s->n_seq], l);
|
|
memcpy(s->seq[s->n_seq], p->ks->seq.s, l);
|
|
s->len[s->n_seq++] = l;
|
|
s->sum_len += l;
|
|
s->nk += l - p->opt->k + 1;
|
|
if (s->sum_len >= p->opt->chunk_size)
|
|
break;
|
|
}
|
|
if (s->sum_len == 0) free(s);
|
|
else return s;
|
|
} else if (step == 1) { // step 2: extract k-mers
|
|
st_data_t *s = (st_data_t*)in;
|
|
int i, n_pre = 1<<p->opt->pre, m;
|
|
// allocate the k-mer buffer
|
|
CALLOC(s->buf, n_pre);
|
|
m = (int)(s->nk * 1.2 / n_pre) + 1;
|
|
for (i = 0; i < n_pre; ++i) {
|
|
s->buf[i].m = m;
|
|
MALLOC(s->buf[i].a, m);
|
|
}
|
|
// fill the buffer
|
|
if (p->opt->w == 1) { // enumerate all k-mers
|
|
for (i = 0; i < s->n_seq; ++i) {
|
|
if (p->opt->is_HPC)
|
|
count_seq_buf_HPC(s->buf, p->opt->k, p->opt->pre, s->len[i], s->seq[i]);
|
|
else
|
|
count_seq_buf(s->buf, p->opt->k, p->opt->pre, s->len[i], s->seq[i]);
|
|
if (!p->is_store) free(s->seq[i]);
|
|
}
|
|
} else { // minimizers only
|
|
// compute minimizers
|
|
CALLOC(s->mz, s->n_seq);
|
|
CALLOC(s->mz_buf, p->opt->n_thread);
|
|
kt_for(p->opt->n_thread, worker_for_mz, s, s->n_seq);
|
|
for (i = 0; i < p->opt->n_thread; ++i)
|
|
free(s->mz_buf[i].a);
|
|
free(s->mz_buf);
|
|
// insert minimizers
|
|
for (i = 0; i < s->n_seq; ++i) {
|
|
uint32_t j;
|
|
for (j = 0; j < s->mz[i].n; ++j)
|
|
ch_insert_buf(s->buf, p->opt->pre, s->mz[i].a[j].x);
|
|
}
|
|
for (i = 0; i < s->n_seq; ++i) {
|
|
free(s->mz[i].a);
|
|
if (!p->is_store) free(s->seq[i]);
|
|
}
|
|
free(s->mz);
|
|
}
|
|
free(s->seq); free(s->len);
|
|
s->seq = 0, s->len = 0;
|
|
return s;
|
|
} else if (step == 2) { // step 3: insert k-mers to hash table
|
|
st_data_t *s = (st_data_t*)in;
|
|
int i, n = 1<<p->opt->pre;
|
|
uint64_t n_ins = 0;
|
|
kt_for(p->opt->n_thread, worker_for_insert, s, n);
|
|
for (i = 0; i < n; ++i) {
|
|
n_ins += s->buf[i].n_ins;
|
|
free(s->buf[i].a);
|
|
}
|
|
p->h->tot += n_ins;
|
|
free(s->buf);
|
|
fprintf(stderr, "[M::%s::%.3f*%.2f] processed %d sequences; %ld distinct k-mers in the hash table\n", __func__,
|
|
yak_realtime(), yak_cputime() / yak_realtime(), s->n_seq, (long)p->h->tot);
|
|
free(s);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static yak_ch_t *yak_count(const char *fn, const yak_copt_t *opt, yak_ch_t *h0, const void *flt_tab)
|
|
{
|
|
pl_data_t pl;
|
|
gzFile fp;
|
|
if ((fp = gzopen(fn, "r")) == 0) return 0;
|
|
memset(&pl, 0, sizeof(pl_data_t));
|
|
pl.ks = kseq_init(fp);
|
|
pl.flt_tab = flt_tab;
|
|
pl.opt = opt;
|
|
if (h0) {
|
|
pl.h = h0, pl.create_new = 0;
|
|
assert(h0->k == opt->k && h0->pre == opt->pre);
|
|
} else {
|
|
pl.create_new = 1;
|
|
pl.h = yak_ch_init(opt->k, opt->pre, opt->bf_n_hash, opt->bf_shift);
|
|
}
|
|
kt_pipeline(3, worker_count, &pl, 3);
|
|
kseq_destroy(pl.ks);
|
|
gzclose(fp);
|
|
return pl.h;
|
|
}
|
|
|
|
static yak_ch_t *yak_count_file(const yak_copt_t *opt, int n_fn, char **fn, const void *flt_tab)
|
|
{
|
|
int i;
|
|
yak_ch_t *h = 0;
|
|
for (i = 0; i < n_fn; ++i)
|
|
h = yak_count(fn[i], opt, h, flt_tab);
|
|
if (opt->bf_shift > 0)
|
|
yak_ch_destroy_bf(h);
|
|
return h;
|
|
}
|
|
|
|
static yak_hh_t *gen_hh(const yak_ch_t *h)
|
|
{
|
|
int i;
|
|
yak_hh_t *hh;
|
|
hh = yak_hh_init();
|
|
yak_hh_resize(hh, h->tot * 2);
|
|
for (i = 0; i < 1<<h->pre; ++i) {
|
|
yak_ht_t *ht = h->h[i].h;
|
|
khint_t k;
|
|
for (k = 0; k < kh_end(ht); ++k) {
|
|
if (kh_exist(ht, k)) {
|
|
uint64_t y = kh_key(ht, k) >> YAK_COUNTER_BITS << h->pre | i;
|
|
int absent;
|
|
yak_hh_put(hh, y, &absent);
|
|
}
|
|
}
|
|
}
|
|
return hh;
|
|
}
|
|
|
|
void *ha_gen_flt_tab(const hifiasm_opt_t *asm_opt)
|
|
{
|
|
yak_hh_t *flt_tab;
|
|
int64_t cnt[YAK_N_COUNTS];
|
|
int peak_hom, peak_het, cutoff;
|
|
yak_copt_t opt;
|
|
yak_ch_t *h;
|
|
yak_copt_init(&opt);
|
|
opt.is_HPC = !asm_opt->no_HPC;
|
|
opt.k = asm_opt->k_mer_length;
|
|
opt.n_thread = asm_opt->thread_num;
|
|
opt.bf_shift = asm_opt->bf_shift;
|
|
h = yak_count_file(&opt, asm_opt->num_reads, asm_opt->read_file_names, 0);
|
|
yak_ch_hist(h, cnt, opt.n_thread);
|
|
peak_hom = yak_analyze_count(YAK_N_COUNTS, cnt, &peak_het);
|
|
if (peak_hom > 0) fprintf(stderr, "[M::%s] peak_hom: %d; peak_het: %d\n", __func__, peak_hom, peak_het);
|
|
cutoff = (int)(peak_hom * asm_opt->high_factor);
|
|
if (cutoff > YAK_MAX_COUNT - 1) cutoff = YAK_MAX_COUNT - 1;
|
|
yak_ch_shrink(h, cutoff, YAK_MAX_COUNT, opt.n_thread);
|
|
flt_tab = gen_hh(h);
|
|
yak_ch_destroy(h);
|
|
fprintf(stderr, "[M::%s] filtered out %ld k-mers occurring %d or more times\n",
|
|
__func__, (long)kh_size(flt_tab), cutoff);
|
|
return (void*)flt_tab;
|
|
}
|
|
|
|
int ha_hf_isflt(const void *hh, uint64_t y)
|
|
{
|
|
yak_hh_t *h = (yak_hh_t*)hh;
|
|
khint_t k;
|
|
k = yak_hh_get(h, y);
|
|
return k == kh_end(h)? 0 : 1;
|
|
}
|
|
|
|
void ha_hf_destroy(void *h)
|
|
{
|
|
yak_hh_destroy((yak_hh_t*)h);
|
|
}
|