/* deadzones: A program for identifying genomic deadzones * Copyright (C) 2009 University of Southern California and * Andrew D. Smith * * Authors: Andrew D. Smith * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "smithlab_utils.hpp" #include "GenomicRegion.hpp" #include "OptionParser.hpp" #include #include #if defined(_OPENMP) #include #else #include #endif #include using std::tr1::unordered_map; using std::string; using std::vector; using std::cout; using std::endl; using std::cerr; class IndexLess { public: IndexLess(const size_t k, const string &s) : kmer(k), itr(s.begin()) {} bool operator()(size_t a, size_t b) const { const string::const_iterator lim(itr + a + kmer); string::const_iterator a_itr(itr + a), b_itr(itr + b); while (a_itr < lim && *(a_itr) == *(b_itr)) { ++a_itr; ++b_itr; } return (*a_itr < *b_itr && a_itr < lim); } private: size_t kmer; const string::const_iterator itr; }; template bool lexico_equal(In first, In last, In first2) { while (first != last) if (*first++ != *first2++) return false; return true; } static void sort_index(const bool VERBOSE, const size_t kmer, const string &prefix, const string &seq, vector &ambigs, const unordered_map &invalid_pool) { if (VERBOSE) cerr << "[BUILDING INDEX] "; vector index; const string::const_iterator lim(seq.end() - kmer + 1); for (string::const_iterator j = seq.begin(); j != lim; ++j) if ((lexico_equal(prefix.begin(), prefix.end(), j)) && (!(invalid_pool.find(j - seq.begin()) != invalid_pool.end()))) index.push_back(j - seq.begin()); if (!index.empty()) { if (VERBOSE) cerr << "[SORTING INDEX] "; IndexLess index_less(kmer, seq); #if defined(_OPENMP) __gnu_parallel::sort(index.begin(), index.end(), index_less); #else sort(index.begin(), index.end(), index_less); #endif if (VERBOSE) cerr << "[FINDING DEADS] "; const size_t len = seq.length(); const string::const_iterator start(seq.begin()); const string::const_iterator end(start + kmer); size_t prev = index.front(); bool prev_inserted = false; for (size_t i = 1; i < index.size(); ++i) { const size_t curr = index[i]; if (lexico_equal(start + prev, end + prev, start + curr) && prev + curr != len) { if (!prev_inserted) ambigs.push_back(prev); ambigs.push_back(curr); prev_inserted = true; } else prev_inserted = false; prev = curr; } } else if (VERBOSE) cerr << "[EMPTY INDEX] "; } static void sort_index(const bool VERBOSE, const bool BISULFITE, const bool AG_WILDCARD, const size_t kmer, const size_t prefix_len, const string &seq, vector &ambigs, const unordered_map &invalid_pool) { static const float DENOM = CLOCKS_PER_SEC; const size_t n_prefix = static_cast(pow(smithlab::alphabet_size, prefix_len)); for (size_t i = 0; i < n_prefix; ++i) { const string prefix(i2mer(prefix_len, i)); if (!BISULFITE || ((!AG_WILDCARD && prefix.find('C') == string::npos) || (AG_WILDCARD && prefix.find('G') == string::npos))) { const clock_t start(clock()); if (VERBOSE) cerr << "[PREFIX=" << prefix << "] "; sort_index(VERBOSE, kmer, prefix, seq, ambigs, invalid_pool); const clock_t end(clock()); if (VERBOSE) cerr << "[" << (end - start)/DENOM << " SEC] [DONE]" << endl; } } } static void write_dead(std::ofstream &out, const string &chrom_name, const char strand, vector::const_iterator curr, const vector::const_iterator lim) { assert(curr <= lim); size_t prev_ambig = *curr; ++curr; for (; curr < lim; ++curr) if (*curr - 1 != *(curr - 1)) { out << GenomicRegion(chrom_name, prev_ambig, *(curr - 1) + 1, "X", 0, strand) << endl; prev_ambig = *curr; } out << GenomicRegion(chrom_name, prev_ambig, *(curr - 1) + 1, "X", 0, strand) << endl; } static void get_dead(const bool VERBOSE, const string &outfile, const size_t kmer, const vector &seqoffsets, const vector &chrom_names, vector &ambigs) { const size_t max_offset = seqoffsets.back(); for (size_t i = 0; i < ambigs.size(); ++i) { if (ambigs[i] >= max_offset) ambigs[i] = 2*max_offset - ambigs[i] - kmer; assert(ambigs[i] < max_offset); } sort(ambigs.begin(), ambigs.end()); ambigs.erase(std::unique(ambigs.begin(), ambigs.end()), ambigs.end()); vector offset_idx; size_t n_ambigs = ambigs.size(); for (size_t i = 0, j = 0; i < seqoffsets.size() && j < n_ambigs; ++i) { while (j < n_ambigs && ambigs[j] < seqoffsets[i]) ++j; offset_idx.push_back(j); } size_t total_length = 0; n_ambigs = ambigs.size(); for (size_t i = 0, prev_idx = 0; i < offset_idx.size(); ++i) { for (size_t j = prev_idx; j < offset_idx[i]; ++j) { assert(j < n_ambigs); ambigs[j] -= total_length; } prev_idx = offset_idx[i]; total_length = seqoffsets[i]; } std::ofstream out(outfile.c_str()); for (size_t i = 0, prev_idx = 0; i < offset_idx.size(); ++i) { write_dead(out, chrom_names[i], '+', ambigs.begin() + prev_idx, ambigs.begin() + offset_idx[i]); prev_idx = offset_idx[i]; } out.close(); } static void get_dead_bs(const bool VERBOSE, const string &outfile, const size_t kmer, const vector &seqoffsets, const vector &chrom_names, vector &ambigs) { assert(!ambigs.empty()); sort(ambigs.begin(), ambigs.end()); const size_t max_offset = seqoffsets.back(); if (VERBOSE) cerr << "[PREPARING POS-STRAND BS DEADS]" << endl; // Do the positive strand bisulfite deadzones const size_t lim = lower_bound(ambigs.begin(), ambigs.end(), max_offset) - ambigs.begin(); // make a partition vector of the offsets, the last being "lim" vector offset_idx; size_t n_ambigs = ambigs.size(); for (size_t i = 0, j = 0; i < seqoffsets.size() && j < n_ambigs; ++i) { while (j < n_ambigs && ambigs[j] < seqoffsets[i]) ++j; offset_idx.push_back(j); } size_t total_length = 0; for (size_t i = 0, prev_idx = 0; i < offset_idx.size(); ++i) { for (size_t j = prev_idx; j < offset_idx[i]; ++j) ambigs[j] -= total_length; prev_idx = offset_idx[i]; total_length = seqoffsets[i]; } std::ofstream out(outfile.c_str()); for (size_t i = 0, prev_idx = 0; i < offset_idx.size(); ++i) { write_dead(out, chrom_names[i], '+', ambigs.begin() + prev_idx, ambigs.begin() + offset_idx[i]); prev_idx = offset_idx[i]; } if (VERBOSE) cerr << "[PREPARING NEG-STRAND BS DEADS]" << endl; // Move the negative strand deadzones into the first portion of the // vector and correct their indexes. for (size_t j = 0, i = lim; i < ambigs.size(); ++i) ambigs[j++] = 2*max_offset - ambigs[i] - kmer; ambigs.erase(ambigs.end() - lim, ambigs.end()); reverse(ambigs.begin(), ambigs.end()); offset_idx.clear(); n_ambigs = ambigs.size(); for (size_t i = 0, j = 0; i < seqoffsets.size() && j < n_ambigs; ++i) { while (j < n_ambigs && ambigs[j] < seqoffsets[i]) ++j; offset_idx.push_back(j); } total_length = 0; for (size_t i = 0, prev_idx = 0; i < offset_idx.size(); ++i) { for (size_t j = prev_idx; j < offset_idx[i]; ++j) ambigs[j] -= total_length; prev_idx = offset_idx[i]; total_length = seqoffsets[i]; } for (size_t i = 0, prev_idx = 0; i < offset_idx.size(); ++i) { write_dead(out, chrom_names[i], '-', ambigs.begin() + prev_idx, ambigs.begin() + offset_idx[i]); prev_idx = offset_idx[i]; } out.close(); } // This function appends the reverse complement in a space efficient way static void append_revcomp(string &long_seq) { const size_t seqlen = long_seq.length(); long_seq.resize(2*seqlen); copy(long_seq.begin(), long_seq.begin() + seqlen, long_seq.begin() + seqlen); revcomp_inplace(long_seq.begin() + seqlen, long_seq.end()); } static void identify_chromosomes(const bool VERBOSE, const string fasta_suffix, const string chrom_file, vector &chrom_files) { if (VERBOSE) cerr << "[IDENTIFYING CHROMS] "; if (isdir(chrom_file.c_str())) read_dir(chrom_file, fasta_suffix, chrom_files); else chrom_files.push_back(chrom_file); if (VERBOSE) { cerr << "[DONE]" << endl << "chromosome files found (approx size):" << endl; for (vector::const_iterator i = chrom_files.begin(); i != chrom_files.end(); ++i) cerr << *i << " (" << roundf(get_filesize(*i)/1e06) << "Mbp)" << endl; cerr << endl; } } int main(int argc, const char **argv) { try { // Parameter variables size_t kmer = 0; size_t prefix_len = 0; string outfile; string fasta_suffix = "fa"; bool VERBOSE = false; bool BISULFITE = false; bool AG_WILDCARD = false; /****************** COMMAND LINE OPTIONS ********************/ OptionParser opt_parse("deadzones", "program for finding deadzones", "<1-or-more-FASTA-chrom-files>"); opt_parse.add_opt("output", 'o', "Name of output file (default: stdout)", true, outfile); opt_parse.add_opt("kmer", 'k', "Width of k-mers", true, kmer); opt_parse.add_opt("prefix", 'p', "prefix length", true, prefix_len); opt_parse.add_opt("bisulfite", 'B', "get bisulfite deadzones", false, BISULFITE); opt_parse.add_opt("ag-wild", 'A', "A/G wildcard for bisulfite", false, AG_WILDCARD); opt_parse.add_opt("suffix", 's', "suffix of FASTA files " "(assumes -c indicates dir)", false , fasta_suffix); opt_parse.add_opt("verbose", 'v', "print more run information", false, VERBOSE); vector leftover_args; opt_parse.parse(argc, argv, leftover_args); if (argc == 1 || opt_parse.help_requested()) { cerr << opt_parse.help_message() << endl; return EXIT_SUCCESS; } if (opt_parse.about_requested()) { cerr << opt_parse.about_message() << endl; return EXIT_SUCCESS; } if (opt_parse.option_missing()) { cerr << opt_parse.option_missing_message() << endl; return EXIT_SUCCESS; } if (leftover_args.empty()) { cerr << opt_parse.help_message() << endl; return EXIT_SUCCESS; } const string chrom_file = leftover_args.front(); /****************** END COMMAND LINE OPTIONS *****************/ vector seqfiles; identify_chromosomes(VERBOSE, fasta_suffix, chrom_file, seqfiles); string long_seq; vector seqoffsets; vector chrom_names; if (VERBOSE) cerr << "[READING SEQUENCE FILES]" << endl; for (size_t i = 0; i < seqfiles.size(); ++i) { if (isdir(seqfiles[i].c_str())) throw SMITHLABException("\"" + seqfiles[i] + "\" not a FASTA format sequence file?"); vector names, sequences; read_fasta_file(seqfiles[i].c_str(), names, sequences); for (size_t j = 0; j < sequences.size(); ++j) { long_seq += sequences[j]; seqoffsets.push_back(long_seq.length()); chrom_names.push_back(names[j]); } if (VERBOSE) cerr << seqfiles[i] << "\t(SEQS: " << names.size() << ")" << endl; } transform(long_seq.begin(), long_seq.end(), long_seq.begin(), std::ptr_fun(&::toupper)); if (VERBOSE) cerr << "[PREPARING CONCATENATED SEQUENCE]" << endl; append_revcomp(long_seq); if (BISULFITE) { if (AG_WILDCARD) replace(long_seq.begin(), long_seq.end(), 'G', 'A'); else replace(long_seq.begin(), long_seq.end(), 'C', 'T'); } if (VERBOSE) cerr << "[PREPARING INVALID INDEXES]" << endl; unordered_map invalid_pool; size_t max = seqoffsets[seqoffsets.size()-1]; for (size_t i = 0; i < seqoffsets.size(); i++) { for (size_t j=seqoffsets[i]-kmer+1; j<=seqoffsets[i]-1; j++) invalid_pool[j] = 1; for (size_t j=max+(max-seqoffsets[i])-kmer+1; j<=max+(max-seqoffsets[i]-1); j++) invalid_pool[j] = 1; } if (VERBOSE) cerr << "[IDENTIFYING AMBIGUOUS INDEXES]" << endl; vector ambigs; sort_index(VERBOSE, BISULFITE, AG_WILDCARD, kmer, prefix_len, long_seq, ambigs, invalid_pool); long_seq.clear(); if (ambigs.empty()) { if (VERBOSE) cerr << "[NO DEADZONES FOUND]" << endl; } else { if (BISULFITE) { if (VERBOSE) cerr << "[PREPARING BS DEADZONES]" << endl; get_dead_bs(VERBOSE, outfile, kmer, seqoffsets, chrom_names, ambigs); } else { if (VERBOSE) cerr << "[PREPARING DEADZONES]" << endl; get_dead(VERBOSE, outfile, kmer, seqoffsets, chrom_names, ambigs); } } } catch (SMITHLABException &e) { cerr << "ERROR: " << e.what() << endl; return EXIT_FAILURE; } catch (std::bad_alloc &ba) { cerr << "ERROR: could not allocate memory" << endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }