|
| 1 | +// ========================================================================== |
| 2 | +// radix_inplace.h |
| 3 | +// ========================================================================== |
| 4 | +// Copyright (c) 2006-2015, Knut Reinert, FU Berlin |
| 5 | +// All rights reserved. |
| 6 | +// |
| 7 | +// Redistribution and use in source and binary forms, with or without |
| 8 | +// modification, are permitted provided that the following conditions are met: |
| 9 | +// |
| 10 | +// * Redistributions of source code must retain the above copyright |
| 11 | +// notice, this list of conditions and the following disclaimer. |
| 12 | +// * Redistributions in binary form must reproduce the above copyright |
| 13 | +// notice, this list of conditions and the following disclaimer in the |
| 14 | +// documentation and/or other materials provided with the distribution. |
| 15 | +// * Neither the name of Knut Reinert or the FU Berlin nor the names of |
| 16 | +// its contributors may be used to endorse or promote products derived |
| 17 | +// from this software without specific prior written permission. |
| 18 | +// |
| 19 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| 20 | +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 21 | +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 22 | +// ARE DISCLAIMED. IN NO EVENT SHALL KNUT REINERT OR THE FU BERLIN BE LIABLE |
| 23 | +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 24 | +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| 25 | +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| 26 | +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| 27 | +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| 28 | +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| 29 | +// DAMAGE. |
| 30 | +// |
| 31 | +// ========================================================================== |
| 32 | +// Author: Sascha Meiers <sascha.meiers@embl.de> |
| 33 | +// Author: Hannes Hauswedell <hannes.hauswedell @ fu-berlin.de> |
| 34 | +// ========================================================================== |
| 35 | +// The Radix Sort functions are adapted from Martin Frith's "last" |
| 36 | +// tool (last.cbrc.jp), but he himself adapted the code from McIlroy, Bostic: |
| 37 | +// "Engineering radix sort" as well as Karkkainen, Rantala: "Engineering radix |
| 38 | +// sort for strings". Thanks to Martin for showing this to me. |
| 39 | +// ============================================================================ |
| 40 | + |
| 41 | +#ifndef CORE_INCLUDE_SEQAN_INDEX_RADIX_INPLACE_H_ |
| 42 | +#define CORE_INCLUDE_SEQAN_INDEX_RADIX_INPLACE_H_ |
| 43 | + |
| 44 | +namespace SEQAN_NAMESPACE_MAIN |
| 45 | +{ |
| 46 | + |
| 47 | +// ============================================================================ |
| 48 | +// struct RadixTextAccessor |
| 49 | +// ============================================================================ |
| 50 | + |
| 51 | +template < |
| 52 | + typename TSAValue, // input |
| 53 | + typename TString, // string object that is referenced |
| 54 | + typename TSpec = void, // Suffix modifier |
| 55 | + typename TSize = unsigned> // return type (ordValue) |
| 56 | +struct RadixTextAccessor; |
| 57 | +/* |
| 58 | + * NOTE: |
| 59 | + * These accessors cannot resolve the correct order of out-of-bound-positions, |
| 60 | + * i.e. when suffixes are equal up to their last character. |
| 61 | + * All these cases get collected in a 0 bucket. |
| 62 | + * The InplaceRadixSorter takes care of that by calling a special |
| 63 | + * sort function on the 0 buckets. |
| 64 | + */ |
| 65 | + |
| 66 | +// ---------------------------------------------------------------------------- |
| 67 | +// struct RadixTextAccessor [String] |
| 68 | +// ---------------------------------------------------------------------------- |
| 69 | + |
| 70 | +template <typename TSAValue, typename TString, typename TSize> |
| 71 | +struct RadixTextAccessor<TSAValue, TString, void, TSize> : |
| 72 | + public std::unary_function<TSAValue, TSize> |
| 73 | +{ |
| 74 | + TString const & text; |
| 75 | + typename Size<TString>::Type const L; |
| 76 | + |
| 77 | + RadixTextAccessor(TString const &str) : text(str), L(length(str)) |
| 78 | + {} |
| 79 | + |
| 80 | + template <typename TSize2> |
| 81 | + inline TSize operator()(TSAValue const &x, TSize2 depth) const |
| 82 | + { |
| 83 | + typename Size<TString>::Type pos = x + depth; |
| 84 | + if (pos >= L) return 0; |
| 85 | + TSize ret = ordValue(text[pos]); |
| 86 | + return ret+1; |
| 87 | + } |
| 88 | +}; |
| 89 | + |
| 90 | +// ---------------------------------------------------------------------------- |
| 91 | +// struct RadixTextAccessor [StringSet] |
| 92 | +// ---------------------------------------------------------------------------- |
| 93 | + |
| 94 | +template <typename TSAValue, typename TString, typename TSetSpec, typename TSize> |
| 95 | +struct RadixTextAccessor<TSAValue, StringSet<TString, TSetSpec>, void, TSize> : |
| 96 | + public std::unary_function<TSAValue, TSize> |
| 97 | + |
| 98 | +{ |
| 99 | + StringSet<TString, TSetSpec> const & text; |
| 100 | + String<typename Size<TString>::Type> L; |
| 101 | + |
| 102 | + RadixTextAccessor(StringSet<TString, TSetSpec> const &str) : text(str) |
| 103 | + { |
| 104 | + resize(L, length(text), Exact()); |
| 105 | + for(typename Size<TString>::Type i = 0; i < length(text); ++i) |
| 106 | + L[i] = length(text[i]); |
| 107 | + } |
| 108 | + |
| 109 | + template <typename TSize2> |
| 110 | + inline TSize operator()(TSAValue const &x, TSize2 depth) const |
| 111 | + { |
| 112 | + typename Size<TString>::Type pos = getSeqOffset(x) + depth; |
| 113 | + typename Size<TString>::Type seq = getSeqNo(x); |
| 114 | + if (pos >= L[seq]) return 0; |
| 115 | + TSize ret = ordValue(text[seq][pos]); |
| 116 | + return ret+1; |
| 117 | + } |
| 118 | +}; |
| 119 | + |
| 120 | +// ---------------------------------------------------------------------------- |
| 121 | +// struct _ZeroBucketComparator [StringSet] |
| 122 | +// ---------------------------------------------------------------------------- |
| 123 | +// Functors to compare suffixes from 0 bucket (suffixes that are lex. equal) |
| 124 | +// ---------------------------------------------------------------------------- |
| 125 | + |
| 126 | +template <typename TSAValue, typename TLimitsString=Nothing const> |
| 127 | +struct _ZeroBucketComparator |
| 128 | +{ |
| 129 | + TLimitsString const & limits; |
| 130 | + _ZeroBucketComparator(TLimitsString const & lim) : limits(lim) { /*std::cout << "limits: " << limits << std::endl;*/ } |
| 131 | + |
| 132 | + inline bool operator()(TSAValue const & a, TSAValue const & b) const |
| 133 | + { |
| 134 | + typename Size<TLimitsString>::Type lena = limits[getSeqNo(a)+1]-limits[getSeqNo(a)] - getSeqOffset(a); |
| 135 | + typename Size<TLimitsString>::Type lenb = limits[getSeqNo(b)+1]-limits[getSeqNo(b)] - getSeqOffset(b); |
| 136 | + if (lena == lenb) |
| 137 | + return getSeqNo(a) > getSeqNo(b); |
| 138 | + else |
| 139 | + return lena < lenb; |
| 140 | + } |
| 141 | +}; |
| 142 | + |
| 143 | +// ---------------------------------------------------------------------------- |
| 144 | +// struct _ZeroBucketComparator [String] |
| 145 | +// ---------------------------------------------------------------------------- |
| 146 | + |
| 147 | +template <typename TSAValue> |
| 148 | +struct _ZeroBucketComparator<TSAValue, Nothing const> |
| 149 | +{ |
| 150 | + _ZeroBucketComparator(Nothing const &) {} |
| 151 | + _ZeroBucketComparator(Nothing &) {} |
| 152 | + |
| 153 | + |
| 154 | + inline bool operator()(TSAValue const & a, TSAValue const & b) const |
| 155 | + { |
| 156 | + return a > b; |
| 157 | + } |
| 158 | +}; |
| 159 | + |
| 160 | +// ---------------------------------------------------------------------------- |
| 161 | +// struct RadixSortContext_ |
| 162 | +// ---------------------------------------------------------------------------- |
| 163 | + |
| 164 | +template <typename TAccessFunctor, // text accessor |
| 165 | + typename TOrderFunctor, // For seperate sort of the 0 bucket. |
| 166 | + typename TSize, // type of depth and bucketCount a.s.o |
| 167 | + unsigned Q> // alph size = ValueSize + 1 |
| 168 | +struct RadixSortContext_ |
| 169 | +{ |
| 170 | + static_assert(Q < 256, "Alphabet size must be smaller 256!"); //TODO really? |
| 171 | + typedef typename TAccessFunctor::argument_type TSAValue; |
| 172 | + typedef typename TAccessFunctor::result_type TOrdValue; // unsigned |
| 173 | + |
| 174 | + static const unsigned ORACLESIZE = 256; |
| 175 | + TAccessFunctor textAccess; |
| 176 | + TOrderFunctor comp; |
| 177 | + |
| 178 | + TSize bucketSize[Q]; |
| 179 | + std::array<TSAValue*,Q> bucketEnd; |
| 180 | + |
| 181 | + RadixSortContext_(TAccessFunctor const & f, TOrderFunctor const & c) : |
| 182 | + textAccess(f), comp(c) |
| 183 | + {} |
| 184 | +}; |
| 185 | + |
| 186 | +template <typename TAccessFunctor, |
| 187 | + typename TOrderFunctor, |
| 188 | + typename TSize, |
| 189 | + unsigned Q> |
| 190 | +inline void |
| 191 | +clear(RadixSortContext_<TAccessFunctor, TOrderFunctor, TSize, Q> & context) |
| 192 | +{ |
| 193 | + memset(context.bucketSize, 0, sizeof(TSize)*Q); |
| 194 | +} |
| 195 | + |
| 196 | +// ---------------------------------------------------------------------------- |
| 197 | +// Function radixSort() |
| 198 | +// ---------------------------------------------------------------------------- |
| 199 | + |
| 200 | +template <typename TSAValue, typename TSize, |
| 201 | + typename TAccessFunctor, typename TOrderFunctor, unsigned Q> |
| 202 | +inline void |
| 203 | +radixSort(std::vector<std::tuple<TSAValue*, TSAValue*, TSize> > & stack, |
| 204 | + RadixSortContext_<TAccessFunctor, TOrderFunctor, TSize, Q> & context, |
| 205 | + std::tuple<TSAValue*, TSAValue*, TSize> const & item) |
| 206 | +{ |
| 207 | + typedef RadixSortContext_<TAccessFunctor, TOrderFunctor, TSize, Q> TContext; |
| 208 | + typedef typename TContext::TOrdValue TOrdValue; |
| 209 | + static_assert(std::is_same<TSAValue, typename TContext::TSAValue>::value, "TSAValue mismatch!"); |
| 210 | + |
| 211 | + clear(context); |
| 212 | + |
| 213 | + // get bucket sizes (i.e. letter counts): |
| 214 | + // The intermediate oracle array makes it faster (see "Engineering |
| 215 | + // Radix Sort for Strings" by J Karkkainen & T Rantala) |
| 216 | + for(TSAValue* i = std::get<0>(item); i < std::get<1>(item); /* noop */ ) |
| 217 | + { |
| 218 | + // buffer for the next chars |
| 219 | + TOrdValue oracle [TContext::ORACLESIZE]; |
| 220 | + TOrdValue* oracleEnd = oracle + std::min(static_cast<std::size_t>(TContext::ORACLESIZE), |
| 221 | + static_cast<std::size_t>(std::get<1>(item) - i)); |
| 222 | + |
| 223 | + for(TOrdValue* j = oracle; j < oracleEnd; ++j ) |
| 224 | + *j = context.textAccess(*i++, std::get<2>(item)); |
| 225 | + |
| 226 | + for(TOrdValue* j = oracle; j < oracleEnd; ++j ) |
| 227 | + ++context.bucketSize[*j]; |
| 228 | + } |
| 229 | + |
| 230 | + // get bucket std::get<1>(item)s, and put buckets on the stack to sort within them later: |
| 231 | + // EDIT: 0 bucket is not sorted here, but later. |
| 232 | + TSize zeroBucketSize = context.bucketSize[0]; |
| 233 | + TSAValue* pos = std::get<0>(item) + context.bucketSize[0]; |
| 234 | + context.bucketEnd[0] = pos; |
| 235 | + |
| 236 | + for(unsigned i = 1; i < Q; ++i ) |
| 237 | + { |
| 238 | + TSAValue* nextPos = pos + context.bucketSize[i]; |
| 239 | + if (nextPos - pos > 1) |
| 240 | + stack.emplace_back(pos, nextPos, std::get<2>(item)+1); |
| 241 | + |
| 242 | + pos = nextPos; |
| 243 | + context.bucketEnd[i] = pos; |
| 244 | + } |
| 245 | + |
| 246 | + // permute items into the correct buckets: |
| 247 | + for(TSAValue* i = std::get<0>(item); i < std::get<1>(item); ) |
| 248 | + { |
| 249 | + TOrdValue subset; // unsigned is faster than uchar! |
| 250 | + TSAValue holdOut = *i; |
| 251 | + while(--context.bucketEnd[subset = context.textAccess(holdOut, std::get<2>(item))] > i ) |
| 252 | + std::swap(*context.bucketEnd[subset], holdOut); |
| 253 | + *i = holdOut; |
| 254 | + i += context.bucketSize[subset]; |
| 255 | + context.bucketSize[subset] = 0; // reset it so we can reuse it //TODO check if we need this, since we clear already! |
| 256 | + } |
| 257 | + |
| 258 | + // sort the 0 bucket using std::sort |
| 259 | + if(zeroBucketSize > 1) |
| 260 | + std::sort(std::get<0>(item), std::get<0>(item) + zeroBucketSize, context.comp); |
| 261 | +} |
| 262 | + |
| 263 | +// ---------------------------------------------------------------------------- |
| 264 | +// Function inplaceFullRadixSort() [default] |
| 265 | +// ---------------------------------------------------------------------------- |
| 266 | + |
| 267 | +//TODO: play with this value |
| 268 | +#define _RADIX_SORT_SWITCH_TO_QUICKSORT_AT 100 |
| 269 | + |
| 270 | +#ifdef _OPENMP |
| 271 | +#define N_THREADS omp_get_max_threads() |
| 272 | +#define I_THREAD omp_get_thread_num() |
| 273 | +#else |
| 274 | +#define N_THREADS 1 |
| 275 | +#define I_THREAD 0 |
| 276 | +#endif |
| 277 | + |
| 278 | +// TODO: serial version |
| 279 | +// TODO: possibly do multiple runs of "secondStep" if alphabet size to small |
| 280 | +// TODO: possibly quicksort directly on buckets in third steps, if buckets have been made small enough |
| 281 | + |
| 282 | +template <typename TSA, typename TText, typename TLambda> |
| 283 | +void inPlaceRadixSort(TSA & sa, TText const & str, TLambda const & progressCallback = [] (unsigned) {}) |
| 284 | +{ |
| 285 | + typedef typename Value<typename Concatenator<TText>::Type>::Type TAlphabet; |
| 286 | + typedef typename Value<TSA>::Type TSAValue; |
| 287 | + typedef typename Size<TText>::Type TSize; |
| 288 | + typedef std::tuple<TSAValue*, TSAValue*, TSize> TItem; |
| 289 | + typedef typename StringSetLimits<TText const>::Type TLimitsString; // "Nothing" for Strings |
| 290 | + |
| 291 | + typedef RadixTextAccessor<TSAValue, TText> TAccessor; |
| 292 | + typedef _ZeroBucketComparator<TSAValue,TLimitsString> TZeroComp; |
| 293 | + |
| 294 | + static const unsigned SIGMA = static_cast<unsigned>(ValueSize<TAlphabet>::VALUE) + 1; |
| 295 | + SEQAN_ASSERT_LT_MSG(SIGMA, 1000u, "Attention: inplace radix sort is not suited for large alphabets"); |
| 296 | + |
| 297 | + typedef RadixSortContext_<TAccessor, TZeroComp, TSize, SIGMA> TContext; |
| 298 | + |
| 299 | + if (empty(sa)) return; // otherwise access sa[0] fails |
| 300 | + |
| 301 | + /* stacks */ |
| 302 | + std::vector<TItem> firstStack; |
| 303 | + firstStack.reserve(SIGMA); |
| 304 | + std::vector<TItem> secondStack; |
| 305 | + secondStack.reserve(SIGMA*SIGMA); |
| 306 | + std::vector<std::vector<TItem>> lStack(N_THREADS); // one per thread |
| 307 | + // reduce memory allocations in threads by reserving space |
| 308 | + for (auto & stack : lStack) |
| 309 | + stack.reserve(length(sa) / 1000); |
| 310 | + |
| 311 | + /* contexts */ |
| 312 | + TContext firstSecondContext{TAccessor(str), TZeroComp(stringSetLimits(str))}; |
| 313 | + std::vector<TContext> lContext(N_THREADS, TContext{TAccessor(str), TZeroComp(stringSetLimits(str))}); |
| 314 | + |
| 315 | + // sort by the first character |
| 316 | + radixSort(firstStack, firstSecondContext, TItem(&sa[0], &sa[0]+length(sa), 0)); |
| 317 | + |
| 318 | + progressCallback(5); // 5% progress guess after first char |
| 319 | + |
| 320 | + // sort by second character |
| 321 | + SEQAN_OMP_PRAGMA(parallel for schedule(dynamic)) |
| 322 | + for (unsigned j = 0; j < length(firstStack); ++j) |
| 323 | + { |
| 324 | + TItem & i = firstStack[j]; |
| 325 | + if (std::get<1>(i) - std::get<0>(i) < _RADIX_SORT_SWITCH_TO_QUICKSORT_AT) |
| 326 | + std::sort(std::get<0>(i), |
| 327 | + std::get<1>(i), |
| 328 | + SuffixLess_<TSAValue, TText const>(str, std::get<2>(i))); |
| 329 | + else if (std::get<1>(i) - std::get<0>(i) >= 2) |
| 330 | + radixSort(lStack[I_THREAD], lContext[I_THREAD], i); |
| 331 | + } |
| 332 | + |
| 333 | + // merge local stacks and clear for next round |
| 334 | + for (auto & stack : lStack) |
| 335 | + { |
| 336 | + secondStack.insert(secondStack.end(), stack.begin(), stack.end()); |
| 337 | + stack.clear(); |
| 338 | + } |
| 339 | + |
| 340 | + progressCallback(10); // 10% progress guess after second char |
| 341 | + |
| 342 | + // sort rest |
| 343 | + SEQAN_OMP_PRAGMA(parallel for schedule(dynamic)) |
| 344 | + for (unsigned j = 0; j < secondStack.size(); ++j) |
| 345 | + { |
| 346 | + lStack[I_THREAD].push_back(secondStack[j]); |
| 347 | + |
| 348 | + while (!lStack[I_THREAD].empty()) |
| 349 | + { |
| 350 | + TItem i = lStack[I_THREAD].back(); |
| 351 | + lStack[I_THREAD].pop_back(); |
| 352 | + |
| 353 | + if (std::get<1>(i) - std::get<0>(i) < _RADIX_SORT_SWITCH_TO_QUICKSORT_AT) |
| 354 | + std::sort(std::get<0>(i), |
| 355 | + std::get<1>(i), |
| 356 | + SuffixLess_<TSAValue, TText const>(str, std::get<2>(i))); |
| 357 | + else if (std::get<1>(i) - std::get<0>(i) >= 2) |
| 358 | + radixSort(lStack[I_THREAD], lContext[I_THREAD], i); |
| 359 | + } |
| 360 | + |
| 361 | + // progressCallback must be thread safe and cope with smaller numbers after big numbers |
| 362 | + // remaining characters alloted 90% of total progress |
| 363 | + progressCallback(10 + (j * 90) / secondStack.size()); |
| 364 | + } |
| 365 | + |
| 366 | + progressCallback(100); // done |
| 367 | +} |
| 368 | + |
| 369 | +} |
| 370 | + |
| 371 | +#endif // #ifndef CORE_INCLUDE_SEQAN_INDEX_RADIX_INPLACE_H_ |
0 commit comments