source: trunk/GDE/SINA/builddir/src/kmer_search.cpp

Last change on this file was 19170, checked in by westram, 2 years ago
  • sina source
    • unpack + remove tarball
    • no longer ignore sina builddir.
File size: 12.7 KB
Line 
1/*
2Copyright (c) 2006-2018 Elmar Pruesse <elmar.pruesse@ucdenver.edu>
3
4This file is part of SINA.
5SINA is free software: you can redistribute it and/or modify it under
6the terms of the GNU General Public License as published by the Free
7Software Foundation, either version 3 of the License, or (at your
8option) any later version.
9
10SINA is distributed in the hope that it will be useful, but WITHOUT ANY
11WARRANTY; without even the implied warranty of MERCHANTABILITY or
12FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13for more details.
14
15You should have received a copy of the GNU General Public License
16along with SINA.  If not, see <http://www.gnu.org/licenses/>.
17
18Additional permission under GNU GPL version 3 section 7
19
20If you modify SINA, or any covered work, by linking or combining it
21with components of ARB (or a modified version of that software),
22containing parts covered by the terms of the
23ARB-public-library-license, the licensors of SINA grant you additional
24permission to convey the resulting work. Corresponding Source for a
25non-source form of such a combination shall include the source code
26for the parts of ARB used as well as that of the covered work.
27*/
28
29#include "kmer_search.h"
30#include "kmer.h"
31#include "idset.h"
32#include "query_arb.h"
33#include "helpers.h"
34#include "timer.h"
35#include "log.h"
36#include "progress.h"
37#include "cache.h"
38
39#include <vector>
40using std::vector;
41
42#include <string>
43using std::string;
44
45#include <iostream>
46
47#include <unordered_map>
48#include <unordered_set>
49#include <mutex>
50
51#include <boost/algorithm/string/predicate.hpp>
52#include <boost/filesystem.hpp>
53namespace fs = boost::filesystem;
54
55#include <tbb/tbb.h>
56#include <cstdio>
57#include <sys/stat.h>
58
59#include "zlib.h"
60
61using namespace sina;
62
63static const char* module_name = "Search (internal)";
64static auto logger = Log::create_logger(module_name);
65
66const uint64_t idx_header_magic = 0x5844494b414e4953; // SINAKIDX
67const uint16_t idx_header_vers  = 0;
68union idx_flags {
69    uint16_t flags{0};
70    struct {
71        uint16_t k :8;
72        uint16_t nofast :1;
73        uint16_t reserved :7;
74    };
75    // must be sortable for use as map key below
76    bool operator<(const idx_flags& r) const {
77        if (k < r.k) return true;
78        if (k > r.k) return false;
79        return nofast < r.nofast;
80    }
81};
82
83struct idx_header {
84    uint64_t magic{idx_header_magic};
85    uint16_t vers{idx_header_vers};
86    uint32_t n_sequences{0};
87    idx_flags flags{0};
88};
89
90class kmer_search::impl {
91public:
92    unsigned int k;
93    unsigned int n_kmers;
94    unsigned int n_sequences{0};
95
96    bool nofast;
97
98    std::vector<std::string> sequence_names;
99    std::vector<vlimap*> kmer_idx;
100
101    query_arb* arbdb;
102    timer_mt timeit;
103
104    using rank_result_type = std::vector<std::pair<idset::inc_t::value_type, int>>;
105    fifo_cache<std::string, rank_result_type> cache{32};
106
107    impl(query_arb* arbdb_, int k_, bool nofast_);
108    ~impl() {
109        logger->info("Timings for Kmer Search: {}", timeit);
110    }
111    void find(const cseq& query, result_vector& results, unsigned int max);
112    void build();
113    void store(const fs::path& filename);
114    bool try_load(const fs::path& filename);
115};
116
117
118using kmer_search_key_t = std::pair<fs::path, idx_flags>;
119static std::map<kmer_search_key_t, std::shared_ptr<kmer_search::impl>> indices;
120static std::mutex indices_access;
121
122kmer_search*
123kmer_search::get_kmer_search(const fs::path& filename, int k, bool nofast) {
124    idx_flags flags;
125    flags.k = k;
126    flags.nofast = nofast;
127    kmer_search_key_t key(filename, flags);
128    if (indices.count(key) == 0u) {
129        std::shared_ptr<kmer_search::impl> pimpl(new impl(query_arb::getARBDB(filename), k, nofast));
130        std::lock_guard<std::mutex> lock(indices_access);
131        indices[key] = pimpl;
132    }
133    return new kmer_search(indices[key]);
134}
135
136void
137kmer_search::release_kmer_search(const fs::path& filename, int k, bool nofast) {
138    idx_flags flags;
139    flags.k = k;
140    flags.nofast = nofast;
141    kmer_search_key_t key(filename, flags);
142    std::lock_guard<std::mutex> lock(indices_access);
143    indices.erase(key);
144}
145
146kmer_search::kmer_search(std::shared_ptr<kmer_search::impl> pimpl_)
147    : pimpl(pimpl_) {}
148kmer_search::~kmer_search() = default;
149unsigned int kmer_search::size() const { return pimpl->n_sequences; }
150
151
152class IndexBuilder {
153    kmer_search::impl *idx;
154    logger_progress *p;
155public:
156    std::vector<vlimap*> kmer_idx;
157
158    void operator()(const tbb::blocked_range<size_t>& r) {
159        size_t end = r.end();
160        std::unordered_set<unsigned int> seen;
161        for (size_t i = r.begin(); i < end; ++i) {
162            const cseq& c = idx->arbdb->getCseqUncached(idx->sequence_names[i]);
163            const auto& bases = c.getAlignedBases();
164            if (idx->nofast) {
165                for (const auto& kmer: unique_kmers(bases, seen, idx->k)) {
166                    if (kmer_idx[kmer] == nullptr) {
167                        kmer_idx[kmer] = new vlimap(idx->n_sequences);
168                    }
169                    kmer_idx[kmer]->push_back(i);
170                }
171            } else {
172                for (unsigned int kmer: unique_prefix_kmers(bases, seen, (int)idx->k, 1, BASE_A)) {
173                    if (kmer_idx[kmer] == nullptr) {
174                        kmer_idx[kmer] = new vlimap(idx->n_sequences);
175                    }
176                    kmer_idx[kmer]->push_back(i);
177                }
178            }
179            p->update();
180        }
181    }
182
183    void join(IndexBuilder& other) {
184        for (unsigned int i = 0; i < idx->n_kmers; ++i) {
185            if (other.kmer_idx[i] != nullptr) {
186                if (kmer_idx[i] != nullptr) {
187                    kmer_idx[i]->append(*other.kmer_idx[i]);
188                } else {
189                    kmer_idx[i] = other.kmer_idx[i];
190                    other.kmer_idx[i] = nullptr;
191                }
192            }
193        }
194    }
195
196    ~IndexBuilder() {
197        for (unsigned int i = 0; i < idx->n_kmers; ++i) {
198            delete kmer_idx[i];
199        }
200    }
201
202    IndexBuilder(IndexBuilder& x, tbb::split)
203        : idx(x.idx), p(x.p), kmer_idx(x.idx->n_kmers, nullptr)
204    {
205    }
206
207    IndexBuilder(kmer_search::impl *idx_, logger_progress *p_)
208        : idx(idx_), p(p_), kmer_idx(idx->n_kmers, nullptr)
209    {
210    }
211};
212
213kmer_search::impl::impl(query_arb* arbdb_, int k_, bool nofast_)
214    : k(k_),
215      n_kmers(1<<(k_*2)),
216      kmer_idx(1<<(k_*2), nullptr),
217      nofast(nofast_),
218      arbdb(arbdb_)
219{
220    fs::path dbpath = arbdb->getFileName();
221    if (dbpath.compare(":") == 0) {
222        logger->warn("Remote database found. Building in memory index.");
223        build();
224        return;
225    }
226
227    fs::path idxpath = fs::path(dbpath).replace_extension("sidx");
228    if (fs::exists(idxpath) && fs::exists(dbpath)) {
229        if (fs::last_write_time(idxpath) >= fs::last_write_time(dbpath)) {
230            if (try_load(idxpath)) {
231                return;
232            }
233        } else {
234            logger->warn("Reference {} newer than {}", dbpath, idxpath);
235        }
236        logger->warn("Failed to load {} - rebuilding", idxpath);
237    } else {
238        logger->warn("No cached index found.");
239    }
240    build();
241    store(idxpath);
242}
243
244void
245kmer_search::impl::build() {
246    timestamp start;
247
248    sequence_names = arbdb->getSequenceNames();
249    n_sequences = sequence_names.size();
250
251    logger_progress p(logger, "Building Index", n_sequences);
252
253    IndexBuilder bi(this, &p);
254    tbb::parallel_reduce(tbb::blocked_range<size_t>(0, n_sequences), bi);
255    p += n_sequences - p.count();
256
257    kmer_idx.clear();
258    kmer_idx.reserve(n_kmers);
259    int total = 0;
260    p.restart("Compressing", n_kmers);
261    for (unsigned int i=0; i < n_kmers; i++) {
262        ++p;
263        if (bi.kmer_idx[i] != nullptr) {
264            if (bi.kmer_idx[i]->size() > n_sequences / 2) {
265                bi.kmer_idx[i]->invert();
266            }
267            total += bi.kmer_idx[i]->size();
268            kmer_idx.push_back(new vlimap(*bi.kmer_idx[i]));
269        } else {
270            kmer_idx.push_back(nullptr);
271        }
272    }
273
274    logger->info("Built index from {} sequences ({} refs) in {}",
275                 n_sequences, total, timestamp()-start);
276}
277
278void
279kmer_search::impl::store(const fs::path& filename) {
280    std::string native = filename.native();
281    std::ofstream out(native, std::ofstream::binary);
282    idx_header header;
283    header.flags.k = k;
284    header.flags.nofast = nofast;
285    header.n_sequences = n_sequences;
286    out.write((char*)&header, sizeof(idx_header));
287    for (auto& name : sequence_names) {
288        out << name << std::endl;
289    }
290    vlimap emptymap(n_sequences);
291    for (unsigned int i = 0; i < n_kmers; i++) {
292        if (kmer_idx[i] != nullptr && kmer_idx[i]->size() > 0) {
293            emptymap.push_back(i);
294        }
295    }
296    emptymap.write(out);
297
298    size_t idxno = 0;
299    for (auto inc : emptymap) {
300        idxno += inc;
301        kmer_idx[idxno]->write(out);
302    }
303}
304
305bool
306kmer_search::impl::try_load(const fs::path& filename) {
307    std::string native = filename.native();
308    std::ifstream in(native, std::ifstream::binary);
309    idx_header header;
310    in.read((char*)&header, sizeof(idx_header));
311    if (idx_header_magic != header.magic) {
312        logger->error("Index file {} has wrong magic. Aborting.",
313                      filename);
314        exit(1);
315    }
316    if (idx_header_vers != header.vers) {
317        logger->error("Index file {} created by different version.",
318                      filename);
319        return false;
320    }
321    if (k != header.flags.k) {
322        logger->error("Index file {} build for k={} not k={}",
323                      filename, header.flags.k, k);
324        return false;
325    }
326    if (nofast != header.flags.nofast) {
327        logger->error("Index file {} build for {} (want {})",
328                      nofast?"no fast":"fast", nofast?"fast":"nofast");
329        return false;
330    }
331    n_sequences = header.n_sequences;
332    for (unsigned int i = 0; i < n_sequences; i++) {
333        string name;
334        getline(in, name);
335        sequence_names.push_back(name);
336    }
337    vlimap emptymap(n_sequences);
338    emptymap.read(in);
339    size_t idxno = 0;
340    int total = 0;
341    for (auto inc : emptymap) {
342        idxno += inc;
343        vlimap *idx = new vlimap(n_sequences);
344        idx->read(in);
345        kmer_idx[idxno] = idx;
346        total += idx->size();
347    }
348    logger->info("Index contains {} sequences ({} refs)", n_sequences, total);
349
350    return true;
351}
352
353double
354kmer_search::match(result_vector&, const cseq&, int, int, float, float, query_arb*,
355                   bool, int, int, int, int, bool) {
356    throw std::runtime_error("Legacy family composition not implemented for internal search");
357    return 0;
358}
359
360void
361kmer_search::find(const cseq& query, result_vector& results, unsigned int max) {
362    pimpl->find(query, results, max);
363}
364
365void
366kmer_search::impl::find(const cseq& query, result_vector& results, unsigned int max) {
367    if (max > n_sequences) {
368        max = n_sequences;
369    }
370    if (max == 0) {
371        return;
372    }
373    idset::inc_t scores(n_sequences, 0);
374    using pair = std::pair<idset::inc_t::value_type, int>;
375    std::vector<pair> ranks;
376
377    std::string bases = query.getBases();
378    if (!cache.try_get(bases, ranks)) {
379        timer& timing = timeit.get_timer();
380        timing.start();
381        const vector<aligned_base>& bases = query.getAlignedBases();
382
383        timing.stop("load query");
384
385        std::unordered_set<unsigned int> seen(query.size()*2-1);
386
387        int offset = 0;
388        if (nofast) {
389            // for (unsigned int kmer: unique_kmers(bases, seen, k)) {
390            for (unsigned int kmer: all_kmers(bases, k, 1)) {
391                if (kmer_idx[kmer] != nullptr) {
392                    offset += kmer_idx[kmer]->increment(scores);
393                }
394            }
395        } else { // fast
396            // for (unsigned int kmer: unique_prefix_kmers(bases, seen, k, 1, BASE_A)) {
397            for (unsigned int kmer: prefix_kmers(bases, k, 1, BASE_A)) {
398                if (kmer_idx[kmer] != nullptr) {
399                    offset += kmer_idx[kmer]->increment(scores);
400                }
401            }
402        }
403        timing.stop("count kmers");
404
405        ranks.reserve(n_sequences);
406        int n = 0;
407        for (auto score: scores) {
408            ranks.emplace_back(score + offset, n++);
409        }
410        timing.stop("store");
411    }
412    std::partial_sort(ranks.begin(), ranks.begin()+max, ranks.end(),  std::greater<pair>());
413
414    results.clear();
415    results.reserve(max);
416    for (unsigned int i=0; i<max; i++) {
417        results.emplace_back(ranks[i].first, &arbdb->getCseq(sequence_names[ranks[i].second]));
418    }
419    cache.store(std::move(bases), std::move(ranks));
420}
421
422/*
423  Local Variables:
424  mode:c++
425  c-file-style:"stroustrup"
426  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
427  indent-tabs-mode:nil
428  fill-column:99
429  End:
430*/
431// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
Note: See TracBrowser for help on using the repository browser.