pax_global_header00006660000000000000000000000064145735506500014524gustar00rootroot0000000000000052 comment=42f8b69799570d73f61bc0d5b6562387d7da4132 bio-0.13.3/000077500000000000000000000000001457355065000123615ustar00rootroot00000000000000bio-0.13.3/.github/000077500000000000000000000000001457355065000137215ustar00rootroot00000000000000bio-0.13.3/.github/FUNDING.yml000066400000000000000000000001501457355065000155320ustar00rootroot00000000000000# These are supported funding model platforms # github: shenwei356 custom: http://paypal.me/shenwei356 bio-0.13.3/.gitignore000077500000000000000000000004241457355065000143540ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.directory .DS_Store bio-0.13.3/CHANGELOG.md000066400000000000000000000043301457355065000141720ustar00rootroot00000000000000# Changelog ### v0.13.3 - 2024-03-11 - fix dependency. ### v0.13.2 - 2024-03-11 - seq: increase default value of `seq.ComplementSeqLenThreshold` from 1000 to 1000000, which means only parallelize complement computation for sequences longer than 1Mb. ### v0.13.1 - 2024-02-22 - util: fix computation of L50. ### v0.13.0 - 2024-02-19 - seq: remove the global variable: `seq.ValidateWholeSeq`. ### v0.12.1 - 2024-01-03 - seqio/fai: when using the whole FASTA header as the sequence ID, replace possible tabs in FASTA header with spaces. ### v0.12.0 - 2023-12-04 - seqio/fastx.Reader: reuse the reader with an object pool, this requires users to call reader.Close() after using. The benefit is that it reduces memory when handling of a lot of sequences. ### v0.11.0 - 2023-12-03 Do not use this version! - seqio/fastx.Reader: delete reader.Recycle() to avoid API changes. ### v0.10.0 - 2023-12-03 - seqio/fastx.Reader: reuse reader with object pool, this requires users to call reader.Recycle() after using. ### v0.9.3 - 2023-11-11 - seqio/fastx.Reader: fix a panic of nil pointer when some files has file size of 0. ### v0.9.2 - 2023-11-10 - seq/alphabet.IsValid: fix panic: close of closed channel ### v0.9.1 - 2023-11-08 - seqio/fastx.Reader: recycle []byte buffer to save memory for reading a large number of sequences. ### v0.9.0 - 2023-06-25 - util/LengthStats: new method to compute N50 for any number - seq/alphabet: faster with asciiset ### v0.8.4 - 2023-02-14 - seqio/fai: report error for non-fasta files ### v0.8.3 - 2022-12-02 - util.LengthStats: fix computing Q1 and Q3 for one element. ### v0.8.2 - 2022-11-16 - faidx: allow empty lines at the end of sequences ### v0.8.1 - 2022-09-06 - fastx: fix concurrency bug of `record.FormatToWriter()`. ### v0.8.0 - 2022-09-06 - sketches: added an iterator of SimHash. ### v0.7.1 - 2022-04-19 - taxdump: allow reading empty merged.dmp and delnodes.dmp ### v0.6.4 - 2022-03-13 - update xopen version ### v0.6.3 - 2022-03-12 - use new versin of xopen which support .xz and .zst ### v0.6.2 - 2021-12-01 - taxdump: more robust ### v0.6.1 - 2021-11-16 - taxdump: fix pkg name in errors ### v0.6.0 - 2021-11-08 - move sketches and taxdump packages from unikmer to here bio-0.13.3/LICENSE000077500000000000000000000021131457355065000133660ustar00rootroot00000000000000Copyright (c) 2013 - 2021 Wei Shen (shenwei356@gmail.com) The MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. bio-0.13.3/README.md000077500000000000000000000012661457355065000136500ustar00rootroot00000000000000# bio - a lightweight and high-performance bioinformatics package for Golang [![Go Reference](https://pkg.go.dev/badge/github.com/shenwei356/bio.svg)](https://pkg.go.dev/github.com/shenwei356/bio) [![Go Report Card](https://goreportcard.com/badge/github.com/shenwei356/bio)](https://goreportcard.com/report/github.com/shenwei356/bio) See sub packages for details. ## Install go get -u github.com/shenwei356/bio ## Support Please [open an issue](https://github.com/shenwei356/bio/issues) to report bugs, propose new functions or ask for help. ## License Copyright (c) 2013-2021, Wei Shen (shenwei356@gmail.com) [MIT License](https://github.com/shenwei356/bio/blob/master/LICENSE) bio-0.13.3/benchmark/000077500000000000000000000000001457355065000143135ustar00rootroot00000000000000bio-0.13.3/benchmark/fastx/000077500000000000000000000000001457355065000154405ustar00rootroot00000000000000bio-0.13.3/benchmark/fastx/README.md000066400000000000000000000036411457355065000167230ustar00rootroot00000000000000 ## FASTA/Q reading and writing ***bio/seqio/fastx has a high performance close to the famous C lib [`kseq.h`](https://github.com/attractivechaos/klib/blob/master/kseq.h).*** To test the performance, three datasets and their gzip-compressed file are used: - dataset_A, bacteria genomes, 2.7G - dataset_B, human genome, 2.9G - dataset_C, Illumina reads, 2.2G Summary by [`seqkit`](https://github.com/shenwei356/seqkit): file seq_format seq_type num_seqs min_len avg_len max_len dataset_A.fa FASTA DNA 67,748 56 41,442.5 5,976,145 dataset_B.fa FASTA DNA 194 970 15,978,096.5 248,956,422 dataset_C.fq FASTQ DNA 9,186,045 100 100 100 [`seqtk`](https://github.com/lh3/seqtk/) (Version [1.3-r119-dirty](https://github.com/lh3/seqtk/commit/f6ea81cc30b9232e244dffa94187114275389132), using `kseq.h`) and [`seqkit`](https://github.com/shenwei356/seqkit) (Version [v2.4.0](https://github.com/shenwei356/seqkit/releases/tag/v2.4.0), using this package) were used to test. **Note** that `seqtk` does not support wrapped (fixed line width) ouputing, so `seqkit` uses `-w 0` to disable outputing wrapping. Script [`memusg`](https://github.com/shenwei356/memusg) is used to assess running time and peak memory usage. [Commands](https://github.com/shenwei356/bio/blob/master/benchmark/) Tests were repeated 4 times and average time and memory usage were computed. Results: Notes: - `seqkit` uses 4 threads by default. - `seqkit_t1` uses 1 thread. - `seqtk` is single-threaded. - `seqtk+gzip`: `seqtk` pipes data to the single-threaded `gzip`. - `seqtk+pigz`: `seqtk` pipes data to the multithreaded `pigz` which uses 4 threads here. ## Run ./run.pl -n 4 run_benchmark_*.sh --outfile benchmark.tsv # PLOT ./plot.sh bio-0.13.3/benchmark/fastx/benchmark.tsv000077500000000000000000000101161457355065000201320ustar00rootroot00000000000000test dataset app time mem Plain text dataset_A.fa seqkit 2.54 28332 Plain text dataset_A.fa seqkit 2.53 28432 Plain text dataset_A.fa seqkit 2.64 29680 Plain text dataset_A.fa seqkit 2.54 27520 Plain text dataset_A.fa seqkit_t1 3.77 31888 Plain text dataset_A.fa seqkit_t1 3.67 31836 Plain text dataset_A.fa seqkit_t1 3.23 29540 Plain text dataset_A.fa seqkit_t1 3.96 27884 Plain text dataset_A.fa seqtk 2.77 7684 Plain text dataset_A.fa seqtk 2.65 7664 Plain text dataset_A.fa seqtk 2.65 7756 Plain text dataset_A.fa seqtk 2.76 7668 Plain text dataset_B.fa seqkit 3.11 1039000 Plain text dataset_B.fa seqkit 3.11 1035532 Plain text dataset_B.fa seqkit 3.22 1034608 Plain text dataset_B.fa seqkit 3.21 1037848 Plain text dataset_B.fa seqkit_t1 3.23 1037696 Plain text dataset_B.fa seqkit_t1 3.34 1035184 Plain text dataset_B.fa seqkit_t1 3.46 1035184 Plain text dataset_B.fa seqkit_t1 3.35 1038660 Plain text dataset_B.fa seqtk 3.11 244940 Plain text dataset_B.fa seqtk 3.11 244988 Plain text dataset_B.fa seqtk 3.12 244948 Plain text dataset_B.fa seqtk 3.20 244944 Plain text dataset_C.fq seqkit 3.22 24560 Plain text dataset_C.fq seqkit 3.28 24496 Plain text dataset_C.fq seqkit 3.28 14176 Plain text dataset_C.fq seqkit 3.23 14296 Plain text dataset_C.fq seqkit_t1 3.79 14192 Plain text dataset_C.fq seqkit_t1 3.58 24628 Plain text dataset_C.fq seqkit_t1 3.68 14524 Plain text dataset_C.fq seqkit_t1 4.15 14288 Plain text dataset_C.fq seqtk 4.15 1028 Plain text dataset_C.fq seqtk 4.15 1088 Plain text dataset_C.fq seqtk 4.15 1020 Plain text dataset_C.fq seqtk 4.26 1028 Gzip compressed dataset_A.fa.gz seqkit 14.27 57764 Gzip compressed dataset_A.fa.gz seqkit 14.38 58748 Gzip compressed dataset_A.fa.gz seqkit 14.45 55004 Gzip compressed dataset_A.fa.gz seqkit 14.49 57792 Gzip compressed dataset_A.fa.gz seqkit_t1 41.86 42856 Gzip compressed dataset_A.fa.gz seqkit_t1 41.43 43884 Gzip compressed dataset_A.fa.gz seqkit_t1 42.50 41936 Gzip compressed dataset_A.fa.gz seqkit_t1 40.89 44064 Gzip compressed dataset_A.fa.gz seqtk+gzip 461.54 7700 Gzip compressed dataset_A.fa.gz seqtk+gzip 460.74 7820 Gzip compressed dataset_A.fa.gz seqtk+gzip 461.59 7748 Gzip compressed dataset_A.fa.gz seqtk+gzip 459.01 7800 Gzip compressed dataset_A.fa.gz seqtk+pigz 118.43 7748 Gzip compressed dataset_A.fa.gz seqtk+pigz 117.57 7664 Gzip compressed dataset_A.fa.gz seqtk+pigz 120.28 7696 Gzip compressed dataset_A.fa.gz seqtk+pigz 119.26 7724 Gzip compressed dataset_B.fa.gz seqkit 25.37 1045592 Gzip compressed dataset_B.fa.gz seqkit 25.05 1044708 Gzip compressed dataset_B.fa.gz seqkit 25.81 1042072 Gzip compressed dataset_B.fa.gz seqkit 25.46 1045288 Gzip compressed dataset_B.fa.gz seqkit_t1 46.53 1043316 Gzip compressed dataset_B.fa.gz seqkit_t1 46.19 1041472 Gzip compressed dataset_B.fa.gz seqkit_t1 46.30 1041008 Gzip compressed dataset_B.fa.gz seqkit_t1 46.80 1042332 Gzip compressed dataset_B.fa.gz seqtk+gzip 457.71 245104 Gzip compressed dataset_B.fa.gz seqtk+gzip 461.24 245072 Gzip compressed dataset_B.fa.gz seqtk+gzip 463.54 245164 Gzip compressed dataset_B.fa.gz seqtk+gzip 467.39 245208 Gzip compressed dataset_B.fa.gz seqtk+pigz 128.74 245028 Gzip compressed dataset_B.fa.gz seqtk+pigz 126.62 245100 Gzip compressed dataset_B.fa.gz seqtk+pigz 128.22 245076 Gzip compressed dataset_B.fa.gz seqtk+pigz 130.17 245100 Gzip compressed dataset_C.fq.gz seqkit 9.05 40948 Gzip compressed dataset_C.fq.gz seqkit 8.87 41016 Gzip compressed dataset_C.fq.gz seqkit 9.23 38484 Gzip compressed dataset_C.fq.gz seqkit 9.05 39380 Gzip compressed dataset_C.fq.gz seqkit_t1 31.18 30100 Gzip compressed dataset_C.fq.gz seqkit_t1 30.59 29512 Gzip compressed dataset_C.fq.gz seqkit_t1 30.50 29052 Gzip compressed dataset_C.fq.gz seqkit_t1 30.66 29424 Gzip compressed dataset_C.fq.gz seqtk+gzip 188.89 1024 Gzip compressed dataset_C.fq.gz seqtk+gzip 190.83 1080 Gzip compressed dataset_C.fq.gz seqtk+gzip 190.95 964 Gzip compressed dataset_C.fq.gz seqtk+gzip 191.15 968 Gzip compressed dataset_C.fq.gz seqtk+pigz 49.13 1080 Gzip compressed dataset_C.fq.gz seqtk+pigz 48.17 968 Gzip compressed dataset_C.fq.gz seqtk+pigz 48.11 1032 Gzip compressed dataset_C.fq.gz seqtk+pigz 49.28 964 bio-0.13.3/benchmark/fastx/benchmark.tsv.png000066400000000000000000005163501457355065000207250ustar00rootroot00000000000000PNG  IHDR pHYs.#.#x?v IDATxy@dAP$5qCEpSs7[̲wͲ|fj+b. ~~s{93̽gqthF`9FhQC`9FhQC`Tsu'?K233޽WޤI`W7u։' o{Q79rԩSBGGG:466ݝs(_AA KKK5jvT n:mý;o>}f̘!$ kױcV\RRRb*[ӦMccc~Ayyyw}w5;ԜVZ3FMNw}p? {;w-[TiРO<ꫯ֩SNJ+yJrrќyyy /TsM8m۶Tão߾f2w?6QhРAk֬ifkW^۷Yk98ھ}bbb^xlWOT ,Yd?|U'33T5jxyyݹs';;ڵk/))qPHptΝkժe*+Wk㪻{޽Gٳg͛{zzt۷/_\9T͞=;33󫯾rssyo>ݻ7mڴ*CHԴiS//k׮#0EvٔǏ=*HإK3g:dee\'1eeeGVFEG9f̘u ;r|re?EL}.\ M0?Of͌ ߿?y̙BZ[ `w7o\ h͛F-Y_~Da9&#0Eh3c>}x.Xưw}\G?~ LѣG=~>^}aÆ(.X@ejԨ!OW} N8‚#G{q =cʨhXX{衦.'xbe%I믣'MdIQyyy=뻪1s;}b 凬;h/p˗Sbcc۶m+%%%>|ꮬ:O>} d7wO8Q裏͛g]Ü GXI>vڦ勯;'EӥKy+>>ɓŁ׏ݻwFí;9;(߈|7ofrwwٳg=z֭bFhѢ[nAf/:t ۷7oޜҦM_hǎgϞ x:udK[$ҥK{9zkrrrԩݵkf͚ٲq|#$4@GGG;[ ; w25ʊ^xa;ϷgϞMKK+((e#B7nl-;˝:uJA}C\y6֫j*b{moЧ %zޓ'Ov횩 :uRkԨ;s-]\\z!CH$OOAm޼ٺ֬Yc&]Z{,--M>2ׅ~ќ.]gk׮O/Oݻ׺}_n]llvڭZʐ_C'':uS:Fa헶m۪,?)xI3g r.<}3AiӦtӦMQQQZ֧Om۶Y]֝B8\fpΝ>iӦ7;ddd8t םܹa˩'N4Ԏ;ܹS٤{vڦ 6mtɒ%*wq.ϟoedɒ6oD&03fPʄoժU,hBEu1c\qQayf*K/ 1c-UXX3a׮]ھ'',,L2ѣFs-Z)( \`m޸q˰E|A3!]K.C͖kժO?_kժr'Olѯ W^իʍYAQ'a|0ŋ#|uss͵F ܊gٲzAAAŸܔ#LUaKH>E>ræ-6k֬m۶FGh6o\[eeeDƍ+ԬY޳gϚ/!vl+l`T'20:e!!!#F>}IbccFaիpNNiiijժuA۶m͏qSF/^nw}!???K.[O^'_{6uk׮K.F3xzz!9߈P&_)m۶^??;wͩfKjܸor'O\2KN>z7noʕ+<)))BuM4rܹSYfڸDתU+aajՂ s+}(IJT~9s\pA3//o͚5B7+#GorY}/^e˖c  33fFJHHqĉ_],0UqڵkǏ_zk sssO8_WF7ol?z-[8{g~m*UV\oTM,we\\<F 6^Iⴔ$6xNxA5l_x~ӕw p|"=޽{BFzu֝3g0-̙ct]96O*ڴi3ue˖]xц]#.9ºS0+zm]uzPG* OtrQQQׯwN+..>tӇ &lamLbիRf0w}rٳG;m43E&L ߰aΝ"rJY'{/_lTiin>}}'~~~W63<-F7o,2tOӧ l+f`TN;'п3wء + sFgy`tLbVNO+Kk53'j̙3M$顇2_Z>|hf}:~3fLVV"Ν"/2s&,E`T/*>gzon*ݻwkԨ!r ))hw6SBCC%Kh/˗xWQ{ǬK27ne㦞hta%2IU?PM$k1y˖-B-}U,N?f>}Zxh۷o*233Mmq &{zi*'}^zbb* y-B`P̞=LUV=V$mZTT$*i\:M}lfٳGXǼFz9nɩgQ`رcʏN+'.8y#v^0²hǎkߺr O^]FCSN-/ wի Ӯ&,E`T~PF&SO ozzѣصș3g:uʺzͰ{`UGŋ-l۶h{Og.݄*}kܸqz(/?9Ǐ/fը;ʋWVSr"y橩ܹs$F۵kpNN'jYh}3D?o߾[nx&܍uf&sww5oKm۶M388Xi˹"Fv=*{|ќNxrY}DŽF.,SٸkFב."* ;˗R'O6iN7l Ҿ}{5}BYᴫ K)[ B3f/{nHPPE 8TfӾK=0#_^u^zɢ,#b-}SiZG^P^Sի'f< F .5={`lch*[5n8lzz2[NNҥ*t:ݎ;cHX^wn{~'6=<< ɔݱc7xC^vΜ9 &Mc\Z;K,^TT$ d0ipON=Ѭ,lZn߾,aEjժ]~frr ͝;WM— S+#9W笫^)*wDA~~~pp#<"ഫ Kr̉'/۴iSy۷W\iubcc7o|?0::ڢݻ{CYrիuut[ʡ P9##C+7z:uXTo=Sْ;L0A9͂yRݻ̶slyF0jʔ)/}۷o/O2m4MӧQ; RZ5RQ3IΞ=+կ_nݺ3*(Ihn%T($+@)ZϞ={ !3F9\~}qq|z}֭7={) >>J}gΞ<ݺu8s>1??={ _^vРA*;/犌^XmKg?x/yzr&''֪UUVnyuIOO7YSl5!~c=Qk#zO\f[eF&Za/qqqcǪ)ֻwo… ΠoM6mT$x;lٲ}vL$=IIIFÅGo IDATXϫI&!!!B? +ܼyS.eGM4QɓBʄ <7f0:ozeee'O9Ͱha'Qv%c]%UoDm޼Yet}ڵk'VX1w\a<5iҤI&SL),,\vg}vA!OnniLoL.<$hBH1t](6h@3K MqU!E= EZL)++O:uK.]xҥKF#ڵk+NwرwEW^-jfff6Sr<|a-Z( [ DytÐ>}|w;v0T_\ixxxw}BKʏwr޾}[1mkԨmYYٍ7ʝ]z-dO^S~KKǢigs7" 0 ;[t޽{[8yo#;vرc{N>!!?իQ.Wᴴ4-[tss|rNN#ʠSD砈&V<,ݟ~srrdʼn*Ç/-[ F /7'شiӧNr[j>tl5's>yMqDW5ek^MKtai`XpݷiƸqۧ?8F˻zyy)*څ0$I;wvDEzF//kBS*++[dIӦMǍ|6 +7H9QT+-oQ9*Ç޽[8 FCBB ._KTRR",f8zG8ᭋ SQ+p9=~Y}gVT|!q')v%ŧrx[j} ? Y10=Ъ\;|rߖ7n0T` пȐاOc~ݱca|QQ<&D`aJw«_FNh^ ;4B%fj֋JL^^^/2Wj 0 {R򶗅  ۭ_~̙ ;vӧ55I&iݯ\KTZZ*b8zA ZO?o77nS|}}|A;Va5___wxx=+**zg7nܢE֭[o.BPrmC8+`!źU%Ç/ olbn۶M>a^~>S[?v긅״ҝ5O(9L)eAnO^gfuUUQ;}N>-OiР>jŦΜ9#v7n}-**:z<寿=0ֳgOaLF]u%Iڿ|U5޽{תU+++ːrʕ[)_- 0<ծ]֭[W\q\]3fP>5h`ĉ1?&*|||OJo߶42YCqq0sQ۷o4c8K?"6T>77WY,Ir gÝN:$V!~* $FsssʬX8ިz5%K)SLywTFFFÆ xۙ3g*3+'iڶm۫jE.' , ɓ'ϝ;W+ޣGС gϞ߄ħzJ-,,̙3:vq>LH|gΝ2FcH͓ o?z.\HE#W.UǏ;bJgϞKN>} \ Yf7ѣG||NKHH;v>t'|II-ZvԩSN;Wݺu-_NP'2D4_ Ν;עE 6RPP`Wj`%G^^ޯ*Oqss0au[ QxRev _ҬZ͛B '3ro`0\ӦM$&&j޽{wѢEqttttte˔دCl)B8{o)FpB=g^*;3]|YrUÇ[ѣS,]JTVmv!QGo4Q$=vaRI"##5kf6kV;SRR,i8ÝO>}ڊΝ;'OO^R,_vϯA=z?~omV P}\RVۤI78e!ڵkFd޽!qF3+Z`޽{mi/"$IEͅGx֭^ F|G¬n6lXsMZJH9wСC}W^yEЩSǫoOX}՗.]h# ,8y͛.\??ƍS OQF˖-Wq7=+^[*$+#F-wߖ>aVI6ݱcGIIȸ!:**JaҥK/^ȁъ u* rJ f|5j׮ipnKpBK7h"!|Nu0ar+WZ4ڵkoΜ9o>yѣm٦ɓ۷?^yԨQu֕1|/_~嗅DeGqܹ0=yu#G(͛+㶒$/۷>|xaaAHHȰa)ü9s渶;-#{9׎ q;w߿_A6zqssb.+V/{޾}{4JBJ*Jw߹sGy4%..N4iipԩSDDhO>D~oPE%cQ\j5kqW^c<ιӅzj^~駟M& )ڜ4׿%Iz7U|7矗WAztt7^TT\Vrvٸ=M{)s \ifFK ._/*ٻ߿ߖQ>7nXeYay,M ewZO2%55|Çw?W矷Uj(b^~E[P.r1|JKKM&tssKJJRfV=qD5m[d0`Ϟ=J)ϙ7n(11100hE(Ͽ[9l˖-Sz7o=z纠xSGUVog?ٽcS,)޽ܪm՟:Nw}:cAaÆgΜ1_iӦB7xL'ut?n&sQQrM^p|-.\Pðݻw {˗>aÆ۷˫...P#_vG5jT-o޼y~M~=<+ ??UV=€~~kРzz7BU\ >Xa_~yn`<͚5S֮]ݻAr}ɓ?ѣGGFF֮];33ɓ_j :*_| / Oʚ>}|0lذN:իWIII֭O|awjuEO^ XjU׮]322&O<{ÇwСnݺiiiw^frv>8*|5*7WGf ?ݎ=FҔKL={^toMII1ҥK#Y޲KoD{߾}[^?hc]|kQvo^i ҋGmٲzV]ǎlΝVհ$aɧ~㏖9jԨuV!+f:Kgٳ!$[N};'O=Js1_p͚5F nܸQMN1.r;#Ft5jݻWeǨ_-twwo9Wƍ}||۵z}5` ? ٵkW1yded{lE;vx]йk ƺ_AY'xB}֭Svaݺu66 p֭[[Qv̘1 !!!f맟~ m„ Go۶|oڴ|֯_ߪU+K[X3gٶmr>___{7rhժAfV^ݻ5j WrбqES)NGyĢ{`ddw2.+lذ慇oݺ_tD*:ۤIJΟ?_ _MJ(l )'Noaaax܊+ BVڌ3.]4k,t'NҲv#eCJTTTݺuRcddM80rH$ԫW_tҲeˎ=|=z?ܶmŋ;ڴi_͛7A*nzӦM+W4?SoIII۷W^zݻ7..N:tW\) mǎ{/TN{ɓQ9i?>s̚5k?pÇ +ћR&@e #"""s΅Rǎk˽?p@3ywyСC;wܷoǯ]V\\̬&ݻ!C΋d#P^rK>?VܹovΝuر#%%%%%Bv 6[nέaaav;wٳ?7(W zk/M6]611;مg6mucu͢mJHH/^(dYfnz9l0ȑ#~ٳ>f̘nݺ'N;8=O=԰a8oooQ///!|||E*G {xxL4iҤINڹsɓ'޽Y~m8EYl9*c&%%Swﮦ{ĉY+rVN<ٳ׭[w-1;9u_.]{n F]t7nѣ-qp|RlR}Zj͛7o̙Vڰa3VÇ:T6-'|.] oy[NuU7{߫VZv}233۵k׻wI&iF}$#@=7bJ +--~zvv{ 4h\eiii?ܹscbbZcaaazzzvv;wj֬YVrL|vK.4i#kݻzjVVVqq```zK}lN\V-N q+WdeeyxxW%q\eѢE=<%''?PYtk׮]~?$$$44Ե8COTa8Ȳe˞x yJqqo߾Uvƍ;v{kݺu+---//= aÆ*{j?y)##CWpppݺu _M (`$I v_Q̘1㣏>2 Vv j PtU?…5"* JOO5k ڞ={o'<4(C~[*^zĈ*>}:11Qti8@9jԨ'OYzʲ:^&#*NxhQ׷o_ە+WܹR ѶmQQQ]ts|G---2dȼy M9yK!/pH*=+++ڵCwСQFիW/,,:}]ۙ1c|&֫'<^ӧ{}M3,Z͎žF`/ @-[&&&hŠ}7|CT'<lvV IDAT<մiӣG VYZj=؉'^z% ;NxTm bEEE[lߓΞ=[ZZ*d 8pȑ#CCC]H^* ƍqSz~9RTIFIAA?sΝ**IR͚5ǎzzzJTTTtR{ ŋ$IVZ5lի/jժ5m4777!Cdd 9r+`UPP駟K4a„ڵk۸7n>>F 4Oz׮]6V \Fŋ$Ij߾mÇ ;wl*aIz`oϞ=$1V8w}g&gDDEvvmF%IӿkӧתU.|EhhHkÆ ^jB`T*--;wn~~$Iԩ|-r+ 6ȰWU p+VׂoԨSO=e͖_oxm(eq 3<<<Ե(F?oIkyyyk˅:N|fê$ݻw|M6ЬY3um4JCsss͛_N8H*))1V#8_|Eff$I:t:t}7n.ؗv7n>>37iҤSNf2?^uKb`hΜ9EEE$ 2$&&ըQΝ;ʍ>O> ǎWP@{=իW7nܸqF =t)SN:۷Our9F ,,Lڵks^7n؁m@`ԡ"""/_n&ٳg/CBB2@۴8I&y>S}G֭[׬YS}E:t0>p#@cԎ233yŋ vڡCH曓&MjѢ$Iwݲeˊ+%I:ushQt:}XS$ +2sK.It_7;;Ё矿ly\T3Î,.KkK.y-f?MRK23wUs55RQ!)qG}m~L{fAyzR'Fd`zcL@Q1'r2iUD0 =0~RI6  ;*SMd(*`JL;mIQC0ʫ617 _n!QC0 @vF(! ;d`QC0 @vF(! ;d`QC0 @vF(!EvuCd`՗f(Fwy((`:*>EI#QC0jɡLQT/eO e(`Ո.'(`}*(FPEd`BE'2iUA0 #YE J#U%$@QRէ|2i@0 @vFa2ɤQT(LCi&(*` 1I(!nj'F#QC0 2N&@0㤖d((!s"'FP6QC0 c0N& d`gɛL@iFaXM'FP"QC0 6PsSϳWz@ F۶m۾};1bȑ#)D0 %HNN~;5r{QC0 @vcJзo_>|bq޼y>4gΜʌT (QF5ҽ~llةS'wwrzAGd`QC0 @vF(! ; BZ,**)Z--*C@RIٺD0*ܽ{7000::K;w?~cU8uTff.54iReKII:t… gΜi>SZm?>F&`4,,, ??W O:>oWWofzjjfQQѻckkkLBGEEݸqCs=h i**۷K/>{lVċWF Ѓ]:tJs޽ڄC|ѳgj. ŠAJֶm[EQQݻw+ё`Lcǎ_}ΪUrrrL5 w)oѺugff֬YjJcJU^SRR4PT* S{њbRRҮ]ƌ˳ΝKLLLJJJMMUT-[W!Gsrr"""bccSRR\\\\\\ZjY߾}O>uuu[nǎkԨQu7@>:;;;;;iӦjW\\ieq >i9sf׮]aaaIIIk֭[oըQ''';::JYP(~Q;{-;{ڵk9r…?_]W4hp4d~z&M {~׬YsȑzyyҤI 6,)+W|W'T*UNL2`7@n9]`jccS`155?3f۷/>>^?SL7nܡC޽? 0`ժUSEsСìYbbb?f_ǎ[\AA̙3.\YZ.)Bll'|ҴiP}Sgee}ٿa ׯ__tς ju#y[he˖⩨ 'O8p`׮]7@;clo>tбc4zzzzەhG`vk֬9z6nܸGǏ/{gxxxX">>~Z/ݻw/--m׮]wݻ JY!z=??W^Ϛ5+!!aILL8p3gti0$$}?xq-ѿYbŝ;wś۷++8c4*****:EEEǏ|ٳuYܹȨPhYreih׮]Ç޼wBVٳ'66vƍ(*e˖{@ IDAT… ŃѩSJ똙!///11;w~X-++CJMMa/^.wvv6P_|4Ltww=zt۶mԩcaavŭ[FFFuVX1hР={o-77_|/ (ׯI&666!!!?Ç5{٠A|8}#L|r~;--Mzqcǎ-Rmk4uܹN:**11ܹsΝԮ]{ɒ%ٳ>\F&MܸqCs?xرcŚ배ֵkrk֬Yѣ :hѣի>*]YZZ;#Wރ4M6\;ŹsΙ3GZ!&&W\~С-[oJVZ꣏>*Ԯ]xѣGoݺUsݻw_~:us?ȬV3fwK{]V,6o|~~~ZղfϞߊw^~ӧOx.9cMKKppp͛gϞ:uj%vdcc3mڴK}zQYU޻v!CBB7neʔ2*KE}I V\ְaI&Q9++KHML^zqqq ǗVQFui3F--- ɾ09GAJ;/7!!a޼y7o0ʸ {@@@N&N()sΝ;wۿ;s)W%Tq HGH]DEE277?|DDč7Μ9SvΝW:Hw]`*m۶\$''䫴TTaBZaIޫW.#޽ \RV-=@0֭[9rdS222/_oiٲeQAڴinZZZHHHKo>͵B(eiZjuIA222BCCtRbݻwk:w{ CK.\=ztܸq%V֜ɓJgƙWjѣG.] 6tqqѪsR{4q_Qnff6p>}ѣAAAʤ>}Z:Q<7ػwo%dU|*sqF???jʕ+{T.4 6l+Zܹ)8Ʀo~5lPsD?0rH ՚7o FAvK/U!66V7xQ[nƈ={Ԫ1vXi.ٺu3gիøN4nnnޭ[nݺ vÇO?裠 tMˬiUL >p@uJJʌ30sMWWѣGk5>/~͚֫5&%%M>]*_Ǐ/X_===7 ￯~eJ֪U+:&&xcǎnnnz#*#??ʕ;#FЪe  2dHd^^v5Iڵk>|b 'O+b.&&ӧUWt#  3޽{Ν!!!?[nÆ Ax۷?!:88̝;%yyyw>zƍ g5R*=~EݺuΝ+'i 8p.\aj3fVv[.\x޻wbːN/_.]|k5Ik֬WիYEEEzOLL0`@ڵKkJRM8111/Q#:uںu:'''$$_i& OO ԩSGPwƨ fff~i;> uCy}G%Y`to׮]hӦ͗_~Yb T ŢEݮ)ShvuuP S ۴icQ,f͒ޙ1cF֭[ /PNr[>|=H4P?zͭK.&L8~x GZXzu p֭Ǐ9W^qww2dH:~8}#  T &틈.=ӧOM*fڴi}=xŋ333ŗ,--_|Ş={VL'FӠA/bԩ#G9fmmT*Ņ_-rrř9{4RT18q⭷nݺ,`Aut2ģ4J;]:{:u-[%11QZy߬Yvڝ={VSܿoV1Ÿ}tСC)5juBBBZZ 5jpuu-w… 5eo޼y5]jGGGggr`=ڱcɓ'o޼ׯ_^S6m*JTbbb.]~zi6gkkiӦ)sww7~zJkӧ#Fx4ܔzZA{;zQFU s`({wL=kƍ;v,N&M͛駟wbbbƏ_Z}B1}tOO_;.]+>/Rk/`ŋ5ٟz_`i ={V*D{uuu-ۉ'/ ~ nݺ^ gP*kk^zRR+U_P(Jsss;;;777www %}]s1cFVV֗_~)ݖD>>>k֭֬ś٧NիVe__wygÆ ۉ1hݦߖ-[ZhQFgϞ0a޽{mMR}'s- [POOπ}}*'Jeooooo_n͛ۻB̟?w_~/E :txwF Znlٲ k׮urrZ| ^ʕ+j?VZo߾S*;v0aȑ#=.yϞ=Ν[xѣGSSSqrr9rI6mZvkyRϺK.,zicǎŋkĝҒ$HRRRΞ={TJѾ}{JkggW~VZ5j8 ʕ䌌 BظqmJA]aaaDD͛7FCQ)QtQ?s`NDGGGGGM______XXXk׮2*!:ujʕmsĉܳ`ܹeTRzC0 @vF{@ ׯ66m*-T pFM65tLT*h.@iXJ@vF(! ;dSQQQ.]o-ZorF+Wo'N$`)=!PW4*`@%/{01IM;ӧ322㣣/]P(:fFM4iҤIMKK۾}ի򒓓'LbŊnݺuJXJbVг8ӦMVVV Ƿo6bPݵnz̙/´R4C iР?2pU(hJe;ȑ#& "jݺxa‘)Q:)wN(FK,^߹sDŽ#R`@uff GFO٠L-N:Kڄ#Rꎸ*/^p$ O ζstt0333P-Ч+Կz믿Fp0@֯_yfzf͚pϙ3Gzw;v1ǐxm۶ݽ{Wz¢{1`@Y*1]lTZwKovDA)ꧏ>//1 #C0VZu__|ݾ};88X,p0AAA>>>&ó~b+W~Ax1ы/_c(4FW^zjEOMM{ٳg/i(m|[7Ξɼt0v͚hήPsx2o߾=**|PuĒ8uڵk'NѣػwǏVyF?Qcǎ\R >Y r>9t0-M13:v~"3.DZԪm^"5իW禝 ؖ-[ 4N0:o޼2RQ`!kܸG}dQ]aV]?fDF$MܩP*kT9TZZU(? % }e1o2m!hf͚5kfQ RS3UZXZ5lTYTu&9wFlpq̃ ¾}Ţ ÇQA J :Sj 232*UÆKETZ7h`fm.(x?EOooЮ]F7>|]ÍAVKJ%JŌQEIx֥=}f_QJ-*V rn\M>ˀAaܾ};55¢~6'''999)))==]R988ԭ[Hb))))))VVV......666Uj^^^bbǏ lupǏŶmzxxhG5|uQQѺubTM04QsH]PϚk 773kk407s ':wQ9:e{Ɇ vqł~ƍG9ydʵzSN:u+xxxtyС 033+^!''^\߸qC;ckk8zQZ>qۃ&׫ڻw~W,Q۶mÇϜ9ֻUV}9rdK{\/mܸQzқo)^$?Ϝ9s*ƍ5IIIҗV\o>17 ՜Bk1.]?~: gϞ!;ޠFwرxbuXXXVV,--=[3^ނBsndf9uƛ?`\jzɒ%OOO/ۺu/\Po߾RSSϟHV\٫W/hiikah ###m# `Ĉ45kQMT緒 IDAT ,W^WZy󦦨P(w?v2dz;wytFc2\*jƁR\X7­"=L.++o߾ӦM+#!!!aРA#qٲeoVHׯgÆ ba۷K. ܻwoȑ&M*KZjlٲSQA6m/JgHHH н{?lرb``FVjņIOJʽP*޾Fiii4;6ƶ޻AiwrrjҤ{kO]ꫯhݯ]]nn'O_.]L-Z8qbn6l(T(梢"bVdK; ˗G5ײFގ ɓ'111ZV\5y j:ujZ6lX^=JWJ7:HLLѣǞ={+}ҟh4n8 oG}$~'N~^zW*VJB@(<+1 @2I0k 3{{aΤ6wp\d]j+gܹZh->|֭W{HV_tfj1cᑭܹs=zt̙Ǐ>}:666==}֭K5E+XpRffA!׿% =t~رGFFFu֕>8k֬ۼСC݌3ܹs֭脄N:bQFiqYOh222v-U_jZFgϞ-~Z[.Y@">>^:} gϞvvvsԪikk;rȈÇK۷O:/r 4+WobO>AAA;wV=Rzå3ӷo^|jzĈҽ,5kW_i-0wrr0aիWśÆ {NЇ "%5tӦMU|aB7I0Ǐ5fVVBl9?᱁ J;ߴiSikk.[^nЦM2[XX[Q$݀r 4 i**‚ X^n]y*^OǏ.Y{{vIsܹ0v,^],&%%%4jņ rGXOA* CT*2 `XFFΝ;Ţڵken#Я_?pJsqXvw}ܧS AHHH() bZnW^㝝۶m[|v K,˗HIibccF5hРK.%T*G1L %F5pzx=}a6P(#鋞暙j;~gA5hРܧfΜiӦ-^֭[7o޼uV>},,t4i"-feeR}M86CBBkԨQ׮]Bm۶m޽uϫzQM144)ՓֱKcƌveܸq0&_l( k&.J6 C\*5ыvT(ʬg NGSڵnXäI*1$o𚟟_F0-[JSN7n\m><88XZ|7u kb0*ӧ6n 6o,S4dܚ5kV^m񕮚|aZ,}GӦM?ӐJ#]T(Ee4U 9rDz{Ν5jT#Z;nݺ533c3jņ0c/.ʤQfͼǂ f@P;8V7nk֬Yn]PlWܸϟ9sѣGZupzammW_қ7nXhѢEv߫W/]q%i?_hoAz qqq:>h*Zuٵޚ2eCzzm&L`UM5bpFubcA(139Xk IMMntlEZЋ/^z֭[o~!=޻rˋqA 4ׯ믿޽{w3Rdi1""C*&!!СC;k׮ZY_"sQ!&_l( S&?I0MRN 3Sj. 3\kWJ*CCCۃb˖-{/)):qqqVZjۘ1cORjn޼YkM-tt_~YO㪰jņ( G&OEVfeͨZPjm5{1emFmFF;uwrãMN:uȑCGرco޼lٲ^z |󍗗׆ cʟ>} ķ_9k֬WSRM0frTM)**STJPif66v~/ju~ 7=v/LMUYYyo`cc#-fdd,Crrr=.^X5kӦMԩ#ܹsu.0 ɓ'O<ݻ-VVV~._vرtRGGG}5jņ ?~ԨQ'N8Ѵ]2.^!I\y ҥޚGGGR)iii֙0aVx2x]nݺI&%&ZΘè_|?>xW7o.ީYxyy988Tb ՜ֱKݺuݻΛ7O,lڴƧjņ C6>V AȌzu%&ר}5[O0~ffk޽[v={HL0o)ws M\-annޱcǎ;Κ5+--m+V8wXpxΝ;bڵkm۶QzgΜ9ݺuP#AAAO1jņi(Tk0A+HKOIKEYMuB/V]-뢢 .`g]VZ4hˑMݓxwuØ1cΞ=;vX'OJmڴ*cw^\c]+<{zbLLLpp1c W&BHHHzzV//Cg;+Nz !}Je$>}{ZP5.G?SNtyeWߥӧ8piQ\_9FJJc2f̘r'-*ʥKnٲPsG+)ҥ4tۻw3tիgϞݠA 6hРG:QF)62lذɓ'gffw֬YSrE0 %KKK7oÇK.]u̬yͪ%ׁܾ%BftTΝ8kf5jTܸ8uasc'A3hР)S͛.\X=ᥝ/--UАZ0bI*Mu|+`ѢEbRREO@2W5h@*OLs$ɱcǨ5Ǐvo]vm޼9_ F׭-P' 1 k״ym۶iw 41=׸'tׯʳy@%<745$%YXR"РF6Z_E:u $b800p͚5S}'NeU߿?qׯW811G!!!ؕl٢poͅ1sL6e}R ..!bM&Ӭm۶s̡_mu>}5 4֭[ԇ`0kO-uܔ u]JM-z٩HdP'1lذLbTRR BU{*L\ yѢg ~FŢ4hD"|t6¯Y4:Cmtm/^b+=:~ۛgffFDD:u*++KR$3g߿̮7. ufff|>?---&&&44^4FFF>}Rؿ#ekk۪U+% ~ʕԕܹ2h f͚eddĄȌ\`Af?͛;xzz8iӦO> Y}N1Mˣ>%f6lد_|rZZZƍkسr~NTzОJ5i v4ʆ2 , hz۵gHp<1G?tPSnhmmMC\9}ou]||޽{?~x;vӫ6|JObݻwy$E3nRɵ*Z5;;;!!,6mTY}Bb+Tj(TDѳgfee9;;;;;;88ԡPE"΂O(h͈#thz#FAS4i%''GGG?z͛7yyyؘZXXxxxk׮}Ab4Bs[#7 :!HܹM )?????O>=~8߿ĉ]]]*S*3iҤiӦ)̊ .]:t5kjQ{0h)11qڴiոɓ{챶V{`59mѢ:>$FA47@233*j󠠠'Oi @?~|\\:ի͛759a*=b"h2YQ:޳gUVQFܹsҤI666K^x1w\- Q.]sjMݗ.]䤰ܹs-[I###o Ab@P{nj͌3fΜI*d7m%%%ʭ[уNǼ=РX]Pgd)hZlllZZY5k*YQիbrrr||:C@b@VVV/F' jݺ5YsN@b@Gƍׯ^? Bb$>;u|233=zT;ǟ?IHFH(K;88T+++re욆j(nl6[OOt!=Dj Q  B鱹"u@bO bQ.K'%%UX¢F1 1 y|ʕjFm۶F1 1 A. Ѻuk?C5:IHH&U eeeӧOSٳgK$iQF^^^ jQM pQN:ׯ_6*^A̚5b9J.$FAԛDVlѢE ,fddL8qĉG###wN<mӦСC51 Q,ue3sss2eLCBB?})Sv%ݻtP?fи4] &SicczNNNPk\PSH6$(ֺuΝKS߸qc0`@hhF"`:/̈́U@h4ZPPm"##  oҤIZ Q*ӣH@mzW:;;ƶ\.ԴI&۷p8:*(PG9;;Ϝ9Z3rȑ#G*6$F紴cjjjggG]63$Fo֬ЬC8q,ٳM@ |}}UlL mllڷo ٲe˩S޿Og={v횦Q"##,XuVt}7P29)Yy%|~H9{407uA ṱ[\]]e IDATmmmuCݢ'&MUqѣG/YIQQ 0 ==]@ QBbcoaA\5PׁhOTߢ%/j M(}VRZBOeS+Ӟ XY>}4o޼ӧO'$$ 1:8pȑu-XF,??̊PG';D U^IBΝ;޽yf;;jtҨ( !ޅļZaބs釯mm&l&FH$ڳgOppn!_+//_hQjj={իdE $FJ(L*iG>}:t˗:J޼ys幹 $#xۍؗrDtXb 騵r\XXV^P&6r>Ii|4ohZ| tE]R;XƍD充%%% /ܻw̙3X,>uv;v411)..CΝu;Ӑ +K/Y?3˭}ii Μ9@1o3^Z"Jsz,F#>NW\!KܒOdztIpj_ePbbb6lOMM}Ν;""ڵk2Wq8 =Z]My+NdͬMi4ي<=':5轤ٽ[%T*--MMMcVVV<''';;bq\5BWa~)77W__а LPheeeeen"7oސE>`dԒ4 Q? [WI[hh={8@,==zXj=AJ˅>s37֫ zy :ͩɋOAJ0Գy-9G}cfϞmiiY.]tΝ;w7oF9h C/--ׯ#7227nh. y>}tDDL`A4k֬w~~~ B^oߞ:uʕ+<ݦMWtZXmS\\L-$eѣGJvur/^MQPdW >>~ĉM6K@]9ˍlٲEz-˶%&&.YDfQR .]ڿƠ/JN4bm.e9?ɺw Z8hT"lݺu͚5:x׭[G?:u誼5k۷DH5kk.___"UV~+++\2bbbN* <;;M6r5kݻWm$:>~k6nXl ڕPk222UD"x"NxfffՋgժUWVyS(@ոnnnX,֮]tRx{A#S#fE 13.cYT\\ܿ *Ɋ!C믪ɓ֭[o۶MA_۷#GT¸pBUɊÇYfU2>>M6;vP%B,;vQ%.44ӧO5TK^l L> _=m޾}lٲDS偁sے)Sb0 ޿iǔ4]Vצ~)//2dȭ[d̚6mj``{xxWz+S߰aCSVV^~- $̙3{DVh4rPX,Nfv0Hϟ?kibbbjjJx<^RRL]v5klJݹsg2NNN7fX/_.t*JXŋSk.]Zt:$ oNS<" QPW`(V!>|H9Ν;wڵk'( `Ĉ!!!d W #11ݻdo߾|ǏիWnȬeAAӧN*X"S?Eٔ)S.5jTtttxr </%%%""ÇԳ}ݵkVbUK^l!TB$>|~Y%`3gd2|ͼy^xA͝1cƘ1c,Y뵴/ ؚڄʀ͐&>iv/խ[y>};F+*ڷo?s@:x i&#>xd7o5+JڵklllG\|Y>1zҥ(pMPaaaz"zQXXؠA|ZeΜ9UNϝ;wÆ ,K!UؠCJׯnjuVhƍO81giI&!!!TÇOJJ^@e_LM%F4NN,BqhXaa3gȢ*ʊJ 0`„ \ZZzMذaɓ'Wzq>}5?*F-Z[[+WqElݺZ,4F+Yjƍu%/6 } 6,!!AԐ!C.\I[rΝ;ɣF:z(uШRlt DfosiyJ!C+꧟~@ زe˜9s >vX6[ 6mJ-hhI]p>###y<^tt4W^QGvЁ:T WW޽{{rab {{SG:kM-yA0@E=\dB__ߖ-[Ν;7..,//߸qD"51P(S[QBHdnCwܡ/+ұcG2\Yák>\[,X`hhw)o|#jqJqY=z9SN=sLHH6o]K^l-$Fz|VSN7nlذkmllNҤI6l1bD~tRխurWZj%U]I+<%|~^^^FFFRRRDDׯ >}DDDh%%/6 cXsU$dΟ?cǎ/tx ч" EzZ Dbr21ׂ߼yCبxav)++{mRRҳgϞ>} 6ZXJzalذAf7olܸqƍ}uuuU%xj188xƍ*~j۷*^s]tK#˗'ꃊ>}誩ؠ9HTYf[lQ1'Bխ[ .,ZD`P{ NL \C "[jA^^u<תݻ˔ԏ?j?=0w)0,,,,, GG :gϞOɡ?~\v0`/gɓ'MֵkW]EUK^l&$F Ҭ -4ڿ&hc.\POOz=4hСC("q2A|[3#M"D@KWIQQX2{)w޽m۶ կR;McOO+ݻw޽*00p Tc6kMpš7o~M'ZbaWz4hpjgEtԩSO8ѨQ#uʹۘ볘^NaP"3lN2),,=ztnΝ;Wiy ,P= i4 &$''ر}[~_~iّ֬#GϪq5a>j!ss3fPk_]QІ:7\Ccǎ533SW g1;;7"M"|/qP.Y"ɺ]-JohhH-~mYYYmrrr|||e2M4quuh׮]NZJ0T0\ٳgϞWFDDT<&NX^^>ej=!/100T=վNׯիb^^^jjj&MsZb!1 _ha6}ұcG#ŪP>AnH#WX֐k_ōŕ%L]Q]ք)N'wSJL2E&ydii9l0oom6mT]^^zfӧO>}P(u֭[ݻ'<}]5dbYf\.125YYYZK֒tS44 UOzuN=ԊZ,UaerEb/'kS#jl=YLKKYuT+op֭Pj͔)SRRRТEUZ)]d2t|;wxcǎLD26,<իuBbN3t/'R<,S\YX"=ҽ%NSK5תU+X,ƪxa{8pZ2dUٲÇb ^! . &Po߾M-k׎ZuV{Lvn](9$F_>>ݲ55׹8=WTZ ) {.|||ϫx͛77>xb{əգ0rss5k֌;]v;v쨴C:믿2 F&Sֽ{wjܹs*JĞ={,--۷o?rȅ ^zUk"cdd訝[גt k,5.6dEӟ={ӧ|@P^^^A?&### d4klȑ|۶ɋ\ S3S "ICaW)[O /'& LzTk55dȐs/'֭[W=111<#% ?~HiѢ*ܾ};==ZSʌtzc4FffQb ~J533355Ζe>DbLL۷{YiYґ WT ǏK;N /6 Hjɍh~~ի\e* T4mʋ N|SX/w4aVe XB}*(H/'\Õ:1_E~~~׮]-[uV%y)`0 8gjj*@0|J>1b)l0\]]5j!-zEy999dɉzQF#G?Ț3f)vϞ=/_$l6{ĈTyb_QQرceY@sbC|jWôf]ϊDYf1+FV*ufW:,>YP"VK"4!_,H-M6bflʕO<%KI5K>}*S_Zx)C0ƍGI&U7+`ѢEbRREO@2W5h`UX-wƍ.]DDDP+܆ P(|op‹ uF@ͪ)QGGGk^gΜ=zvnW鳘zŹ')^Nѩ , ̌8z,:_H%XPW..㗋r& VZtNɹb8000%%eɒ%2kϝ;ȑ#tۿ]vō7vOa~AfF)u$,,,-[S~ͅ1s;vi1**o߾Gupp\ ,[oŚ6mLmΙ3gd_}6mw{ɩSRhVT|bZr+:+H|~^^^RR۷eVe%F޽r+U^^^Ν5O3fLM uB-iѯ#%*ENI֎m۶i NХE{G뽷5ѩ%%4`lF# !B9PO֭Wݻ_x!-+V=zt۷777̌8uTVV%S̙3O.?nܸ֭[˗/ӓ~>}vlmm[j%$>>^:@Rsaۯ\rٲed͝;w\\\ ӬY3 ,h֬ڼystt45;pM>}4$$Df :~ɊgWiٜ9sjrڵk{衦XT$F@%_SSԕ6l؀dVTRRٱE-3T=IGfJGJIRV&[;m*p{葜zjv͛XbdX,>~̶92ZjuYOOϢ"iMEC;wb;޻wOz OOOMxG?J:'bȐ!k֬Qxb1&+%ׯ_O&yС.*?BOOo.wv~N ^^^ׯ^?䱅E1贞{6N)/W@̈́-8^N-aZq< z򖖖aaaM^ .((زe*yZJOOM62>͕miitҟYaoIIIdOsa03g̞={߾}*.K,Xn:% .ܵkP(T[{{C[I՟X-`0egggld]2@87|Ȯg=}cou܎h IDATnsvzcOͣeE,--\rwww h4ßÈQ޽{wggggggjӶm[6@b@#F: lP 1 J$$$ks.ۢE u}H|%Ə{uM u}H\5P!@=5FQDllC30b$FAb$FAb$FAb$FAb$FAb$FAb$FAb$FAb$FAbP(|srr 077777wwwo޼9Ff0)))=JKK+))p8M6uuufP 1 P[,?:ua<xgϞ=|DDW{ҤIg0)77ϟkP!1 DRV^0MP bf #f\G kiIYYǍXZ޿yÇ*owŋ/:88l۶mذa ĉ'N ϺkP' 1 PPn^F4[R}~S"`9܆,lYW\5kVJJʨQtKP۞H$ڱcKJJtw>hРǏ7I&U%B$%''陛;99ب.<ŋ<O$ںhyM zK,N>B,QLy{-G|kjհ`&0뗴ܹsΨO,//os玒6B\ٿozjƍʕ+隷pKKK%Ahh+WS999 8w5]=z4..N"P4h0hР!C = Q%y)KLۄ4d0htA$4B"JD|D/63i27Axcmt1:K~eddG~N331ck֖fQTTٳw;vLgϞ#&&L-WRk|}}7mԺuki&;;P-:'F?}jժ?* !0 (--+*cѮk׮8p@-}Z0`Fz=bt߾}_V0`I?ܹݻA$'':ujĉUEaaΝ;9[hA;vmۤ?A/]CUo.Q*`9fk2WuwjM׮]oܸzƍm۶%3AZjܸqNM---4iRSGݿ_zD͊7|GGGiٳg hK.fE p8?+KxJDA3ԡYsl M{0P8k,jŋ:*Nfhff޽{k\ y71z=rmtݻwU@ oC Ś6m(22JE Ǥ3,#EDF5dIǍ'Y^XꚳgϾ~Z{9lذ]Rk=ZTT7o|ذaiiiJFSˀtgϞI}Ν;51bĬYQ!QQg[UZǽ{:u*%%Ea-ZM2Ef<$qȑHAW cǎǏ+Jݻw{9o<@P>|ءC__#G(5==}ǎ-ZU: c׮]...k׮(+JDRR[lTRRJQQQڵ W8U$ݿڴi-Zz*}LIL&ٳg.]ZhQ.]97t=z_ wy#NN: QD"ѝ;w,X а[QzШQ#-6lHgddTFors${gX4Akׯ_{zz^|Yy3D믿<|7|cՃ)//_n] j'%K̚5K-bzYi#G<}Tn߼y3p_Uƺu jgϞ_הOOϋ/Vܹs}U13YJеk>}PPP Xd 9Su҃J ES8999;;[IO> ש MPSNg1Og,CJYl~s^Ҩ}quu500Í7ӥBBBTʕ+?Lɩcǎ>zHf7{5Gd իիW>| OuHz,?lPa.\i&j ѣ)FxO>:::z޼yW oNMFۣGƍXSӦbx޼yԫ4\(GrqqrP,AGnٲw.]!޽{S%RH˙3gdpvv^|y5VA'TE Jc;vʕ+Jvqb+y%=`hLPPLDqړ2e 5CѦN~zpX|̙9sdff1=Jʤ#٣p>Ym۶Q37oޔϙ3gǎ䩃lR;aHI$Sk.]LU]۷SL!#СCK,qrR{.\Hiժ͛dZ|rA+WԩS޽ɚ=1 )--}=m۶ @@qXXؠASӦM3gNEN2 \f͂ Ν;7**モR0D"H䠠cǎ)\ZB j{1AUo2 dds[`}ZZܺuh۳ğt:}̘1111*GDÇhҤITTTE{78p_~V޸qCY#22y֯_/%ʕ+>>>dH$:y|˒1cPx=zÇ峢A4o<,,lŊdX,7n\iiso߾[j{nhh(Yd0NZd^XݺuvMAb|ݻwYfذaҟ|>?44tŊUZ:bdCjPU,u tQAI!h.U~zjqJ]v@;F-ٳ sqpp |>_:Ȯ&4u5AӧOW!ܽ{7F#kHI.h} p^ya%u\{dccHeݺuyFYQc++˗/s8_344.9ѺunϞ=ϟ??t.eMMU"|YoX4F/z& b۷b dr=6mlٲJ{~YtwwW8Q z$*!QG»vܹm۶Ç_pId۷E&JaZ|۶mԎ 5+]B6KOOf-,,V^Yq6 ʕ+gϞOGii҆zzzqCM絶R2J"[2Oh ! *wEj- @\UPPի/fbbҡCCk81\sa,5m۶{*޽{JPǥ5o\P͇JOIIIJJrssSZmyah4upXs%j6|̘1Ru^ڷoߙ3gڟb]vyĉ&Mt~aUOڢE կI>xJ]됄jQ;;v@8U1Z92ID6ѳT%uAb1α }$B" JWXT.!$& Z6 MNW%:֖xmVjrwޥH}?{,666''GF5 N>^zBݵ д@((-_z昻hظ `[_ֶKxxﵟPcƌ^׭[׺u:ܺuKaՕZYɡɿeCUOA 6?\IK*۷-+qIĻw޼y#=ԩBFfM,#-EBXXEp9&CCCU={mٲe?V%(((,,짟~RG ͛qqq ,prrRѣG{W NɧhF5PkNuڕZTZ?:ujHHH UZO7z: ޽{kz~nBTPe "Z^Hqjݶa۪ VuWQQ 8XA&?b~ `iιp/ϸrom65:Fdd$ cֹx<SNBnܸ1|;K>|OO ]'T 5];~uEi5O֕DB7<6))i SS^qttׯ?66V02agΜxbTTT}'߿Pv3S]]ZyZ:t;kJKK:~xHHHHH!~[nmn L.η>|wkQGGH|LIIټyg}ƞ2-ÇK|>޼yRGFFJ>kiiy{{K52eʅ i~1W&M `l14/!PƘ`9ʲ2LQ6>;'~~~R #F̞=ޓqpu6YˇagggggbŊ۷o_tqqqR7۷gԨQL!iFm]iΘdchhrGӴ삂e-&F !K.ꫯ$o\\ܼy$333sss%-)j=WZZzg##ډQ}}%K4M[O>}ȸz*-i kwKhqQnRYqQЬL L4]mc=˵z𡤘#ڏ? ,,YI)ׁYcb<mڵ/^ `GWvbӧL1))I:::&L`&BֈJLLdL25 -++{䉍\38x{=F !7n~'wYYٹs炃O:dEuuu׬Y#} tRͣ߻w]N8dE:@tU8k**J22feH`@en루dkI9ÌGv}+V'IIMMey>R Q\\@ ۷+ܹ.ϟ'N?]uֹs<ږ-[E]]1c4yFqssu IDATc~24݁]'F !۶m;wqړ&Mڽ{w߾}sÇرٹdl==ӧo۶ K/0f [UE~ei42#48B٤`OA$߲۰iBfϞ-UUUR f&FƳgϖ,Yann.v…wN2dxњy%l۶QWWdv풳c|w5kÇ&,]TGGYM*1z!9;;vL @E6~qINW"vjmm/,unݺ_ 99ږ666( $>#&Ut`T %YQmҒƏ;wɓVWW Bv<:$huuu-\.(ܽ{7S<}MkhhTTTHZZZ@ `̙3a #, Bʕ+k7牵uֱHKK8qbdd %KktttVX(nff|.\lo..\xj3FEYZZzzzN4iҤI666ĨĄ ǎ;uTooo;;;dEʬ}&%bQN5eE/i._%bEEE-[Lv''_KU2iڷ`n˄ѱcGv ,!!AERI4 k/_.N+Wdebb2zyb-cȑR^r+==]===v0ذa¢l[t)Ss̑=ɷL* o8Q]gv,0q8jJ_TdѢ'0Ѵ,}E&ƶ[ oϞ=hѢL81--a^t//lwR+=zaHe?/^G#?^ͲeH d's琐vͪUؿPOekz}r777v.2jԨO?^[BN@Zp!??r>>>.UGqmRā&tUy^e/_DRAEUՕEiy*KiZ,9mɴ󂶙%teÆ #FDGG4 ;qDvСC|H$ݲ288wɵw zk׮UWW԰'*/9s氓biiiZz-pc4=e3a(2dH ]N899dwѣԴݻ899TVVFDDwuttd`\ĕ+WBdUR8yd:t0dȐ=z9r׷/^$U)nmmUO bccV'֒bbbƌ[_${HC9;;?^*+E(J5#""Ǝw!{u֭]Vw4hP(yfLL s򊊊zwem6ilgAYAjDQnzַ2p5'bk;(:rȴiN8T4}ڵk׮ncY6BƏϞAeϟsή]2'GEEXl%Ç%&1~~?gWfgg2'ח#ڵXjoQQQxx1Ξ=[_VZ;?mڴ7oـid5k֬;w)'@YFSS󫯾bWİ޽{ڵRzh_[:Ϸ~:(d Qiou-f{OtMފ?vؖ-[ܞm.\mpLoopCCCyٳٳgCBBٓO {QQQfff )kIg֬YoDDٳ;v(q|}}/]4r A'N|uzQϞ=+21(7c {[UUU||{hСC>}eo#X,~ABBB^^^II@ 066֭[~x<^sBD111 EXYY999d Ϟ=+,,,))xVVVk;wdff b]]]CCCGGG&|gZENNν{Ӌ Eۊ;][)qjjOkvhOɓ'7nܐ?g̘$&&Ν;(Ab(ˋ_N{L4a%C !\Eq)q)]UϤVu~cZ;FbuLbt͚5R:::H.x $lyM՛'Et5ut54\GLL+jK* *Jf_ }\(j@5m9'&8]]]%o޼y}GGGPYYyv͈#n$iia Nk\4MH}J(b-p0iܹ_ɊB8GnȬwnu9"ſ'^*QsܹsLq֭ӧUUULcǎǏwvvfIHSSSa[QPe4MǞ9!$.#EnkRTC`_Y`С@C+.#%.#N(@}.uͭO>L^ZZrrrѣGc~(QPe'^zO4eY׸s\a'3]CBӄn'@;3E777BԩSÇ+5rv{JՐu;Gw$Mu-M9M!&'Qd*g/c&({5H$b1]d@bTSaEYHYBH\FnWi޹I&Zz溆G_^#=@{>3ҭ[2Olll+P NtM5I3t J*2R[w~揩TO<)((]vuuuҥBerrׯ򊊊x{fSL㏭j_-))խ#Y]]ByaԖt:zyy͟?Vh=zJ\.e̙}Z݉?.J>) ͔F5+WN>]ۻwy-ZB:w!f͚kײ6Ynnq3i˰TP8Xb\5S7 Sb83fĈuV!QQQÆ c'tΝ1"44TFhkkzjXܸmll6nX_VÀ 7w…YQBH$u֢ElmmϜ9#ϘmPee%T%MM)S0E##ѣG3Ųh|2;??N{޽?xРA=R-$F@Ոhqԋd:_lmᐸ_+|xI߾}###e7izǎf͒?w^77wLuuM.]* cʕqii<={ 6 2f̘;vӸ9~x~~>S8q"^>`CBBZ(;vX-cbbܒxv K@<fUeh|upԵK42ºo4999J ÇKSS3--۰0yF>}y󤲨ݺusqqѩ qqqRǂ :QSS|ҘK֖|J qĉ[ktttګW/}}}Ba||ٷY|_@@{7СC;w񲳳oݺN˗^ݫOݻ]JBFmjjȲ\~}Ȑ! {ݙ# [ذ7cwTXyyyRUy~xŊIk֬8p'SӴ'b.]K05AAAJΚ5k֬Ycǎe.-ZhٲeuR .|)fܹ[ll={vҥ Ν?}}}ىQkk'OַRzP5/ r%t0]TB߽rtƺtɓ'"EQwp|}}ccc?=f޽L{7nܨ.]ٳ~`W?^U-ٛ._|͵KKӧO35"СC[6mڝ;wjgE !={X}&O_̝;WX,f/WSS x F3BoFr]z=ťW^5ʙٳ~6bQQQL{֭E HocǎIo)jOw vZ&SYz^EaUk5R'OL~~~[},XnݺjmAbbbjjӧOSSS  gHb3+/ o${̛7oʸ˞EճgOyB5440a6?55ÇvvvmEb}E%O_2\jjH*\y sskBȭ[OǀkΘ1þEUISi1GiFE%"۵ׯ'NO>- stttttllHR+n6puueKKK7l A@ rwwgoZEEELQ]]WF{ooofAAAm#/˗ũSq_usn79s5k3[K@d.E"%݂iXܚUSvlO߿>˅+ cR3:<ؽ{wWW߹sGȷHj>}Ҡonԭ[ѣGʘqܦS eĦpx޽|622֭]\\sߜ#G2dȯڜ1Fg7b>&TTi*JQ$Qǀ}Oϟ?gzzzrݻwsn]]]ŋTɺ襤ׯ_nhsp8򋧧gUUS)o߾}5k鹻1bȑ vS!i$uzuG611aΣDڵk^̋իbبz2JKKG3W1H4pCBHqU2nQTf\7Q^~'){l:4. .\|911 V mgϮ}Ǐ'O0GFBY(p(ex;w5cƌi8~*k yʋ-VF}D"ѴiO>}аiՐUcoU򡰢ftVJݫ2yx IDATBtuu+lY^^cǎFhJ ׷o߾/t钌fɛ6m߿-[(WTT J6YFF=aXb::W.%ݗŋ###';j@bTa'CMq)UꚊZTSTYܚKqY+v%OcDzfذacǎu͛_ accs{~^OQONMl555ThAAAms{=Mƍ)ڷo;@bT pA4!$$_Ѭɾ}:uhzKQB8ORRj_255GG~1+|cccC-F޽ ;;;;;+VTWW߾}ҥKO:lg߾}>>>Fbj hEEENNNc7r}+H,X uܼl^^^KKtHH֭[lm666fը{]xqܹ?pժUD 1 *H_Sq4(Niy~[;wngҥc6˵z𡤘cjj*OǏnv}8Ȋxnnnnnnk׮}E```@@;=믿fffO>eIIIaSIOu:!dܸqS ݰaC^l}}}KKKf!JJJII;?޽{'OfҘ`U:kh7<{M7ܣaEyŕͭ\}=1bߟ]c}+V'yDIMMeyw Q\\\PP {4 ۷+Nfwuueϟ?/OIII%%%wi`۷oc9s氋 N>mfI͛7%g˴4oooX[[pN'$F@5i?uKuaEFK$>IPv?YÇK$߲۰gB\.]UU%.lfHIa<{lɒ%_a.\N32dxQܶmɀv%g&~̙M˫s욠fmb/OJ9ㅅLidd~TUHr`9y(!LQ4M((ʓ ޭuņLǏg~~qy+vMTT{di[NSNe`@y8q"{@MMSNu֭iѪ$F@2 !uAyكܗŕe0#P2;m XAOOoLb޼y-[LvɮyIddd|R݂],+㷣0:vhooY^t`v˥_iʕlѣk7)޽{EOO&oRŮ nzdrk[NjUUլYdOmD~x=zťMF 1 *n$k+kj 3S^gT5Ψz^0eiUEsíW ſMigϞ]hQ} KJJ&Nఽzb׬-==+;;[Q3=j02}ы/d\YYtRv,[=Kv: gժU_(C'$aaa욦yG[sX)Ŗ?UV3xwwwD&)'O>|xUUh !+WZn2nܸ;+8T^.;ܚRP^kkjy\5EфTTWUV!Ȩ\33Yf15)))...k֬Yh٫W_ٕ΋-?)_UXX'LМ̙s5XXXxQsJKKjZZZ6'6e,vN߽{nSNͽy޽{a۶mcl޼yΜ9%%%eeeUUU"q\jZ~+ P{1lݺãuP0Ȕ֍uq\uuBHiu{/ċh1!$.#Ev{I{~XK }izʔ)ǎcWR5dȐfgg_v-11.FGG8=??GRv2===666""o#;`:WI_rݽH();'OuaȐ!=zYYYT#Gŋk&$nmmU}* bccV'$3f8x`s,++ԩ{xcccmBT:Xc;v,SܱcG[F(nT̄z˖-]mذa_ 2EEE=*Hj3V^h?7||w"EH=eTv5瀮z&[E9rdڴi'N`*iv{Riǎ2l?}zjj$wٵkW:[ח|yᆱ0Ə?|?S]voذ]YTT.cL33g֗%r?1exիW5YG/5mڴ_~{n~9l~6lhp #EQ;w444d'FmjRv-<&zeu~S7gTtt ;uoj&~%+*;e˖:d}l@p8 y䉷w}&L~ݽ앶ӣS'@bx_!v 8ޞ]s'+Rߨ&HLLtҥK `hhgkk;cƌ;!!a9>N[[[Q1+cTaQho({4ݡC>}8;;SմbJJJqnH$P((Դ%Ð(((HHHxYaaaII 344ׯ^-//sNffP(,..544ttttpphwQO dSp;vX|9Snjӊy|E p5 庺nC:tbTzb ({ةS֊䭆(@+HJJ{HΎ gӳgOh%%%[nevZxqrrrGSGGG): :uJ^k׮fFRpXʊ);w.""Bv}1EfϞTcƌ)S-333,Y2gvٳ*"444;;[I[ZZ*iv/ }XYYjժ7չ݋vvv@b@E&&&*ip$FNKKÇb*Ο?yN>-RzVcoo=zh9se˖ݼyL<$FZEDDĥKe4[lٓ'Ov! KTDBBBkM.G'!Mޓ !lPJTTe1e X L&\쀩H'NP_iO, @OOvH$...~gbbb={n;cƌ(Iaaall,f}ڐU'O'DQCtL(m#& ]JCН8[BӹCuںiӦuQNZ|ӧO7o>>> $55ia (X$NO3$KtM$YP2Lm(mC"I~!Tq5cܸqW\177gWܹSQ~]PȠzU&zfE2hZxC̈~g"ɍfBm=ue욛7o*dJvSN TO/l**HtLZ9/JK2 ЙI8hw|}}$Wb]TWWWȰUG'|t)Kw|F'=z` -I$E3>*3(3?#&T۝rTYYy…'O]4hرu (/_zjrrׯ򊊊x/2 23d!c̨ج| ccccccggg.UUU]~YYY555:t4hCCCvQCCC=g_JIIz*Sx12ިƪx䛛inn0ydYtt4o%{&}:9WÆ ל9s SN߼y}gg Ν;WnݺÇـO8qƍu6hk^x.4y}[KvڵkS422sjި*))ߵkW^^^%GWM:5##Cri͚5k׮e7f_m\cfϵok4U"mi5En2HqՈI[W8\~v͵%%%}ݠA7پ}{=6o`8k֬C֙ieQ^^>u߸qCFVpBggG֭[{gϞ0[[UVIm222RSS]kF^옘{{7rMMMxx[[HUiEk6$L9N<ᑙ`X//Rh3gΊ+JKKOTTԄ d姤0D"_%nnn^mճg^reYYcS7ϳZuņزe {JBw^) f) ?˗/!|#G5:::SL6lXΝy<^vv['NL>M~bmM֭ fj͗/_ޜ1ݙǿ;sՕ)jii;*jO4IjK>}ddd\t)<<"fΜ);GCzzzxx8fɒ%FFF9>sFܻwad֭ O+|zw/؉?*媫s5M?xBf\۲el0***:uĤtuu#FP$mܸQj'[nnEk͛e!ՁVbϠVXQ%S MMMvMVVVǎetyťKx ϻ)*c2;v,[ΖJ}WSSӯ_{15w1cTK9RXX&%%15ާNR(taגݲԵ|(z[4YQ@p…n٭[sM<9""BgvĄ[sP(l).J ٳuufE !\.w͚5yyy?SV;1gk?:xy޽'LVԩS}?][3`h555O500?m6bzzz}s;t,[l/H IDAT͹'aaa*%HB`.'eTiޛՑ>駟"##k^VV6m$O-277gLU%EDDHmbȋS8x+p8_~EQ"\LQGG'""6#fjTD7Ӣ!22]5kMMMҥ!1'I430r@3glj\(#4i͹5!d۶mc\.ѣ-(NH !ӔvɣX`)Fq8rHyz{{{GOOo߾}?$̬H$oK.^~o߾|̈́ tuuQG"1QBHTTےuuu޹B,WVVzJr:G{8+CRg5JΎ&LhfbԩSR;<=9cuUCХyJ\Mt~-kDQS'֞/J%555;;;9;:;;˓SMM̓n߾}gϲ/>xGy#9ǏϚ5<ӳO>JS^PP Aj?#gVw1٧Kddd\v- 66D-2333f2cl:%"͵RΝ;>;駟.]9ckJ^ OC)tMjtUUU1t颮.$VT$|255ٳg>|Q o0"hUUՕ+W${xx5jԨQR+kKKKcl M6|X9Ѫ=L{P^DXC;ڴ=#_LLL1bӶmd'xDZ;Td ) dBZiվ~鮙뾮~7Ml` hOniiic/\044lݻwo$m 1oF6DE,T ("`0]5$444f999mpw^^ٳ=jjjWZ[ ~ʕ>~KRSS;6o<r;|!֩n%F%%>zzz:)))m` vqjO``k&i{Pc""_HgZ>( adԛДI&"}F?i+=J=HWW7..n VZ" C^^~Ȑ!)))B{ϟ?Omvܙڤ\!B=V5| 6Ȕz?ܹsW,[a01ZXXp޽k׮]xʕ+Oߟ|mڴK.tf:]TLrIIIW^}u=̙3Ԟ/_R 6IF(d6ٳgB=Vvpp6oݺEF#ݻ7}tjcӼmfb4==ɓ>>>G]]][[W^C9r ~'''өS'nݺM0a߾}/_z%if5DQ>-0! &Nj!3$!tɓ'URR&~ k FAg|9zTQիnݺ)++[ZZ=СCtuqq BLm?~ .5jUꎡ}=Jmv҅ʔ?4SǎsW^^^HHӥ8ӧҧHRHٳcǎӧO?p'OUYY͛/.ZVOOo޼yXI ҤՁg2A0F6R~Gi{y\~g~YȆ f77FRU2ڵ#>lC_XSSSZZJ[Rz;[`"مajj388NQ:B_~e%%%;vΞ=K}=*)N3|irrr$t2K,L777k&&&W\QRRjZmO\.… ~~~/^hTcǎSѩ7~ 7JV&e FLT !nn |||&ϟ1csl޽{+~Z6-ArWEfP&bŊ7n3sZZPFSh̙3MLL&swwxb}sVWWO:5::ڹeicǏ;ֿoooONDU\\0Y>!3^EķlBAWf $>[KV+b%*k1Na[}NKRQQ9v옛ɓ'[n ;|͛Ly]7|Lhd||ȄA WVV N$eƘ1c‘#G={… mll#Ξ=KM7[[[{xxͩ|E2VQQ1a„1cƬ\o߾dƳի֭{ uѣGhcͬO>B{|~UUUQQQ}.]l޼۫N>/Yd29ҧO;v?~ׯ_cbb.\@sܹsܹC-// +//S5BQQqʔ)tb_pº)((ɩG 񒒒ܹs޽Xb͚5|r̙;v899l<Dnorb)((Px\~}p~e)!H%Hr ALkWB𶔝;wXBSYYk׮_|yMmm-ĉ/\@6=zD]Fٳ^ѷo_[[[--'O|:fΜ9G!^^^">}O>}9?666unXOOB]]2;;;))IrLLLϞ=Eԩ3gM?ꚙijj~s좢TUUENKJ)֩455G9w\|@XXبQf@@ŋQ1Ǐ?y===ܹGAgDDvvv=zxW߼y#H4nݺݼysĈBGI抜\0HHH}YQ Mfhh8qĢ"j^^dСCˊ߱ɜ1cuuuѲ`7{XXWH?RXP4 }왽]Ȭɮ]233CCCݛttt믿 D?/YD|)0\F1NaX92 1t:1420{{c _Ǵv/޽1L&sʕ'OѣG͚5͛7C%_Ycd?"'|m1pϟ689A c„ _.D6lXBB7E{}PQ!߱ٳgGiL?QM⒘Cf̙۟~ H MHHׯ^zyytS͞8qĉ_~u֠ U ʚ -PQ._|ĉR%MMѣG/]GA <|itt79-MEEi:t ΝL6߿O}G+V `XB 6e͛7O>[vX,?o```bbd6TRuuu]]]}}}|jjj>~`0LLLoQd0߾}+--eڝ:u|ܼ555mm7ƾc L?QҵcǎUVaÆ`<ߩ***˗/_|7ӧ%KH/eaaaaa!9;t ~!^ӰlKKKKK˖ TIkFc L?QҕImJi 1jeedhh<ш!8-|VPP@riH(U^Z:)PTT4lٲf͚zճgfϞ=lBl$*M`bbB0+8bG-<oԞq$@s E D[=̑Ȕn>}ׯ_oذaӦMrs΍ {TTTN*@($FU/Zw2fQ bccWX1dEmݺ˗ׯ_߮] mihFFד|tŃN)Q'%%EFk۶m25u'Nܻw WRRԬMII~˖-kx)'F~z-ee \ҥK\.ȑ{ܹt#Bc M^[[+d #44tԨQQQQ ٳg,'"Kgg\A355Uh}n/**9rMMM~RIkb(U޽555e4f6|s^pҥKO>cll8vQF1R[#Bb_~!A|Ky+++׭['tbee%ۯzMO= 9rCИ3gΜ9s*(((..f2jjj;vTSSk)d>}D166.**"!!!yyyd!99ӧOSL!oݺVN[౧###ׯ[nȊʂymnnEv^pVWW 533#SNWO8!y<К((4W^eff ߿?h [nMꀀ%%%5u$H{.yzjCCCdsرB455 &NII)..0$hdF>IIII/Rw7k%%!Cӽ{w:--Mē41J$''gjjZw@xx8yࠠPw us=VI-,,\uB>|@6DNR[[K^l C֩c7=$ibTQQQpQQQQUrQ ENC^Kx&F]𵵵B޼yӳ9Ill,ym`` aHIѣy}}Ko&nnnLǽÇkccc--- COĨyԐ9~WWWϛ7ם @F$Mr8sss嗷o8p`͚50]]]WWW{ӇExyyI@$b\߸qƍuod3((׮]0ѣ'Q f͚Uq+Wܾ};$$UPP8|0Œ<eX/_:tW;w|EEEjg.]MEEK.q8ɃVj6h%BMMΝ;&;555-[gee%48p`BBˆ# @PcT`̟?999:::m֭ԩSPhT IDATvڵkNΝ;~ZꏆVjnr mt|Gb@ -rQcZZٴSWWoxͨzɋ/ }Fvn۽{w9fJL_rɓ'媪涶FjjWXX<4(++ȨH=7(H2;;RQQQYYPUUj&Gݲe |Q~Z06݂|wZ;oܸqԩׯ=wܺvK7;wnڴ}AbG@bŋӦMۺuhDZzuzzzDDD mKHHxW233޽;ӧ|GJLL\`mllZ:C+|.^qF޽?͛eTڭ[7Y< XNOO:uj@@ۇ  1 ),, ؽ{wYYe,J---555LV\\Lg^GWR\4(7o޼rJ^^'..0s޽{=ZCCy#mܸ1 c>w,33_zjcox|իW{%ݨBBBdE $Fx $F...C߿#طoU$㑮k׮={ollO?9RSSS3f̙3%yBД0=(4Ç9"HP(///(YSSSQQApׯO2e-M6mߚhχ#Վ;iii{_|Ym۶MGWWWPH,==gϞR||wHO/^?࿍GGŋ>\v%r>}WVV#F8~tT]]}رׯ|5=====ʕ+ ,2dHQUU%r_,EJСCw!/%\w|eeeQQQ~~`09tPllҥKtTBBBZUkmXLLP9Q&:}CR_r'N8~xEE_RR2f̘ǏK2oߞ}tj,i\.wO>%{tuu;wTXXAZrnnnMxǏy<Ԃnst)Qh??wޑ):hkkYخ]v}-##\@|76S[ZZPV߿w"oaX}ӧϲefΜITVV6f̘gϞI|>lI>-%|/]Ǐί_޽{;vtqqquu:tvc#};wؽ{,׹x"500񡮠  5/ ss>%%%Ep`0=*hj~tңH@[.555..`3LkhhңgժU2 rƍͥvΛ7/00&cSS[nyyy]xLIIټyΝ;%trr|NhcmΝdϟ9r LMM쬬͍tttZUZsrr?͛ϟ{|l2p@ HJKK<}}?Sw&:ujӦM}رq'Hݲe ܹNc'a0III v%r>Ϟ=,X`g^xAv۷D𪪪M,W$TQQqݺu ,ؽ{w```ݓ?~H.)Ā֮]*T!Bf͚U"!CDEE ~_|Y\\ި .,,,$ܹs^dE͵<`?>ٻwU*hݺuԞAݻ(**:uW^dª{ IBIĠwJGGg˖-iii۷o733b4SN}ittLA|RppČtpp\xϟ?7)iiik$FSjj*ScǎdETTT F\\Çcbb${uo߾M6}!Մz1ydjٳgkkk% !M?V^SSsʕ+V8ykׄj7d2 9n8B^^}n555kr Y(Ҳ q@>|iiiI$Fv 8z(i'\WUUݾ}ѣGIIIEEE;v߿Q$?ϑl6[CCfbqhuFF'O LAd9-VQQ֭[FFFvӧOW^Q_JNNfɕpuܹsÇ߿/**bgdD7 ^zĮa~ ڷoocc3n8===rأG]&&&B&spph5Y ɉ>|ݻqqqO>[r׮] 0hРA5M___ JKKGϋAZIR:%g0ZZZ:t_hJLLLLLcX:u۷/((l{?,ɥxǎ,,,z+VXz5rdgg;wJzK.koEEȻfΜI^+((;T¨ʕ+;wf.gϞϟ5kw/++kƍgϞB%//y|gϞ $r8Ν;0ή L'NlܸQK&suttȄ2Uqqq@@}^:t萂œ9s6oެ!M0`@®tΝA ._Ӿ}{ &L)xi6ldmI$5p@rLUUUUUU ###!7 $cbcc,cǎ,0jjjE]vگ_'k׮Q3׮]\Kqf&idd$ ]z&F߿陕U߀R__߫W7jݻwŋiӦ>|888 g^5sӦM^۷ŋtHرcӦMbTWW]ti7on+nܸAmY}IS``3H'ONOOo@UUU``ŒeF\\܄ Ĕst۷>֬YǏol%&&8066VwfΜ9'O9gmmm޾}{eee4{QÆ k $6nܸRSS.0nݺ/^\\>$F?>i$h߾}'NإK%%7nVTT[:3ܹS(9f̘~uIUU2///!!!88Çԑ<8vجY???=zDpB}}}u }vСCޥKMMMgR˛={vDDw oosQ{TUUǏءC6˗^t60$$Kh1o1ILL6ʕ+m^p?̊͟? 0`pY|sss__߱cǒ=ǎMDlT&#+)GNO]R?NڥKs犿E]]](#RNNu7!C䌨67as!ԓ)~B]]E-Yd߾}׮]pЀCM -[ _m߾=믿JKKMA]IHHӴ1c[TTT!hcϡa2ֱHV:{,TUU ~ $_>}yfAl ,,,|}}544dnݺl&wax{{i&i]!OI\.WvOڵki|ƍl׮]~~oRRRRSSiD$@ C(lٲ7n?,j,Rtt4-;vPl{``۷gϞ,`ڪ|ܸqڜ:u*Fmhh(r!!!yeaa1lذp ~bΟ?߳gO m=z@up8˗/ݶ *999---ABVpffffffbPRRR.rkUTT## tRPP>|8uX}444$JXa :ڼ1f55&)hԑ$ADGGhݽdNӾ_,k̘1VBѼq̘1&F\"T?ψ#${ :{,Wcz{{OBl:455srr/Y\.r8UU&J!uc䬭iسgO:Qjkk߼y㈈7oR_jJ k׮...M&//ܫW/vr^bm拋( i|\.GDWWbIoAbxw4lߋ+++?\`СûwY.d122R]]MillLMr?Էo߾{bJ%cǎWhuu{g:99 ͯKD$9&+;:ua#!gO=zH+t'Q';YZZ6ɣF*//'{F-tU'F\Ν;>|(hjhh]t|f믿 ʆ'r OYZZ l Zk%ZQ]]ڍz˗/Ϝ9kLaoƍ'N b04iɓ;PxHҚJ |B۷Cj2bFOhώfNQSSc2MX斛Kٝ={VvGZKiϞ=dV}vJV ebb"NMM{QužK\UTT$dV7 u!3A/^pqqt$͖ZZѽ{??? B|~LL̢E:tnݺcm?}"a\i_ ī6U`())5ባFJNN&{¾ߒMCz*Yd˖-M+\&%%ϝ;xb>|p-2?,###qqqTVVVVVpTTTZrr}vӬzԩ9s戟YP۴_~{ٸq#HZ< k׮\2<<ҥKׯ_ȩr͗/_Q,a...t.Rݟ>hРwMr͙$^|r#BZ}bP:|>_( K]v~dM?~\paÆ&dEkkkSRRl6sW]\\)0667n >|󫭭%dΝ; _ 6llK2dHk8yIYr?t${{{իWӵkWJ7~h0l#Fܹs=yׯ_</44TVqd3...'']vyƍ߿b0NNNoߖ8+++kDWyv7G.\J6_N%?nb̙3t$Azzzϟsc׮]fQQ:::dU@YYg˖-O<ٳsL&3;;#?ӧOb.ϟ?p|"aʆ۷oEEEbKkZIhhhrrr\ٳgS{V^-~zcW0 ʕ+>|xK.R;vl޼yd51r333;vشZk׎x .,Z^rl|sssIGWW<)//_; JBOOZ'\GGcǎP{]]]5I&F333 {dnnNM6,^800Pp ׯ_~]w>}1c b{zzlr 焄b>ihhطoN:5!`,[lł ߿yP]</))D~ŊRY*-...{%'OYSHIIIXX1s 1bx={Fr"G,*0^z!wo۷i]\\444B!Lmkii x'O$+Wܾ}m2kkdϪU;զM=k֬i¬B'N,Y.^(0ݩcǎM:BBB̟N=>}|ZlEEE&t.۲%GCCCobtر-hW^qFAn͛7B@QUU,8`\l\]]5###>|(C_XSSSZZJypq@@\)0LMM?|@Aǣ#_~dɅ;voÇgR{DccÆ !!!;\QQz}SSS~~~۷oF9~6???''Gyp8-j ,Yr,AÇ7n ۷Æ sb{޽޽K6}}} Pk׮yzz]VTmLLwXvaQ VXannViii ,xzz 9s]>}$hx}޽{u._|ذaU\\}*f]ϟhpf,,,+++V)++SdN9sfݬfaaׯ>.**JUUU4߱攗7n8繋4cƌ#GqG9w\Ĵ@XXبQf@@ŋL~ڵ [KLLCMGGG\ۨ 4iPmM6mdڵkɦ&EI>Ok#~~~BYQ===GGnݺ]޳g+WP1СC>&UWW7sq:p8>}<{,""ӧԧHrrrR|r6|rj\pv)S,Zb888|?& ڿ;w3"""Dѣ˗/EAOat͛#FZ +rr!!!eE 6mĉyyybrLC /+J~ǚnDDĺuO=*MMݻw7K"1֭[G&ԩsww j0i``GjbTQQQ!e|yH##s ^%K y# 6k'''7k֬l]f͢)r/^닜0 HNNn@'1a„L:ߋO>y{{Yĭw^.߱fES ,ʢ9>~Ŋ],K($0n޼鈈o `X?iӬDQuuĉo߾c5ZZZgbRǚӧCCCo޼T@===ȉfw% ѡyR]&&&!!!BBB1q-]RF1Sn۶-<<<666%%XEE~رZZZB{2 75F MyyW^ }8tuu5F֯_i&A$\njjj~~>700011_ƑRuuu]]]}}}c^^211/XFaddd|fkkkwIkjjrssJJJԴ۷o}ǚ_UUUvvvIIIII.yQ+sss :wLN]zTT}5FemǎV"Æ k0SqYxxJ Zj߾}A<{F8X, )١Cf[ZZ-( 2SSSYLkddddd$5? >-(33ڔG!߈>}J^{zz6a%%%777uYYϟ% ;~y{{oݺҥK!yК䭐+F9M}ׯ_oذAx.;gΜGEEeԩM41j``@^xiP!JLJsuu{.˥;vڿ~v5Gm5FuF^_xwލ!99ѣGk###mmm C1h^zm۶MFԩSO8q='<<<<<\IIRSS&777%%./[ym $M>>p… ۷oOv>ᅮaÆI+&&;zT[[+ :jԨ(jEEٳ/,cƌ %,kԨQݻwwrr*,,$b111׮];}teey󦋋 YYYL&SMMcǎjjj-T!hII;PPP`X"_ݶm۪U$wqQo8{F U(iY ineUJ2oen(B"J* *c<7003}>׹4@*""g0xbv A˖-͛P(y{]l*g+TJJի7nx] Ӻt2sI&7LQѵkN>~ҤIP;Ξ=[PP`ӑ70 fwԤ3gܼy3333??w^=v 5TI?Ξ1cƶmtWlB,*,, `P}Q~t[xomgVHHHPPPmg=JRPbݲet׷cǎvvv&&&Պc>P%] /))ںut kannn7oug*J=k,>] bMhZuttfff:F'@h^͛7uOї_~q)<<\|be綖] yyybShZۖ:F'@hΝŶt[=YF,cbbtOQ//nݺ?Cnn)aZdk׮ ddd?TZsҥ={ ve4K>bΜ9Ň޽c&M 6q,--~iѵ0z…9swȑ#Gn4//cǎhka433?K*dQ_(0:nٳs VVV yP全ÔK*{.BHISe|?]SSSwwʞںk׮uwyԬIA;GhMLu\ hllm[vrrsNeOuu1}EL O5kbX!g.`+=Caѡ0 T}h޽/^(v :jҤB@e.6iյfO0ZRR'v--- \gXI<~ KQF(Caѡ0 P`t(0:Fuݻ+ӷ^zj~pΝk;hP(0:F}HE++N:(8jӦMϞ=5ϹrJNNݨQ#wwwvS  0D*^^^ǎ3\|e3! o\fn+\͝;wܹ@P"AڷooȨEZ1Ll͚v. Ԡ0dKKCPP;w.<<رc7oT*O=СC===ejfڵԩSW\,++k֬۠A Լys=~;?ӘSN-[G_~aj1Ν5kӧܹsΝ_$ /ppp4._Ts??S?m߾嫢j '77 .9h0X1 0Feee&MڴixVڶm+oݺu ߸q#<<,66vEEEBԩS˖-MLL/]-)lH IDAT%>MKKKHH!a(-X@9v؋/޺u+:::**IIIӧOo{=ɘ͘1C\RT^xQ*{m۶JJJJPPP> QѹpŮɎ;l*OBvڽ{>]t̙3bwǎt\.?uꔓ8/UsP J+?ѣ+{n K,y215믿vN*٢E/R즦&$$TsP]k~eРA=|ddcVbD#G1b٫Vjڴi Qq9r䈴h֜9s < Jbڵk۶m0ŋaaa}]``SO=Uo@Ca`\ݢE ///mرcϞ= J}^{%%%U+- ryݻwo_ӧؾpt!bVm۶#G......fڷoߣGƦq3ucknnn|0,R);wɥ5k֜;wիW+~ذa#FҥaT]m޼{G(mmmFڽiZjٲ#G&LpܹO?~we̘1oc+=<|PmҤZXXHN::uj]|yѢE;v\dIQQQu QvsrrWN9CĬ\>eʔ+Vxyy) g-^x񥥥54$FFTZ|]ֆY3;v;wc23380gΜ=z}J@@a`\+Wfff&M :믿sʕ+۶m+rJ60rFƥW^b;*_:d2''{/>>^/^o@=Ba`\/޽[qqq1PLͲ׬Y3sL___''h󭬬>HJJ( 6L0`iwСBG:tWA011sPaÆݻwo߾]+111֭NNN~~~s='v*/}:qℴ۵kW? Q㏥ <|}U6ԩS/Rii87n w?~|aaae7{xxjժZ(?#G2eץRRR͛啙)M8^ԩ ۷VA~~I~40(>,>>~niiO?O?mV.߾};!!LV>}mV񆈩BظqąqqqC~g5kVRR~ҥ|oȑ#9h`(\. }W^-OLLLLL1cO2f}ݽ{ѣχۻQ#Hl >>~ҤI}}}k7BBBTgn>K.N:)`t(0:F QF(Caѡ0 P`t(0:F QF(Caѡ0 P`t(0:F QF(Caѡ0 P`t(0:k;Z믿޽[^z?r+W"##/_T*KJJ윜߯_?sssm{za .ٳGɹW>yt0======66v˖-@q+}vv~[VV Hkfff666bK,Q+z72eeeZqㆪ1a„:}Ⲳ+W:99mѭݷo_ԩS-Zi `cc/YDPPXXaCDwƵbƍ7nq=<׮]݌3d2ڄgy& !..ʕ+;woDۥKGG:@aD+F󃂂A<ݻ /hffVᴡCZXXǏo0o#GJ@U@gDקK/U9}lxJGHBBիWk;U3111 X[[巴'.733k׮:u:q YYYiii-[W/톅V&rrr,YrV0z޽Yy+99Yprr\Huvv۷o˚GZ1GIcǎܾ}P 0ZRR_ 0lذ>}hRT5,77Wܐ]v͝;wҥ{+sĉcNJ/`͚5ؿ~oao߾ѣG۴iǯkݺ-T:TRR2l0mOO7nhSTT4qď>HsUTT\\W_+U߿ƍ>>>6 hҥKUJ:uQi+FSN?NKKkٲee3^j6o\5V lٲcǎIG||| ֹsg[[[LT*ϟ?uV顙Jrjǥ|m۶IG,--GP(]vݺuK:nܸ={Tŋ F;vܸqaaak׮MOOݻ^BBx =w?W8p)Q``a}}]ӂT4u&n!O>=jԨ ƖE/F~~_-vv9x`i .\tŋSN=sî[N*:eʔe˖5mT:8z˗/XH57̙3|ؒqIOtttܽ{?/4m=00pɪ˗5$h>>>∻#GjpBXpT*wyUlTJoff^ &&&AGGG ?wСUGHhYYYb7((|UTE./Z(00P:R~fjj/ ^~ZUTEP|777,XV~͛/^(v좢UQQӦMwkUc+WܻwO۷ѣG0N 0G{}j6nܨjt]uV٭//ˆ#4#.ZJsfv˖-}7|ӥKb 6kN󧫔s]qȑ#:FPOQ:u?r努k׮?PrԩS *I 5k￿z۷MĮ_|M&ӧOwrr֭$*B\\\||uqqy뭷4lҤ7|+s5oooU__߃ZYY@կ>O?U|wy晛gee?e2̙3;vhh&E FDDXXXhx%((HӘi_&B1jԨ5k֨ӈ &h߿e˖n̯׽SSSőC޽̬4_|!=$//bMz/"ڕ6mzQ8qB]OOOi7&&Fr|ȑ qㆷ;wđ#GRѪYZZ~G/_>~ŋk׮W69??ҥw>~P(\Zl)ՊǑ5K`ʔ)֭1c? N:_~5X 0 @CP(bcc###'N啟? 2 Bw]ZZw%LLL,,,rssU]b*Ukh} +VOKK ܲeK bhJh'I&S4+++**jܹ۷Wa鈝.))QIҥJP5Z3f X[[]Vt֭{nL Q>\MLLbŊׯݻWm.OϜ94z!.^s='>7ސL6MTV7,(!3DnVEKJJwҥ*'"W+;_=|\re͚5OHH(eسgjCeVZ%߻wo̙PQ4pcŋufaa/bH؈݇JzyyI!!!Slٲ#FZZZ~GҧvWmbܹS4l߾}ǎ:U0 h`5Sڵk(ֹgϞ**--v?>ۮ];ѣ˗kLbb֭[#~~~n޽=<\:2š ]bLUZZK4OQQн6V7 )))}oK/ի\.#AAAxEEE∫ڮ|A}iwڴi޼ys?]-k׮ӧO%@Da`,t)k͒HyEDDhfi6--mwܩ07|||[jtvvV[k׮m۶8*ɓ'k Ca`Dj.9rdϞ=n~~çNZ"gϞBd׮]nj6bΝH^^ޫ:jԨӧOEEEwvwwW~̘1mܸ.-&^r5888++Kʕ+_m~eԩjj^A˖-E-Y$22222rҟ+uGN'MU謰fW~Z 7nاO\Hiiׯ_߼y;Z[[秥%%%iaae˖ƍ+(s&MZjooߡC[[K.yxx㏕%ܡC[=:??_5{͛7k׮͛7W* SP5kcFAd?c=߿?uD֭[PPxbժU+`FF=ѭ[=zH:f!;;P77&l^AT*ٳg:-ŕ>§Qb+= dLꫯ^tig4(..7T{B666V:rڱ0`@\\K/a_~qgnΜ9#A={vjjju4jhӦM>|r `HСccc7o|˗/Kw {/tI˰mڴ_?ȑ#O^=/]>}F=f Eմm6444::zӦMU͚5{W̙ezJzrr={tpӦM2Rv=66v޼y{ɑ~BÊ]H O||IT˗n>gϞ$\.755feAQQѝ;w?~P(6mڦMfdd(GYYY5mIeee۷\p_|!vcbb U*2ήm۶fff bKPkazꩧzJae2Ce B*W3F QF(Caѡ0 P`t(0:kL IDAT;kKb2 x~@VzF(CaQЃԜ\niiimmݶm[KKN T(DYYYBB±cN8S4GGǮ]0`NNNO8IP P=yyyw޴i͛7%B޽x7nea޽˗/A =wܹs眝Ν;dK̉'Ο?fUQѝ;wfϞ=qČ }(Tƍӧ5Od斖r\ӧO+qqqT[ J#~=۷oӦL&S=zQrrrRRٳgO8Q~}{z~g1(&?6mZh>}z-//F*ފgee:jԨ3gOtN^^;ҺukC@EJ%&&JG6moޕUE~Æ 7ntvv>裏4 Qԅ o.Сۇ V}ݳg[nq(@kZ-ZjK>!C֭+,,%,t_) PsΉFZyG˗/ő}:6 ƃ(TlӦMѣwﮯfff-رC_QDP DEE]\>}t~w=؍~?-ITHHP fgg]oo-[+cƌۥ'N'@(@oiwРA hV~(FP $%%I3W̺v*v]f(@_. ۋ[n+LeCY4ZO[N&yZ XTTwB4v%PwP G999sssni;655U_uPN|P(4O؝Q5fZ2ߘQQQoFzzzm'dРATEaT(@w.;v_ٳga@fUQjhJKK;~%%%⸅[-&<Fzv8 -Ǐ7o,vMLLO?vwwۇ\rW{?N2eҥ666yyyӘk|}}|p+V۷O>ZO{W_z'Nh 7m477+Why;v\~}eUQA CBBt`Rmž8qu嫢*k֬|6 /H۷С~?T/hbڵ.]* Ba*֪UJGG8`ZZZppݻ㦦-qXQ_tקMaaW<T*000**J\y6uԞ={MӦ`jժkVoҥ۷=[hP lٲcǎIG||| ֹsg[[[LT*ϟ?uV顙Jrjǥ|m۶IG,--GP(]vݺuK:nܸ={Tŋjaa8ҨQcǎ7"999,,lڵ ܽ{_OG|||7Apuu}SNTmCWuI2̠uQT.]o>q$66666WE5jx#F%ߪhݼ)??믿VVV;w,-…K.]x8uԩ{|u֩UELlٲMJG| CCC9s[RR2n8鑗w~őMN}֬Yk߾}W'קO }]&nig];iI#""4W4<[_~e;;;m2Q(FZfK0Ο0a6a[lY*]~;55U:tݻj @Aa֤I3gv[TmGڍOFYCrO8!jX!&&FZYd\>rȵkjʍ7ܹ#92$$Dq0 hIӥK>|Xzj@@I~  /k rsA;jccc]2g2lϞ=)y'|rmI('SSF7lзo_pO¨(A}}}:tPԊT*vii[Įqw^O/ZTmp鞞ܹ[WFFFmT)""sΕMP*!!!'NlѢEW^}&gff+휜[mM6WEAHII={vuChx(@BBBFaÆ={fqnl&|XL\f͚'$$Sڍ2l@Zj"|޽3g@}Da0eʔ!CHG.\0f̘۷oVJŋufaa/bH؈݇JzyyI!!!ceGjiiGI8P_YPPsN->88X:};vVzWU-#iQBaaʕ+v&''Z̡]va{)))Rj?ڵ=Z|6$&&nݺU:'CLJU_~;vå#3g%TuqI/F-Z~&LrJooZL**KKKOOϨ(U7%% Vxx^zI>@$(([mɓ'#jAx'L vMַo_GG޼ys8ղvhqlFFuYjbb"-}bH@}QBAA1s˗+ Q~~ᆱvk 9o޼ 󓓓g̘!Q[-¤Iڶm+vKKKGzXXX8a„_|Q~1cuش޹s°7noYm]mVɓ'kJu$ 0 U(../?,>,^o-++A#GS>|ԩ/b{)-Dvu̘1j3-,,vijj*ꫣF:}|QQݻ`̘1/иq]ʕ+YYYTW\jP-SNU;G5000==fZl).Zhɒ%7o~b8ԑ4PiaT>};˗֯_/qcZ 7nاO\Hiiׯ_߼y;Z[[秥%%%=iaae˖ s&MZjooߡC[[K.yxx㏕%ܡC[=:??_5{͛7k׮͛7W* SP5kcFAd?c=߿?uD֭[PPxbժU+gi+F /0i׮ݥaaa'O~L ?ݺu;x𠕕xFFƟycǎ]zU׳]DD[ea"""lmmƕJ3g:tUQ(KKK :tF:XXXwȑKƍ:uh5бcGiP{nڴ<<}zرwޭ`<==ϟ?~}Du G#bWbK`9BQhԐKbcA4$ v 6@a> 0*Zk} LJUD2lW^S?s.]O.=WtǏW:R%;;K. 2DŋoݺYe-=@SǗ4N```ڵU>~z){;lR_|yÆ }}}[n5jƍ͛7PeY[[=zŋ_uuKAtuu۷o٢E þ[l?~9 wmԨQ*iܸ}"""ۗ$>Yׯ/=9TM SN~% خ]/~w#5̿촘Ffͤ_{o Pz2lڴi53gx'OFf͚ݻWnz:uԩS'Arrr=zt\naa.lffϞ={윜Ĥ4  eEƍ_~]N&M4h߾'A۷oɹw^RRL&377oܸaR*#m1uT10 e5lذz}Wiiiq-^ȨrsC'˭#lׯݰ2vE[͛W7$ M=znР8wm߾E0 ammk.+Ϟ=[Y5(ԬYs۶m *Q&CCUV;P-d~7H@9r{7u@e/ܹSN;vO?SpFaK^f͚!Eeg>}-๹xQ;./͛ڵKKI0 *8:::::Vv555dJ̬o߾"< -0vرcVv_)P[T9FT9l´>/t!77S3 ۷>ݘM4=>Bi(?lPPPPPp(ݘҮ'L(`ll\eʺuGJʡ0 ʡ0 ʡ0 ʡ0 VzUё#G=ZX T0 *sNXXXhf*@QJʡ0 a+=nƍԩcbb%&&*444۷L&J(hܸqNKKK4iR\\+?CWWN:ӓ'O޽ʕ++Vh1e*v ֮]J@0 *244TYٔŪhV,YҼys5ԩSgĈ#F8| A/7lؠh"11Q.|=|۴iS @ښ5k^hkW_rrrڵkWzSNY\JAAƍѣÇիYz%аaCaÆeee 7rȺuj?Wť>)('?Fc(k׮yȑ#K'..ٳgveoW_}cǎUQAݻo}*ý/]T!11qӦM3g|uFP:v(nݚS87oۭZ*kZ@K/_9s]KK޽{7h@FFF&'' pƍyO?~ՊL&ҥK۶m>|&BllŋϟࡲGPw._|̙% _=zh-?@ͭ]v:uzdoo_##.^s3gܼy3%%E.7lذk׮#F4hbevvŋWƌӨQ#E[ܹs֭]7k֬RH޽ P*wƒ%KN>h4hرzz[Zzudd3F)BZZ_|]Ϟ=[ +VDGG+}СC._,~-K,߿co;w.[LѾp«Wvذab?6m?흝GDDi%75tuu Xu!K|ʁ*S4+^ ۢE4ia}͜9ƍj4jh::nQoOv Og^zm۶YYY)ښo{zz#[n F;tСCbIW*e]|YѐdڵS}flllffxS#\r|͚5 ,(6Wٳg;v͚5;ydzsjԨaooooo"vbڴi.]6lPZImii2m4WWW8;qm۶_HGzuAsssqD&)]b )))E}:::yJرc۶mzQݻw{xxHcƌ+EJM~CJܻwOѰTxPiAAV"trgڵoא~~Gޜ믿*UEtR޽{ OٰaحVZXXXQ еk3g4lP UE+WBڪUGJR_xZE =z*:i$;e;q7|Q񊆚K֭+Vӧ\.W?qƓ'O>~̙3tmFǎ;vO._\U\>lÆ 7nܨҜ|UvҥTEwUZ15i$++jժx"&&ѣ_ q+ꪏ, XhN:_|Ś5kđM6mڴnl׮#G@Si[Ķ~V4 [etر]v)J; F>#EaÆ۷wqq{ ׮] 8qb|Lte/_*-G=z uupp4h={&:/^\ni^B-ƒgvvvV(TJ_ܹskH iJ,*L]\sZh^^^j}8p 44رc/_,jrddd޽ÕnLom244\`Akjj* ]Ueź[l0336m8>o޼iӦP(:WveMC R~}-Ft<=====򢣣Ϝ9S4GG7nXZZvffɓK(&&&rKUS \cWWWALsγg*eeeyyy={{Awɻw޼y'Nܼy}mM4Q4 !%3iԨ#PFݺuÓ:gI<~ԬYSڕ,uDkEEaɒ% :::CrM@{7 2… /^| O}خWn۶&xKͥYhӦM:w,}*nZڍV]v111xEmRZVZґ <[, `cch<~ڵkjf>|XlJ#g-ݻ+Ph"11122[]zIGnݺ%I;voYvCݻ'}ڧOiѣ<1cFvnvvWu b퀀\:tEF]._>uԥK hK?T+(VTTTڵƏbŊ;vhVͥ峳O?i744TRK/'OvvvoFmll /^&s΍7*R77jժ PPPO??ҧ_^tuذa5jzرΌ}֫WIu={ޚ$sqMFvnBBˋ h"mQGҤꪆӥ#/|?7;[aџڵk>|ao߾@L6}ƍ+E033/kȘ?ܹsiӦ/ܹs͛71bD\\\Wb v  ,P311qرgg-Z(MSΛ7j†^ZG ݤ$ _,OӦMnNNUZo2⧼.\?+N|uXXXi&&&ޝ:uR.++kڵ PӦM̙+)-F@Jzf… I344t̙ґq)qqq߿t9s={dK\\\>|(xnoo'*YYYAAA.>> .,KX]^1*Bڵ.]:vXKKOW>lذuUU8pʕ+;tPSSSwwK_ѭ[ѣGKG,YҡC5k* *((xG1xLqRLLT~}5k5k?_zU!##̙3_}ץ/^,Hjݺu͛7...C=rbjj#G 2|piz݀}Κ5k֬Yƍ+z{{{,Y]P2ﶂ響 ԨQQFM6)Ai8%%ڵk/^̬^zƍ7o^ee˗/3F^dxڹse .zr\b,s Mx{E]dhhreӦMϝ;'I*&&fРA *dee|4nܸ7 Ν;~ݻw ?JdK5ݻʰҝJ_޲eKEwĉ@^LMMmݺǏő6m?E WUndƍ>EDzUzsJ"""-E={ )**Bǎ tʪY͛>akkӧO;;;T㥅Q5a]]]CBBT>FKF׮];w?\hVz(?yyy)))?NJJ*V_ԭ[7**꧟~RS5lpڵEiР7olee~L&ׯ_tttUQ1sέ\Դ9͛74i&6l`ggQllq8;;5J:|ϗ: UUV@DDD?/^KOO>hܸqNz٭[76R3fx{{9r䯿tҽ{^|.-,,tЯ_?zyyyxx={IIIiii&&&666ݺusssS]%==iӦ3{u:ut2|p777\^5k ߼y={\$ͭU`[[۹sݢCZzu-#w֭[*gV)1 MKK۾}5_vQFyyy\q(Uf͒q_!@1.]j* <{l~S57P:F@}yxxu%zIIIǏ zV8ct̙Yf~d``PfMccjժedd'%%egg+M_`~X!)P^x1uThǎ?N:YYYrss⢣CBB_.̝;¢Ra+=~4۠A͛7رcȑ͚5S ײeK{ 6TP@F@ϟ]++]vC?]vժUKٵkWzzEaTQ֮][fEhذիe2k9KPZF@H=bĈ&M"HǎS ۷Ŷc 4Hl߸qL9VzPŋbUVөS'ɓ2Tm^z֨QJL;(hrccR177իWIׯ_#T J1@0 *XXX[n:ݻwh5ʚ \ѰR9rض.SN@{( ]vAAA uVۡC-d(( _JD||رcӥ1"( ؍uuu=tP^^^捻lذ޽{`Ϟ=4iR.0 M>^zb7!!x+P֭[%v6m/hB[-[?Q޽LKK۸q9s1]$&&r333Oϟ/v6mJN[KBbb޽{￿m۶bVZm۶aÆ,`ƍ-[|Qe EaT8uTnnzj E_utߏ]'NnTѣDŽ ^xQٹxsQN:%]\\4_+*պukKϟBfҥ ('r2@W^˫,| T-˝!666b3FTׯ\"Q"^5Jǿͽ IDATBGG'??_$-tڵgϊ]OOw\[YŠ0 *rK٥_ddd( TVMkx?~|b.oÇKbCaThڴKGnzʔ ۷ToahhX ^ ݺuZ~Ziii?ydTTTBBBrre Я_?KK˒?wÇ#"">}[v:u޾t/^s3gܼy3%%E.7lذk׮#F4hL&!;;{+cƌiԨ-}t9iuծ][ӛ5kV)SؿLLtw (u >{۷U֠Amێ9@1mŊ|HHw}gdd$vCCCa bw…Ç+|󍢝3;;;VQ.]ڳgO˗/{׮],'RUt~nݺ)UE<~xSL*ݜ9sFOHHHǎWaYFaT/u}ׯ_/ś7ozyyKQ!99o߾qqqទ ѣ޽{=z#Lb+ӧu2Yf}A}}}kkkSSSq0++ ///iU5$$Dܨ^,X0m4Bᅺk֬:ujy_#~ HGի׹sg+++]]]xjj7O@鰕T裏đ . :޾M67666666LKK{˗#"".^ɩgϞ>iӦ=zH֬YsԩNNN-[^ 111۶m۲eX˛0a­[jO rtG&L۷o ttt?~̙`qqݻw rGh"Hݺu.\8|5jPPPxcǎ ;f̘v###PիW޽[|m6+++E[ `OOϼ|3fѨJEQF@@u#tqÆ (G_U**կ_KJG[xZLL̆ nj”R]v=sLÆ ő@i]UrJ9Z:z**_/[-?!!!G'M_YUQA/_s۲e˓'OJ"L6y䠠 &cGGǗ/_#E]{U 2lJUQQ-o .#F8qէA 6AӧϦM\/>^믿>>g.-֑#Gqi򖹹#nZO7n?\͚5>~jIuxD~~~xx3AAaJd DDGGٿ+V 0~ ۶mjժ1H888鉥̇kO>1b})SNIݻw]¨Ҏ=zhɩ vuu.]t~[+^6U0:k,%ZJ7>i3{-=$ԩSFQ:Fo1ss=Ν;O Pz`u/֬Y#lڴiӦMFFFvvv 8p`v4 %9rDZOJׯ_voݺ%[jabN-cǎڵKPLV{VZi,GlܸQiM4/jN:u$޼ySɠ0 `oddׯ=zQA֭0h ={L+ub/^۹3a---rq4/N```ٳg;;;?@$=|UVM>]&''iˢիዂ {OVrСI&IG|||F]E| ҉J7ө^TM֖L///ͯT*'/_vMMM5z;6::zȑүO?-VMLLtt4-RJiii #LMM7o1tPׅgffvvv/_.}铒rcǎ)7ݻwxxI JqSSSLVPP*- UOzS|9ݲeٴiy 5];vpuuaʔ);w<{QVVٳgK',,,d--33EzGVZ:$-pk]$dffJWkbxwnn~C}gJw?~ԬYSD+b[Ò%KUQAtttrM@:uTVM^~]\u[X?(??̙3∡4iyRNJJp۷]omP044trrڴiS\\\ΝBCC֭[Kʡk׮b &&F/^TU۪UQ:`V===Yw}ߦO]Lu=zh44m۶ P5jk׮]ewVbbbddE HN `gg'jSt177ofڵwi>}]󍌌<1cmvvWuR޽]iR={h8_~Yztd#F2Ҽ~1io߾ƍ씔7nDDD}tdŚo֢6mH޼ysɒ%_;v&###===~GKt߿yb͙3'##C캻KJ(h_޽7nnٲEG4J\\\Wb v  ,P311QܢE iJ5y>}ZMذ0-jJ_S>999^^^Viw>WB)OM̙3Gڝ7o^䜜O>$**ذׯ_2dHVV8ҥKbj͛7L8tК5kĮ\.?/(@033[~PPP0{RTR }WҮߌ3, 9stdܸqJs\\\/3gΔ)S^~2f\\Ç]]Eioo'ݬ,vٳ?/kQbff&kbQU/ݡe޽}zȑ!CTvRx7um,Y_ׯYfrӧQQQwbooS&u1>>^\f1eggMfddeffJ#,^;֭[wyƧt?x={455MMM=}Jx݀;wK'Ob!qFqdɒ%mICʕ+#""?E7 >hݺu5={vWK+tСC߿/qrrZn]jjׯsrr#V011Y~}Y4OO?:uTz/^>~̙-RƍSG~ݻw}۷O\tL&k߾K (*\*ϝ;w޾}{XXXڵN7~cWYd75~gz*27JHzpm3f>r_uҥ{|2==].[XXX[[w_~8{#""㓒LLL,,,lllu&]zzzӦM3f:utennnJUf͛7ٳʕ+III2ZZ.,8TիWhB:rΝnݺ(=dnnnnnnIII.\sNjjjׯ߹sgkkkqfFFE3N-###i[gg͛7^rŋFFF 4ڵ!C˞-@aÇŶ5qvrӧO>}V5jk+^xkmIoOUa+ ϟ?vZΘ1<*Vdff6}ӧk7%0@y 5\P"_.ŋ 6NBۏMO>|}"##zQYYx۝;w 6lѢE-Əoccɋ633+X0 *]vݺu%}@߂ :y,,, }:rȗ/_5w8ҨQÇo[PLPىu7܏?xȑEȑ#͛7?~SVLMMsss/_[fddd׳ 0 *ԫWSN/((x˗/>}*oӦP믿_đgϞ-\p… d2fZ/TYo k)^LII9{l``ŋA8p{̴#*g?shQUQ׻v[S@fΜʕ+&Mɩ /^|ܹ>};S__>* ĊQ(~izzo&ŋ׮];mڴN xOO5d*W׮]#""n߾uʕ/^蘘[۷sZ*;"uuܹbu֕ &uO-u1c(K,߿;w.[LѾp«W*7QnnӃA?k׮]k``[[P^>cE;;;{޽QrԱcG}JHQrdaa!*1 Eaы/vjjj%f(@9t644LQ(/iiiAAAb IDATbWT. P.={6iҤDqƦRznݺu풾UPPt+##C^{ 20 *;vl͚5Z X~}'''-eVz(wzzzK.cm o PLMM׮]kkk[ىů+ԭ[?vwwUVe {wUq0;(" *7ťsL-p}J3\R#wSS3-ws\sK I4LM$%]Ydmaf眹sGA7ZFLfccSZ5L`/*@iaQ( `@C0 !Pp*=G)vСcǎe|SE9pŋiڵkW#*P1믣GM}}}־}wwwwwһKJJ_եKѣG]`Ǐ׭[CߏC-;Ub"l>Ӕ5k-233oܸT*\ү_?''Ҹ I&m۶-66`@ **>>>99󋩨 & T*WXQNm۶w-k333vssʺsNRRƕVVVm۶dҗu>}4##O.wҥy榚<>>~վoߖd~~~/ӧO?~yW@0 ":th~,33sԨQnRw۷oXYYѣGv튍U-^҄C7333??7n888dZj?s\\ܶm,-----mmmWGy.ꫯʕ+c1@!PYYYiM6>1W… tͭW^z駟̙*G7ߘ~K.5hڵk*jٲe ͚5+;;[*U8;;?{ݻw/*ʕ+^n?ټyTTSN;wV{̙+WJ(\.6mZÆ APg'N0x6Tt޼y&ѣGK(wݻw[uvʕ%ks֭{𡉫DqOn|6Z8uss3qxm+`عsqA PJ&Uְaݻw>lT^J1 Z?^lwyzj*uzܹѣG8:3gNlllI}SԇZYY?m^^^RRңGTRJLs4sss//U?gQPPpΝG5k}sssܹjmmZ7L`s玺!j֬i<+WVڽ{A7Im0a T'Nl۶-22R<=LotY(Y,pΝ[O.\T5jԨSN}_~I NKK۰aÎ;q//^z?K;XBX%?A}0,,,;VT*ɓ7I~m&4޽{՟ ٳGݾpҥKLb++͛3-v?,^XN6]vߒiӦ۷_|ٳg⸛[޽+Gz왔n8pylFN `Pga y:0Ae0TI4t҈#͛7o\z… {=}}9sV^]Θ111111 ,xΝϴVջw.Zh ,;vR_MOO/IEEEmqߒRTÇ_ntpƌe (1՟yyyǏ_fMsrrN>}ٳg/YG:f/pС+YbŌ3~=z|+WN8qΜ9ϟOLLTڦM륯LcQB\b~4`b( c˂qoMEתU+ݻw;vbrJF-[VyA6m nFQzkĉZ5ӧOǍ7~b',%*jȑ?*I-''SNZSQ{?4Myq,ZM6B3VիWzٻwou떘:;;8Au/h*zڵ{/''G:$ɒ4.Xrq㊚ԩSom5kzzz?z?>{N:5R֭ƃ2y 4IHH8qDVV˗$ĕ*J|I⻤[3o,Yd„ LeZ>~uss vwwOIIxkפ/^X.s=z:tHcѱvvvvҭ .^رc\cnZ,wwwWY@bhܸ޴ii~wb^zƖSнnMEA9r4lժӧSSS/]?;v,:::55wwwqҥԋ ݔ)Sn߾}Ǐ_zoU&^ӯ_?Ξ=[# qƅ ֯_|?|p޼yR9s5mycbbėĘUcǎ]jؕdk֬yRGm۶Mvrrڰaý{nW_mذիgΜiР-_}ܹs5Rz߿?)))**ԩS7oLHHOsΝ;c?#$O.}DЊ`rʀIN8{nh` Ee/n*/bCǏoee_tIzXFFIT{Ojoϯ^JggaÆ{z%])}#Ǐ?rHڵ~SA(((w`z&LXrؕ6l>|x[Ai xo!5jƒaݸqc޼yґ}oY` TϴNz;vܹsm۶'F@iӦEií?,]VfaaѹsgSF7n HSQAΝqwww) ?-Μ9#vkԨWԴ-[G _9o<*zkҥEMo>LVdҤI (͛7k^lmm#""|||jmm}___qׯ7^gϖnйs~AJF@-Zw֭իW\ZzըQFw?oT#G,u6:~pR¥ h,۷o_ӧ+U7nHW6k,$$DJ֭o={611CСCO<]t,vکS]l2et ++Qߑ+?{WJ*<ߦMW^yEW-w%];`UaÆ͙3ѣG%Wa<رcjj8[oIk0PS&&&FFFJ^rE,Xg1O>v5ܼySl^*Uڇ/]~ܳgnZ ұǨ>MYFT-#==]8|_iGmGݍٳg׬Y.Ȉ?|I۝hx+4xѣGl2vX΍@z7Zn{ .H+իW*UʬB4R_|QJ,1>||/effܹsnڶm#JIIv 'N+ RJ7϶gܸqc=`TǏxuҥ'NȦP(= K,Z߿_{ݽ{WڭT5Y6l vvQˀn/y0cǎ-[HGlll233cn޼9qٳgkmӧO?۷o#VVVǏqQÍ@PtСC9997oLHHP?P(lmmk֬Z5}N.[, ?|qQܹs믿믫T'L#5t*K ?OQDaÆ]zUSD2^ "JtLomiiieeS; aÆ?\:|;t@9=w*ݻkN}hR۾}р9s\e˖f@@_V-Aӏ9cǎ|Jd2~αcǎ;ֹsmۖw-YYYhѢE] TT*g ֭ۦM~ᇨ(W?_}/^jyyyd\/_ѣ4~֭GZon_d2ٰaî] /2|p ݻwΣF*IÆ CCC[Abbb_<3_JJ>ڋU^}ԨQFϿxǏ?~+FT~}qEҲ}}}/I&999Oq􌈈S ڵ8pƍW޲emYJȐ U}?]\\ݤ$+3zJHHܹ4mڴ-[ry~V u\.o׮# Ç׮]S;tP9;vGFFJ_2~蟊ц  FO8QFBhѢO:i&͕JƩM޸qTT^]l=x@7jnorsUj/Voǧѣ1cƔj%T*  5??ݫrD8..NtR7JMMرSz,^`TemmJwww&ѽEB=zTLw}w#9UV+W.J:rHR#}7Jzmۆݭ[jDEMR/BصkW JZ*6TĈmQƣ xxxTSl߽{W5@y{x#IE՞lѣGz+W.6YݧS.]tI}U?^^#}666IJvZh.]~]`('O{&%%=zΝ&o߾xb[[[ݗ_>>>%]1*BNnڴO?-6xٿ͛7VQz==~X#BEXc_¤_;?ׯ_72U{Ο[t?ϖ~xѺߒ4b}&_|ŋ#ӦMJZ$PR/Q=ZJ0XHRwspp=e EYzst\(++k999qZuLaaavAA!C""",--ue֬YҼ꼏?xb7..۱c<.\KG^'[Ny~hҤsǎg8'11ڃ ڶm?,3m۶%tѣGFĮ4 0ҥK5g>LZGH}{QaǎVH޽bݳgĉ#C ;waE%RWn߾ҥK궟_6mėrss=>I\!M2CQ_wf͚ < eeeƯX7zUIr7f {̙:;i裏[9RƍkW֮]{ґ &|'O3f̙Z凞jbW kڵ'֓\ )FBkXR} x)==}Ȑ!͓Ο?[E]bݺu.4h =F(Νׯ_AA8ҵkoư +FO/UK7?r4bKKKKOO믿/^~)**f͚|I„ORyGGǠ]vjʕ+<|0//O@իϜ9sڴiȩStK.ر#11Qɓ'/xK.oު] IDATSjj˗wءqR\.߲eKQyINڻw8{n ڵ9sdnnޣG鞪*ѣψW(~mHHHQd]vK;;;jj,  )~\~= I ;v;g7.88O~wݫN!oܸ{ѷ~{ӦMFQP޽{ǏgeeݻwN  hlݟWEHW=UGU*պuT>_eb_MH/\0~/6ml޼Y|7!!aРAYYY[U.UzJ,\^N{{{A=qlܹsȑ/?hffV5G0a*^ÇCCCu_fmmw{|ժU:ԠA}:uԢ^nr~+tJII>|aD>}6nX}fff3fسgI-9rdƌVVV:.7oޖ-[4>t{ b4;;>}zݺu _imm-ԫ>tWP3e˖7*((uI6OOѣGϞ=[MLLGUUUػwg?aCT޺u+$$$EBXjU,XpI VVVݻwOI Œ%Kԟ?1W>tI&wҥKCCC'O|ԩ_.ZYܽn3gϟ?~ =')^z}ta7m$]YW_}uĈ'O,BڵٳMxSBk׮ݻwkפ^^^z9rdZAXO]l kwx9s޽͚5C2^M899jo߾YRW\tO kN FA8rhppp(UVepjѢŁϜ9ennRVf͚nݺԮ]_nٳgO>}L{{{ ͛7/vga'Nw޾}?}ͭYfݻw*r|޼y{쉍MNNdεjՒ>oKKիWؙLKG >TN8qؔ͛wY3 ={ٳyDx5<<$GHWQ+W,\PN-UTQǚ)))'ؖ~g ^h+WV(wփ յ[nݺu3VOO1cƘp:ux^www 377oӦQeƍ7nW!mذaQj%k˖- )`b'N̜9SLE|͹sNEAE,5jp/4b(4xA%&&9r$>>^z>_..(/0zKO7d7n>ԩSGz+OuI3x;wN+JoرMÆ '}ޕ{1Vj5%Z~mee5m4".\(겼o1 ^h۷ov5&{՝;w5k֤sTd/s0vZunܹ͛7nnnÇ8FMtQq#a=V mٲeӧOWgnn޽{ QN1aǎ7oiӦIG_*ziJR P(MfC+w-ŋ qڵM6~~~L>s~vuȑ%K )\.nNNN5v ڵkW۶mGn@ ڵ+55UnذaJJ/R|}}V*vϹ?~ԩ R/}ɑ#Gn3Ă^xBg|HOOOKKqttttttrrzw4k֬۷?}TݽsNPPP˖-;uT^=''gϞ%%%]reg.9::~Q59rD^|q̘1`TI&M>=>>^?:--M\)FuNg xbTTTRRƫrϯI&m۶533+"4xyymذ3gΜ9O/g0od*y\R\-`ԨQ-Z(Ǐ[n׮]'C5\~[l8p`޽-,,ʲN(={:t|KӦM7m_y9Gp6;;)SEFFƦy{{7m7ް+FDDӧ*JPL>ȩryppp֭0ƥK&MTx۷oҥKJ6(K:u~W_}wݻwOǕ58qb~X> ϛ+W  bqƎXOKLL_~ʕ+W1/^tzXFF\.Qpp0+r,,,j֬y֭.D/DEE}gZSQ1P(ҭԲFcǎ5kzKf͚ߑGyW_r^{ʻp/L_&$$xĈ*rFN%kԨa$aƍ3頻{hhhV|||Aذa×_~~iĈ111}d2ܹؾze!-.^xQ ТEӧbFHڵ3gBzxx?C.]k@˻[Fɓ'?|P=]\\Ə߻wҬ`Ѵi#GlٲeƍIIIWխ[gϞݻw,@@񬬬 2hР(???qϯ{666NNN>>>7-R>F@_ryґ-[lٲ%(LN:fϞ}MQdJqҥAd.`geeհa0͛7oٲ tݽeǏ?yP(<<<ڶmۦMxg%66v ۷ G~tp^^^aaa5*Zc*MEA}e{HE޽ۿR(h:rlq`TRM6-99y'LpΝ(`[hQzzt$ `C GGGKG,,,-,,đ찰R{ҥK+l".Z%==}ԩ'NP`{eS D0 ZHwԩSoqAv\\iF#-=z$^%v]\\ׯqNMM5ipEVV!ܴ^3(4Q(O<)J!F@\uB.Ӆ -ZhLT+`T7rssdSݠ$''owvv6ipؾ~ Ҥ4TZeZ`DbUVZٶmnԨ!-!֭[U*v۶m[xN}``Ik#-vll+uvvnҤU*&MZp8hgg'/Eyϣz5hիUnܸѷoʕ+,ZH4$$DߏW?̙35&۷UTA0 ڍ?>/dzxzzj\>|(G@_gϞ^֭[7Hե]WW5kښ>`Q(̙3۵k__ߏ>HcZjfff (]ݻϯ%ǣP$B|M6\2++K͛/_ɩ ݦMWWײ*("أGÇGEE%%%C5y,(ήgϞl9 ^1 !P^Jell T*UyP͛7oٲ tݽeǏ?yP(<<<ڶmۦMxg%66v ۷ G~tp^^^aaa5*Zx*MEA}e{HE޽ۿR(h:rlq`TRM6-99y'LpΝ(`[hQzzt$ `C GGGKG,,,-,,đ찰R{ҥK+l".Z%==}ԩ'NP`{eS D0 ZHwԩSoqAv\\iF#-=z$^%v]\\ׯqNMM5ipEVV!ܴ^3(4Q(O<)J!F@\uB.Ӆ -ZhLT+`T7rssdSݠ$''owvv6ipؾ~ Ҥ4TZeZ`DbUVZٶmnԨ!-!֭[U*v۶m[xN}``Ik#-vll+uvvnҤU*&MZp8hgg'/Eyϣz5hիUnܸѷoʕ+,ZH4$$DߏW?̙35&۷UTA0 ڍ?>/dzxzzj\>|(G@_gϞ^֭[7Hե]WW5kښ>`Q(̙3۵k__ߏ>HcZjfff (]ݻϯ%ǣP$B|M6\2++K͛/_ɩ ݦMWWײ*("أGÇGEE%%%C5y,(ήgϞl9 ^CNF2g4 (P e!P{ 23kMܪT*P=Nf抡;͝@EF0 ]ֽ{K.*`$QIf*o;hTTV) UTTg}T* ҰaC___qDвYYVVѣKJPrʙ̾(((YYYƍ{t}ʔ)?ӹsvս{w#F\xqڵ]tVFFƈ#ʮtPQϨlT.%%E*ɓ';vl>>>ZЪUݻw{yy ۶m+F< FIE@yɦMĮBXvСC>/_Xzo.FW^cBAF--… ,`s(((eΝիwQFFF2` /ze]rElG&6OΝիW- (N1(((c qb;99٨k8 ;'Y^Wg^,{V6n&#hBv:-sA* Ljj!ɪTbLu0((Ϟ=S7<^+RT*msssTLx(ԍdA2 4/Eg(wO.HRQPՍtIJJR...& +FQ2u]=*9$̲' IDATee(,*wmbgU*Ց[IO2'nNM23^\]7zbB!'SZ쵁UTI|=&&uֆsy]jUTL`%#j8TZaYnzr|aMCFU*պߎTARZ(̽\MQ2^x2'^AP Wy^~7oTwmp0k.k2` `QH& hg|6Z8(ؔ%IEէ-F@)srrܹ ]tӧO}oJJŋ 'N^xQ ТEӧbFa,òQRQIg*F6 Lv̙P(ХKZcPFa%FIEQ$=RQ5QPf}ݍ7VZU...yXXuU D0 ?%ENEF@iڴ#G>ʕ+ ŭ533_YN<ٻwﲪ Osd٨ GnF _1 k"kHEQjl !;Mی(j@cee5dȐAEEE~~~ݻwqtttrriܸm9 A0 SҝHjd,͛KGZlٲߺĊzTE2"UzPR^7Rd2z@* )e^IEմ5q%B0R~!T*(8I FU6`޽{^^^]((J:UTk%=IW~3(KU 7@%Ȝ=g@a!#)AP+9_֮];qĒm۶ ><''ǴU#bTϠW'9F+6O7l]xYz)vܹxvvv666O>}Ϥ$ϟ??mڴEI@/01Pzъνٻ_wŸyy#ݺuիW VݻߺukJJ8xСΝ;n@o<^ S**USg|]sn…bN:w3gNÆ uk̘1?SHHt|źne`&;U#ƍgΜ7oNNNK,.MLLN(LCTTl/mkk+Wۗh\>gΜjժ#&`&*F6 _TT uww7`+W_0 Q(sw۝:u2x}{CcJ&D0 ynd2VxRӧOMP0QΘTTl-Bn!=lsb{ƍϞ=3`^ݖdiF{E|6CBD$AA&\EċWT@D.> DQԋA).C H$d?kdْd>̞9sH|=oa޼y76呒3g޿QF!!!>hӦMM|-8^;/3 ëR_ph?7bX@>KsKYeץK?C5k,^V-/\0;;[|-9 X>(55URSٿ֭[ oٲgϞgu jFrFA 9Ņ.ɹ[8je 1(̋Jf@mZjҤI lٲ7ސeRhѢ'N[^` jIuKu:ݚ5kTz>O3Mٳg_y~Vo1M} 4A~╿_ܼy _L0aeee Onܸ'R610fYܹs?}vi􀀀quΝ;| oֺulтCyxWA{^1p۽Ppŋ/ 0f̘m۶UTTp{{{w1"""00ɩ(??֭[׮]KHH0rΜ9t„ &LPQ0zիWǛHRRΝ;r֭~my}ݹs֭[AgΜi4({'O~U~TPPp̙3gLj?..Ě}1&ZGGGϝ;V ۷ot .^X4%O=Ԑ!CÇ޽kX]Fccc|͵kwL<2??ԩSbyUV{gZ :ѣm-4`ɓ .vx믏;֔gϟ?/N_~U ԩX>~u[`/ZֵΉs&@h{G^z饠 geƊV۾}{.]!)))''G:̙3 `"##NYnݺ%BBB\]]jhB*߾}[5-o-4`4<<|&n*j޽{bA%7wҥZ` <5/Ae???ʾR9//Z-T'&&&--M[Tf+--MR)]M-o:;v8pBpkқ\*;;+TTTX6B0ZJTOI^A[6B0Z5V+ՑWpqqV l`jRTrIITvuuV l×YTT$pt-TgŊ+VPpʍjƌjl Vl-V+44T,)LMM͛7b l`Zmڴ 5o޼)BBB[ V׮] O;u$?hš4i"8P]sΥ[@0Z-F3rH|oָNVV'|"k'|R󫯾/ʤO-Z!^>...Vo9ۻ7\dIaa^߽{C"##=<<A:th-o1c[^reƍ³gFGG3QF͚5v-.f,,,lƍQQQGMJJvuܸqn1ڵ;#&M4iҤܬR//VkK-o17Y??????Kny ,T`PQC0 @uF(!:T`PQC0 @uF(!:T`PQC0 @uF(!:T`PQC0 @uF(!:T`PQC0 @uF(!:T`vj.(!`)q(F@=B0 ">rwwaOòz`R*fQ!E}e(l3:@CE0z uv r2>pςiFi'ox޽2Q8:<Ԛ-QE6tQ`_,=zq*Ԛ-bBj6RQ`wpPEUN5`a6 cV2IE# #DQcdVLT8Q8SQ(-XlA0 bTTd^66̘:j٦Vĩp MEElz[N C"E* [#K( PQ8[դov=# :T`á=Q؟76PѺ{͸ NgKL())ٷoߑ#GҤ{9rd6m7H. u!11wIOO7_RR믿FGG?gh4v^}phxI* ,ŋKO=ۢE Nee~_حeS,٘1j[:nժU h4g}vܸqZV4&&fa޽:txGƊF. *CݹsG,Ϙ1cĉR**Bdd$^~T[V7J* 66E>Qb9%%ٳu׿”lςiUvIET`Ԇnܸ!A?lذVѣGppX>vXuͨp3{ŋrݫhu? p9N'_nӉh`ԆĂo&Mjhii۷[n]s0QQQG㩣NG@RzJJJ !!!5RSSm'!1Q 5m'F Rz[(**>>>ʕrAAruEGG+Th֬i}tDZP/fАJYYTvwwW,PZZ\9'''%%E t\FIEPJyyTvr,?^٪OfTT(ڊF1reeT1EU M"QԈ*GTQQ!]\\lէzHL9M\YO$ ڊV+WWTkΝ;OQ eee 5cbbBhh{ uMՊǏWW-''ʕ+bGu3@FmǧW^bСCUV믥C *F0j[&Mh4 ,_\<^nϞ=꺋8ۻ \֭ǎw^A_{qu#99.\k̘1îԂ`O/e˖-[Yti@@=:KmN̙3祗^BΝ׮]ۦM:Z fĈO<\URRժU=znԅ`۷o߾v,:T ˗z{P6p۶mwRzÌQ `O]аeSTTtoeewLҡC ԣlʕ;SNm۶5(..~wwO޺uk{jf3fl^dgg{;/RhhS[...ptm<==#"":.11Q~' .=z@Fl*//`5nܘQ4uM4aRSS F]HH401 @uF(!:T`PQC0 @u4fРA;M4WgZ`٫3P gggQרQ#{u*j0WglDS,:T`PQC0 @uF;Eӥxzz6j=+++(((prr vrǛH6jfJMp:%%%w-)) tssm,o+%%%++Oi:tq QڨQ#ӟr2%/QXGll޽{/\PVV&| Oڱop@%%%/#Fx衇?իM|Ν;ݫqv:x)5׭[WGz>::qqqM''.] 2o߾56ny p|{駟j n:ȸ;s?OAfΜ9|;W8QXJo޼9**J|O?-XUVQLL{wnQqqsΝ;׽{yU͛7-Vm,3V|ʋ/^xgϞs] PGpp~ᫌa {R/K&<<_~ݺu%%%w] Gqʕe˖IsDDD~z*Upŋlq*nݲҥKJwqqܹs~"""4xٳNyyZ@ִiS;0nಲ'//Uưȱc:$|_e˖eIIW_}o>ArssWZn:?yNW֭9/|Jȍ76l fIII|%KIHH Fn><ZUư`;q:A*2eJXXX޳gOuGK!cVƍWe5wwYfIǏ7  ڶm[n0nUH3M65c:i/Z3g jOtQQQVo Ν;/^({ױcJIIYr~(?6Uư`;vXիWu4͈#۷sps{Gu}___qhBX0>ɒqBz>11Q,1f_^TT$wPS3999qqqVl N۰aNW_(++СCᡇZ~}.]Lyq$?Bl~jق _jֹs[tJsK >2F |||6mjqBiii_ť233BCCO蒘u qqqh[@Cw۷o姞z0z-YHHȳ>+NuLšʕCBBĀ)55ն݂jg~zNNXbfee6m;v~~g u#F8pxƭ Iwrr ٽ{ÇҤ ...:u8qbǎONN &$$DEM6h4PLIIb h0w)5k6vj}19jzLR8%B``rF@7|##H>dӦM_7o8xbMH* \]]͛g,//p… 8{lR}___ggijZ???1ӗ`l۶D,O>]a<}5mtݺu=_e Kcf͕ʕBQQCّ#G.\ u֮];`TLE5Mppp7o.A? [LIIl7iD^_~Y|(//O,8`٘),,b h޽?m[-¾5r2%p@AP̸BYYYZ111Xvss{ *ȃQV;v#FHBm۶;]V L*$ͼgy=<<;}TTfʕ+}K/$="STԊ-aرc_>ʕ>n8W8 fLRƍ[=bccWXQ^^.^k͛77#?{9by#F]VZpaVm2229J͛7߰aåtI3f,YDu8pΝ;Ri@TG>`,o @vvѣGrXXI0n8W8 fL\ҹ7Ց/C6>… Vvߛ6mZ~Y޽{ f͚͟?ɒ%}.jgcsUVٳɓmzgΜ)~$ 3cy h8 M?~reGh`ᫌa ($?硢M|3WWWv ӑ#G>#iL6mĉU7xXN"""bccAHNNNOO#nAܩS'cǎݽ{}Woʬ%|X;^O?倀39¸E_e K0) ʕ Bmz͛?1jgϮ.nݺIdEu<<<===] O35yOOO-tRVVX8p)MdqL}򓾫#m'7EEE+VKK:*7nX*K1n@y8`ucPEGGKAYqư`fj֬TT!̀ iiiϗ~^V9Y z-T9f6m*ϗ6mҤtPsΉ-[Z}ư`fjٲTkLӥ?5[`AJJxvGQQѝ;w\r S -\dMjpӧORY,hZl*P]~]Y"Gh`ᫌa Lb%qS7nHuС.:[oIA 4{lS6۷o߶mҥK{PڵkbAՆeƭڬ]˂ h4oFa:^uܦMdoի۷WJ刈ly ,X#[40Uư0_=!M1vqjvZ=y+TTL6^3()Ay"UVVvSNG[U  z̙3 5O:/A"B@@4N8Ђixx|7[[@#\\\_b aܢq2%p@0tDyy7|Sew>|X,GZu*,,\jtYju}׮]}}}rtt'|RQQ!nj#q*ʻv*//ZAA.܆ &T3'OǏ'&&!C|jy z}\\XU  #|1,!Zhѻwo~lqIff[oUZZ*V2eJ]w?wXM4y' *X{I"+,6  #|1,._}@=/axb\\FIOOׯ_/=eʔ}ڵ?bY6kܹs$66K.R#ڵ?qȑb777N~ĉK;5klɒ%u"ƭt!::ZL;hKKK:{ݽ{W#oh4ZmhhBEEůQYYyݻwoڴIp>XꩫW;vL,?9¸EPTTߋ=ziӦUưFo7nXlYAABΙ3lPkFGG@iޒVNhhʕ+)[oIwWwofu;7nTF5k׳uֱcGhI2&&!!!]t֭1-ٷoӧ+77_X;vlϞ=MyPN:T`PQC0 @uF(!:T`PQC0 @uF(!:ٰa?㏿K]{?={z{/%%g1FqvvvssnժU޽{lN֍e˖EGG={^^z7|ধ}ZymZСC;۶m3.]Գgrgg+Wo^}/b StqqhԨQ-vگ_ƍÇ*BFf͚ `XJ3~e˖=V]H? wwyiVVV?cǎ 0u:{w.ffPM6\_| =jC Yti>}k2dذazo_k׮nWǮ]{6Zo+WA^l:t۷ܹsu:rUV999 p/}$QVyy… l{1Ҏ;G7o\fX?~pp֗O>]N.]~ip\w hpXJ@t2hР>tyyyΝϗf͚9lݻgr沲{ڨK_͛WZZ*BF^Z=k7hР.]////++IIIʒu>}̜9Se˖m߾"==}ٝԉ`*)5?ŋhY8WWײ2k׮&S?cvv zȑ#5) &uz?hѢҥK~i?? :u?|۵kgv?b)=? ?Ν;~n!CH={TVV۷Kp&(=k,rhF!ںuFo-4`PA͟?_~gΝL0qD/g6⨨(ܩS:X9r){ӧj<ҥK'OzI_ƍ3g\t)55’֤]p… R bbbrXX4MD0LhåKSǤI7|n8| "Rݿ3gјF=}twww^\\wޟѣ tiĈ/B-LoV,X ό^ѣG[ &+O޽{ǏONAArϞ=[nmƫYhӧ :u$BRRҺuv!=<cVۭ[3gN6M7o~'?˗=<< 0r{ƳΝ{%駟k.;;{ѢE7o#cVۿW_}uȐ!cƌRX”~tѣGs7nF6mʓ'Oh4z܎ïX{ԨQb[RRe˖9sذ@C`^zɌF+o$55jomͷmf{u:5jd׸… ʪkm„ RCVWŋo\t)Mn:^?bNXXV{q^_+;H'&&># }ӧϽ{jܿԩMԬY?Mɓ=''cǎUvʕaÆIޅ5vU?IDAT߿_INN6)c%%%\߾o?6Oa)SLyj#P9@n޼)5Mƍdgg|mgս^V{ݪU+//”ӧOd;v4i|r^Oܿ_z єϚ5K޽{ĉ+9|Xׯ_b5[j$;wܰatֶm[__ .ek,XO :޽{~799̙3$ӟ~ٳ|+W+ٮ]yyyҧ[nwkaUݑDSNa̘15ɭ[>mI s)Wo><`3g?sܹeM"44Ch߷o_DDM40 6aRzN7p@y ,06ydyW۵kA_VQ^-$$U(yg{1Ԍ`@dI0ZRR2c Z6..ΠjAz]]III3 0@qU~OqrrM`4..YfҧV!*.+l֬NShdbMgg猌 )ɔ-~5)SJKKk^zxrq.]߿o\Y, OKK3&H(O<_ݟѣMќ;wθCC˓׹qT>w_]D_|E RRRwmڴy*ݻwdd/"((ĉ)mܸQzãVD0 a2/MLL\~?AMf\{RMJIubccܤGTHNNhѢ;~zy?ϟ?o\`4>>^> oo4QuTVV|tKtiaä$SV荂x|Iyeggg`]"_'.YI@[l'ڱc1FDN:I{P3!!A>O<ܲ{y3Fkk„ fRuIy#.]m qUzz{ر߃>8w\PUV Ǐpt|rYUj߾|[Ə>ȠիW7o/b^z/ 6T8`i.V/׿x ܹv풎2-~5Ɩ.]Qݧɒ ;8mS~)?{GW_H|BhzÇK}ݝ;w;wŸH$?-[6mGe/^;?J ?ݼy3**VO/(UHKKޙ3gbbbH`p_>%ݽƃhD'N)))ׯ_:x۷o=zUV56hx;w$^:;;;u@ҜG77qƙ޾-~5ƌi˖-Æ Sܸqc{q?Kx`ȑ}+R_U/?ټ:SNϸܺuku5Yr  RYwyl=,wҥYfu?#]&&&ڠk@CéP}~U;qT޽ NSФIihC0 ϰ6 UZZظW)--M*[Ҕ`gl„ /rrrr6mڴf͚ NIIɸq\Rx5nXʣSSS0TSNMh4<@DDDDDrHTTTTTT$]?V^%YYYΝ;{̈́3%o(y饗bbbƶzJJv$ұ^^^&ͯƠӀ}ݿ_~i -ZHA;L=M4wxuV`TwMp nY}dlS]V\\q?܌)fHMM3gΗ_~Y=][Ξ=+njSTֿUb @gar0jnuNvZrΝ|Ѩ3~x;\%yfak-BBB:ԱcG鸭֭[:&f Q㒣Gk7PNEzf#**sҝ-[f4ev*j٦έB^e&+$AyqSM}iW]G?i$˃EΛVnYEXXİC|YSQ5s1@;8qbYY}FӲeGDDtܹG:th4׮]{7jR//} 0Yfz_|1&&zꩧ+w޽~zi9L֪Yj`3Je+||ϝ;'^nݺub9##CY^///iaALl6 ?kpEޯr{sI9… v)))/(KhT>}tjGEEÇMCWNz4.'wLiӦ9lMzjqewwwaa-]JLL4+jdXU9NzkW l⧟~kEF-r+O3m__A?_tI>xeZ6;;[~zG/T||hGNnðx4.3gʕ+7oݙ3g:pUǏ(0CMLWT*ڵk2\wڵkSqfI&Ys_%^("0 Xdh߹s￷殒yyzz;vΜ9rVyìp]*DDD[kP?Al-2a^Z]]xyӕ^{M.&c=?7\~ݚ+#LG,((%¬)n ^/rNNzg4NX`wuuua}G0`+Wۄ q۷o75:::{9'c9ڵkb Ioow:qD ZW^N:6odvq99o׮]ޞ%Ǐ_tcWb x޽2>ޞ=}4.RDB(F`0ed r򣏏쌅i]]]vllf={@][[먑?s3gܹsIIIr Go̝;WX` FFKMMϜ9s߾}W^5uر5kܓv1+VsN/^?Immm6{ʉiyyyVi'|RGr!))It{zzVZYXXhZn4\_1SFcMʡ z8T%&&:pZ/sgfO lܸq&M MNNoTYYY&Lfg sG kjjJKKW.]qFEQܴZݻwE9!22rϞ=G۲eKNN8+'99O? w^SSSkk|KRRҥKَuuu6__ߴ4/^iֈϗ`;uyٳȑ#Jqqqqq(===Russ;xK/U[ɓS:STTTQQ]ZZO8p 66֚wvvʁѨ( x1 vIMMcoooGGYMRmذP-6&}}}.\8uTuutȐO`,//0o.[Lto޼9 _~emVr|4oMNNV>}zYYYBBSW'j]ցMbcc5fJ{8GDDDMM믿n'"""4B`;IIIۨT˗WTT۷m_7ްt۶m?Lm^}Uo(=f/,,gA=111Fh*jǎuuuc'ONKKܝ,DC"E~N}d̘1 .ܺukCCCiiiPP?H\\wL#!<߳gϊ3<I{zzN8QQQqcN8qSL~GVUUݸq[ׇ 8qB$u떩9hgZsKΝ9sXؒᅴr8ۦV]p5zyyY[ݾ}[> 44r"ɓ'Eʧ^t=Ϟ=֦Vf͚pBkr6kkk >>>r!پ}֭[M%KTVV6e_~]`Auukmmmhh0V<<SqqqNwݼ<'M4?͛]`!0 l- fkÆ bF۷o:t7 ]0xbNNi&SӍ6SRR4k,1 `0onDDDii's玿fThhS=ìW755)2cƌzCB(ܑ#G׬Y$l۶m0nܸO>>}0bf4EEEٵkQQ`CBBO óϝ;AQKVTT ϼ\|YQ袢"Wy2e^5kVff-CdffT*EQ*++;6lS?TojtW/]\lYYYرcs%aaa |7? :::w6mkPlV)**:z˗}٘˗d%===/¹sEٻwƍ] WSSS6!0 `a+=Q(Q(Q(Q(Q(Q(Q(Q(Q(Q(QIrYIENDB`bio-0.13.3/benchmark/fastx/plot.R000077500000000000000000000113361457355065000165500ustar00rootroot00000000000000#!/usr/bin/env Rscript library(argparse) library(ggplot2) library(dplyr) library(scales) library(ggthemes) library(ggrepel) parser <- ArgumentParser(description = "", formatter_class = "argparse.RawTextHelpFormatter") parser$add_argument("-i", "--infile", type = "character", help = "result file generated by run.pl") parser$add_argument("-o", "--outfile", type = "character", default = "", help = "result figure file") parser$add_argument("--width", type = "double", default = 8, help = "result file width") parser$add_argument("--height", type = "double", default = 6, help = "result file height") parser$add_argument("--lx", type = "double", default = 0.85, help = "x of legend position") parser$add_argument("--ly", type = "double", default = 0.25, help = "y of legend position") parser$add_argument("--dpi", type = "integer", default = 300, help = "DPI") parser$add_argument("--labcolor", type = "character", default = "Datasets", help = "label of color") parser$add_argument("--labshape", type = "character", default = "Tools", help = "label of shape") args <- parser$parse_args() if (is.null(args$infile)) { write("ERROR: Input file (generated by run.pl) needed!\n", file = stderr()) quit("no", 1) } if (args$outfile == "") { args$outfile = paste(args$infile, ".png", sep = "") } w <- args$width h <- args$height df <- read.csv(args$infile, sep = "\t") # sort df$test <- factor(df$test, levels = unique(df$test), ordered = TRUE) df$app <- factor(df$app, levels = unique(df$app), ordered = TRUE) df$dataset <- factor(df$dataset, levels = unique(df$dataset), ordered = TRUE) # humanize mem unit max_mem <- max(df$mem) unit <- "KB" if (max_mem > 1024 * 1024) { df <- df %>% mutate(mem2 = mem / 1024 / 1024) unit <- "GB" } else if (max_mem > 1024) { df <- df %>% mutate(mem2 = mem / 1024) unit <- "MB" } else { df <- df %>% mutate(mem2 = mem / 1) unit <- "KB" } df2 <- df %>% group_by(test, dataset, app) %>% summarize( mem_stdev = sd(mem2) / sqrt(length(mem2)), mem_mean = mean(mem2), time_stdev = sd(time) / sqrt(length(time)), time_mean = mean(time) ) p <- ggplot( df2, aes( x = mem_mean, y = time_mean, xmin = mem_mean - mem_stdev, xmax = mem_mean + mem_stdev, ymin = time_mean - time_stdev, ymax = time_mean + time_stdev, color = dataset, shape = app, label = app ) ) + # geom_hline(aes(yintercept = time_mean, color = app), size = 0.15, alpha = 0.4) + # geom_vline(aes(xintercept = mem_mean, color = app), size = 0.15, alpha = 0.4) + geom_point(size = 3.5, alpha = 0.7) + # geom_text_repel(size = 6, max.iter = 200000, min.segment.length = 0) + scale_color_wsj() + facet_grid(test ~ ., scales = "free_y") + # ylim(0, max(df$time)) + xlim(0, max(df$mem2)) + ggtitle(paste("FASTA/Q reading + writing performance")) + ylab("Time (s)") + xlab(paste("Peak Memory (", unit, ")", sep = "")) + labs(color = args$labcolor, shape = args$labshape) p <- p + theme_bw() + theme( panel.border = element_rect(color = "grey20", size = 0.8), panel.background = element_blank(), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), plot.margin = unit(c(0.1,0.4,0.1,0.1),"cm"), axis.ticks.y = element_line(size = 0.6), axis.ticks.x = element_line(size = 0.6), strip.background = element_rect( colour = "white", fill = "grey95", size = 0.2 ), strip.text.y = element_text(size = 13), legend.text = element_text(size = 13), # legend.position = c(args$lx,args$ly), legend.position = "right", legend.background = element_rect(fill = "transparent"), legend.key.size = unit(0.6, "cm"), legend.key = element_blank(), legend.text.align = 0, legend.box.just = "left", legend.spacing.y = unit(0.1, "cm"), # space between key and text text = element_text( size = 12, family = "arial" #, face = "bold" ), plot.title = element_text(size = 15) ) if (grepl("tiff?$", args$outfile, perl = TRUE, ignore.case = TRUE)) { ggsave( p, file = args$outfile, width = w, height = h, dpi = args$dpi, compress = "lzw" ) } else { ggsave( p, file = args$outfile, width = w, height = h, dpi = args$dpi ) } # p <- p + scale_color_manual(values = rep("black", length(df$app))) # # ggsave( # p, file = paste("benchmark-", gsub(" ", "-", tolower(test1)), ".grey.png", sep = ""), width = w, height = h # ) bio-0.13.3/benchmark/fastx/plot.sh000077500000000000000000000001311457355065000167500ustar00rootroot00000000000000#!/bin/sh ./plot.R -i benchmark.tsv --dpi 300 --width 6 --height 6 --lx 0.82 --ly 0.23 bio-0.13.3/benchmark/fastx/plot_old.R000077500000000000000000000121731457355065000174060ustar00rootroot00000000000000#!/usr/bin/env Rscript library(argparse) library(ggplot2) library(dplyr) library(scales) library(ggthemes) library(ggrepel) parser <- ArgumentParser(description = "", formatter_class = "argparse.RawTextHelpFormatter") parser$add_argument("-i", "--infile", type = "character", help = "result file generated by run.pl") parser$add_argument("-o", "--outfile", type = "character", default = "", help = "result figure file") parser$add_argument("--width", type = "double", default = 8, help = "result file width") parser$add_argument("--height", type = "double", default = 6, help = "result file height") parser$add_argument("--lx", type = "double", default = 0.85, help = "x of legend position") parser$add_argument("--ly", type = "double", default = 0.25, help = "y of legend position") parser$add_argument("--dpi", type = "integer", default = 300, help = "DPI") parser$add_argument("--labcolor", type = "character", default = "Tools", help = "label of color") parser$add_argument("--labshape", type = "character", default = "Datasets", help = "label of shape") args <- parser$parse_args() if (is.null(args$infile)) { write("ERROR: Input file (generated by run.pl) needed!\n", file = stderr()) quit("no", 1) } if (args$outfile == "") { args$outfile = paste(args$infile, ".png", sep = "") } w <- args$width h <- args$height df <- read.csv(args$infile, sep = "\t") # sort df$test <- factor(df$test, levels = unique(df$test), ordered = TRUE) df$app <- factor(df$app, levels = unique(df$app), ordered = TRUE) df$dataset <- factor(df$dataset, levels = unique(df$dataset), ordered = TRUE) # humanize mem unit max_mem <- max(df$mem) unit <- "KB" if (max_mem > 1024 * 1024) { df <- df %>% mutate(mem2 = mem / 1024 / 1024) unit <- "GB" } else if (max_mem > 1024) { df <- df %>% mutate(mem2 = mem / 1024) unit <- "MB" } else { df <- df %>% mutate(mem2 = mem / 1) unit <- "KB" } df2 <- df %>% group_by(test, dataset, app) %>% summarize( mem_stdev = sd(mem2) / sqrt(length(mem2)), mem_mean = mean(mem2), time_stdev = sd(time) / sqrt(length(time)), time_mean = mean(time) ) p <- ggplot( df2, aes( x = mem_mean, y = time_mean, xmin = mem_mean - mem_stdev, xmax = mem_mean + mem_stdev, ymin = time_mean - time_stdev, ymax = time_mean + time_stdev, color = app, shape = dataset, label = app ) ) + geom_hline(aes(yintercept = time_mean, color = app), size = 0.1, alpha = 0.4) + geom_vline(aes(xintercept = mem_mean, color = app), size = 0.1, alpha = 0.4) + geom_point(size = 3) + # geom_errorbar(width = 20, size = 1, alpha = 1) + # geom_errorbarh(height = 20/max(df$mem2)*max(df$time), # size = 1, alpha = 1) + # # geom_errorbar(aes(ymin = time_mean, ymax = time_mean), # width = 40, size = 1, alpha = 1) + # geom_errorbarh(aes(xmin = mem_mean, xmax = mem_mean), # height = 40/max(df$mem2)*max(df$time), # size = 1, alpha = 1) + # geom_point(data=df, aes(x = mem2, y = time, xmin=NULL, xmax=NULL, ymin=NULL, ymax=NULL), size = 1, alpha = 0.6) + geom_text_repel(size = 6, max.iter = 200000) + scale_color_wsj() + facet_wrap( ~ test) + ylim(0, max(df$time)) + xlim(0, max(df$mem2)) + # ggtitle(paste("FASTA/Q Manipulation Performance\n", test1, sep = "")) + ylab("Time (s)") + xlab(paste("Peak Memory (", unit, ")", sep = "")) + labs(color = args$labcolor, shape = args$labshape) p <- p + theme_bw() + theme( panel.border = element_rect(color = "black", size = 1.2), panel.background = element_blank(), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), axis.ticks.y = element_line(size = 0.8), axis.ticks.x = element_line(size = 0.8), strip.background = element_rect( colour = "white", fill = "white", size = 0.2 ), legend.text = element_text(size = 14), legend.position = c(args$lx,args$ly), legend.background = element_rect(fill = "transparent"), legend.key.size = unit(0.6, "cm"), legend.key = element_blank(), legend.text.align = 0, legend.box.just = "left", strip.text.x = element_text(angle = 0, hjust = 0), text = element_text( size = 14, family = "arial", face = "bold" ), plot.title = element_text(size = 15) ) if (grepl("tiff?$", args$outfile, perl = TRUE, ignore.case = TRUE)) { ggsave( p, file = args$outfile, width = w, height = h, dpi = args$dpi, compress = "lzw" ) } else { ggsave( p, file = args$outfile, width = w, height = h, dpi = args$dpi ) } # p <- p + scale_color_manual(values = rep("black", length(df$app))) # # ggsave( # p, file = paste("benchmark-", gsub(" ", "-", tolower(test1)), ".grey.png", sep = ""), width = w, height = h # ) bio-0.13.3/benchmark/fastx/plot_old.sh000077500000000000000000000001571457355065000176160ustar00rootroot00000000000000#!/bin/sh ./plot2.R -i benchmark.tsv --dpi 100 --width 5 --height 5 --lx 0.75 --ly 0.28 -o benchmark.tsv2.png bio-0.13.3/benchmark/fastx/run.pl000077500000000000000000000077461457355065000166220ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use Getopt::Long; use File::Basename; $0 = basename $0; my $usage = < \$N, 'outfile|o=s' => \$resultfile, "help|h" => \$help, ) or die $usage; die $usage if scalar @ARGV == 0 or $help; my @tests = @ARGV; open my $fh_result, ">", $resultfile or die "failed to write file: $resultfile\n"; my $title_line = "test\tdataset\tapp\ttime\tmem\n"; print $fh_result $title_line; for my $test (@tests) { print STDERR "Test: $test\n"; my $stat = {}; # dataset->app->round my $stat_mem = {}; my $t = ""; # test name # run $N times for my $n ( 1 .. $N ) { print STDERR "Round: $n\n"; my $outfile = "$test.round$n.out"; # unlink $outfile if -e $outfile; # run my $cmd = "sh $test 2>&1 | tee $outfile"; my $fail = run($cmd); die "failed to run:$cmd\n" if $fail; # stat my ( $app, $dataset, $time, $mem ); open my $fh, "<", $outfile or die "failed to read file: $outfile\n"; for my $line (<$fh>) { if ( $t eq "" and $line =~ /Test: (.+)/ ) { $t = $1; } if ( $line =~ /== (.+)/ ) { $app = $1; } if ( $line =~ /data: (.+)/ ) { $dataset = $1; } if ( $line =~ /time: (.+)/ ) { $time = $1; if ( not exists $$stat{$dataset}{$app} ) { $$stat{$dataset}{$app} = []; } push @{ $$stat{$dataset}{$app} }, $time; } if ( $line =~ /rss: (\d+)/ ) { $mem = $1; if ( not exists $$stat_mem{$dataset}{$app} ) { $$stat_mem{$dataset}{$app} = []; } push @{ $$stat_mem{$dataset}{$app} }, $mem; } } close($fh); } # benchmark result my $statfile = "$test.benchmark.tsv"; open my $fh, ">", $statfile or die "failed to write file: $statfile\n"; print "\n=========[ benchmark result ]========\n"; print $title_line; print $fh $title_line; for my $dataset ( sort keys %$stat ) { my $st = $$stat{$dataset}; for my $app ( sort keys %$st ) { my $times = $$st{$app}; my $mems = $$stat_mem{$dataset}{$app}; # my ( $time_mean, $time_stdev ) = mean_and_stdev($times); # my ( $mem_mean, $mem_stdev ) = mean_and_stdev($mems); for my $i (0..@$times-1) { my ($time, $mem) = ($$times[$i], $$mems[$i]); my $data_line = sprintf "%s\t%s\t%s\t%.2f\t%d\n", $t, $dataset, $app, $time, $mem; printf $data_line; printf $fh $data_line; printf $fh_result $data_line; } } } close($fh); } close($fh_result); sub run { my ($cmd) = @_; system($cmd); if ( $? == -1 ) { die "[ERROR] fail to run: $cmd. Command (" . ( split /\s+/, $cmd )[0] . ") not found\n"; } elsif ( $? & 127 ) { printf STDERR "[ERROR] command died with signal %d, %s coredump\n", ( $? & 127 ), ( $? & 128 ) ? 'with' : 'without'; } else { # 0, ok } return $?; } sub mean_and_stdev($) { my ($list) = @_; return ( 0, 0 ) if @$list == 0; my $sum = 0; $sum += $_ for @$list; my $sum_square = 0; $sum_square += $_ * $_ for @$list; my $mean = $sum / @$list; my $variance = $sum_square / @$list - $mean * $mean; my $std = sqrt $variance; return ( $mean, $std ); } bio-0.13.3/benchmark/fastx/run_benchmark_01_plain.sh000077500000000000000000000017111457355065000223000ustar00rootroot00000000000000#!/bin/sh echo Test: Plain text echo Output sequences of all apps are not wrapped to fixed length. echo -en "\n============================================\n"; for f in dataset_*.f{a,q}; do echo read file once with cat cat $f > /dev/null; echo -en "\n------------------------------------\n"; echo == seqkit echo data: $f; memusg -t -H seqkit seq $f -w 0 > $f.seqkit.fx; md5sum $f.seqkit.fx; /bin/rm $f.seqkit.fx; echo -en "\n------------------------------------\n"; echo == seqkit_t1 echo data: $f; memusg -t -H seqkit seq $f -w 0 -j 1 > $f.seqkit.fx; md5sum $f.seqkit.fx; /bin/rm $f.seqkit.fx; echo -en "\n------------------------------------\n"; echo == seqtk echo data: $f; memusg -t -H seqtk seq $f > $f.seqtk.fx; md5sum $f.seqtk.fx; /bin/rm $f.seqtk.fx; done bio-0.13.3/benchmark/fastx/run_benchmark_01_plain.sh.benchmark.tsv000066400000000000000000000030461457355065000250440ustar00rootroot00000000000000test dataset app time mem Plain text dataset_A.fa seqkit 2.54 28332 Plain text dataset_A.fa seqkit 2.53 28432 Plain text dataset_A.fa seqkit 2.64 29680 Plain text dataset_A.fa seqkit 2.54 27520 Plain text dataset_A.fa seqkit_t1 3.77 31888 Plain text dataset_A.fa seqkit_t1 3.67 31836 Plain text dataset_A.fa seqkit_t1 3.23 29540 Plain text dataset_A.fa seqkit_t1 3.96 27884 Plain text dataset_A.fa seqtk 2.77 7684 Plain text dataset_A.fa seqtk 2.65 7664 Plain text dataset_A.fa seqtk 2.65 7756 Plain text dataset_A.fa seqtk 2.76 7668 Plain text dataset_B.fa seqkit 3.11 1039000 Plain text dataset_B.fa seqkit 3.11 1035532 Plain text dataset_B.fa seqkit 3.22 1034608 Plain text dataset_B.fa seqkit 3.21 1037848 Plain text dataset_B.fa seqkit_t1 3.23 1037696 Plain text dataset_B.fa seqkit_t1 3.34 1035184 Plain text dataset_B.fa seqkit_t1 3.46 1035184 Plain text dataset_B.fa seqkit_t1 3.35 1038660 Plain text dataset_B.fa seqtk 3.11 244940 Plain text dataset_B.fa seqtk 3.11 244988 Plain text dataset_B.fa seqtk 3.12 244948 Plain text dataset_B.fa seqtk 3.20 244944 Plain text dataset_C.fq seqkit 3.22 24560 Plain text dataset_C.fq seqkit 3.28 24496 Plain text dataset_C.fq seqkit 3.28 14176 Plain text dataset_C.fq seqkit 3.23 14296 Plain text dataset_C.fq seqkit_t1 3.79 14192 Plain text dataset_C.fq seqkit_t1 3.58 24628 Plain text dataset_C.fq seqkit_t1 3.68 14524 Plain text dataset_C.fq seqkit_t1 4.15 14288 Plain text dataset_C.fq seqtk 4.15 1028 Plain text dataset_C.fq seqtk 4.15 1088 Plain text dataset_C.fq seqtk 4.15 1020 Plain text dataset_C.fq seqtk 4.26 1028 bio-0.13.3/benchmark/fastx/run_benchmark_01_plain.sh.round1.out000066400000000000000000000031751457355065000243200ustar00rootroot00000000000000Test: Plain text Output sequences of all apps are not wrapped to fixed length. ============================================ read file once with cat ------------------------------------ == seqkit data: dataset_A.fa elapsed time: 2.535 peak rss: 28332 3eac76c3c6a1e6591f04ff7bd44bbb32 dataset_A.fa.seqkit.fx ------------------------------------ == seqkit_t1 data: dataset_A.fa elapsed time: 3.775 peak rss: 31888 3eac76c3c6a1e6591f04ff7bd44bbb32 dataset_A.fa.seqkit.fx ------------------------------------ == seqtk data: dataset_A.fa elapsed time: 2.767 peak rss: 7684 3eac76c3c6a1e6591f04ff7bd44bbb32 dataset_A.fa.seqtk.fx read file once with cat ------------------------------------ == seqkit data: dataset_B.fa elapsed time: 3.108 peak rss: 1039000 af9e765363ffe03aaaaa0a23509f7b32 dataset_B.fa.seqkit.fx ------------------------------------ == seqkit_t1 data: dataset_B.fa elapsed time: 3.225 peak rss: 1037696 af9e765363ffe03aaaaa0a23509f7b32 dataset_B.fa.seqkit.fx ------------------------------------ == seqtk data: dataset_B.fa elapsed time: 3.106 peak rss: 244940 af9e765363ffe03aaaaa0a23509f7b32 dataset_B.fa.seqtk.fx read file once with cat ------------------------------------ == seqkit data: dataset_C.fq elapsed time: 3.216 peak rss: 24560 f01c87fd0f852d6ec378fab0d58952ad dataset_C.fq.seqkit.fx ------------------------------------ == seqkit_t1 data: dataset_C.fq elapsed time: 3.795 peak rss: 14192 f01c87fd0f852d6ec378fab0d58952ad dataset_C.fq.seqkit.fx ------------------------------------ == seqtk data: dataset_C.fq elapsed time: 4.152 peak rss: 1028 f01c87fd0f852d6ec378fab0d58952ad dataset_C.fq.seqtk.fx bio-0.13.3/benchmark/fastx/run_benchmark_01_plain.sh.round2.out000066400000000000000000000031751457355065000243210ustar00rootroot00000000000000Test: Plain text Output sequences of all apps are not wrapped to fixed length. ============================================ read file once with cat ------------------------------------ == seqkit data: dataset_A.fa elapsed time: 2.532 peak rss: 28432 3eac76c3c6a1e6591f04ff7bd44bbb32 dataset_A.fa.seqkit.fx ------------------------------------ == seqkit_t1 data: dataset_A.fa elapsed time: 3.670 peak rss: 31836 3eac76c3c6a1e6591f04ff7bd44bbb32 dataset_A.fa.seqkit.fx ------------------------------------ == seqtk data: dataset_A.fa elapsed time: 2.654 peak rss: 7664 3eac76c3c6a1e6591f04ff7bd44bbb32 dataset_A.fa.seqtk.fx read file once with cat ------------------------------------ == seqkit data: dataset_B.fa elapsed time: 3.114 peak rss: 1035532 af9e765363ffe03aaaaa0a23509f7b32 dataset_B.fa.seqkit.fx ------------------------------------ == seqkit_t1 data: dataset_B.fa elapsed time: 3.339 peak rss: 1035184 af9e765363ffe03aaaaa0a23509f7b32 dataset_B.fa.seqkit.fx ------------------------------------ == seqtk data: dataset_B.fa elapsed time: 3.114 peak rss: 244988 af9e765363ffe03aaaaa0a23509f7b32 dataset_B.fa.seqtk.fx read file once with cat ------------------------------------ == seqkit data: dataset_C.fq elapsed time: 3.278 peak rss: 24496 f01c87fd0f852d6ec378fab0d58952ad dataset_C.fq.seqkit.fx ------------------------------------ == seqkit_t1 data: dataset_C.fq elapsed time: 3.578 peak rss: 24628 f01c87fd0f852d6ec378fab0d58952ad dataset_C.fq.seqkit.fx ------------------------------------ == seqtk data: dataset_C.fq elapsed time: 4.150 peak rss: 1088 f01c87fd0f852d6ec378fab0d58952ad dataset_C.fq.seqtk.fx bio-0.13.3/benchmark/fastx/run_benchmark_01_plain.sh.round3.out000066400000000000000000000031751457355065000243220ustar00rootroot00000000000000Test: Plain text Output sequences of all apps are not wrapped to fixed length. ============================================ read file once with cat ------------------------------------ == seqkit data: dataset_A.fa elapsed time: 2.637 peak rss: 29680 3eac76c3c6a1e6591f04ff7bd44bbb32 dataset_A.fa.seqkit.fx ------------------------------------ == seqkit_t1 data: dataset_A.fa elapsed time: 3.226 peak rss: 29540 3eac76c3c6a1e6591f04ff7bd44bbb32 dataset_A.fa.seqkit.fx ------------------------------------ == seqtk data: dataset_A.fa elapsed time: 2.647 peak rss: 7756 3eac76c3c6a1e6591f04ff7bd44bbb32 dataset_A.fa.seqtk.fx read file once with cat ------------------------------------ == seqkit data: dataset_B.fa elapsed time: 3.218 peak rss: 1034608 af9e765363ffe03aaaaa0a23509f7b32 dataset_B.fa.seqkit.fx ------------------------------------ == seqkit_t1 data: dataset_B.fa elapsed time: 3.459 peak rss: 1035184 af9e765363ffe03aaaaa0a23509f7b32 dataset_B.fa.seqkit.fx ------------------------------------ == seqtk data: dataset_B.fa elapsed time: 3.123 peak rss: 244948 af9e765363ffe03aaaaa0a23509f7b32 dataset_B.fa.seqtk.fx read file once with cat ------------------------------------ == seqkit data: dataset_C.fq elapsed time: 3.283 peak rss: 14176 f01c87fd0f852d6ec378fab0d58952ad dataset_C.fq.seqkit.fx ------------------------------------ == seqkit_t1 data: dataset_C.fq elapsed time: 3.679 peak rss: 14524 f01c87fd0f852d6ec378fab0d58952ad dataset_C.fq.seqkit.fx ------------------------------------ == seqtk data: dataset_C.fq elapsed time: 4.151 peak rss: 1020 f01c87fd0f852d6ec378fab0d58952ad dataset_C.fq.seqtk.fx bio-0.13.3/benchmark/fastx/run_benchmark_01_plain.sh.round4.out000066400000000000000000000031751457355065000243230ustar00rootroot00000000000000Test: Plain text Output sequences of all apps are not wrapped to fixed length. ============================================ read file once with cat ------------------------------------ == seqkit data: dataset_A.fa elapsed time: 2.540 peak rss: 27520 3eac76c3c6a1e6591f04ff7bd44bbb32 dataset_A.fa.seqkit.fx ------------------------------------ == seqkit_t1 data: dataset_A.fa elapsed time: 3.961 peak rss: 27884 3eac76c3c6a1e6591f04ff7bd44bbb32 dataset_A.fa.seqkit.fx ------------------------------------ == seqtk data: dataset_A.fa elapsed time: 2.764 peak rss: 7668 3eac76c3c6a1e6591f04ff7bd44bbb32 dataset_A.fa.seqtk.fx read file once with cat ------------------------------------ == seqkit data: dataset_B.fa elapsed time: 3.209 peak rss: 1037848 af9e765363ffe03aaaaa0a23509f7b32 dataset_B.fa.seqkit.fx ------------------------------------ == seqkit_t1 data: dataset_B.fa elapsed time: 3.347 peak rss: 1038660 af9e765363ffe03aaaaa0a23509f7b32 dataset_B.fa.seqkit.fx ------------------------------------ == seqtk data: dataset_B.fa elapsed time: 3.200 peak rss: 244944 af9e765363ffe03aaaaa0a23509f7b32 dataset_B.fa.seqtk.fx read file once with cat ------------------------------------ == seqkit data: dataset_C.fq elapsed time: 3.226 peak rss: 14296 f01c87fd0f852d6ec378fab0d58952ad dataset_C.fq.seqkit.fx ------------------------------------ == seqkit_t1 data: dataset_C.fq elapsed time: 4.152 peak rss: 14288 f01c87fd0f852d6ec378fab0d58952ad dataset_C.fq.seqkit.fx ------------------------------------ == seqtk data: dataset_C.fq elapsed time: 4.259 peak rss: 1028 f01c87fd0f852d6ec378fab0d58952ad dataset_C.fq.seqtk.fx bio-0.13.3/benchmark/fastx/run_benchmark_02_gzip.sh000077500000000000000000000024031457355065000221460ustar00rootroot00000000000000#!/bin/sh echo Test: Gzip compressed echo Output sequences of all apps are not wrapped to fixed length. echo -en "\n============================================\n"; for f in dataset_*.f{a,q}.gz; do echo read file once with cat cat $f > /dev/null; echo -en "\n------------------------------------\n"; echo == seqkit echo data: $f; memusg -t -H seqkit seq $f -w 0 -o $f.seqkit.gz --compress-level 6; pigz -cd $f.seqkit.gz | md5sum; /bin/rm $f.seqkit.gz; echo -en "\n------------------------------------\n"; echo == seqkit_t1 echo data: $f; memusg -t -H seqkit seq $f -w 0 -j 1 -o $f.seqkit.gz --compress-level 6; pigz -cd $f.seqkit.gz | md5sum; /bin/rm $f.seqkit.gz; echo -en "\n------------------------------------\n"; echo == seqtk+gzip echo data: $f; memusg -t -H seqtk seq $f | gzip -c > $f.seqtk.gz; pigz -cd $f.seqtk.gz | md5sum /bin/rm $f.seqtk.gz; echo -en "\n------------------------------------\n"; echo == seqtk+pigz echo data: $f; memusg -t -H seqtk seq $f | pigz -p 4 -c > $f.seqtk.gz; pigz -cd $f.seqtk.gz | md5sum /bin/rm $f.seqtk.gz; done bio-0.13.3/benchmark/fastx/run_benchmark_02_gzip.sh.benchmark.tsv000066400000000000000000000051021457355065000247060ustar00rootroot00000000000000test dataset app time mem Gzip compressed dataset_A.fa.gz seqkit 14.27 57764 Gzip compressed dataset_A.fa.gz seqkit 14.38 58748 Gzip compressed dataset_A.fa.gz seqkit 14.45 55004 Gzip compressed dataset_A.fa.gz seqkit 14.49 57792 Gzip compressed dataset_A.fa.gz seqkit_t1 41.86 42856 Gzip compressed dataset_A.fa.gz seqkit_t1 41.43 43884 Gzip compressed dataset_A.fa.gz seqkit_t1 42.50 41936 Gzip compressed dataset_A.fa.gz seqkit_t1 40.89 44064 Gzip compressed dataset_A.fa.gz seqtk+gzip 461.54 7700 Gzip compressed dataset_A.fa.gz seqtk+gzip 460.74 7820 Gzip compressed dataset_A.fa.gz seqtk+gzip 461.59 7748 Gzip compressed dataset_A.fa.gz seqtk+gzip 459.01 7800 Gzip compressed dataset_A.fa.gz seqtk+pigz 118.43 7748 Gzip compressed dataset_A.fa.gz seqtk+pigz 117.57 7664 Gzip compressed dataset_A.fa.gz seqtk+pigz 120.28 7696 Gzip compressed dataset_A.fa.gz seqtk+pigz 119.26 7724 Gzip compressed dataset_B.fa.gz seqkit 25.37 1045592 Gzip compressed dataset_B.fa.gz seqkit 25.05 1044708 Gzip compressed dataset_B.fa.gz seqkit 25.81 1042072 Gzip compressed dataset_B.fa.gz seqkit 25.46 1045288 Gzip compressed dataset_B.fa.gz seqkit_t1 46.53 1043316 Gzip compressed dataset_B.fa.gz seqkit_t1 46.19 1041472 Gzip compressed dataset_B.fa.gz seqkit_t1 46.30 1041008 Gzip compressed dataset_B.fa.gz seqkit_t1 46.80 1042332 Gzip compressed dataset_B.fa.gz seqtk+gzip 457.71 245104 Gzip compressed dataset_B.fa.gz seqtk+gzip 461.24 245072 Gzip compressed dataset_B.fa.gz seqtk+gzip 463.54 245164 Gzip compressed dataset_B.fa.gz seqtk+gzip 467.39 245208 Gzip compressed dataset_B.fa.gz seqtk+pigz 128.74 245028 Gzip compressed dataset_B.fa.gz seqtk+pigz 126.62 245100 Gzip compressed dataset_B.fa.gz seqtk+pigz 128.22 245076 Gzip compressed dataset_B.fa.gz seqtk+pigz 130.17 245100 Gzip compressed dataset_C.fq.gz seqkit 9.05 40948 Gzip compressed dataset_C.fq.gz seqkit 8.87 41016 Gzip compressed dataset_C.fq.gz seqkit 9.23 38484 Gzip compressed dataset_C.fq.gz seqkit 9.05 39380 Gzip compressed dataset_C.fq.gz seqkit_t1 31.18 30100 Gzip compressed dataset_C.fq.gz seqkit_t1 30.59 29512 Gzip compressed dataset_C.fq.gz seqkit_t1 30.50 29052 Gzip compressed dataset_C.fq.gz seqkit_t1 30.66 29424 Gzip compressed dataset_C.fq.gz seqtk+gzip 188.89 1024 Gzip compressed dataset_C.fq.gz seqtk+gzip 190.83 1080 Gzip compressed dataset_C.fq.gz seqtk+gzip 190.95 964 Gzip compressed dataset_C.fq.gz seqtk+gzip 191.15 968 Gzip compressed dataset_C.fq.gz seqtk+pigz 49.13 1080 Gzip compressed dataset_C.fq.gz seqtk+pigz 48.17 968 Gzip compressed dataset_C.fq.gz seqtk+pigz 48.11 1032 Gzip compressed dataset_C.fq.gz seqtk+pigz 49.28 964 bio-0.13.3/benchmark/fastx/run_benchmark_02_gzip.sh.round1.out000066400000000000000000000036751457355065000241740ustar00rootroot00000000000000Test: Gzip compressed Output sequences of all apps are not wrapped to fixed length. ============================================ read file once with cat ------------------------------------ == seqkit data: dataset_A.fa.gz elapsed time: 14.269 peak rss: 57764 3eac76c3c6a1e6591f04ff7bd44bbb32 - ------------------------------------ == seqkit_t1 data: dataset_A.fa.gz elapsed time: 41.863 peak rss: 42856 3eac76c3c6a1e6591f04ff7bd44bbb32 - ------------------------------------ == seqtk+gzip data: dataset_A.fa.gz elapsed time: 461.541 peak rss: 7700 3eac76c3c6a1e6591f04ff7bd44bbb32 - ------------------------------------ == seqtk+pigz data: dataset_A.fa.gz elapsed time: 118.433 peak rss: 7748 3eac76c3c6a1e6591f04ff7bd44bbb32 - read file once with cat ------------------------------------ == seqkit data: dataset_B.fa.gz elapsed time: 25.374 peak rss: 1045592 af9e765363ffe03aaaaa0a23509f7b32 - ------------------------------------ == seqkit_t1 data: dataset_B.fa.gz elapsed time: 46.531 peak rss: 1043316 af9e765363ffe03aaaaa0a23509f7b32 - ------------------------------------ == seqtk+gzip data: dataset_B.fa.gz elapsed time: 457.706 peak rss: 245104 af9e765363ffe03aaaaa0a23509f7b32 - ------------------------------------ == seqtk+pigz data: dataset_B.fa.gz elapsed time: 128.742 peak rss: 245028 af9e765363ffe03aaaaa0a23509f7b32 - read file once with cat ------------------------------------ == seqkit data: dataset_C.fq.gz elapsed time: 9.047 peak rss: 40948 f01c87fd0f852d6ec378fab0d58952ad - ------------------------------------ == seqkit_t1 data: dataset_C.fq.gz elapsed time: 31.182 peak rss: 30100 f01c87fd0f852d6ec378fab0d58952ad - ------------------------------------ == seqtk+gzip data: dataset_C.fq.gz elapsed time: 188.893 peak rss: 1024 f01c87fd0f852d6ec378fab0d58952ad - ------------------------------------ == seqtk+pigz data: dataset_C.fq.gz elapsed time: 49.132 peak rss: 1080 f01c87fd0f852d6ec378fab0d58952ad - bio-0.13.3/benchmark/fastx/run_benchmark_02_gzip.sh.round2.out000066400000000000000000000036741457355065000241740ustar00rootroot00000000000000Test: Gzip compressed Output sequences of all apps are not wrapped to fixed length. ============================================ read file once with cat ------------------------------------ == seqkit data: dataset_A.fa.gz elapsed time: 14.379 peak rss: 58748 3eac76c3c6a1e6591f04ff7bd44bbb32 - ------------------------------------ == seqkit_t1 data: dataset_A.fa.gz elapsed time: 41.432 peak rss: 43884 3eac76c3c6a1e6591f04ff7bd44bbb32 - ------------------------------------ == seqtk+gzip data: dataset_A.fa.gz elapsed time: 460.744 peak rss: 7820 3eac76c3c6a1e6591f04ff7bd44bbb32 - ------------------------------------ == seqtk+pigz data: dataset_A.fa.gz elapsed time: 117.571 peak rss: 7664 3eac76c3c6a1e6591f04ff7bd44bbb32 - read file once with cat ------------------------------------ == seqkit data: dataset_B.fa.gz elapsed time: 25.051 peak rss: 1044708 af9e765363ffe03aaaaa0a23509f7b32 - ------------------------------------ == seqkit_t1 data: dataset_B.fa.gz elapsed time: 46.188 peak rss: 1041472 af9e765363ffe03aaaaa0a23509f7b32 - ------------------------------------ == seqtk+gzip data: dataset_B.fa.gz elapsed time: 461.238 peak rss: 245072 af9e765363ffe03aaaaa0a23509f7b32 - ------------------------------------ == seqtk+pigz data: dataset_B.fa.gz elapsed time: 126.616 peak rss: 245100 af9e765363ffe03aaaaa0a23509f7b32 - read file once with cat ------------------------------------ == seqkit data: dataset_C.fq.gz elapsed time: 8.866 peak rss: 41016 f01c87fd0f852d6ec378fab0d58952ad - ------------------------------------ == seqkit_t1 data: dataset_C.fq.gz elapsed time: 30.589 peak rss: 29512 f01c87fd0f852d6ec378fab0d58952ad - ------------------------------------ == seqtk+gzip data: dataset_C.fq.gz elapsed time: 190.831 peak rss: 1080 f01c87fd0f852d6ec378fab0d58952ad - ------------------------------------ == seqtk+pigz data: dataset_C.fq.gz elapsed time: 48.170 peak rss: 968 f01c87fd0f852d6ec378fab0d58952ad - bio-0.13.3/benchmark/fastx/run_benchmark_02_gzip.sh.round3.out000066400000000000000000000036741457355065000241750ustar00rootroot00000000000000Test: Gzip compressed Output sequences of all apps are not wrapped to fixed length. ============================================ read file once with cat ------------------------------------ == seqkit data: dataset_A.fa.gz elapsed time: 14.449 peak rss: 55004 3eac76c3c6a1e6591f04ff7bd44bbb32 - ------------------------------------ == seqkit_t1 data: dataset_A.fa.gz elapsed time: 42.497 peak rss: 41936 3eac76c3c6a1e6591f04ff7bd44bbb32 - ------------------------------------ == seqtk+gzip data: dataset_A.fa.gz elapsed time: 461.587 peak rss: 7748 3eac76c3c6a1e6591f04ff7bd44bbb32 - ------------------------------------ == seqtk+pigz data: dataset_A.fa.gz elapsed time: 120.277 peak rss: 7696 3eac76c3c6a1e6591f04ff7bd44bbb32 - read file once with cat ------------------------------------ == seqkit data: dataset_B.fa.gz elapsed time: 25.806 peak rss: 1042072 af9e765363ffe03aaaaa0a23509f7b32 - ------------------------------------ == seqkit_t1 data: dataset_B.fa.gz elapsed time: 46.299 peak rss: 1041008 af9e765363ffe03aaaaa0a23509f7b32 - ------------------------------------ == seqtk+gzip data: dataset_B.fa.gz elapsed time: 463.542 peak rss: 245164 af9e765363ffe03aaaaa0a23509f7b32 - ------------------------------------ == seqtk+pigz data: dataset_B.fa.gz elapsed time: 128.218 peak rss: 245076 af9e765363ffe03aaaaa0a23509f7b32 - read file once with cat ------------------------------------ == seqkit data: dataset_C.fq.gz elapsed time: 9.227 peak rss: 38484 f01c87fd0f852d6ec378fab0d58952ad - ------------------------------------ == seqkit_t1 data: dataset_C.fq.gz elapsed time: 30.496 peak rss: 29052 f01c87fd0f852d6ec378fab0d58952ad - ------------------------------------ == seqtk+gzip data: dataset_C.fq.gz elapsed time: 190.946 peak rss: 964 f01c87fd0f852d6ec378fab0d58952ad - ------------------------------------ == seqtk+pigz data: dataset_C.fq.gz elapsed time: 48.109 peak rss: 1032 f01c87fd0f852d6ec378fab0d58952ad - bio-0.13.3/benchmark/fastx/run_benchmark_02_gzip.sh.round4.out000066400000000000000000000036731457355065000241750ustar00rootroot00000000000000Test: Gzip compressed Output sequences of all apps are not wrapped to fixed length. ============================================ read file once with cat ------------------------------------ == seqkit data: dataset_A.fa.gz elapsed time: 14.493 peak rss: 57792 3eac76c3c6a1e6591f04ff7bd44bbb32 - ------------------------------------ == seqkit_t1 data: dataset_A.fa.gz elapsed time: 40.889 peak rss: 44064 3eac76c3c6a1e6591f04ff7bd44bbb32 - ------------------------------------ == seqtk+gzip data: dataset_A.fa.gz elapsed time: 459.014 peak rss: 7800 3eac76c3c6a1e6591f04ff7bd44bbb32 - ------------------------------------ == seqtk+pigz data: dataset_A.fa.gz elapsed time: 119.257 peak rss: 7724 3eac76c3c6a1e6591f04ff7bd44bbb32 - read file once with cat ------------------------------------ == seqkit data: dataset_B.fa.gz elapsed time: 25.457 peak rss: 1045288 af9e765363ffe03aaaaa0a23509f7b32 - ------------------------------------ == seqkit_t1 data: dataset_B.fa.gz elapsed time: 46.805 peak rss: 1042332 af9e765363ffe03aaaaa0a23509f7b32 - ------------------------------------ == seqtk+gzip data: dataset_B.fa.gz elapsed time: 467.395 peak rss: 245208 af9e765363ffe03aaaaa0a23509f7b32 - ------------------------------------ == seqtk+pigz data: dataset_B.fa.gz elapsed time: 130.173 peak rss: 245100 af9e765363ffe03aaaaa0a23509f7b32 - read file once with cat ------------------------------------ == seqkit data: dataset_C.fq.gz elapsed time: 9.055 peak rss: 39380 f01c87fd0f852d6ec378fab0d58952ad - ------------------------------------ == seqkit_t1 data: dataset_C.fq.gz elapsed time: 30.655 peak rss: 29424 f01c87fd0f852d6ec378fab0d58952ad - ------------------------------------ == seqtk+gzip data: dataset_C.fq.gz elapsed time: 191.151 peak rss: 968 f01c87fd0f852d6ec378fab0d58952ad - ------------------------------------ == seqtk+pigz data: dataset_C.fq.gz elapsed time: 49.281 peak rss: 964 f01c87fd0f852d6ec378fab0d58952ad - bio-0.13.3/featio/000077500000000000000000000000001457355065000136305ustar00rootroot00000000000000bio-0.13.3/featio/_bed/000077500000000000000000000000001457355065000145215ustar00rootroot00000000000000bio-0.13.3/featio/_bed/bed.go000066400000000000000000000235401457355065000156060ustar00rootroot00000000000000//Package bed is used to read bed features. // ref: https://genome.ucsc.edu/FAQ/FAQformat.html#format1 // ref: https://github.com/biogo/biogo/blob/master/io/featio/bed/bed.go package bed import ( "errors" "fmt" "os" "reflect" "regexp" "runtime" "strconv" "strings" "github.com/shenwei356/breader" ) var ( // ErrBadBEDType error ErrBadBEDType = errors.New("bad BED type") // ErrBadBrowserLine error ErrBadBrowserLine = errors.New("bad browser line") ) // Feature is the interface of BED feature type Feature interface { Chr() string Start() int End() int Len() int Strand() *string String() string } // BED3 struct type BED3 struct { Chrom string ChromStart int ChromEnd int } // Chr returns chromosome func (b *BED3) Chr() string { return b.Chrom } // Start returns start func (b *BED3) Start() int { return b.ChromStart } // End returns end. (the site is not included) func (b *BED3) End() int { return b.ChromEnd } // Length returns length func (b *BED3) Length() int { return b.ChromEnd - b.ChromStart } // Strand returns strand func (b *BED3) Strand() *string { return nil } // Name returns name func (b *BED3) Name() string { return b.String() } func (b *BED3) String() string { return fmt.Sprintf("BED3 %s:[%d,%d)", b.Chrom, b.ChromStart, b.ChromEnd) } func parseBED3(line string) (interface{}, bool, error) { if len(line) == 0 { return nil, false, nil } items := strings.Split(strings.TrimRight(line, "\n"), "\t") if len(items) < 3 { return nil, false, ErrBadBEDType } start, err := strconv.Atoi(items[1]) if err != nil { return nil, false, fmt.Errorf("bad start: %s", items[1]) } end, err := strconv.Atoi(items[2]) if err != nil { return nil, false, fmt.Errorf("bad end: %s", items[2]) } return BED3{items[0], start, end}, true, nil } // BED4 struct type BED4 struct { Chrom string ChromStart int ChromEnd int FeatName string } // Chr returns chromosome func (b *BED4) Chr() string { return b.Chrom } // Start returns start func (b *BED4) Start() int { return b.ChromStart } // End returns end. (the site is not included) func (b *BED4) End() int { return b.ChromEnd } // Length returns length func (b *BED4) Length() int { return b.ChromEnd - b.ChromStart } // Strand returns strand func (b *BED4) Strand() *string { return nil } // Name returns name func (b *BED4) Name() string { return b.FeatName } func (b *BED4) String() string { return fmt.Sprintf("BED4 %s:[%d,%d) %s", b.Chrom, b.ChromStart, b.ChromEnd, b.FeatName) } func parseBED4(line string) (interface{}, bool, error) { if len(line) == 0 { return nil, false, nil } items := strings.Split(strings.TrimRight(line, "\n"), "\t") if len(items) < 4 { return nil, false, ErrBadBEDType } start, err := strconv.Atoi(items[1]) if err != nil { return nil, false, fmt.Errorf("bad start: %s", items[1]) } end, err := strconv.Atoi(items[2]) if err != nil { return nil, false, fmt.Errorf("bad end: %s", items[2]) } return BED4{items[0], start, end, items[3]}, true, nil } // BED5 struct type BED5 struct { Chrom string ChromStart int ChromEnd int FeatName string FeatScore int } // Chr returns chromosome func (b *BED5) Chr() string { return b.Chrom } // Start returns start func (b *BED5) Start() int { return b.ChromStart } // End returns end. (the site is not included) func (b *BED5) End() int { return b.ChromEnd } // Length returns length func (b *BED5) Length() int { return b.ChromEnd - b.ChromStart } // Strand returns strand func (b *BED5) Strand() *string { return nil } // Name returns name func (b *BED5) Name() string { return b.FeatName } func (b *BED5) String() string { return fmt.Sprintf("BED5 %s:[%d,%d) %s (score: %d)", b.Chrom, b.ChromStart, b.ChromEnd, b.FeatName, b.FeatScore) } func parseBED5(line string) (interface{}, bool, error) { if len(line) == 0 { return nil, false, nil } items := strings.Split(strings.TrimRight(line, "\n"), "\t") if len(items) < 5 { return nil, false, ErrBadBEDType } start, err := strconv.Atoi(items[1]) if err != nil { return nil, false, fmt.Errorf("bad start: %s", items[1]) } end, err := strconv.Atoi(items[2]) if err != nil { return nil, false, fmt.Errorf("bad end: %s", items[2]) } score, err := strconv.Atoi(items[4]) if err != nil { return nil, false, fmt.Errorf("bad score: %s", items[4]) } return BED5{items[0], start, end, items[3], score}, true, nil } // BED6 struct type BED6 struct { Chrom string ChromStart int ChromEnd int FeatName string FeatScore int FeatStrand *string } // Chr returns chromosome func (b *BED6) Chr() string { return b.Chrom } // Start returns start func (b *BED6) Start() int { return b.ChromStart } // End returns end. (the site is not included) func (b *BED6) End() int { return b.ChromEnd } // Length returns length func (b *BED6) Length() int { return b.ChromEnd - b.ChromStart } // Strand returns strand func (b *BED6) Strand() *string { return b.FeatStrand } // Name returns name func (b *BED6) Name() string { return b.FeatName } func (b *BED6) String() string { return fmt.Sprintf("BED6 %s:[%d,%d)%s %s (score: %d)", b.Chrom, b.ChromStart, b.ChromEnd, *b.FeatStrand, b.FeatName, b.FeatScore) } func parseBED6(line string) (interface{}, bool, error) { if len(line) == 0 { return nil, false, nil } items := strings.Split(strings.TrimRight(line, "\n"), "\t") if len(items) < 6 { return nil, false, ErrBadBEDType } start, err := strconv.Atoi(items[1]) if err != nil { return nil, false, fmt.Errorf("bad start: %s", items[1]) } end, err := strconv.Atoi(items[2]) if err != nil { return nil, false, fmt.Errorf("bad end: %s", items[2]) } score, err := strconv.Atoi(items[4]) if err != nil { return nil, false, fmt.Errorf("bad score: %s", items[4]) } var strand *string if items[5] != "." { strand = &items[5] } return BED6{items[0], start, end, items[3], score, strand}, true, nil } // BED12 struct type BED12 struct { Chrom string ChromStart int ChromEnd int FeatName string FeatScore int FeatStrand *string ThickStart int ThickEnd int RGB string BlockCount int BlockSizes []int BlockStarts []int } // Chr returns chromosome func (b *BED12) Chr() string { return b.Chrom } // Start returns start func (b *BED12) Start() int { return b.ChromStart } // End returns end. (the site is not included) func (b *BED12) End() int { return b.ChromEnd } // Length returns length func (b *BED12) Length() int { return b.ChromEnd - b.ChromStart } // Strand returns strand func (b *BED12) Strand() *string { return b.FeatStrand } // Name returns name func (b *BED12) Name() string { return b.FeatName } func (b *BED12) String() string { return fmt.Sprintf("BED12 %s:[%d,%d)%s %s (score: %d)", b.Chrom, b.ChromStart, b.ChromEnd, *b.FeatStrand, b.FeatName, b.FeatScore) } func parseBED12(line string) (interface{}, bool, error) { if len(line) == 0 { return nil, false, nil } items := strings.Split(strings.TrimRight(line, "\n"), "\t") if len(items) < 12 { return nil, false, ErrBadBEDType } start, err := strconv.Atoi(items[1]) if err != nil { return nil, false, fmt.Errorf("bad start: %s", items[1]) } end, err := strconv.Atoi(items[2]) if err != nil { return nil, false, fmt.Errorf("bad end: %s", items[2]) } score, err := strconv.Atoi(items[4]) if err != nil { return nil, false, fmt.Errorf("bad score: %s", items[4]) } var strand *string if items[5] != "." { strand = &items[5] } thickStart, err := strconv.Atoi(items[6]) if err != nil { return nil, false, fmt.Errorf("bad thick start: %s", items[6]) } thickEnd, err := strconv.Atoi(items[7]) if err != nil { return nil, false, fmt.Errorf("bad thick end: %s", items[7]) } blockCount, err := strconv.Atoi(items[9]) if err != nil { return nil, false, fmt.Errorf("bad block count: %s", items[9]) } blockSizes := []int{} if blockCount > 0 { items2 := strings.Split(items[10], ",") for _, size := range items2[0:blockCount] { s, err := strconv.Atoi(size) if err != nil { return nil, false, fmt.Errorf("bad block size: %s", size) } blockSizes = append(blockSizes, s) } } blockStarts := []int{} if blockCount > 0 { items2 := strings.Split(items[11], ",") for _, start := range items2[0:blockCount] { s, err := strconv.Atoi(start) if err != nil { return nil, false, fmt.Errorf("bad block start: %s", start) } blockStarts = append(blockStarts, s) } } return BED12{items[0], start, end, items[3], score, strand, thickStart, thickEnd, items[8], blockCount, blockSizes, blockStarts}, true, nil } type meta struct { name string details map[string]string } // TrackItemRegexp is regular expression for parsing track items var TrackItemRegexp = regexp.MustCompile(`(\w+)="?[^#]+?"?`) // ReadFeatures returns bed data of a file, availabe type values are 3,4,5,6 func ReadFeatures(file string, n int) ([]Feature, map[string]map[string]string, error) { if _, err := os.Stat(file); os.IsNotExist(err) { return nil, nil, err } fn := func(line string) (interface{}, bool, error) { if line[0] == '#' { return nil, false, nil } if string(line[0:7]) == "browser" { items := strings.Split(strings.TrimRight(line, "\n"), " ") if len(items) < 3 { return nil, false, ErrBadBrowserLine } details := make(map[string]string) details[items[1]] = items[2] return meta{"browser", details}, true, nil } if string(line[0:5]) == "track" { details := make(map[string]string) found := TrackItemRegexp.FindAllStringSubmatch(line, -1) for _, sub := range found { details[sub[0]] = sub[1] } return meta{"track", details}, true, nil } return nil, false, nil } reader, err := breader.NewBufferedReader(file, runtime.NumCPU(), 100, fn) if err != nil { return nil, nil, err } features := []Feature{} for chunk := range reader.Ch { if chunk.Err != nil { return nil, nil, chunk.Err } for _, data := range chunk.Data { fmt.Println(reflect.TypeOf(data).Kind()) } } return features, nil, nil } bio-0.13.3/featio/gtf/000077500000000000000000000000001457355065000144105ustar00rootroot00000000000000bio-0.13.3/featio/gtf/gtf.go000066400000000000000000000107511457355065000155230ustar00rootroot00000000000000// Package gtf is used to read gtf features. // ref: http://mblab.wustl.edu/GTF22.html package gtf import ( "fmt" "os" "runtime" "strconv" "strings" "github.com/shenwei356/breader" ) // Version is the GTF version const Version = 2.2 var strandPositive = "+" var strandNegative = "-" var strandNotspecified = "." // Feature is the gff feature struct type Feature struct { SeqName string Source string Feature string Start int End int Score *float64 Strand *string Frame *int Attributes []Attribute } // Attribute is the attribute type Attribute struct { Tag, Value string } // Threads for bread.NewBufferedReader() var Threads = runtime.NumCPU() // ReadFeatures returns gtf features of a file func ReadFeatures(file string) ([]Feature, error) { return ReadFilteredFeatures(file, []string{}, []string{}, []string{}) } // ReadFilteredFeatures returns gtf features of specific chrs in a file func ReadFilteredFeatures(file string, chrs []string, feats []string, attrs []string) ([]Feature, error) { if _, err := os.Stat(file); os.IsNotExist(err) { return nil, err } chrsMap := make(map[string]struct{}, len(chrs)) for _, chr := range chrs { chrsMap[strings.ToLower(chr)] = struct{}{} } featsMap := make(map[string]struct{}, len(feats)) for _, f := range feats { featsMap[strings.ToLower(f)] = struct{}{} } attrsMap := make(map[string]struct{}, len(attrs)) for _, f := range attrs { attrsMap[strings.ToLower(f)] = struct{}{} } fn := func(line string) (interface{}, bool, error) { if len(line) == 0 || line[0] == '#' { return nil, false, nil } line = strings.TrimRight(line, "\r\n") items := strings.Split(line, "\t") if len(items) != 9 { return nil, false, nil } if len(chrs) > 0 { // selected chrs if _, ok := chrsMap[strings.ToLower(items[0])]; !ok { return nil, false, nil } } if len(feats) > 0 { // selected features if _, ok := featsMap[strings.ToLower(items[2])]; !ok { return nil, false, nil } } var err error start, err := strconv.Atoi(items[3]) if err != nil { return nil, false, fmt.Errorf("%s: bad start: %s", items[0], items[3]) } end, err := strconv.Atoi(items[4]) if err != nil { return nil, false, fmt.Errorf("%s: bad end: %s", items[0], items[4]) } // if start > end { // return nil, false, fmt.Errorf("%s: start (%d) must be < end (%d)", items[0], start, end) // } var score *float64 if items[5] != "." { s, err := strconv.ParseFloat(items[5], 64) if err != nil { return nil, false, fmt.Errorf("%s: bad score: %s", items[0], items[5]) } score = &s } var strand *string if items[6] != "." { s := items[6] if s == "+" { strand = &strandPositive } else if s == "-" { strand = &strandNegative } else { return nil, false, fmt.Errorf("%s: illegal strand: %s", items[0], s) } strand = &s if start > end { if s == "+" { return nil, false, fmt.Errorf(`%s: start (%d) should be < end (%d) when the strand is "+"`, items[0], start, end) } else { strand = &strandNegative tmp := start start = end end = tmp } } } else { strand = &strandNotspecified } var frame *int if items[7] != "." { f, err := strconv.Atoi(items[7]) if err != nil { return nil, false, fmt.Errorf("%s: bad frame: %s", items[0], items[7]) } if !(f == 0 || f == 1 || f == 2) { return nil, false, fmt.Errorf("%s: illegal frame: %d", items[0], f) } frame = &f } feature := Feature{items[0], items[1], items[2], start, end, score, strand, frame, nil} tagValues := strings.Split(items[8], "; ") if len(tagValues) > 0 { var ok bool feature.Attributes = []Attribute{} for _, tagValue := range tagValues[0 : len(tagValues)-1] { items2 := strings.SplitN(tagValue, " ", 2) tag := items2[0] if _, ok = attrsMap[tag]; !ok { continue } value := items2[1] // if value[len(value)-1] == ';' { // value = value[0 : len(value)-1] // } if len(value) > 2 { value = value[1 : len(value)-1] } else { value = "" } feature.Attributes = append(feature.Attributes, Attribute{tag, value}) } } return feature, true, nil } reader, err := breader.NewBufferedReader(file, Threads, 100, fn) if err != nil { return nil, err } features := []Feature{} for chunk := range reader.Ch { if chunk.Err != nil { return nil, chunk.Err } for _, data := range chunk.Data { features = append(features, data.(Feature)) } } return features, nil } bio-0.13.3/featio/gtf/gtf_test.go000066400000000000000000000014261457355065000165610ustar00rootroot00000000000000package gtf import ( "fmt" "testing" ) func TestGTF(t *testing.T) { file := "test1.gtf" features, err := ReadFeatures(file) if err != nil { t.Error(err) return } if len(features) != 14 { t.Error(err) return } for _, feature := range features { fmt.Println(feature) } } func TestGTF2(t *testing.T) { file := "test2.gtf" features, err := ReadFeatures(file) if err != nil { t.Error(err) return } if len(features) != 10 { t.Error(err) return } for _, feature := range features { fmt.Println(feature) } } func TestGTF3(t *testing.T) { file := "test3.gtf" features, err := ReadFeatures(file) if err != nil { t.Error(err) return } if len(features) != 6 { t.Error(err) return } for _, feature := range features { fmt.Println(feature) } } bio-0.13.3/featio/gtf/test1.gtf000066400000000000000000000021621457355065000161530ustar00rootroot00000000000000140 Twinscan inter 5141 8522 . - . gene_id ""; transcript_id ""; 140 Twinscan inter_CNS 8523 9711 . - . gene_id ""; transcript_id ""; 140 Twinscan inter 9712 13182 . - . gene_id ""; transcript_id ""; 140 Twinscan 3UTR 65149 65487 . - . gene_id "140.000"; transcript_id "140.000.1"; 140 Twinscan 3UTR 66823 66992 . - . gene_id "140.000"; transcript_id "140.000.1"; 140 Twinscan stop_codon 66993 66995 . - 0 gene_id "140.000"; transcript_id "140.000.1"; 140 Twinscan CDS 66996 66999 . - 1 gene_id "140.000"; transcript_id "140.000.1"; 140 Twinscan intron_CNS 70103 70151 . - . gene_id "140.000"; transcript_id "140.000.1"; 140 Twinscan CDS 70207 70294 . - 2 gene_id "140.000"; transcript_id "140.000.1"; 140 Twinscan CDS 71696 71807 . - 0 gene_id "140.000"; transcript_id "140.000.1"; 140 Twinscan start_codon 71805 71806 . - 0 gene_id "140.000"; transcript_id "140.000.1"; 140 Twinscan start_codon 73222 73222 . - 2 gene_id "140.000"; transcript_id "140.000.1"; 140 Twinscan CDS 73222 73222 . - 0 gene_id "140.000"; transcript_id "140.000.1"; 140 Twinscan 5UTR 73223 73504 . - . gene_id "140.000"; transcript_id "140.000.1"; bio-0.13.3/featio/gtf/test2.gtf000066400000000000000000000014411457355065000161530ustar00rootroot00000000000000381 Twinscan exon 150 200 . + . gene_id "381.000"; transcript_id "381.000.1"; 381 Twinscan exon 300 401 . + . gene_id "381.000"; transcript_id "381.000.1"; 381 Twinscan CDS 380 401 . + 0 gene_id "381.000"; transcript_id "381.000.1"; 381 Twinscan exon 501 650 . + . gene_id "381.000"; transcript_id "381.000.1"; 381 Twinscan CDS 501 650 . + 2 gene_id "381.000"; transcript_id "381.000.1"; 381 Twinscan exon 700 800 . + . gene_id "381.000"; transcript_id "381.000.1"; 381 Twinscan CDS 700 707 . + 2 gene_id "381.000"; transcript_id "381.000.1"; 381 Twinscan exon 900 1000 . + . gene_id "381.000"; transcript_id "381.000.1"; 381 Twinscan start_codon 380 382 . + 0 gene_id "381.000"; transcript_id "381.000.1"; 381 Twinscan stop_codon 708 710 . + 0 gene_id "381.000"; transcript_id "381.000.1"; bio-0.13.3/featio/gtf/test3.gtf000066400000000000000000000017701457355065000161610ustar00rootroot00000000000000NC_002516.2 genbank gene 6261828 6263564 . - 0 gene_id ""; transcript_id ""; db_xref "GeneID:878127"; product ""; NC_002516.2 genbank CDS 6261828 6263564 . - 0 gene_id ""; transcript_id ""; protein_id "NP_254255.1"; db_xref "GI:15600761"; db_xref "GeneID:878127"; product "inner membrane protein translocase subunit YidC"; NC_002516.2 genbank gene 6263805 6264212 . - 0 gene_id "rnpA"; transcript_id "rnpA"; gene_id "rnpA"; db_xref "GeneID:877910"; product ""; NC_002516.2 genbank CDS 6263805 6264212 . - 0 gene_id "rnpA"; transcript_id "rnpA"; gene_id "rnpA"; protein_id "NP_254256.1"; db_xref "GI:15600762"; db_xref "GeneID:877910"; product "ribonuclease P"; NC_002516.2 genbank gene 6264227 6264361 . - 0 gene_id "rpmH"; transcript_id "rpmH"; gene_id "rpmH"; db_xref "GeneID:877911"; product ""; NC_002516.2 genbank CDS 6264227 6264361 . - 0 gene_id "rpmH"; transcript_id "rpmH"; gene_id "rpmH"; protein_id "NP_254257.1"; db_xref "GI:15600763"; db_xref "GeneID:877911"; product "50S ribosomal protein L34"; bio-0.13.3/go.mod000066400000000000000000000017031457355065000134700ustar00rootroot00000000000000module github.com/shenwei356/bio go 1.17 require ( github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8 github.com/edsrzf/mmap-go v1.0.0 github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab github.com/shenwei356/breader v0.3.2 github.com/shenwei356/kmers v0.1.0 github.com/shenwei356/util v0.5.0 github.com/shenwei356/xopen v0.3.2 github.com/twotwotwo/sorts v0.0.0-20160814051341-bf5c1f2b8553 github.com/will-rowe/nthash v0.4.0 github.com/zeebo/wyhash v0.0.1 ) require ( github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect github.com/dsnet/compress v0.0.1 // indirect github.com/klauspost/compress v1.16.3 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect ) bio-0.13.3/go.sum000066400000000000000000001705231457355065000135240ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1vaoKsclOGD3ADKpshg3SRtYBbwso= github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8 h1:LpMLYGyy67BoAFGda1NeOBQwqlv7nUXpm+rIVHGxZZ4= github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY= github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shenwei356/breader v0.3.1/go.mod h1:UR11JJCxU9s7eUdU4xn3L/VodxoXzWhjJPh8WZbb+us= github.com/shenwei356/breader v0.3.2 h1:GLy2clIMck6FdTwj8WLnmhv0PW/7Pp+Wcx7TVEHG0ks= github.com/shenwei356/breader v0.3.2/go.mod h1:BimwolkMTIr/O4iX7xXtjEB1z5y39G+8I5Tsm9guC3E= github.com/shenwei356/kmers v0.1.0 h1:zPmWftXQWDugG99Wxd3rFmCIF2QZEUpEba3jOSEn7nE= github.com/shenwei356/kmers v0.1.0/go.mod h1:23Ltr95n98LYy9OtJMFSzkmU/1nmdYwgzqB3walAQ6g= github.com/shenwei356/natsort v0.0.0-20190418160752-600d539c017d/go.mod h1:SiiGiRFyRtV7S9RamOrmQR5gpGIRhWJM1w0EtmuQ1io= github.com/shenwei356/util v0.5.0 h1:gbPuGYVggNLOSORuZLnpaB2DrIpyDFolHiZQkyja+XU= github.com/shenwei356/util v0.5.0/go.mod h1:goFN/u2HgvfbOsEgoHA2hUEet+9KjZpRavrVGz9cm30= github.com/shenwei356/xopen v0.1.0/go.mod h1:6EQUa6I7Zsl2GQKqcL9qGLrTzVE+oZyly+uhzovQYSk= github.com/shenwei356/xopen v0.2.2/go.mod h1:6EQUa6I7Zsl2GQKqcL9qGLrTzVE+oZyly+uhzovQYSk= github.com/shenwei356/xopen v0.3.2 h1:gD/0EvcMa6m2Y1XSdALs9WdhIgiZmn5wVZTjKldCCQo= github.com/shenwei356/xopen v0.3.2/go.mod h1:6EQUa6I7Zsl2GQKqcL9qGLrTzVE+oZyly+uhzovQYSk= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/twotwotwo/sorts v0.0.0-20160814051341-bf5c1f2b8553 h1:DRC1ubdb3ZmyyIeCSTxjZIQAnpLPfKVgYrLETQuOPjo= github.com/twotwotwo/sorts v0.0.0-20160814051341-bf5c1f2b8553/go.mod h1:Rj7Csq/tZ/egz+Ltc2IVpsA5309AmSMEswjkTZmq2Xc= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/will-rowe/nthash v0.4.0 h1:YiHdqR0phP9o/kKVMJJiuXYY9qOH8QHofptDqUCOxrU= github.com/will-rowe/nthash v0.4.0/go.mod h1:5ezweuK0J5j+/7lih/RkrSmnxI3hoaPpQiVWJ7rd960= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zeebo/wyhash v0.0.1 h1:VEByEMek3iHhV65CgG3SRAWVtg/6TcmbEKj5jPOKDrc= github.com/zeebo/wyhash v0.0.1/go.mod h1:Ti+OwfNtM5AZiYAL0kOPIfliqDP5c0VtOnnMAqzuuZk= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= bio-0.13.3/seq/000077500000000000000000000000001457355065000131515ustar00rootroot00000000000000bio-0.13.3/seq/README.md000077500000000000000000000007051457355065000144350ustar00rootroot00000000000000# seq [![Go Reference](https://pkg.go.dev/badge/github.com/shenwei356/bio/seq.svg)](https://pkg.go.dev/github.com/shenwei356/bio/seq) This package defines `Seq` and `Alphabet` type, and provides some basic operations of sequence, like validation of DNA/RNA/Protein sequence, getting reverse complement sequence and translation of RNA to protein. This package was inspired by [biogo](https://github.com/biogo/biogo/blob/master/alphabet/alphabet.go). bio-0.13.3/seq/alphabet.go000077500000000000000000000231031457355065000152620ustar00rootroot00000000000000/* Package seq defines a *Seq* type, and provides some basic operations of sequence, like validation of DNA/RNA/Protein sequence and getting reverse complement sequence. This package was inspired by [biogo](https://code.google.com/p/biogo/source/browse/#git%2Falphabet). IUPAC nucleotide code: ACGTURYSWKMBDHVN http://droog.gs.washington.edu/parc/images/iupac.html code base Complement A A T C C G G G C T/U T A M A/C K R A/G Y W A/T W S C/G S Y C/T R K G/T M V A/C/G B H A/C/T D D A/G/T H B C/G/T V X/N A/C/G/T X . not A/C/G/T or- gap IUPAC amino acid code A Ala Alanine B Asx Aspartic acid or Asparagine [2] C Cys Cysteine D Asp Aspartic Acid E Glu Glutamic Acid F Phe Phenylalanine G Gly Glycine H His Histidine I Ile Isoleucine J Isoleucine or Leucine [4] K Lys Lysine L Leu Leucine M Met Methionine N Asn Asparagine O pyrrolysine [6] P Pro Proline Q Gln Glutamine R Arg Arginine S Ser Serine T Thr Threonine U Sec selenocysteine [5,6] V Val Valine W Trp Tryptophan Y Tyr Tyrosine Z Glx Glutamine or Glutamic acid [2] X unknown amino acid . gaps * End Reference: 1. http://www.bioinformatics.org/sms/iupac.html 2. http://www.dnabaser.com/articles/IUPAC%20ambiguity%20codes.html 3. http://www.bioinformatics.org/sms2/iupac.html 4. http://www.matrixscience.com/blog/non-standard-amino-acid-residues.html 5. http://www.sbcs.qmul.ac.uk/iupac/AminoAcid/A2021.html#AA21 6. https://en.wikipedia.org/wiki/Amino_acid */ package seq import ( "errors" "fmt" "runtime" "sync" "github.com/elliotwutingfeng/asciiset" "github.com/shenwei356/util/byteutil" ) /* Alphabet could be defined. Attention that, **the letters are case sensitive**. For example, DNA: DNA, _ = NewAlphabet( "DNA", []byte("acgtACGT"), []byte("tgcaTGCA"), []byte(" -"), []byte("nN")) */ type Alphabet struct { t string isUnlimit bool letters []byte pairs []byte gap []byte ambiguous []byte allLetters []byte pairLetters []byte } // NewAlphabet is Constructor for type *Alphabet* func NewAlphabet( t string, isUnlimit bool, letters []byte, pairs []byte, gap []byte, ambiguous []byte, ) (*Alphabet, error) { a := &Alphabet{t, isUnlimit, letters, pairs, gap, ambiguous, []byte{}, []byte{}} if isUnlimit { return a, nil } if len(letters) != len(pairs) { return a, errors.New("mismatch of length of letters and pairs") } for i := 0; i < len(letters); i++ { a.allLetters = append(a.allLetters, letters[i]) } // add gap and ambiguous code a.allLetters = append(a.allLetters, gap...) a.allLetters = append(a.allLetters, ambiguous...) // construct special slice. // index are the integer of a byte, and value is the original byte. // it's faster than map!!!! max := -1 for i := 0; i < len(a.allLetters); i++ { b := int(a.allLetters[i]) if max < b { max = b } } a.pairLetters = make([]byte, max+1) for i := 0; i < len(letters); i++ { a.pairLetters[letters[i]-'\x00'] = pairs[i] } for _, v := range gap { a.pairLetters[v-'\x00'] = v } for _, v := range ambiguous { a.pairLetters[v-'\x00'] = v } return a, nil } // Clone of a Alphabet func (a *Alphabet) Clone() *Alphabet { return &Alphabet{ t: a.t, isUnlimit: a.isUnlimit, letters: []byte(string(a.letters)), pairs: []byte(string(a.pairs)), gap: []byte(string(a.gap)), ambiguous: []byte(string(a.ambiguous)), allLetters: []byte(string(a.allLetters)), pairLetters: []byte(string(a.pairLetters)), } } // Type returns type of the alphabet func (a *Alphabet) Type() string { return a.t } // Letters returns letters func (a *Alphabet) Letters() []byte { return a.letters } // Gaps returns gaps func (a *Alphabet) Gaps() []byte { return a.gap } // AmbiguousLetters returns AmbiguousLetters func (a *Alphabet) AmbiguousLetters() []byte { return a.ambiguous } // AllLetters return all letters func (a *Alphabet) AllLetters() []byte { return a.allLetters } // String returns type of the alphabet func (a *Alphabet) String() string { return a.t } // IsValidLetter is used to validate a letter func (a *Alphabet) IsValidLetter(b byte) bool { if a.isUnlimit { return true } i := int(b) if i >= len(a.pairLetters) { return false } return a.pairLetters[i] != 0 } // ValidSeqLengthThreshold is the threshold of sequence length that // needed to parallelly checking sequence var ValidSeqLengthThreshold = 10000 // ValidSeqThreads is the threads number of parallelly checking sequence var ValidSeqThreads = runtime.NumCPU() type seqCheckStatus struct { err error } // IsValid is used to validate a byte slice func (a *Alphabet) IsValid(s []byte) error { if len(s) == 0 { return nil } if a == nil || a.isUnlimit { return nil } l := len(s) var i int if l < ValidSeqLengthThreshold { for _, b := range s { i = int(b) if i >= len(a.pairLetters) || a.pairLetters[i] == 0 { return fmt.Errorf("seq: invalid %s letter: %s", a, []byte{b}) } } return nil } chunkSize, start, end := int(l/ValidSeqThreads)+1, 0, 0 var wg sync.WaitGroup tokens := make(chan int, ValidSeqThreads) ch := make(chan seqCheckStatus, ValidSeqThreads) done := make(chan struct{}) var once sync.Once finished := false for i := 0; i < ValidSeqThreads; i++ { start = i * chunkSize end = (i + 1) * chunkSize if end > l { end = l } tokens <- 1 wg.Add(1) go func(start, end int) { defer func() { <-tokens wg.Done() }() select { case <-done: if !finished { finished = true // close(ch) return } default: } var j int for i := start; i < end; i++ { j = int(s[i]) if j >= len(a.pairLetters) || a.pairLetters[j] == 0 { ch <- seqCheckStatus{fmt.Errorf("seq: invalid %s lebtter: %s at %d", a, []byte{s[i]}, i)} once.Do(func() { close(done) }) return } } ch <- seqCheckStatus{nil} }(start, end) } wg.Wait() close(ch) for status := range ch { if status.err != nil { return status.err } } return nil } // PairLetter return the Pair Letter func (a *Alphabet) PairLetter(b byte) (byte, error) { if a.isUnlimit { return b, nil } if int(b) >= len(a.pairLetters) { return b, fmt.Errorf("seq: invalid letter: %c", b) } p := a.pairLetters[b-'\x00'] if p == 0 { return b, fmt.Errorf("seq: invalid letter: %c", b) } return p, nil } /* Four types of alphabets are pre-defined: DNA Deoxyribonucleotide code DNAredundant DNA + Ambiguity Codes RNA Oxyribonucleotide code RNAredundant RNA + Ambiguity Codes Protein Amino Acide single-letter Code Unlimit Self-defined, including all 26 English letters */ var ( DNA *Alphabet DNAredundant *Alphabet RNA *Alphabet RNAredundant *Alphabet Protein *Alphabet Unlimit *Alphabet abProtein asciiset.ASCIISet abDNAredundant asciiset.ASCIISet abDNA asciiset.ASCIISet abRNAredundant asciiset.ASCIISet abRNA asciiset.ASCIISet ) func init() { DNA, _ = NewAlphabet( "DNA", false, []byte("acgtACGT"), []byte("tgcaTGCA"), []byte(" -."), []byte("nN.")) DNAredundant, _ = NewAlphabet( "DNAredundant", false, []byte("acgtryswkmbdhvACGTRYSWKMBDHV"), []byte("tgcayrswmkvhdbTGCAYRSWMKVHDB"), []byte(" -."), []byte("nN.")) RNA, _ = NewAlphabet( "RNA", false, []byte("acguACGU"), []byte("ugcaUGCA"), []byte(" -."), []byte("nN")) RNAredundant, _ = NewAlphabet( "RNAredundant", false, []byte("acguryswkmbdhvACGURYSWKMBDHV"), []byte("ugcayrswmkvhdbUGCAYRSWMKVHDB"), []byte(" -."), []byte("nN")) Protein, _ = NewAlphabet( "Protein", false, []byte("abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVWYZ"), []byte("abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVWYZ"), []byte(" -"), []byte("xX*_.")) Unlimit, _ = NewAlphabet( "Unlimit", true, nil, nil, nil, nil) abProtein = slice2map(byteutil.Alphabet(Protein.AllLetters())) abDNAredundant = slice2map(byteutil.Alphabet(DNAredundant.AllLetters())) abDNA = slice2map(byteutil.Alphabet(DNA.AllLetters())) abRNAredundant = slice2map(byteutil.Alphabet(RNAredundant.AllLetters())) abRNA = slice2map(byteutil.Alphabet(RNA.AllLetters())) } // AlphabetGuessSeqLengthThreshold is the length of sequence prefix of the first FASTA record // based which FastaRecord guesses the sequence type. 0 for whole seq var AlphabetGuessSeqLengthThreshold = 10000 // GuessAlphabet guesses alphabet by given func GuessAlphabet(seqs []byte) *Alphabet { if len(seqs) == 0 { return Unlimit } var alphabetMap asciiset.ASCIISet if AlphabetGuessSeqLengthThreshold == 0 || len(seqs) <= AlphabetGuessSeqLengthThreshold { alphabetMap = slice2map(byteutil.Alphabet(seqs)) } else { // reduce guessing time alphabetMap = slice2map(byteutil.Alphabet(seqs[0:AlphabetGuessSeqLengthThreshold])) } if isSubset(alphabetMap, abDNA) { return DNA } if isSubset(alphabetMap, abRNA) { return RNA } if isSubset(alphabetMap, abDNAredundant) { return DNAredundant } if isSubset(alphabetMap, abRNAredundant) { return RNAredundant } if isSubset(alphabetMap, abProtein) { return Protein } return Unlimit } // GuessAlphabetLessConservatively change DNA to DNAredundant and RNA to RNAredundant func GuessAlphabetLessConservatively(seqs []byte) *Alphabet { ab := GuessAlphabet(seqs) if ab == DNA { return DNAredundant } if ab == RNA { return RNAredundant } return ab } // isSubset returns true if query is a subset of subject func isSubset(query, subject asciiset.ASCIISet) bool { // A ⊆ B iff (A ∪ B) = B union := query.Union(subject) return union.Equals(subject) } func slice2map(s []byte) asciiset.ASCIISet { m, _ := asciiset.MakeASCIISet(string(s)) return m } bio-0.13.3/seq/alphabet_test.go000077500000000000000000000051161457355065000163250ustar00rootroot00000000000000package seq import ( "regexp" "strings" "testing" ) func TestAlphabet(t *testing.T) { dna := []byte("acgtACGT") dna2 := []byte("ACGTRYSWKMBDHV") rna := []byte("AUGCUUCCGGCCUGUUCCCUGAGACCUCAAGUGUGAGUGUACUAU" + "UGAUGCUUCACACCUGGGCUCUCCGGGUACCAGGACGGUUUGAGCAGAU") rna2 := []byte("ACGURYSWKMBDHV") protein := []byte(regexp.MustCompile(`\r?\n|\s`).ReplaceAllString( `MGLNRFMRAMMVVFITANCITINPDIIFAATDSEDSSLNTDEWEEEKTEEQPSEVNTGPR YETAREVSSRDIKELEKSNKVRNTNKADLIAMLKEKAEKGPNINNNNSEQTENAAINEEA SGADRPAIQVERRHPGLPSDSAAEIKKRRKAIASSDSELESLTYPDKPTKVNKKKVAKES VADASESDLDSSMQSADESSPQPLKANQQPFFPKVFKKIKDAGKWVRDKIDENPEVKKAI VDKSAGLIDQLLTKKKSEEVNASDFPPPPTDEELRLALPETPMLLGFNAPATSEPSSFEF PPPPTDEELRLALPETPMLLGFNAPATSEPSSFEFPPPPTEDELEIIRETASSLDSSFTR GDLASLRNAINRHSQNFSDFPPIPTEEELNGRGGRPTSEEFSSLNSGDFTDDENSETTEE EIDRLADLRDRGTGKHSRNAGFLPLNPFASSPVPSLSPKVSKISAPALISDITKKTPFKN PSQPLNVFNKKTTTKTVTKKPTPVKTAPKLAELPATKPQETVLRENKTPFIEKQAETNKQ SINMPSLPVIQKEATESDKEEMKPQTEEKMVEESESANNANGKNRSAGIEEGKLIAKSAE DEKAKEEPGNHTTLILAMLAIGVFSLGAFIKIIQLRKNN`, "")) ok := DNA.IsValid(dna) == nil && DNAredundant.IsValid(dna2) == nil && RNA.IsValid(rna) == nil && RNAredundant.IsValid(rna2) == nil && Protein.IsValid(protein) == nil if !ok { t.Error("validating sequence failed.") return } // fmt.Println("protein", GuessAlphabet(protein)) // fmt.Println("dna2", GuessAlphabet(dna2)) // fmt.Println("dna", GuessAlphabet(dna)) // fmt.Println("rna2", GuessAlphabet(rna2)) // fmt.Println("rna", GuessAlphabet(rna)) ok = GuessAlphabet(dna) == DNA && GuessAlphabet(dna2) == DNAredundant && GuessAlphabet(rna) == RNA && GuessAlphabet(rna2) == RNAredundant && GuessAlphabet(protein) == Protein if !ok { t.Error("guessing alphabet error") return } } func TestAlphabet2(t *testing.T) { s := regexp.MustCompile(`\r?\n|\s`).ReplaceAllString( `MGLNRFMRAMMVVFITANCITINPDIIFAATDSEDSSLNTDEWEEEKTEEQPSEVNTGPR YETAREVSSRDIKELEKSNKVRNTNKADLIAMLKEKAEKGPNINNNNSEQTENAAINEEA SGADRPAIQVERRHPGLPSDSAAEIKKRRKAIASSDSELESLTYPDKPTKVNKKKVAKES VADASESDLDSSMQSADESSPQPLKANQQPFFPKVFKKIKDAGKWVRDKIDENPEVKKAI VDKSAGLIDQLLTKKKSEEVNASDFPPPPTDEELRLALPETPMLLGFNAPATSEPSSFEF PPPPTDEELRLALPETPMLLGFNAPATSEPSSFEFPPPPTEDELEIIRETASSLDSSFTR GDLASLRNAINRHSQNFSDFPPIPTEEELNGRGGRPTSEEFSSLNSGDFTDDENSETTEE EIDRLADLRDRGTGKHSRNAGFLPLNPFASSPVPSLSPKVSKISAPALISDITKKTPFKN PSQPLNVFNKKTTTKTVTKKPTPVKTAPKLAELPATKPQETVLRENKTPFIEKQAETNKQ SINMPSLPVIQKEATESDKEEMKPQTEEKMVEESESANNANGKNRSAGIEEGKLIAKSAE DEKAKEEPGNHTTLILAMLAIGVFSLGAFIKIIQLRKNN>`, "") protein := []byte(strings.Repeat(s, 1000)) ok := Protein.IsValid(protein) == nil if ok { t.Error("validating sequence failed.") return } } bio-0.13.3/seq/ambiguous_bases.go000077500000000000000000000067701457355065000166650ustar00rootroot00000000000000package seq import "errors" /* base bases code A A 1 C C 2 G G 4 T/U T 8 M A/C 3 R A/G 5 W A/T 9 S C/G 6 Y C/T 10 K G/T 12 V A/C/G 7 H A/C/T 11 D A/G/T 13 B C/G/T 14 N A/C/G/T 15 */ var code2base = [16]byte{'-', 'A', 'C', 'M', 'G', 'R', 'S', 'V', 'T', 'W', 'Y', 'H', 'K', 'D', 'B', 'N'} // ErrInvalidDNABase means invalid DNA base var ErrInvalidDNABase = errors.New("seq: invalid DNA base") func base2code(b byte) (int, error) { var c int switch b { case 'A', 'a': c = 1 case 'C', 'c': c = 2 case 'G', 'g': c = 4 case 'T', 't', 'U', 'u': c = 8 case 'N', 'n': c = 15 case 'M', 'm': c = 3 case 'R', 'r': c = 5 case 'W', 'w': c = 9 case 'S', 's': c = 6 case 'Y', 'y': c = 10 case 'K', 'k': c = 12 case 'V', 'v': c = 7 case 'H', 'h': c = 11 case 'D', 'd': c = 13 case 'B', 'b': c = 14 case ' ', '*', '-': c = 0 default: return 0, ErrInvalidDNABase } return c, nil } // Bases2AmbBase converts list of bases to ambiguous base func Bases2AmbBase(bs []byte) (byte, error) { var code, c int var err error for _, b := range bs { c, err = base2code(b) if err != nil { return 0, err } code |= c } return code2base[code], nil } // Codes2AmbCode converts list of codes of bases to code of ambiguous base func Codes2AmbCode(codes []int) (int, error) { var code int for _, c := range codes { code |= c } return code, nil } // AmbBase2Bases0 converts ambiguous base to bases it represents, slower than AmbBase2Bases func AmbBase2Bases0(b byte) ([]byte, error) { code, err := base2code(b) if err != nil { return nil, err } bases := []byte{} var c int for comb := 1; comb <= code; comb++ { c = 0 for i := uint(0); i < 4; i++ { if (comb>>i)&1 == 1 && // this combination needs this bit being 1 code&(1< 0 { // this bit is 1 c += 1 << i } } if c == comb { // all bits are set bases = append(bases, code2base[c]) } } return bases, nil } // AmbBase2Bases holds relationship of ambiguous base and bases it represents, faster than AmbBase2Bases0 var AmbBase2Bases = map[byte][]byte{ 'A': {'A'}, 'a': {'A'}, 'C': {'C'}, 'c': {'C'}, 'G': {'G'}, 'g': {'G'}, 'T': {'T'}, 't': {'T'}, 'U': {'T'}, 'u': {'T'}, 'M': {'A', 'C', 'M'}, 'm': {'A', 'C', 'M'}, 'R': {'A', 'G', 'R'}, 'r': {'A', 'G', 'R'}, 'W': {'A', 'T', 'W'}, 'w': {'A', 'T', 'W'}, 'S': {'C', 'G', 'S'}, 's': {'C', 'G', 'S'}, 'Y': {'C', 'T', 'Y'}, 'y': {'C', 'T', 'Y'}, 'K': {'G', 'T', 'K'}, 'k': {'G', 'T', 'K'}, 'V': {'A', 'C', 'G', 'M', 'R', 'S', 'V'}, 'v': {'A', 'C', 'G', 'M', 'R', 'S', 'V'}, 'H': {'A', 'C', 'T', 'M', 'W', 'Y', 'H'}, 'h': {'A', 'C', 'T', 'M', 'W', 'Y', 'H'}, 'D': {'A', 'G', 'T', 'R', 'W', 'K', 'D'}, 'd': {'A', 'G', 'T', 'R', 'W', 'K', 'D'}, 'B': {'C', 'G', 'T', 'S', 'Y', 'K', 'B'}, 'b': {'C', 'G', 'T', 'S', 'Y', 'K', 'B'}, 'N': {'A', 'C', 'M', 'G', 'R', 'S', 'V', 'T', 'W', 'Y', 'H', 'K', 'D', 'B', 'N'}, 'n': {'A', 'C', 'M', 'G', 'R', 'S', 'V', 'T', 'W', 'Y', 'H', 'K', 'D', 'B', 'N'}, } // AmbCodes2Codes is code version of AmbBase2Bases var AmbCodes2Codes = map[int][]int{ 1: {1}, 2: {2}, 4: {4}, 8: {8}, 3: {1, 2, 3}, 5: {1, 4, 5}, 9: {1, 8, 9}, 6: {2, 4, 6}, 10: {2, 8, 10}, 12: {4, 8, 12}, 7: {1, 2, 4, 3, 5, 6, 7}, 11: {1, 2, 8, 3, 9, 10, 11}, 13: {1, 4, 8, 5, 9, 12, 13}, 14: {2, 4, 8, 6, 10, 12, 14}, 15: {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, } bio-0.13.3/seq/ambiguous_bases.tsv000066400000000000000000000002241457355065000170550ustar00rootroot00000000000000base bases complement A A T C C G G G C T/U T A M A/C K R A/G Y W A/T W S C/G S Y C/T R K G/T M V A/C/G B H A/C/T D D A/G/T H B C/G/T V N A/C/G/T N bio-0.13.3/seq/ambiguous_bases_test.go000066400000000000000000000064301457355065000177120ustar00rootroot00000000000000package seq import ( "bytes" "sort" "testing" "github.com/cznic/sortutil" ) func TestBases2AmbBase(t *testing.T) { type Test struct { bases []byte ambase byte err error } tests := []Test{ {[]byte{'A'}, 'A', nil}, {[]byte{'c'}, 'C', nil}, {[]byte{'t'}, 'T', nil}, {[]byte{'u'}, 'T', nil}, {[]byte{'g'}, 'G', nil}, {[]byte{'a', 'c'}, 'M', nil}, {[]byte{'c', 'a'}, 'M', nil}, {[]byte{'a', 'g'}, 'R', nil}, {[]byte{'g', 'a'}, 'R', nil}, {[]byte{'a', 't'}, 'W', nil}, {[]byte{'t', 'a'}, 'W', nil}, {[]byte{'c', 'g'}, 'S', nil}, {[]byte{'g', 'c'}, 'S', nil}, {[]byte{'c', 't'}, 'Y', nil}, {[]byte{'t', 'c'}, 'Y', nil}, {[]byte{'g', 't'}, 'K', nil}, {[]byte{'t', 'g'}, 'K', nil}, {[]byte{'a', 'c', 'g'}, 'V', nil}, {[]byte{'a', 'c', 't'}, 'H', nil}, {[]byte{'a', 'G', 't'}, 'D', nil}, {[]byte{'C', 'g', 't'}, 'B', nil}, {[]byte{'C', 'g', 't', 'a'}, 'N', nil}, {[]byte{'j'}, 'N', ErrInvalidDNABase}, } var amb byte var err error for _, test := range tests { amb, err = Bases2AmbBase(test.bases) if err != nil { if err != ErrInvalidDNABase { t.Errorf("error should be: %s, got: %s", ErrInvalidDNABase, err) } else { continue } } if amb != test.ambase { t.Errorf("Test Bases2AmbBase Err: %s, need: %c, got: %c\n", test.bases, test.ambase, amb) } } } type _AmbBase2BasesTest struct { ambase byte bases []byte err error } var tests4AmbBase2Bases = []_AmbBase2BasesTest{ {'A', []byte{'A'}, nil}, {'C', []byte{'C'}, nil}, {'G', []byte{'G'}, nil}, {'T', []byte{'T'}, nil}, {'M', []byte{'A', 'C', 'M'}, nil}, {'R', []byte{'A', 'G', 'R'}, nil}, {'W', []byte{'A', 'T', 'W'}, nil}, {'S', []byte{'C', 'G', 'S'}, nil}, {'Y', []byte{'C', 'T', 'Y'}, nil}, {'K', []byte{'G', 'T', 'K'}, nil}, {'V', []byte{'A', 'C', 'G', 'M', 'R', 'S', 'V'}, nil}, {'H', []byte{'A', 'C', 'T', 'M', 'W', 'Y', 'H'}, nil}, {'D', []byte{'A', 'G', 'T', 'R', 'W', 'K', 'D'}, nil}, {'B', []byte{'C', 'G', 'T', 'S', 'Y', 'K', 'B'}, nil}, {'N', []byte{'A', 'C', 'M', 'G', 'R', 'S', 'V', 'T', 'W', 'Y', 'H', 'K', 'D', 'B', 'N'}, nil}, } func TestAmbBase2Bases(t *testing.T) { var bases []byte var err error var b1, b2 sortutil.ByteSlice for _, test := range tests4AmbBase2Bases { bases, err = AmbBase2Bases0(test.ambase) if err != nil { if err != ErrInvalidDNABase { t.Errorf("error should be: %s, got: %s", ErrInvalidDNABase, err) } else { continue } } b1 = sortutil.ByteSlice(bases) b2 = sortutil.ByteSlice(test.bases) sort.Sort(b1) sort.Sort(b2) if !bytes.Equal(b1, b2) { t.Errorf("Test AmbBase2Bases0 Err: %c, need: %s, got: %s\n", test.ambase, b2, b1) } } } var RESULT [][]byte func BenchmarkAmbBase2Bases0(b *testing.B) { for i := 0; i < b.N; i++ { var err error result := [][]byte{} for _, test := range tests4AmbBase2Bases { var bases []byte bases, err = AmbBase2Bases0(test.ambase) if err != nil { b.Errorf("Bench AmbBase2Bases0 Err") } result = append(result, bases) } RESULT = result } } func BenchmarkAmbBase2Bases(b *testing.B) { for i := 0; i < b.N; i++ { var ok bool result := [][]byte{} for _, test := range tests4AmbBase2Bases { var bases []byte if bases, ok = AmbBase2Bases[test.ambase]; ok { result = append(result, bases) } } RESULT = result } } bio-0.13.3/seq/codon_table_standard.amb.tsv000066400000000000000000000000601457355065000205720ustar00rootroot00000000000000ACN T CCN P CGN R CTN L GCN A GGN G GTN V TCN S bio-0.13.3/seq/codon_table_standard.tsv000066400000000000000000000012171457355065000200410ustar00rootroot00000000000000b1 b2 b3 aa m T T T F - T T C F - T T A L - T T G L M T C T S - T C C S - T C A S - T C G S - T A T Y - T A C Y - T A A * * T A G * * T G T C - T G C C - T G A * * T G G W - C T T L - C T C L - C T A L - C T G L M C C T P - C C C P - C C A P - C C G P - C A T H - C A C H - C A A Q - C A G Q - C G T R - C G C R - C G A R - C G G R - A T T I - A T C I - A T A I - A T G M M A C T T - A C C T - A C A T - A C G T - A A T N - A A C N - A A A K - A A G K - A G T S - A G C S - A G A R - A G G R - G T T V - G T C V - G T A V - G T G V - G C T A - G C C A - G C A A - G C G A - G A T D - G A C D - G A A E - G A G E - G G T G - G G C G - G G A G - G G G G - bio-0.13.3/seq/codon_tables.go000066400000000000000000000520001457355065000161310ustar00rootroot00000000000000// https://www.ncbi.nlm.nih.gov/Taxonomy/taxonomyhome.html/index.cgi?chapter=tgencodes package seq import ( "bytes" "errors" "fmt" "sort" "strings" ) // ErrInvalidCodon means the length of codon is not 3. var ErrInvalidCodon = errors.New("seq: invalid codon") // ErrUnknownCodon means the codon is not in the codon table, or the codon contains bases expcet for A C T G U. var ErrUnknownCodon = errors.New("seq: unknown codon") // CodonTable represents a codon table type CodonTable struct { ID int Name string InitCodons map[string]struct{} // upper-case of codon as string, map for fast quering StopCodons map[string]struct{} // upper-case of codon as string, map for fast quering table [16][16][16]byte // matrix is much faster than map for quering } // NewCodonTable contructs a CodonTable with ID and Name, // you need to set the detailed codon table by calling Set or Set2. func NewCodonTable(id int, name string) *CodonTable { t := &CodonTable{ID: id, Name: name} t.InitCodons = make(map[string]struct{}, 1) t.StopCodons = make(map[string]struct{}, 1) t.table = [16][16][16]byte{} return t } // String returns details of the CodonTable. func (t CodonTable) String() string { return t.string(false) } // StringWithAmbiguousCodons returns details of the CodonTable, including ambiguous codons. func (t CodonTable) StringWithAmbiguousCodons() string { return t.string(true) } func (t CodonTable) string(showAmbiguousCodon bool) string { var b bytes.Buffer b.WriteString(fmt.Sprintf("%s (transl_table=%d)\n", t.Name, t.ID)) b.WriteString(fmt.Sprintf("Source: https://www.ncbi.nlm.nih.gov/Taxonomy/taxonomyhome.html/index.cgi?chapter=tgencodes#SG%d\n", t.ID)) b.WriteString("\nInitiation Codons:\n ") codons := make([]string, len(t.InitCodons)) i := 0 for codon := range t.InitCodons { codons[i] = codon i++ } sort.Strings(codons) b.WriteString(strings.Join(codons, ", ")) b.WriteString("\n\nStop Codons:\n ") codons = make([]string, len(t.StopCodons)) i = 0 for codon := range t.StopCodons { codons[i] = codon i++ } sort.Strings(codons) b.WriteString(strings.Join(codons, ", ")) b.WriteString("\n\nStranslate Table:\n") var aa byte var flag, flag2 bool var buf []string for i := 1; i < 16; i++ { if !showAmbiguousCodon && i != 1 && i != 2 && i != 4 && i != 8 { continue } flag2 = false for j := 1; j < 16; j++ { if !showAmbiguousCodon && (j != 1 && j != 2 && j != 4 && j != 8) { continue } buf = make([]string, 0, 16) flag = false for k := 1; k < 16; k++ { if !showAmbiguousCodon && k != 1 && k != 2 && k != 4 && k != 8 { continue } aa = t.table[i][j][k] if aa != 0 { buf = append(buf, fmt.Sprintf("%c%c%c: %c", code2base[i], code2base[j], code2base[k], aa)) flag = true flag2 = true } } if flag { b.WriteString(" " + strings.Join(buf, ", ") + "\n") } } if flag2 { b.WriteString("\n") } } return b.String() } // codon2idx returns the location of a codon in the matrix. func codon2idx(codon []byte) (int, int, int, error) { if len(codon) != 3 { return 0, 0, 0, ErrInvalidCodon } var err error var i, j, k int i, err = base2code(codon[0]) if err != nil { return 0, 0, 0, err } j, err = base2code(codon[1]) if err != nil { return 0, 0, 0, err } k, err = base2code(codon[2]) if err != nil { return 0, 0, 0, err } return i, j, k, nil } // Set sets a codon of byte slice. func (t *CodonTable) Set(codon []byte, aminoAcid byte) error { i, j, k, err := codon2idx(codon) if err != nil { return err } t.table[i][j][k] = aminoAcid return nil } // Set2 sets a codon of string. func (t *CodonTable) Set2(codon string, aminoAcid byte) error { return t.Set([]byte(codon), aminoAcid) } // Get returns the amino acid of the codon ([]byte), codon can be DNA or RNA. // When allowUnknownCodon is true, codons that not int the codon table will // still be translated to 'X', and "---" is translated to "-". func (t *CodonTable) Get(codon []byte, allowUnknownCodon bool) (byte, error) { i, j, k, err := codon2idx(codon) if err != nil { if allowUnknownCodon && err == ErrInvalidDNABase { return 'X', nil } return 0, err } if codon[0] == '-' && codon[1] == '-' && codon[2] == '-' { return '-', nil } aa := t.table[i][j][k] if aa == 0 { aa = 'X' } return aa, nil } // Get2 returns the amino acid of the codon (string), codon can be DNA or RNA. func (t *CodonTable) Get2(codon string, allowUnknownCodon bool) (byte, error) { return t.Get([]byte(codon), allowUnknownCodon) } // Clone returns a deep copy of the CodonTable. func (t *CodonTable) Clone() CodonTable { initCodons := make(map[string]struct{}, len(t.InitCodons)) for k, v := range t.InitCodons { initCodons[k] = v } stopCodons := make(map[string]struct{}, len(t.StopCodons)) for k, v := range t.StopCodons { stopCodons[k] = v } table := [16][16][16]byte{} for i := 0; i < 16; i++ { for j := 0; j < 16; j++ { for k := 0; k < 16; k++ { table[i][j][k] = t.table[i][j][k] } } } return CodonTable{ID: t.ID, Name: t.Name, InitCodons: initCodons, StopCodons: stopCodons, table: table} } // Translate translates a DNA/RNA sequence to amino acid sequences. // Available frame: 1, 2, 3, -1, -2 ,-3. // If option trim is true, it removes all 'X' and '*' characters from the right end of the translation. // If option clean is true, it changes all STOP codon positions from the '*' character to 'X' (an unknown residue). // If option allowUnknownCodon is true, codons not in the codon table will be translated to 'X'. // If option markInitCodonAsM is true, initial codon at beginning will be represented as 'M'. func (t *CodonTable) Translate(sequence []byte, frame int, trim bool, clean bool, allowUnknownCodon bool, markInitCodonAsM bool) ([]byte, error) { if len(sequence) < 3 { return nil, fmt.Errorf("seq: sequence too short to translate: %d", len(sequence)) } if frame < -3 || frame > 3 || frame == 0 { return nil, fmt.Errorf("seq: invalid frame: %d. available: 1, 2, 3, -1, -2, -3", frame) } aas := make([]byte, 0, int((len(sequence)+2)/3)) var aa byte var err error first := true var ok bool if frame < 0 { l := len(sequence) codon := make([]byte, 3) rc := DNA.PairLetter for i := l + frame; i >= 2; i -= 3 { codon[0], _ = rc(sequence[i]) codon[1], _ = rc(sequence[i-1]) codon[2], _ = rc(sequence[i-2]) aa, err = t.Get(codon, allowUnknownCodon) if err != nil { return nil, err } if markInitCodonAsM { if first { // convert amino acid of start codon to 'M' if _, ok = t.InitCodons[strings.ToUpper(string(codon))]; ok { aa = 'M' } first = false } else if aa == '*' { first = true } } if trim && (aa == 'X' || aa == '*') { break } if clean && aa == '*' { aa = 'X' } aas = append(aas, aa) } } else { for i := frame - 1; i < len(sequence)-2; i += 3 { aa, err = t.Get(sequence[i:i+3], allowUnknownCodon) if err != nil { return nil, err } if markInitCodonAsM { if first { // convert amino acid of start codon to 'M' _, ok = t.InitCodons[strings.ToUpper(string(sequence[i:i+3]))] if ok { aa = 'M' } first = false } else if aa == '*' { first = true } } if trim && (aa == 'X' || aa == '*') { break } if clean && aa == '*' { aa = 'X' } aas = append(aas, aa) } } return aas, nil } // CodonTables contains all the codon tables from NCBI: // // 1: The Standard Code // 2: The Vertebrate Mitochondrial Code // 3: The Yeast Mitochondrial Code // 4: The Mold, Protozoan, and Coelenterate Mitochondrial Code and the Mycoplasma/Spiroplasma Code // 5: The Invertebrate Mitochondrial Code // 6: The Ciliate, Dasycladacean and Hexamita Nuclear Code // 9: The Echinoderm and Flatworm Mitochondrial Code // 10: The Euplotid Nuclear Code // 11: The Bacterial, Archaeal and Plant Plastid Code // 12: The Alternative Yeast Nuclear Code // 13: The Ascidian Mitochondrial Code // 14: The Alternative Flatworm Mitochondrial Code // 16: Chlorophycean Mitochondrial Code // 21: Trematode Mitochondrial Code // 22: Scenedesmus obliquus Mitochondrial Code // 23: Thraustochytrium Mitochondrial Code // 24: Pterobranchia Mitochondrial Code // 25: Candidate Division SR1 and Gracilibacteria Code // 26: Pachysolen tannophilus Nuclear Code // 27: Karyorelict Nuclear // 28: Condylostoma Nuclear // 29: Mesodinium Nuclear // 30: Peritrich Nuclear // 31: Blastocrithidia Nuclear // var CodonTables map[int]*CodonTable // https://www.ncbi.nlm.nih.gov/Taxonomy/taxonomyhome.html/index.cgi?chapter=cgencodes#SG1 func codonTableFromText(id int, name string, text string) *CodonTable { t := NewCodonTable(id, name) data := strings.Split(text, "\n") if !(len(data) == 5 && len(data[0]) == 64 && len(data[1]) == 64 && len(data[2]) == 64 && len(data[3]) == 64 && len(data[4]) == 64) { panic("please paste the right text from NCBI") } var aa, start byte var codon []byte for i := 0; i < 64; i++ { aa = data[0][i] start = data[1][i] codon = []byte{data[2][i], data[3][i], data[4][i]} t.Set(codon, aa) if start == 'M' { t.InitCodons[strings.ToUpper(string(codon))] = struct{}{} } else if start == '*' { t.StopCodons[strings.ToUpper(string(codon))] = struct{}{} } } // supporting codon containing ambiguous base var m map[byte][]int // aa - > bases var ok bool var codes, ambcodes []int var code, ambcode int // base3 for i := 1; i < 16; i++ { for j := 1; j < 16; j++ { m = make(map[byte][]int, 16) for k := 1; k < 16; k++ { aa = t.table[i][j][k] if aa == 0 { continue } if _, ok = m[aa]; !ok { m[aa] = make([]int, 0, 4) } m[aa] = append(m[aa], k) } if len(m) == 0 { continue } for aa, codes = range m { ambcode, _ = Codes2AmbCode(codes) if ambcodes, ok = AmbCodes2Codes[ambcode]; ok { for _, code = range ambcodes { t.table[i][j][code] = aa } } } } } // base2 for i := 1; i < 16; i++ { for k := 1; k < 16; k++ { m = make(map[byte][]int, 16) for j := 1; j < 16; j++ { aa = t.table[i][j][k] if aa == 0 { continue } if _, ok = m[aa]; !ok { m[aa] = make([]int, 0, 4) } m[aa] = append(m[aa], j) } if len(m) == 0 { continue } for aa, codes = range m { ambcode, _ = Codes2AmbCode(codes) if ambcodes, ok = AmbCodes2Codes[ambcode]; ok { for _, code = range ambcodes { t.table[i][code][k] = aa } } } } } // base1 for j := 1; j < 16; j++ { for k := 1; k < 16; k++ { m = make(map[byte][]int, 16) for i := 1; i < 16; i++ { aa = t.table[i][j][k] if aa == 0 { continue } if _, ok = m[aa]; !ok { m[aa] = make([]int, 0, 4) } m[aa] = append(m[aa], i) } if len(m) == 0 { continue } for aa, codes = range m { ambcode, _ = Codes2AmbCode(codes) if ambcodes, ok = AmbCodes2Codes[ambcode]; ok { for _, code = range ambcodes { t.table[code][j][k] = aa } } } } } return t } func init() { CodonTables = make(map[int]*CodonTable, 31) CodonTables[1] = codonTableFromText(1, "The Standard Code", `FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG ---M------**--*----M---------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[2] = codonTableFromText(2, "The Vertebrate Mitochondrial Code", `FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNKKSS**VVVVAAAADDEEGGGG ----------**--------------------MMMM----------**---M------------ TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[3] = codonTableFromText(3, "The Yeast Mitochondrial Code", `FFLLSSSSYY**CCWWTTTTPPPPHHQQRRRRIIMMTTTTNNKKSSRRVVVVAAAADDEEGGGG ----------**----------------------MM---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[4] = codonTableFromText(4, "The Mold, Protozoan, and Coelenterate Mitochondrial Code and the Mycoplasma/Spiroplasma Code", `FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG --MM------**-------M------------MMMM---------------M------------ TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[5] = codonTableFromText(5, "The Invertebrate Mitochondrial Code ", `FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNKKSSSSVVVVAAAADDEEGGGG ---M------**--------------------MMMM---------------M------------ TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[6] = codonTableFromText(6, "The Ciliate, Dasycladacean and Hexamita Nuclear Code", `FFLLSSSSYYQQCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG --------------*--------------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[9] = codonTableFromText(9, "The Echinoderm and Flatworm Mitochondrial Code", `FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIIMTTTTNNNKSSSSVVVVAAAADDEEGGGG ----------**-----------------------M---------------M------------ TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[10] = codonTableFromText(10, "The Euplotid Nuclear Code", `FFLLSSSSYY**CCCWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG ----------**-----------------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[11] = codonTableFromText(11, "The Bacterial, Archaeal and Plant Plastid Code", `FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG ---M------**--*----M------------MMMM---------------M------------ TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[12] = codonTableFromText(12, "The Alternative Yeast Nuclear Code", `FFLLSSSSYY**CC*WLLLSPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG ----------**--*----M---------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[13] = codonTableFromText(13, "The Ascidian Mitochondrial Code", `FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNKKSSGGVVVVAAAADDEEGGGG ---M------**----------------------MM---------------M------------ TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[14] = codonTableFromText(14, "The Alternative Flatworm Mitochondrial Code", `FFLLSSSSYYY*CCWWLLLLPPPPHHQQRRRRIIIMTTTTNNNKSSSSVVVVAAAADDEEGGGG -----------*-----------------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[16] = codonTableFromText(16, "Chlorophycean Mitochondrial Code", `FFLLSSSSYY*LCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG ----------*---*--------------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[21] = codonTableFromText(21, "Trematode Mitochondrial Code", `FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIMMTTTTNNNKSSSSVVVVAAAADDEEGGGG ----------**-----------------------M---------------M------------ TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[22] = codonTableFromText(22, "Scenedesmus obliquus Mitochondrial Code", `FFLLSS*SYY*LCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG ------*---*---*--------------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[23] = codonTableFromText(23, "Thraustochytrium Mitochondrial Code", `FF*LSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG --*-------**--*-----------------M--M---------------M------------ TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[24] = codonTableFromText(24, "Pterobranchia Mitochondrial Code", `FFLLSSSSYY**CCWWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSSKVVVVAAAADDEEGGGG ---M------**-------M---------------M---------------M------------ TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[25] = codonTableFromText(25, "Candidate Division SR1 and Gracilibacteria Code", `FFLLSSSSYY**CCGWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG ---M------**-----------------------M---------------M------------ TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[26] = codonTableFromText(26, "Pachysolen tannophilus Nuclear Code", `FFLLSSSSYY**CC*WLLLAPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG ----------**--*----M---------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[27] = codonTableFromText(27, "Karyorelict Nuclear", `FFLLSSSSYYQQCCWWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG --------------*--------------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[28] = codonTableFromText(28, "Condylostoma Nuclear", `FFLLSSSSYYQQCCWWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG ----------**--*--------------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[29] = codonTableFromText(29, "Mesodinium Nuclear", `FFLLSSSSYYYYCC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG --------------*--------------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[30] = codonTableFromText(30, "Peritrich Nuclear", `FFLLSSSSYYEECC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG --------------*--------------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) CodonTables[31] = codonTableFromText(31, "Blastocrithidia Nuclear", `FFLLSSSSYYEECCWWLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG ----------**-----------------------M---------------------------- TTTTTTTTTTTTTTTTCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGG TTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGGTTTTCCCCAAAAGGGG TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG`) // ks := make([]int, len(CodonTables)) // i := 0 // for k := range CodonTables { // ks[i] = k // i++ // } // sort.Ints(ks) // for _, i = range ks { // fmt.Println(CodonTables[i]) // // fmt.Printf("%d: %s\n", CodonTables[i].ID, CodonTables[i].Name) // break // } } bio-0.13.3/seq/codon_tables_test.go000066400000000000000000000124071457355065000171770ustar00rootroot00000000000000package seq import ( "regexp" "testing" ) type codonTableTest struct { table int nt string aa string frame int trim bool clean bool allowUnknownCodon bool markInitCodonAsM bool } var codonTableTests []codonTableTest func init() { re := regexp.MustCompile(`\r?\n|\s`) codonTableTests = make([]codonTableTest, 0, 10) // https://www.ncbi.nlm.nih.gov/nuccore/AB021961.1 codonTableTests = append(codonTableTests, codonTableTest{ table: 1, nt: re.ReplaceAllString(`atgactgccatggaggagtcacagtcggatatcagcctcgagctccctctgagccaggag acattttcaggcttatggaaactacttcctccagaagatatcctgccatcacctcactgc atggacgatctgttgctgccccaggatgttgaggagttttttgaaggcccaagtgaagcc ctccgagtgtcaggagctcctgcagcacaggaccctgtcaccgagacccctgggccagtg gcccctgccccagccactccatggcccctgtcatcttttgtcccttctcaaaaaacttac cagggcaactatggcttccacctgggcttcctgcagtctgggacagccaagtctgttatg tgcacgtactctcctcccctcaataagctattctgccagctggcgaagacgtgccctgtg cagttgtgggtcagcgccacacctccagctgggagccgtgtccgcgccatggccatctac aagaagtcacagcacatgacggaggtcgtgagacgctgcccccaccatgagcgctgctcc gatggtgatggcctggctcctccccagcatcgtatccgggtggaaggaaatttgtatccc gagtatctggaagacaggcagacttttcgccacagcgtggtggtaccttatgagccaccc gaggccggctctgagtataccaccatccactacaagtacatgtgtaatagctcctgcatg gggggcatgaaccgccgacctatccttaccatcatcacactggaagactccagtgggaac cttctgggacgggacagctttgaggttcgtgtttgtgcctgccctgggagagaccgccgt acagaagaagaaaatttccgcaaaaaggaagtcctttgccctgaactgcccccagggagc gcaaagagagcgctgcccacctgcacaagcgcctctcccccgcaaaagaaaaaaccactt gatggagagtatttcaccctcaagatccgcgggcgtaaacgcttcgagatgttccgggag ctgaatgaggccttagagttaaaggatgcccatgctacagaggagtctggagacagcagg gctcactccagctacctgaagaccaagaagggccagtctacttcccgccataaaaaaaca atggtcaagaaagtggggcctgactcagactga`, ""), aa: re.ReplaceAllString(`MTAMEESQSDISLELPLSQETFSGLWKLLPPEDILPSPHCMDDL LLPQDVEEFFEGPSEALRVSGAPAAQDPVTETPGPVAPAPATPWPLSSFVPSQKTYQG NYGFHLGFLQSGTAKSVMCTYSPPLNKLFCQLAKTCPVQLWVSATPPAGSRVRAMAIY KKSQHMTEVVRRCPHHERCSDGDGLAPPQHRIRVEGNLYPEYLEDRQTFRHSVVVPYE PPEAGSEYTTIHYKYMCNSSCMGGMNRRPILTIITLEDSSGNLLGRDSFEVRVCACPG RDRRTEEENFRKKEVLCPELPPGSAKRALPTCTSASPPQKKKPLDGEYFTLKIRGRKR FEMFRELNEALELKDAHATEESGDSRAHSSYLKTKKGQSTSRHKKTMVKKVGPDSD`, ""), frame: 1, trim: true, clean: false, }) // ORF25 from https://www.ncbi.nlm.nih.gov/nuccore/AJ245616.1 codonTableTests = append(codonTableTests, codonTableTest{ table: 11, nt: re.ReplaceAllString(`atggaggaacaagcatggcgagaagtcctcgaacgtttagctcga attgaaacaaagttagataactatgaaacagttcgagataaagcagaacgagcgctccta atagctcaatcaaatgcgaaacttatagaaaaaatggaagctaataataagtgggcttgg ggctttatgcttactcttgccgtaactgttattggttatttattcactaaaattagattc tga`, ""), aa: re.ReplaceAllString(`MEEQAWREVLERLARIETKLDNYETVRDKAERALLIAQSNAKLI EKMEANNKWAWGFMLTLAVTVIGYLFTKIRF`, ""), frame: 1, trim: true, clean: false, }) codonTableTests = append(codonTableTests, codonTableTest{ table: 11, nt: re.ReplaceAllString(`tcagaatctaattttagtgaataaataaccaataacagttacggcaagagtaagcataaa gccccaagcccacttattattagcttccattttttctataagtttcgcatttgattgagc tattaggagcgctcgttctgctttatctcgaactgtttcatagttatctaactttgtttc aattcgagctaaacgttcgaggacttctcgccatgcttgttcctccat`, ""), aa: re.ReplaceAllString(`MEEQAWREVLERLARIETKLDNYETVRDKAERALLIAQSNAKLI EKMEANNKWAWGFMLTLAVTVIGYLFTKIRF`, ""), frame: -1, trim: true, clean: false, }) codonTableTests = append(codonTableTests, codonTableTest{ table: 11, nt: re.ReplaceAllString(`tcagaatctaattttagtgaataaataaccaataacagttacggcaagagtaagcataaa gccccaagcccacttattattagcttccattttttctataagtttcgcatttgattgagc tattaggagcgctcgttctgctttatctcgaactgtttcatagttatctaactttgtttc aattcgagctaaacgttcgaggacttctcgccatgcttgttcctccatc`, ""), aa: re.ReplaceAllString(`MEEQAWREVLERLARIETKLDNYETVRDKAERALLIAQSNAKLI EKMEANNKWAWGFMLTLAVTVIGYLFTKIRF`, ""), frame: -2, trim: true, clean: false, }) codonTableTests = append(codonTableTests, codonTableTest{ table: 11, nt: re.ReplaceAllString(`tcagaatctaattttagtgaataaataaccaataacagttacggcaagagtaagcataaa gccccaagcccacttattattagcttccattttttctataagtttcgcatttgattgagc tattaggagcgctcgttctgctttatctcgaactgtttcatagttatctaactttgtttc aattcgagctaaacgttcgaggacttctcgccatgcttgttcctccatcc`, ""), aa: re.ReplaceAllString(`MEEQAWREVLERLARIETKLDNYETVRDKAERALLIAQSNAKLI EKMEANNKWAWGFMLTLAVTVIGYLFTKIRF`, ""), frame: -3, trim: true, clean: false, }) codonTableTests = append(codonTableTests, codonTableTest{ table: 11, nt: re.ReplaceAllString(`atNgaggaacaagcatggcgagaagtcctcgaacgtttagctcga attgaaacaaagttagataactatgaaacagttcgagataaagcagaacgagcgctccta atagctcaatcaaatgcgaaacttatagaaaaaatggaagctaataataagtgggcttgg ggctttatgcttactcttgccgtaactgttattggttatttattcactaaaattagattc tga`, ""), aa: re.ReplaceAllString(`XEEQAWREVLERLARIETKLDNYETVRDKAERALLIAQSNAKLI EKMEANNKWAWGFMLTLAVTVIGYLFTKIRF*`, ""), frame: 1, trim: false, clean: false, allowUnknownCodon: true, }) } func TestCodonTableStranslation(t *testing.T) { var aa []byte var err error for i, test := range codonTableTests { aa, err = CodonTables[test.table].Translate([]byte(test.nt), test.frame, test.trim, test.clean, test.allowUnknownCodon, test.markInitCodonAsM) if err != nil { t.Errorf("test %d err: %s", i, err) } if len(aa) != len(test.aa) { t.Errorf("test %d err: len unequal, answer: %d, result: %d", i, len(aa), len(test.aa)) } if string(aa) != test.aa { t.Errorf("test %d err: result not right", i) } } } bio-0.13.3/seq/qual.go000077500000000000000000000144021457355065000144460ustar00rootroot00000000000000package seq import ( "errors" "math" "github.com/shenwei356/util/byteutil" ) // ErrInvalidPhredQuality occurs for phred quality less than 0. var ErrInvalidPhredQuality = errors.New("seq: invalid Phred quality") // ErrInvalidSolexaQuality occurs for solexa quality less than -5. var ErrInvalidSolexaQuality = errors.New("seq: invalid Solexa quality") // Phred2Solexa converts Phred quality to Solexa quality. func Phred2Solexa(q float64) (float64, error) { if q == 0 { return -5, nil } if q < 0 { return -5, ErrInvalidPhredQuality } return max(-5, 10*math.Log10(math.Pow(10, q/10)-1)), nil } // Solexa2Phred converts Solexa quality to Phred quality. func Solexa2Phred(q float64) (float64, error) { if q < -5 { return 0, ErrInvalidSolexaQuality } return 10 * math.Log10(math.Pow(10, q/10)+1), nil } func max(a float64, b float64) float64 { if a > b { return a } return b } // QualityEncoding is the type of quality encoding type QualityEncoding int // NQualityEncoding is the number of QualityEncoding + 1: 5 + 1 = 6 const NQualityEncoding int = 6 const ( // Unknown quality encoding Unknown QualityEncoding = iota // Sanger format can encode a Phred quality score from 0 to 93 using // ASCII 33 to 126 (although in raw read data the Phred quality score // rarely exceeds 60, higher scores are possible in assemblies or read maps). Sanger // Solexa /Illumina 1.0 format can encode a Solexa/Illumina quality score // from -5 to 62 using ASCII 59 to 126 (although in raw read data Solexa // scores from -5 to 40 only are expected). Solexa // Illumina1p3 means Illumina 1.3+. // Starting with Illumina 1.3 and before Illumina 1.8, the format // encoded a Phred quality score from 0 to 62 using ASCII 64 to 126 // (although in raw read data Phred scores from 0 to 40 only are expected). Illumina1p3 // Illumina1p5 means Illumina 1.5+. // Starting in Illumina 1.5 and before Illumina 1.8, the Phred scores // 0 to 2 have a slightly different meaning. The values 0 and 1 are // no longer used and the value 2, encoded by ASCII 66 "B", is used // also at the end of reads as a Read Segment Quality Control Indicator. Illumina1p5 // Illumina1p8 means Illumina 1.8+. // Starting in Illumina 1.8, the quality scores have basically // returned to the use of the Sanger format (Phred+33) Illumina1p8 ) func (qe QualityEncoding) String() string { switch qe { case Sanger: return "Sanger" case Solexa: return "Solexa" case Illumina1p3: return "Illumina-1.3+" case Illumina1p5: return "Illumina-1.5+" case Illumina1p8: return "Illumina-1.8+" } return "Unknown" } // QualityRange is the typical quality range func (qe QualityEncoding) QualityRange() []int { switch qe { case Sanger: return []int{33, 126} case Solexa: return []int{59, 126} case Illumina1p3: return []int{64, 126} case Illumina1p5: return []int{66, 126} case Illumina1p8: return []int{33, 126} } return []int{127, 256} } // Offset is the ASCII offset func (qe QualityEncoding) Offset() int { switch qe { case Sanger: return 33 case Solexa: return 64 case Illumina1p3: return 64 case Illumina1p5: return 64 case Illumina1p8: return 33 } return 0 } // IsSolexa tells whether the encoding is Solexa func (qe QualityEncoding) IsSolexa() bool { switch qe { case Solexa: return true } return false } // ErrUnknownQualityEncoding is error for Unknown quality encoding type var ErrUnknownQualityEncoding = errors.New("unknown quality encoding") // QualityValue returns quality value for given encoding and quality string func QualityValue(encoding QualityEncoding, quality []byte) ([]int, error) { offset := encoding.Offset() if offset == 0 { return nil, ErrUnknownQualityEncoding } qv := make([]int, len(quality)) for i, q := range quality { qv[i] = int(q) - offset } return qv, nil } // QualityConvert convert quality from one encoding to another encoding. // Force means forcely truncate scores > 40 to 40 when converting Illumina-1.8+ // to Sanger. func QualityConvert(from, to QualityEncoding, quality []byte, force bool) ([]byte, error) { if from == to || from == Unknown || to == Unknown { return quality, nil } qv, _ := QualityValue(from, quality) isSolexaFrom := from.IsSolexa() isSolexaTo := to.IsSolexa() offsetTo := to.Offset() var err error var q2 float64 qualityNew := make([]byte, len(quality)) for i, q := range qv { if force { // Illumina -> Sanger if from == Illumina1p8 && to == Sanger && q > 40 { q = 40 } } if from == Illumina1p5 && q == 2 { // special case of Illumina 1.5 q = 0 } if isSolexaFrom == isSolexaTo { qualityNew[i] = byte(q + offsetTo) } else if isSolexaFrom { q2, err = Solexa2Phred(float64(q)) if err != nil { return nil, err } qualityNew[i] = byte(int(q2) + offsetTo) } else { q2, err = Phred2Solexa(float64(q)) if err != nil { return nil, err } qualityNew[i] = byte(int(q2) + offsetTo) } } return qualityNew, nil } // NMostCommonThreshold is the threshold of 'B' in // top N most common quality for guessing Illumina 1.5. var NMostCommonThreshold = 2 // GuessQualityEncoding returns potential quality encodings. func GuessQualityEncoding(quality []byte) []QualityEncoding { var encodings []QualityEncoding min, max := qualRange(quality) var encoding QualityEncoding var r []int var count map[byte]int var countSorted byteutil.ByteCountList var BEnriched bool for i := 1; i < NQualityEncoding; i++ { encoding = QualityEncoding(i) r = encoding.QualityRange() if min >= r[0] && max <= r[1] { if encoding == Illumina1p5 { if count == nil { count = byteutil.CountOfByte(quality) } if count['@'] > 0 || count['A'] > 0 { // exclude Illumina 1.5 continue } else { // countSorted = byteutil.SortCountOfByte(count, true) BEnriched = false for i := 0; i < len(countSorted) && i < NMostCommonThreshold; i++ { if countSorted[i].Key == 'B' { BEnriched = true break } } if BEnriched { return []QualityEncoding{Illumina1p5} } } } encodings = append(encodings, encoding) } } return encodings } func qualRange(quality []byte) (int, int) { var min, max int = 126, 0 var v int for _, q := range quality { v = int(q) if v > max { max = v } if v < min { min = v } } return min, max } bio-0.13.3/seq/qual_test.go000077500000000000000000000005541457355065000155100ustar00rootroot00000000000000package seq import ( "math" "testing" ) func TestPhredAndSolexa(t *testing.T) { var q2, q3 float64 var err error for q := 2.0; q <= 40; q++ { q2, err = Phred2Solexa(q) if err != nil { t.Error(err) } q3, err = Solexa2Phred(q2) if err != nil { t.Error(err) } if math.Abs(q3-q) > 0.01 { t.Errorf("%.2f, %.2f, %.2f", q, q2, q3) } } } bio-0.13.3/seq/seq.go000066400000000000000000000412141457355065000142720ustar00rootroot00000000000000package seq import ( "bytes" "fmt" "math" "runtime" "strings" "sync" "github.com/shenwei356/util/byteutil" ) var QUAL_MAP [256]float64 func initQualMap() { for i := range QUAL_MAP { QUAL_MAP[i] = math.Pow(10, float64(i)/-10) } QUAL_MAP[255] = 1.0 } func init() { initQualMap() } // Seq represents a FASTA/Q record type Seq struct { Alphabet *Alphabet Seq []byte Qual []byte QualValue []int } // ValidateSeq decides whether check sequence or not var ValidateSeq = true // NewSeq is constructor for type *Seq* func NewSeq(t *Alphabet, s []byte) (*Seq, error) { if ValidateSeq { // check sequene first if err := t.IsValid(s); err != nil { return nil, err } } seq := &Seq{Alphabet: t, Seq: s} return seq, nil } // NewSeqWithQual is used to store fastq sequence func NewSeqWithQual(t *Alphabet, s []byte, q []byte) (*Seq, error) { if len(s) != len(q) { return nil, fmt.Errorf("seq: unmatched length of sequence (%d) and quality (%d)", len(s), len(q)) } seq, err := NewSeq(t, s) if err != nil { return nil, err } seq.Qual = q return seq, nil } // NewSeqWithoutValidation create Seq without check the sequences func NewSeqWithoutValidation(t *Alphabet, s []byte) (*Seq, error) { seq := &Seq{Alphabet: t, Seq: s} return seq, nil } // NewSeqWithQualWithoutValidation create Seq with quality without check the sequences func NewSeqWithQualWithoutValidation(t *Alphabet, s []byte, q []byte) (*Seq, error) { if len(s) != len(q) { return nil, fmt.Errorf("seq: unmatched length of sequence (%d) and quality (%d)", len(s), len(q)) } seq := &Seq{Alphabet: t, Seq: s, Qual: q} return seq, nil } // Length returns the length of sequence func (seq *Seq) Length() int { return len(seq.Seq) } func (seq *Seq) String() string { return fmt.Sprintf("%s, len:%d, seq:%s, qual:%s", seq.Alphabet.String(), len(seq.Seq), seq.Seq, seq.Qual) } // Clone of a Seq func (seq *Seq) Clone() *Seq { s := make([]byte, len(seq.Seq)) copy(s, seq.Seq) q := make([]byte, len(seq.Qual)) copy(q, seq.Qual) qv := make([]int, len(seq.QualValue)) copy(qv, seq.QualValue) return &Seq{ Alphabet: seq.Alphabet.Clone(), Seq: s, Qual: q, QualValue: qv, } } // Clone2 clones the sequence except the alphabet func (seq *Seq) Clone2() *Seq { s := make([]byte, len(seq.Seq)) copy(s, seq.Seq) q := make([]byte, len(seq.Qual)) copy(q, seq.Qual) qv := make([]int, len(seq.QualValue)) copy(qv, seq.QualValue) return &Seq{ Alphabet: seq.Alphabet, Seq: s, Qual: q, QualValue: qv, } } /* SubSeq returns a sub seq. start and end is 1-based. Examples: 1-based index 1 2 3 4 5 6 7 8 9 10 negative index 0-9-8-7-6-5-4-3-2-1 seq A C G T N a c g t n 1:1 A 2:4 C G T -4:-2 c g t -4:-1 c g t n -1:-1 n 2:-2 C G T N a c g t 1:-1 A C G T N a c g t n 1:12 A C G T N a c g t n -12:-1 A C G T N a c g t n */ func (seq *Seq) SubSeq(start int, end int) *Seq { var newseq *Seq start, end, ok := SubLocation(len(seq.Seq), start, end) if ok { s := make([]byte, end-start+1) copy(s, seq.Seq[start-1:end]) newseq, _ = NewSeqWithoutValidation(seq.Alphabet, s) if len(seq.Qual) > 0 { newseq.Qual = []byte(string(seq.Qual[start-1 : end])) } if len(seq.QualValue) > 0 { qv := make([]int, end-start+1) for i, v := range seq.QualValue[start-1 : end] { qv[i] = v } newseq.QualValue = qv } } else { newseq, _ = NewSeqWithoutValidation(seq.Alphabet, []byte("")) } return newseq } // SubSeqInplace return subseq inplace func (seq *Seq) SubSeqInplace(start int, end int) *Seq { start, end, ok := SubLocation(len(seq.Seq), start, end) if ok { if len(seq.Seq) > 0 { seq.Seq = seq.Seq[start-1 : end] } if len(seq.Qual) > 0 { seq.Qual = seq.Qual[start-1 : end] } if len(seq.QualValue) > 0 { seq.QualValue = seq.QualValue[start-1 : end] } } else { seq.Seq = []byte{} seq.Qual = []byte{} seq.QualValue = []int{} } return seq } /* SubLocation is my sublocation strategy, start, end and returned start and end are all 1-based 1-based index 1 2 3 4 5 6 7 8 9 10 negative index 0-9-8-7-6-5-4-3-2-1 seq A C G T N a c g t n 1:1 A 2:4 C G T -4:-2 c g t -4:-1 c g t n -1:-1 n 2:-2 C G T N a c g t 1:-1 A C G T N a c g t n 1:12 A C G T N a c g t n -12:-1 A C G T N a c g t n */ func SubLocation(length, start, end int) (int, int, bool) { if length == 0 { return 0, 0, false } if start < 1 { if start == 0 { start = 1 } else if start < 0 { if end < 0 && start > end { return start, end, false } if -start > length { return start, end, false } start = length + start + 1 } } if start > length { return start, end, false } if end > length { end = length } if end < 1 { if end == 0 { end = -1 } end = length + end + 1 } if start-1 > end { return start - 1, end, false } return start, end, true } // RemoveGaps return a new seq without gaps func (seq *Seq) RemoveGaps(letters string) *Seq { if len(letters) == 0 { return seq.Clone() } // do not use map querySlice := make([]byte, 256) for i := 0; i < len(letters); i++ { querySlice[int(letters[i])] = letters[i] } s := make([]byte, len(seq.Seq)) q := make([]byte, len(seq.Qual)) var b, g byte var j int for i := 0; i < len(seq.Seq); i++ { b = seq.Seq[i] g = querySlice[int(b)] if g == 0 { // not gap s[j] = b if len(seq.Qual) > 0 { q[j] = seq.Qual[i] } j++ } } var newSeq *Seq if len(seq.Qual) > 0 { newSeq, _ = NewSeqWithQualWithoutValidation(seq.Alphabet, s[0:j], q[0:j]) } else { newSeq, _ = NewSeqWithoutValidation(seq.Alphabet, s[0:j]) } return newSeq } // RemoveGapsInplace removes gaps in place func (seq *Seq) RemoveGapsInplace(letters string) *Seq { if len(letters) == 0 { return seq } // do not use map querySlice := make([]byte, 256) for i := 0; i < len(letters); i++ { querySlice[int(letters[i])] = letters[i] } s := make([]byte, len(seq.Seq)) q := make([]byte, len(seq.Qual)) var b, g byte var j int for i := 0; i < len(seq.Seq); i++ { b = seq.Seq[i] g = querySlice[int(b)] if g == 0 { // not gap s[j] = b if len(seq.Qual) > 0 { q[j] = seq.Qual[i] } j++ } } seq.Seq = s[0:j] if len(seq.Qual) > 0 { seq.Qual = q[0:j] } return seq } // Bases counts non-gap bases func (seq *Seq) Bases(gapLetters string) int { if len(gapLetters) == 0 { return seq.Length() } // do not use map querySlice := make([]byte, 256) for i := 0; i < len(gapLetters); i++ { querySlice[int(gapLetters[i])] = gapLetters[i] } var n int for i := 0; i < len(seq.Seq); i++ { if querySlice[int(seq.Seq[i])] == 0 { // not gap n++ } } return n } // RevCom returns reverse complement sequence func (seq *Seq) RevCom() *Seq { return seq.Reverse().Complement() } // RevComInplace reverses complement sequence in place func (seq *Seq) RevComInplace() *Seq { return seq.ReverseInplace().ComplementInplace() } // Reverse a sequence func (seq *Seq) Reverse() *Seq { return seq.Clone().ReverseInplace() } // ReverseInplace reverses the sequence content func (seq *Seq) ReverseInplace() *Seq { if len(seq.Qual) > 0 { byteutil.ReverseByteSliceInplace(seq.Qual) } byteutil.ReverseByteSliceInplace(seq.Seq) return seq } // ComplementSeqLenThreshold is the threshold of sequence length that // needed to parallelly complement sequence var ComplementSeqLenThreshold = 1000000 // ComplementThreads is the threads number of parallelly complement sequence var ComplementThreads = runtime.NumCPU() // Complement returns complement sequence. func (seq *Seq) Complement() *Seq { return seq.Clone().ComplementInplace() } // ComplementInplace returns complement sequence. func (seq *Seq) ComplementInplace() *Seq { if seq.Alphabet == Unlimit { return seq } l := len(seq.Seq) if l < ComplementSeqLenThreshold { var p byte for i := 0; i < len(seq.Seq); i++ { p, _ = seq.Alphabet.PairLetter(seq.Seq[i]) seq.Seq[i] = p } return seq } chunkSize, start, end := int(l/ComplementThreads), 0, 0 var wg sync.WaitGroup tokens := make(chan int, ComplementThreads) for i := 0; i < ComplementThreads; i++ { start = i * chunkSize end = (i + 1) * chunkSize if i == ComplementThreads-1 && end < l { end = l } if end > l { end = l } tokens <- 1 wg.Add(1) go func(alphabet *Alphabet, start, end int) { var p byte for i := start; i < end; i++ { p, _ = seq.Alphabet.PairLetter(seq.Seq[i]) seq.Seq[i] = p } <-tokens wg.Done() }(seq.Alphabet, start, end) } wg.Wait() return seq } // FormatSeq wrap seq func (seq *Seq) FormatSeq(width int) []byte { return byteutil.WrapByteSlice(seq.Seq, width) } /* BaseContent returns base content for given bases. For example: seq.BaseContent("gc") */ func (seq *Seq) BaseContent(list string) float64 { if len(seq.Seq) == 0 { return float64(0) } return float64(seq.BaseCount(list)) / float64(len(seq.Seq)) } // BaseCount counts bases func (seq *Seq) BaseCount(list string) int { if len(seq.Seq) == 0 { return 0 } sum := 0 for _, b := range []byte(list) { up := bytes.ToUpper([]byte{b}) lo := bytes.ToLower([]byte{b}) if string(up) == string(lo) { sum += bytes.Count(seq.Seq, up) } else { sum += bytes.Count(seq.Seq, up) + bytes.Count(seq.Seq, lo) } } return sum } // BaseContentCaseSensitive returns base content for given case sensitive bases. func (seq *Seq) BaseContentCaseSensitive(list string) float64 { if len(seq.Seq) == 0 { return float64(0) } return float64(seq.BaseCountCaseSensitive(list)) / float64(len(seq.Seq)) } // BaseCountCaseSensitive counts bases, case is not ignored. func (seq *Seq) BaseCountCaseSensitive(list string) int { if len(seq.Seq) == 0 { return 0 } sum := 0 for _, b := range []byte(list) { sum += bytes.Count(seq.Seq, []byte{b}) } return sum } // GC returns the GC content func (seq *Seq) GC() float64 { return seq.BaseContent("gcs") } // DegenerateBaseMapNucl mappings nucleic acid degenerate base to // regular expression var DegenerateBaseMapNucl = map[byte]string{ 'A': "A", 'T': "[TU]", 'U': "[TU]", 'C': "C", 'G': "G", 'R': "[AG]", 'Y': "[CTU]", 'M': "[AC]", 'K': "[GTU]", 'S': "[CG]", 'W': "[ATU]", 'H': "[ACTU]", 'B': "[CGTU]", 'V': "[ACG]", 'D': "[AGTU]", 'N': "[ACGTU]", 'a': "a", 't': "[tu]", 'u': "[tu]", 'c': "c", 'g': "g", 'r': "[ag]", 'y': "[ctu]", 'm': "[ac]", 'k': "[gtu]", 's': "[cg]", 'w': "[atu]", 'h': "[actu]", 'b': "[cgtu]", 'v': "[acg]", 'd': "[agtu]", 'n': "[acgtu]", } // DegenerateBaseMapNucl2 mappings nucleic acid degenerate base to all bases. var DegenerateBaseMapNucl2 = map[byte]string{ 'A': "A", 'T': "TU", 'U': "TU", 'C': "C", 'G': "G", 'R': "AG", 'Y': "CTU", 'M': "AC", 'K': "GTU", 'S': "CG", 'W': "ATU", 'H': "ACTU", 'B': "CGTU", 'V': "ACG", 'D': "AGTU", 'N': "ACGTU", 'a': "a", 't': "tu", 'u': "tu", 'c': "c", 'g': "g", 'r': "ag", 'y': "ctu", 'm': "ac", 'k': "gtu", 's': "cg", 'w': "atu", 'h': "actu", 'b': "cgtu", 'v': "acg", 'd': "agtu", 'n': "acgtu", } // DegenerateBaseMapProt mappings protein degenerate base to // regular expression var DegenerateBaseMapProt = map[byte]string{ 'A': "A", 'B': "[DN]", 'C': "C", 'D': "D", 'E': "E", 'F': "F", 'G': "G", 'H': "H", 'I': "I", 'J': "[IL]", 'K': "K", 'L': "L", 'M': "M", 'N': "N", 'P': "P", 'Q': "Q", 'R': "R", 'S': "S", 'T': "T", 'V': "V", 'W': "W", 'X': "[A-Z]", 'Y': "Y", 'Z': "[QE]", 'a': "a", 'b': "[dn]", 'c': "c", 'd': "d", 'e': "e", 'f': "f", 'g': "g", 'h': "h", 'i': "i", 'j': "[il]", 'k': "k", 'l': "l", 'm': "m", 'n': "n", 'p': "p", 'q': "q", 'r': "r", 's': "s", 't': "t", 'v': "v", 'w': "w", 'x': "[a-z]", 'y': "y", 'z': "[qe]", } // Degenerate2Regexp transforms seqs containing degenrate base to regular expression func (seq *Seq) Degenerate2Regexp() string { var m map[byte]string if seq.Alphabet == Protein { m = DegenerateBaseMapProt } else { m = DegenerateBaseMapNucl } s := make([]string, len(seq.Seq)) for i, base := range seq.Seq { if _, ok := m[base]; ok { s[i] = m[base] } else { s[i] = string(base) } } return strings.Join(s, "") } // Degenerate2Seqs transforms seqs containing degenrate bases to all possible sequences. func Degenerate2Seqs(s []byte) (dseqs [][]byte, err error) { dseqs = [][]byte{} var i, j, k int var ok bool var dbases string var dbase byte for _, base := range s { if dbases, ok = DegenerateBaseMapNucl2[base]; ok { if len(dbases) == 1 { dbase = dbases[0] for i = 0; i < len(dseqs); i++ { dseqs[i] = append(dseqs[i], dbase) } } else { // 2nd more := make([][]byte, len(dseqs)*(len(dbases)-1)) k = 0 for i = 1; i < len(dbases); i++ { for j = 0; j < len(dseqs); j++ { more[k] = []byte(string(append(dseqs[j], dbases[i]))) k++ } } // 1th for i = 0; i < len(dseqs); i++ { dseqs[i] = append(dseqs[i], dbases[0]) } dseqs = append(dseqs, more...) } } else { return dseqs, fmt.Errorf("seq: invalid letter: %c", base) } } return dseqs, nil } // Translate translates the RNA/DNA to amino acid sequence. // Available frame: 1, 2, 3, -1, -2 ,-3. // If option trim is true, it removes all 'X' and '*' characters from the right end of the translation. // If option clean is true, it changes all STOP codon positions from the '*' character to 'X' (an unknown residue). // If option allowUnknownCodon is true, codons not in the codon table will be translated to 'X'. // If option markInitCodonAsM is true, initial codon at beginning will be represented as 'M'. func (seq *Seq) Translate(transl_table int, frame int, trim bool, clean bool, allowUnknownCodon bool, markInitCodonAsM bool) (*Seq, error) { if !(seq.Alphabet.String() == "DNA" || seq.Alphabet.String() == "DNAredundant" || seq.Alphabet.String() == "RNA" || seq.Alphabet.String() == "RNAredundant") { return nil, fmt.Errorf("seq: only DNA/RNA sequence can all method Translate, the alphabet is %s", seq.Alphabet.String()) } var codonTable *CodonTable var ok bool if codonTable, ok = CodonTables[transl_table]; !ok { return nil, fmt.Errorf("seq: invalid codon table: %d", transl_table) } if !(frame == 1 || frame == 2 || frame == 3 || frame == -1 || frame == -2 || frame == -3) { return nil, fmt.Errorf("seq: invalid frame: %d. available: 1, 2, 3, -1, -2, -3", frame) } aa, err := codonTable.Translate(seq.Seq, frame, trim, clean, allowUnknownCodon, markInitCodonAsM) if err != nil { return nil, err } t, err := NewSeqWithoutValidation(Protein, aa) if err != nil { return nil, err } return t, nil } // ParseQual parses sequence quality, asciiBase = 33 for Phred+33. func (seq *Seq) ParseQual(asciiBase int) { if len(seq.Qual) == 0 { return } if seq.QualValue != nil { seq.QualValue = seq.QualValue[:0] for _, q := range seq.Qual { seq.QualValue = append(seq.QualValue, int(q)-asciiBase) } return } qv := make([]int, len(seq.Qual)) for i, q := range seq.Qual { qv[i] = int(q) - asciiBase } seq.QualValue = qv } // AvgQual calculates average quality value. func (seq *Seq) AvgQual(asciiBase int) float64 { if len(seq.Qual) > 0 { seq.ParseQual(asciiBase) } if len(seq.QualValue) == 0 { return 0.0 } var sum float64 for _, q := range seq.QualValue { sum += QUAL_MAP[q] } return -10 * math.Log10(sum/float64(len(seq.QualValue))) } // Slider returns a function for sliding the sequence. // Circular is for circular genome, and it overides greedy. // If not circular and greedy is true, last fragment shorter than window will be returned. func (seq *Seq) Slider(window int, step int, circular bool, greedy bool) func() (*Seq, bool) { alphabet := seq.Alphabet originalLen := len(seq.Seq) sequence := seq.Seq qual := seq.Qual fastq := len(qual) > 0 l := len(sequence) end := l - 1 if end < 0 { end = 0 } var i, e int var s, q []byte i = 0 return func() (*Seq, bool) { if i > end { return nil, false } e = i + window if e > originalLen { if circular { e = e - originalLen s = sequence[i:] s = append(s, sequence[0:e]...) if fastq { q = qual[i:] q = append(q, qual[0:e]...) } } else if greedy { s = sequence[i:] if fastq { q = qual[i:] } e = l } else { // end return nil, false } } else { s = sequence[i : i+window] if fastq { q = qual[i : i+window] } } i += step var newseq *Seq if fastq { newseq, _ = NewSeqWithQualWithoutValidation(alphabet, s, q) return newseq, true } newseq, _ = NewSeqWithoutValidation(alphabet, s) return newseq, true } } bio-0.13.3/seq/seq_test.go000077500000000000000000000066071457355065000153430ustar00rootroot00000000000000package seq import ( "fmt" "testing" ) func TestValidateSequence(t *testing.T) { dna := []byte("acgt") dna2 := []byte("ACGTRYMKSWHBVDN") rna := []byte("acgu") fake := []byte("acgturymkswhbvdnz") ok := DNA.IsValid(dna) == nil && DNAredundant.IsValid(dna2) == nil && RNA.IsValid(rna) == nil && RNA.IsValid(fake) != nil if !ok { t.Error("validate sequence failed.") return } } func TestRevCom(t *testing.T) { dna, _ := NewSeq(DNA, []byte("acgtccn-")) if string(dna.RevCom().Seq) != "-nggacgt" { t.Error("revcom sequence failed.") return } rna, _ := NewSeq(RNA, []byte("auguccn-")) if string(rna.RevCom().Seq) != "-nggacau" { t.Error("revcom sequence failed.") return } } func TestBaseContent(t *testing.T) { dna, _ := NewSeq(DNA, []byte("acgtACGT")) content := dna.BaseContent("gc") wanted := 0.5 if content != wanted { t.Error(fmt.Printf("compution of base content failed: %f != %f", content, wanted)) return } } func TestSubSeq(t *testing.T) { s, _ := NewSeqWithoutValidation(DNA, []byte("ACGTNacgtn")) ok := string(s.SubSeq(1, 1).Seq) == "A" && string(s.SubSeq(2, 4).Seq) == "CGT" && string(s.SubSeq(-4, -2).Seq) == "cgt" && string(s.SubSeq(-4, -1).Seq) == "cgtn" && string(s.SubSeq(-1, -1).Seq) == "n" && string(s.SubSeq(2, -2).Seq) == "CGTNacgt" && string(s.SubSeq(1, -1).Seq) == "ACGTNacgtn" && string(s.SubSeq(12, 14).Seq) == "" && string(s.SubSeq(-10, -1).Seq) == "ACGTNacgtn" && string(s.SubSeq(-10, -3).Seq) == "ACGTNacg" && string(s.SubSeq(1, 10).Seq) == "ACGTNacgtn" && string(s.SubSeq(3, 12).Seq) == "GTNacgtn" && string(s.SubSeq(3, 100).Seq) == "GTNacgtn" if !ok { t.Error(fmt.Printf("subseq error")) } ok = string(s.SubSeq(-4, 2).Seq) == "" && string(s.SubSeq(-3, -4).Seq) == "" if !ok { t.Error(fmt.Printf("subseq error")) } s, _ = NewSeqWithoutValidation(DNA, []byte("")) ok = string(s.SubSeq(1, 4).Seq) == "" && string(s.SubSeq(2, 4).Seq) == "" && string(s.SubSeq(1, -1).Seq) == "" && string(s.SubSeq(-4, -1).Seq) == "" if !ok { t.Error(fmt.Printf("subseq error")) } } func TestSubSeqInplace(t *testing.T) { s, _ := NewSeqWithoutValidation(DNA, []byte("ACGTNacgtn")) ok := string(s.Clone().SubSeqInplace(1, 1).Seq) == "A" && string(s.Clone().SubSeqInplace(2, 4).Seq) == "CGT" && string(s.Clone().SubSeqInplace(-4, -2).Seq) == "cgt" && string(s.Clone().SubSeqInplace(-4, -1).Seq) == "cgtn" && string(s.Clone().SubSeqInplace(-1, -1).Seq) == "n" && string(s.Clone().SubSeqInplace(2, -2).Seq) == "CGTNacgt" && string(s.Clone().SubSeqInplace(1, -1).Seq) == "ACGTNacgtn" && string(s.Clone().SubSeqInplace(-10, -1).Seq) == "ACGTNacgtn" && string(s.Clone().SubSeqInplace(-10, -3).Seq) == "ACGTNacg" && string(s.Clone().SubSeqInplace(1, 10).Seq) == "ACGTNacgtn" && string(s.Clone().SubSeqInplace(3, 10).Seq) == "GTNacgtn" && string(s.Clone().SubSeqInplace(3, 100).Seq) == "GTNacgtn" if !ok { t.Error(fmt.Printf("SubSeqInplace error")) } ok = string(s.Clone().SubSeqInplace(-4, 2).Seq) == "" && string(s.Clone().SubSeqInplace(-3, -4).Seq) == "" if !ok { t.Error(fmt.Printf("SubSeqInplace error")) } s, _ = NewSeqWithoutValidation(DNA, []byte("")) ok = string(s.Clone().SubSeqInplace(1, 4).Seq) == "" && string(s.Clone().SubSeqInplace(2, 4).Seq) == "" && string(s.Clone().SubSeqInplace(1, -1).Seq) == "" && string(s.Clone().SubSeqInplace(-4, -1).Seq) == "" if !ok { t.Error(fmt.Printf("subseq error")) } } bio-0.13.3/seqio/000077500000000000000000000000001457355065000135015ustar00rootroot00000000000000bio-0.13.3/seqio/fai/000077500000000000000000000000001457355065000142405ustar00rootroot00000000000000bio-0.13.3/seqio/fai/README.md000077500000000000000000000066541457355065000155350ustar00rootroot00000000000000# fai [![GoDoc](https://godoc.org/github.com/shenwei356/bio?status.svg)](https://godoc.org/github.com/shenwei356/bio/seqio/fai) Package fai implements fasta sequence file index handling, including creating , reading and random accessing. Code of fai data structure were copied and edited from [1]. But I wrote the code of creating and reading fai, and so did test code. Code of random accessing subsequences were copied from [2], but I extended them a lot. Reference: [1]. https://github.com/biogo/biogo/blob/master/io/seqio/fai/fai.go [2]. https://github.com/brentp/faidx/blob/master/faidx.go ## General Usage import "github.com/shenwei356/bio/seqio/fai" file := "seq.fa" faidx, err := fai.New(file) checkErr(err) defer func() { checkErr(faidx.Close()) }() // whole sequence seq, err := faidx.Seq("cel-mir-2") checkErr(err) // single base s, err := faidx.Base("cel-let-7", 1) checkErr(err) // subsequence. start and end are all 1-based seq, err := faidx.SubSeq("cel-mir-2", 15, 19) checkErr(err) ## Extended SubSeq For extended SubSeq, negative position is allowed. This is my custom locating strategy. Start and end are all 1-based. To better understand the locating strategy, see examples below: 1-based index 1 2 3 4 5 6 7 8 9 10 negative index 0-9-8-7-6-5-4-3-2-1 seq A C G T N a c g t n 1:1 A 2:4 C G T -4:-2 c g t -4:-1 c g t n -1:-1 n 2:-2 C G T N a c g t 1:-1 A C G T N a c g t n Examples: // last 12 bases seq, err := faidx.SubSeq("cel-mir-2", -12, -1) checkErr(err) ## Advanced Usage Function `fai.New(file string)` is a wraper to simplify the process of creating and reading FASTA index . Let's see what's happend inside: func New(file string) (*Faidx, error) { fileFai := file + ".fai" var index Index if _, err := os.Stat(fileFai); os.IsNotExist(err) { index, err = Create(file) if err != nil { return nil, err } } else { index, err = Read(fileFai) if err != nil { return nil, err } } return NewWithIndex(file, index) } By default, sequence ID is used as key in FASTA index file. Inside the package, a regular expression is used to get sequence ID from full head. The default value is `^([^\s]+)\s?`, i.e. getting first non-space characters of head. So you can just use `fai.Create(file string)` to create .fai file. If you want to use full head instead of sequence ID (first non-space characters of head), you could use `fai.CreateWithIDRegexp(file string, idRegexp string)` to create faidx. Here, the `idRegexp` should be `^(.+)$`. For convenience, you can use another function `CreateWithFullHead`. ## More Advanced Usages Note that, ***by default, whole file is mapped into shared memory***, which is OK for small files (smaller than your RAM). For very big files, you should disable that. Instead, file seeking is used. // change the global variable fai.MapWholeFile = false // then do other things ## Documentation [Documentation on godoc](https://godoc.org/github.com/shenwei356/bio/seqio/fai). bio-0.13.3/seqio/fai/doc.go000066400000000000000000000064531457355065000153440ustar00rootroot00000000000000/*Package fai implements fasta sequence file index handling, including creating , reading and random accessing. Code of fai data structure were copied and edited from [1]. But I wrote the code of creating and reading fai, and so did test code. Code of random accessing subsequences were copied from [2], but I extended them a lot. Reference: [1]. https://github.com/biogo/biogo/blob/master/io/seqio/fai/fai.go [2]. https://github.com/brentp/faidx/blob/master/faidx.go ## General Usage import "github.com/shenwei356/bio/seqio/fai" file := "seq.fa" faidx, err := fai.New(file) checkErr(err) defer func() { checkErr(faidx.Close()) }() // whole sequence seq, err := faidx.Seq("cel-mir-2") checkErr(err) // single base s, err := faidx.Base("cel-let-7", 1) checkErr(err) // subsequence. start and end are all 1-based seq, err := faidx.SubSeq("cel-mir-2", 15, 19) checkErr(err) ## Extended SubSeq For extended SubSeq, negative position is allowed. This is my custom locating strategy. Start and end are all 1-based. To better understand the locating strategy, see examples below: 1-based index 1 2 3 4 5 6 7 8 9 10 negative index 0-9-8-7-6-5-4-3-2-1 seq A C G T N a c g t n 1:1 A 2:4 C G T -4:-2 c g t -4:-1 c g t n -1:-1 n 2:-2 C G T N a c g t 1:-1 A C G T N a c g t n 1:12 A C G T N a c g t n -12:-1 A C G T N a c g t n Examples: // last 12 bases seq, err := faidx.SubSeq("cel-mir-2", -12, -1) checkErr(err) ## Advanced Usage Function `fai.New(file string)` is a wraper to simplify the process of creating and reading FASTA index . Let's see what's happened inside: func New(file string) (*Faidx, error) { fileFai := file + ".fai" var index Index if _, err := os.Stat(fileFai); os.IsNotExist(err) { index, err = Create(file) if err != nil { return nil, err } } else { index, err = Read(fileFai) if err != nil { return nil, err } } return NewWithIndex(file, index) } By default, sequence ID is used as key in FASTA index file. Inside the package, a regular expression is used to get sequence ID from full head. The default value is `^([^\s]+)\s?`, i.e. getting first non-space characters of head. So you can just use `fai.Create(file string)` to create .fai file. If you want to use full head instead of sequence ID (first non-space characters of head), you could use `fai.CreateWithIDRegexp(file string, idRegexp string)` to create faidx. Here, the `idRegexp` should be `^(.+)$`. For convenience, you can use another function `CreateWithFullHead`. ## More Advanced Usages Note that, ***by default, whole file is mapped into shared memory***, which is OK for small files (smaller than your RAM). For very big files, you should disable that. Instead, file seeking is used. // change the global variable fai.MapWholeFile = false // then do other things */ package fai bio-0.13.3/seqio/fai/fai.go000077500000000000000000000201641457355065000153340ustar00rootroot00000000000000package fai import ( "bufio" "bytes" "fmt" "os" "regexp" "strconv" "strings" ) // Record is FASTA index record type Record struct { Name string Length int Start int64 BasesPerLine int BytesPerLine int } // Index is FASTA index type Index map[string]Record // Read faidx from .fai file func Read(fileFai string) (Index, error) { fh, err := os.Open(fileFai) if err != nil { return nil, fmt.Errorf("read faidx: %s", err) } defer fh.Close() index := make(map[string]Record) scanner := bufio.NewScanner(fh) items := make([]string, 5) var line, name string var length int var start int64 var BasesPerLine, bytesPerLine int for scanner.Scan() { line = scanner.Text() if line != "" { line = dropCRStr(line) stringSplitNByByte(line, '\t', 5, &items) if len(items) != 5 { return nil, fmt.Errorf("invalid fai records: %s", line) } name = items[0] length, err = strconv.Atoi(items[1]) if err != nil { return nil, fmt.Errorf("invalid fai records: %s", line) } start, err = strconv.ParseInt(items[2], 10, 64) if err != nil { return nil, fmt.Errorf("invalid fai records: %s", line) } BasesPerLine, err = strconv.Atoi(items[3]) if err != nil { return nil, fmt.Errorf("invalid fai records: %s", line) } bytesPerLine, err = strconv.Atoi(items[4]) if err != nil { return nil, fmt.Errorf("invalid fai records: %s", line) } index[name] = Record{ Name: name, Length: length, Start: start, BasesPerLine: BasesPerLine, BytesPerLine: bytesPerLine, } } if err := scanner.Err(); err != nil { return nil, err } } return index, nil } // CreateWithFullHead uses full head instead of just sequence ID func CreateWithFullHead(fileSeq, fileFai string) (Index, error) { return CreateWithIDRegexp(fileSeq, fileFai, `^(.+)$`) } // CreateWithIDRegexp uses custom regular expression to get sequence ID func CreateWithIDRegexp(fileSeq, fileFai string, idRegexp string) (Index, error) { if idRegexp != defaultIDRegexp { if !reCheckIDregexpStr.MatchString(idRegexp) { return nil, fmt.Errorf(`regular expression must contain "(" and ")" to capture matched ID. default: %s`, `^([^\s]+)\s?`) } var err error IDRegexp, err = regexp.Compile(idRegexp) if err != nil { return nil, fmt.Errorf("fail to Compile idRegexp: %s", err) } isUsingDefaultIDRegexp = false } return Create(fileSeq, fileFai) } // Create .fai for file func Create(fileSeq, fileFai string) (Index, error) { fh, err := os.Open(fileSeq) if err != nil { return nil, fmt.Errorf("fail to open seq file: %s", err) } defer fh.Close() outfh, err := os.Create(fileFai) if err != nil { return nil, fmt.Errorf("fail to write fai file: %s", err) } defer outfh.Close() index := make(map[string]Record) reader := bufio.NewReader(fh) checkSeqType := true seqLen := 0 var hasSeq bool var lastName, thisName []byte var id string var lastStart, thisStart int64 var lineWidths, seqWidths []int var lastLineWidth, lineWidth, seqWidth int var chances int var line, lineDropCR []byte var seenSeqs bool for { line, err = reader.ReadBytes('\n') if err != nil { // end of file id = string(parseHeadID(lastName)) if strings.Contains(id, "\t") { id = reTabs.ReplaceAllString(id, " ") } // check lineWidths lastLineWidth, chances = -2, 2 seenSeqs = false for i := len(lineWidths) - 1; i >= 0; i-- { if !seenSeqs && seqWidths[i] == 0 { // skip empty lines in the end continue } seenSeqs = true if lastLineWidth == -2 { lastLineWidth = lineWidths[i] continue } if lineWidths[i] != lastLineWidth { chances-- if chances == 0 || lineWidths[i] < lastLineWidth { return nil, fmt.Errorf("different line length in sequence: %s. Please format the file with 'seqkit seq'", id) } } lastLineWidth = lineWidths[i] } // lineWidth = 0 if len(lineWidths) > 0 { lineWidth = lineWidths[0] } // seqWidth = 0 if len(seqWidths) > 0 { seqWidth = seqWidths[0] } if len(line) > 0 && line[len(line)-1] != '\n' { fmt.Fprintln(os.Stderr, `[WARNING]: newline character ('\n') not detected at end of file, truncated file?`) } seqLen += len(line) if _, ok := index[id]; ok { // return index, fmt.Errorf(`ignoring duplicate sequence "%s" at byte offset %d`, id, lastStart) os.Stderr.WriteString(fmt.Sprintf("[fai warning] ignoring duplicate sequence \"%s\" at byte offset %d\n", id, lastStart)) } else { fmt.Fprintf(outfh, "%s\t%d\t%d\t%d\t%d\n", id, seqLen, lastStart, seqWidth, lineWidth) index[id] = Record{ Name: id, Length: seqLen, Start: lastStart, BasesPerLine: seqWidth, BytesPerLine: lineWidth, } } seqLen = 0 break } if checkSeqType { if line[0] == '@' { os.Remove(fileFai) return nil, fmt.Errorf("FASTQ format not supported") } checkSeqType = false } if line[0] == '>' { hasSeq = true thisName = dropCR(line[1 : len(line)-1]) if lastName != nil { // not the first record id = string(parseHeadID(lastName)) if strings.Contains(id, "\t") { id = reTabs.ReplaceAllString(id, " ") } // check lineWidths lastLineWidth, chances = -1, 2 seenSeqs = false for i := len(lineWidths) - 1; i >= 0; i-- { if !seenSeqs && seqWidths[i] == 0 { // skip empty lines in the end continue } seenSeqs = true if lastLineWidth == -1 { lastLineWidth = lineWidths[i] continue } if lineWidths[i] != lastLineWidth { chances-- if chances == 0 || lineWidths[i] < lastLineWidth { return nil, fmt.Errorf("different line length in sequence: %s. Please format the file with 'seqkit seq'", id) } } lastLineWidth = lineWidths[i] } // lineWidth = 0 if len(lineWidths) > 0 { lineWidth = lineWidths[0] } // seqWidth = 0 if len(seqWidths) > 0 { seqWidth = seqWidths[0] } if _, ok := index[id]; ok { // return index, fmt.Errorf(`ignoring duplicate sequence "%s" at byte offset %d`, id, lastStart) os.Stderr.WriteString(fmt.Sprintf("[fai warning] ignoring duplicate sequence \"%s\" at byte offset %d\n", id, lastStart)) } else { fmt.Fprintf(outfh, "%s\t%d\t%d\t%d\t%d\n", id, seqLen, lastStart, seqWidth, lineWidth) index[id] = Record{ Name: id, Length: seqLen, Start: lastStart, BasesPerLine: seqWidth, BytesPerLine: lineWidth, } } seqLen = 0 } lineWidths = []int{} seqWidths = []int{} thisStart += int64(len(line)) lastStart = thisStart lastName = thisName } else if hasSeq { lineDropCR = dropCR(line[0 : len(line)-1]) seqLen += len(lineDropCR) thisStart += int64(len(line)) lineWidths = append(lineWidths, len(line)) seqWidths = append(seqWidths, len(lineDropCR)) } else { return nil, fmt.Errorf("invalid fasta file: %s", fileSeq) } } return index, nil } // ------------------------------------------------------------ var reCheckIDregexpStr = regexp.MustCompile(`\(.+\)`) var defaultIDRegexp = `^(\S+)\s?` var reTabs = regexp.MustCompile(`\t+`) // IDRegexp is regexp for parsing record id var IDRegexp = regexp.MustCompile(defaultIDRegexp) var isUsingDefaultIDRegexp = true func parseHeadID(head []byte) []byte { if isUsingDefaultIDRegexp { if i := bytes.IndexByte(head, ' '); i > 0 { return head[0:i] } if i := bytes.IndexByte(head, '\t'); i > 0 { return head[0:i] } return head } found := IDRegexp.FindSubmatch(head) if found == nil { // not match return head } return found[1] } func dropCR(data []byte) []byte { if len(data) > 0 && data[len(data)-1] == '\r' { return data[0 : len(data)-1] } return data } func dropCRStr(data string) string { if len(data) > 0 && data[len(data)-1] == '\r' { return data[0 : len(data)-1] } return data } func stringSplitNByByte(s string, sep byte, n int, a *[]string) { if a == nil { tmp := make([]string, n) a = &tmp } n-- i := 0 for i < n { m := strings.IndexByte(s, sep) if m < 0 { break } (*a)[i] = s[:m] s = s[m+1:] i++ } (*a)[i] = s (*a) = (*a)[:i+1] } bio-0.13.3/seqio/fai/faidx.go000077500000000000000000000114621457355065000156710ustar00rootroot00000000000000package fai import ( "fmt" "io" "os" "github.com/edsrzf/mmap-go" ) // MapWholeFile is a globle flag to decides whether map whole file var MapWholeFile = true var pageSize = int64(os.Getpagesize()) var pageSizeInt = os.Getpagesize() // Faidx is type Faidx struct { file string reader *os.File Index Index mmap mmap.MMap } // New try to get Faidx from fasta file func New(fileSeq string) (*Faidx, error) { fileFai := fileSeq + ".fai" return NewWithCustomExt(fileSeq, fileFai) } // NewWithCustomExt try to get Faidx from fasta file, and .fai is specified func NewWithCustomExt(fileSeq, fileFai string) (*Faidx, error) { var index Index if _, err := os.Stat(fileFai); os.IsNotExist(err) { index, err = Create(fileSeq, fileFai) if err != nil { return nil, err } } else { index, err = Read(fileFai) if err != nil { return nil, err } } return NewWithIndex(fileSeq, index) } // NewWithIndex return faidx from file and readed Index. // Useful for using custom IDRegexp func NewWithIndex(file string, index Index) (*Faidx, error) { reader, err := os.Open(file) if err != nil { return nil, fmt.Errorf("fail to open seq file: %s", err) } var m mmap.MMap if MapWholeFile { m, err = mmap.Map(reader, mmap.RDONLY, 0) if err != nil { return nil, fmt.Errorf("mmap err: %s", err) } } return &Faidx{file, reader, index, m}, nil } // p is 0-based func position(r Record, p int) int64 { if p < 0 { p = 0 } if p > r.Length { p = r.Length } return r.Start + int64(p/r.BasesPerLine*r.BytesPerLine+p%r.BasesPerLine) } // ErrSeqNotExists means that sequence not exists var ErrSeqNotExists = fmt.Errorf("sequence not exists") // SubSeq returns subsequence of chr from start to end. start and end are 1-based. func (f *Faidx) SubSeq(chr string, start int, end int) ([]byte, error) { sequence, err := f.SubSeqNotCleaned(chr, start, end) if err != nil { return nil, err } return cleanSeq(sequence), nil } // SubSeqNotCleaned returns subsequence of chr from start to end. // start and end are 1-based. // "\r" and "\n" are not cleaned. func (f *Faidx) SubSeqNotCleaned(chr string, start int, end int) ([]byte, error) { index, ok := f.Index[chr] if !ok { return nil, ErrSeqNotExists } if index.Length == 0 { return []byte{}, nil } start, end, ok = SubLocation(index.Length, start, end) if !ok { return []byte{}, nil } pstart := position(index, start-1) pend := position(index, end) if MapWholeFile { if pend > int64(len(f.mmap)) { // for truncated file pend = int64(len(f.mmap)) } return f.mmap[pstart:pend], nil } data := make([]byte, pend-pstart) n, err := f.reader.ReadAt(data, pstart) if err != nil { if err == io.EOF { // for truncated file return data[0:n], nil } return nil, err } return data, nil } // Seq returns sequence of chr func (f *Faidx) Seq(chr string) ([]byte, error) { sequence, err := f.SeqNotCleaned(chr) if err != nil { return nil, err } return cleanSeq(sequence), nil } // SeqNotCleaned returns sequences without cleaning "\r", and "\n" func (f *Faidx) SeqNotCleaned(chr string) ([]byte, error) { sequence, err := f.SubSeqNotCleaned(chr, 1, -1) if err != nil { return nil, err } return sequence, nil } // Base returns base in position pos. pos is 1 based func (f *Faidx) Base(chr string, pos int) (byte, error) { sequence, err := f.SubSeqNotCleaned(chr, pos, pos) if err != nil { return ' ', err } return sequence[0], nil } // Close the readers func (f *Faidx) Close() error { f.reader.Close() if f.mmap == nil { return nil } return f.mmap.Unmap() } /*SubLocation is my sublocation strategy, start, end and returned start and end are all 1-based 1-based index 1 2 3 4 5 6 7 8 9 10 negative index 0-9-8-7-6-5-4-3-2-1 seq A C G T N a c g t n 1:1 A 2:4 C G T -4:-2 c g t -4:-1 c g t n -1:-1 n 2:-2 C G T N a c g t 1:-1 A C G T N a c g t n 1:12 A C G T N a c g t n -12:-1 A C G T N a c g t n */ func SubLocation(length, start, end int) (int, int, bool) { if length == 0 { return 0, 0, false } if start < 1 { if start == 0 { start = 1 } else if start < 0 { if end < 0 && start > end { return start, end, false } if -start > length { return start, end, false } start = length + start + 1 } } if start > length { return start, end, false } if end > length { end = length } if end < 1 { if end == 0 { end = -1 } end = length + end + 1 } if start-1 > end { return start - 1, end, false } return start, end, true } func cleanSeq(slice []byte) []byte { newSlice := make([]byte, 0, len(slice)) for _, b := range slice { switch b { case '\r', '\n': default: newSlice = append(newSlice, b) } } return newSlice } bio-0.13.3/seqio/fai/faidx_test.go000077500000000000000000000064071457355065000167330ustar00rootroot00000000000000package fai import ( "bytes" "testing" "github.com/shenwei356/bio/seqio/fastx" ) func TestFastaReader(t *testing.T) { file := "seq.fa" idx, err := New(file) if err != nil { t.Errorf("failed to create faidx for %s: %s", file, err) return } // all sequences seqs, err := fastx.GetSeqs(file, nil, 4, 10, fastx.DefaultIDRegexp) if err != nil { t.Errorf("failed to read seqs: %v", err) } for _, rec := range seqs { seq, err := idx.Seq(string(rec.ID)) checkErr(t, err) if !bytes.Equal(seq, rec.Seq.Seq) { t.Errorf("unmatched sequences %s: %s", rec.ID, seq) } } // chr := "cel-let-7" s, err := idx.Base(chr, 1) checkErr(t, err) if s != 'U' { t.Errorf("unmatched sequences %s: %s", chr, []byte{s}) } chr = "blank" seq, err := idx.Seq(chr) checkErr(t, err) if string(seq) != "" { t.Errorf("unmatched sequences %s: %s", chr, seq) } chr = "cel-mir-2" seq, err = idx.Seq(chr) checkErr(t, err) if string(seq) != "UAAACAGUAUACAGAAAGCCAUCAAAGC" { t.Errorf("unmatched sequences %s: %s", chr, seq) } start, end := 15, 19 seq, err = idx.SubSeq(chr, start, end) checkErr(t, err) if string(seq) != "AAAGC" { t.Errorf("unmatched sequences %s from %d to %d: %s", chr, start, end, seq) } start, end = -3, -1 seq, err = idx.SubSeq(chr, start, end) checkErr(t, err) if string(seq) != "AGC" { t.Errorf("unmatched sequences %s from %d to %d: %s", chr, start, end, seq) } start, end = 1, -1 seq, err = idx.SubSeq(chr, start, end) checkErr(t, err) if string(seq) != "UAAACAGUAUACAGAAAGCCAUCAAAGC" { t.Errorf("unmatched sequences %s from %d to %d: %s", chr, start, end, seq) } start, end = 2, -2 seq, err = idx.SubSeq(chr, start, end) checkErr(t, err) if string(seq) != "AAACAGUAUACAGAAAGCCAUCAAAG" { t.Errorf("unmatched sequences %s from %d to %d: %s", chr, start, end, seq) } start, end = 20, -2 seq, err = idx.SubSeq(chr, start, end) checkErr(t, err) if string(seq) != "CAUCAAAG" { t.Errorf("unmatched sequences %s from %d to %d: %s", chr, start, end, seq) } start, end = 50, -2 seq, err = idx.SubSeq(chr, start, end) checkErr(t, err) if string(seq) != "" { t.Errorf("unmatched sequences %s from %d to %d: %s", chr, start, end, seq) } chr = "seq" seq, err = idx.Seq(chr) checkErr(t, err) if string(seq) != "ACTGACTG" { t.Errorf("unmatched sequences %s: %s", chr, seq) } err = idx.Close() if err != nil { t.Errorf("fail to close faidx: %v", err) } } func TestFastaReaderNotMapWholeFile(t *testing.T) { MapWholeFile = false TestFastaReader(t) MapWholeFile = true } func TestFastaReader2NotMapWholeFile(t *testing.T) { MapWholeFile = false TestFastaReader2(t) MapWholeFile = true } func checkErr(t *testing.T, err error) { if err != nil { t.Error(err) } } func TestFastaReader2(t *testing.T) { file := "seq2.fa" idx, err := New(file) if err != nil { t.Errorf("failed to create faidx for %s: %s", file, err) return } seqs, err := fastx.GetSeqs(file, nil, 4, 10, fastx.DefaultIDRegexp) if err != nil { t.Errorf("failed to read seqs: %v", err) } for _, rec := range seqs { seq, err := idx.Seq(string(rec.ID)) checkErr(t, err) if !bytes.Equal(seq, rec.Seq.Seq) { t.Errorf("unmatched sequences %s: %s", rec.ID, seq) } } err = idx.Close() if err != nil { t.Errorf("fail to close faidx: %v", err) } } bio-0.13.3/seqio/fai/seq.fa000066400000000000000000000014031457355065000153360ustar00rootroot00000000000000>cel-let-7 MI0000001 Caenorhabditis elegans let-7 stem-loop UACACUGUGGAUCCGGUGAGGUAGUAGGUUCC GUAUAGUUUGGAAUAUUACCACCGGUGAACCC UAUGCAAUUUUCUACCUUACCGGAGACAGACC ACUCUUCGA >cel-lin-4 MI0000002 Caenorhabditis elegans lin-4 stem-loop AUGCUUCCGGCCUGUUCCCUGAGACCUCAA GUGUGAGUGUACUAUUGAUGCUUCACACCU GGGCUCUCCGGGUACCAGGACGGUUUGAGC AGAU >blank >cel-mir-1 MI0000003 Caenorhabditis elegans miR-1 stem-loop AAAGUGACCGUACCGAGCUGCAUACUUCCUAAA UACAUGCCCAUACUAUAUCAUAAAUGGAUAAAA UGGAAUGUAAAGAAGUAUGUAGAACGGGGUAAA GGUAGU >cel-mir-2 MI0000004 Caenorhabditis elegans miR-2 stem-loop UAAACAGUAUAC AGAAAGCCAUCA AAGC >cel-mir-34 MI0000005 Caenorhabditis elegans miR-34 stem-loop CGGACAAUGCUCGAGAGGCAGUGUGGUUAG CUGGUUGCAUAUUUCCUUGACAACGGCUAC CUUCACUGCCACCCCGAACAUGUCGUCCAU CUUUGAA >seq ACTG ACTGbio-0.13.3/seqio/fai/seq.fa.fai000066400000000000000000000002251457355065000160750ustar00rootroot00000000000000cel-let-7 105 60 32 33 cel-lin-4 94 229 30 31 blank 0 334 30 31 cel-mir-1 105 394 33 34 cel-mir-2 28 563 12 13 cel-mir-34 97 656 30 31 seq 8 762 4 5 bio-0.13.3/seqio/fai/seq2.fa000066400000000000000000000000611457355065000154170ustar00rootroot00000000000000>s1 acgt ACGT ac >s2 >s3 >s4 acccc accc >s5 a bio-0.13.3/seqio/fai/seq2.fa.fai000066400000000000000000000000741457355065000161610ustar00rootroot00000000000000s1 10 4 4 5 s2 0 22 0 1 s3 0 27 0 1 s4 9 31 5 6 s5 1 47 1 2 bio-0.13.3/seqio/fastx/000077500000000000000000000000001457355065000146265ustar00rootroot00000000000000bio-0.13.3/seqio/fastx/README.md000077500000000000000000000044201457355065000161100ustar00rootroot00000000000000# fastx [![Go Reference](https://pkg.go.dev/badge/github.com/shenwei356/bio/seqio/fastx.svg)](https://pkg.go.dev/github.com/shenwei356/bio/seqio/fastx) This package seamlessly parses both FASTA and FASTQ formats. ## Examples ### Common operation package main import ( "fmt" "io" "os" // "github.com/shenwei356/bio/seq" "github.com/shenwei356/bio/seqio/fastx" "github.com/shenwei356/xopen" ) func main() { // use buffered out stream for output outfh, err := xopen.Wopen("-") // "-" for STDOUT checkError(err) defer outfh.Close() // disable sequence validation could reduce time when reading large sequences // seq.ValidateSeq = false reader, err := fastx.NewDefaultReader("-") checkError(err) var record *fastx.Record for { record, err = reader.Read() if err != nil { if err == io.EOF { break } checkError(err) break } // fmt is slow for output, because it's not buffered // fmt.Printf("%s", record.Format(0)) record.FormatToWriter(outfh, 0) } reader.Close() // Please remember to call this !!! } func checkError(err error) { if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } ***Note that***, similar with `bytes.Buffer.Bytes()` method, the current record will change after your another call of this method. You may use `record.Clone()` to make a copy. ### Asynchronously parsing `ChunkChan` asynchronously reads FASTA/Q records, and returns a channel of Record Chunk, from which you can easily access the records. `bufferSize` is the number of buffered chunks, and `chunkSize` is the size of records in a chunk. reader, err := fastx.NewDefaultReader(file) checkError(err) for chunk := range reader.ChunkChan(bufferSize, chunkSize) { checkError(chunk.Err) for _, record := range chunk.Data { fmt.Print(record) } } ***Note that***, these's no need to clone the record by `record.Clone()` here. ### Custom alphabet and identifier regular expression import ( "github.com/shenwei356/bio/seq" "github.com/shenwei356/bio/seqio/fastx" ) reader, err := fastx.NewReader(seq.DNA, file, "^([^\s]+)\s?") bio-0.13.3/seqio/fastx/blank.fx000066400000000000000000000000021457355065000162440ustar00rootroot00000000000000 bio-0.13.3/seqio/fastx/blank1.fx000066400000000000000000000000011457355065000163240ustar00rootroot00000000000000 bio-0.13.3/seqio/fastx/doc.go000066400000000000000000000041661457355065000157310ustar00rootroot00000000000000/*Package fastx seamlessly parses FASTA and FASTQ format file This package seamlessly parses both FASTA and FASTQ formats. ## Examples ### Common operation package main import ( "fmt" "io" "os" // "github.com/shenwei356/bio/seq" "github.com/shenwei356/bio/seqio/fastx" "github.com/shenwei356/xopen" ) func main() { // use buffered out stream for output outfh, err := xopen.Wopen("-") // "-" for STDOUT checkError(err) defer outfh.Close() // disable sequence validation could reduce time when reading large sequences // seq.ValidateSeq = false reader, err := fastx.NewDefaultReader("-") checkError(err) for { record, err := reader.Read() if err != nil { if err == io.EOF { break } checkError(err) break } // fmt is slow for output, because it's not buffered // fmt.Printf("%s", record.Format(0)) record.FormatToWriter(outfh, 0) } } func checkError(err error) { if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } ***Note that***, similar with `bytes.Buffer.Bytes()` method, the current record will change after your another call of this method. You may use `record.Clone()` to make a copy. ### Asynchronously parsing `ChunkChan` asynchronously reads FASTA/Q records, and returns a channel of Record Chunk, from which you can easily access the records. `bufferSize` is the number of buffered chunks, and `chunkSize` is the size of records in a chunk. reader, err := fastx.NewDefaultReader(file) checkError(err) for chunk := range reader.ChunkChan(bufferSize, chunkSize) { checkError(chunk.Err) for _, record := range chunk.Data { fmt.Print(record) } } ***Note that***, these's no need to clone the record by `record.Clone()` here. ### Custom alphabet and identifier regular expression import ( "github.com/shenwei356/bio/seq" "github.com/shenwei356/bio/seqio/fastx" ) reader, err := fastx.NewReader(seq.DNA, file, "^([^\s]+)\s?") */ package fastx bio-0.13.3/seqio/fastx/empty.fx000066400000000000000000000000001457355065000163110ustar00rootroot00000000000000bio-0.13.3/seqio/fastx/reader.go000077500000000000000000000365521457355065000164350ustar00rootroot00000000000000package fastx import ( "bytes" "errors" "fmt" "io" "regexp" "sync" "github.com/shenwei356/bio/seq" "github.com/shenwei356/xopen" ) // ErrNotFASTXFormat means that the file is not FASTA/Q var ErrNotFASTXFormat = errors.New("fastx: invalid FASTA/Q format") // ErrBadFASTQFormat means bad fastq format var ErrBadFASTQFormat = errors.New("fastx: bad fastq format") // ErrUnequalSeqAndQual means unequal sequence and quality var ErrUnequalSeqAndQual = errors.New("fastx: unequal sequence and quality") // ErrNoContent means nothing in the file or stream var ErrNoContent = errors.New("fastx: no content found") var bufSize = 65536 var poolReader = &sync.Pool{New: func() interface{} { t := &Reader{} t.buf = make([]byte, bufSize) t.buffer = bytes.NewBuffer(make([]byte, 0, defaultBytesBufferSize)) t.seqBuffer = bytes.NewBuffer(make([]byte, 0, defaultBytesBufferSize)) t.qualBuffer = bytes.NewBuffer(make([]byte, 0, defaultBytesBufferSize)) t.record = &Record{ ID: nil, Name: nil, Desc: nil, Seq: &seq.Seq{}, // can't be nil } return t }} // Reader seamlessly parse both FASTA and FASTQ formats type Reader struct { fh *xopen.Reader // file handle, xopen is such a wonderful package buf []byte // for store readed data from fh r int buffer *bytes.Buffer // buffer of a record needMoreCheckOfBuf bool lastByte byte checkSeqType bool lastPart bool finished bool firstseq bool // for guess alphabet by the first seq delim byte IsFastq bool // if the file is fastq format t *seq.Alphabet // alphabet IDRegexp *regexp.Regexp // regexp for parsing record id head, seq, qual []byte seqBuffer *bytes.Buffer qualBuffer *bytes.Buffer record *Record // only for compatibility of empty files Err error } func (fastxReader *Reader) Reset() { n := bufSize - len(fastxReader.buf) for i := 0; i < n; i++ { fastxReader.buf = append(fastxReader.buf, 0) } fastxReader.r = 0 fastxReader.buffer.Reset() fastxReader.needMoreCheckOfBuf = false fastxReader.lastByte = 0 fastxReader.checkSeqType = true fastxReader.lastPart = false fastxReader.finished = false fastxReader.firstseq = true fastxReader.delim = 0 fastxReader.IsFastq = false fastxReader.seqBuffer.Reset() fastxReader.qualBuffer.Reset() fastxReader.head = nil fastxReader.seq = nil fastxReader.qual = nil fastxReader.Err = nil } // regexp for checking idRegexp string. // The regular expression must contain "(" and ")" to capture matched ID var reCheckIDregexpStr = regexp.MustCompile(`\(.+\)`) // DefaultIDRegexp is the default ID parsing regular expression var DefaultIDRegexp = `^(\S+)\s?` var isUsingDefaultIDRegexp bool // NewDefaultReader automaticlly recognizes sequence type and parses id with default manner func NewDefaultReader(file string) (*Reader, error) { return NewReader(nil, file, "") } var defaultBytesBufferSize = 10 << 20 // NewReader is constructor of FASTX Reader. // // Parameters: // // t sequence alphabet // if nil is given, it will guess alphabet by the first record // file file name, "-" for stdin // idRegexp id parsing regular expression string, must contains "(" and ")" to capture matched ID // "" for default value: `^([^\s]+)\s?` // if record head does not match the idRegxp, whole name will be the id // // Please call reader.Close() afer using the records!!! func NewReader(t *seq.Alphabet, file string, idRegexp string) (*Reader, error) { var r *regexp.Regexp if idRegexp == "" { r = regexp.MustCompile(DefaultIDRegexp) isUsingDefaultIDRegexp = true } else { if !reCheckIDregexpStr.MatchString(idRegexp) { return nil, fmt.Errorf(`fastx: regular expression must contain "(" and ")" to capture matched ID. default: %s`, DefaultIDRegexp) } var err error r, err = regexp.Compile(idRegexp) if err != nil { return nil, fmt.Errorf("fastx: fail to compile regexp: %s", idRegexp) } if idRegexp == DefaultIDRegexp { isUsingDefaultIDRegexp = true } } fh, err := xopen.Ropen(file) if err != nil { if err == xopen.ErrNoContent { fastxReader := poolReader.Get().(*Reader) fastxReader.Reset() fastxReader.Err = io.EOF // so the first call of Read will return an io.EOF error. return fastxReader, nil } return nil, fmt.Errorf("fastx: %s", err) } fastxReader := poolReader.Get().(*Reader) fastxReader.Reset() fastxReader.fh = fh fastxReader.t = t fastxReader.IDRegexp = r return fastxReader, nil } // NewReaderFromIO is constructor of FASTX Reader. // // Parameters: // // t sequence alphabet // if nil is given, it will guess alphabet by the first record // file an io.Reader // idRegexp id parsing regular expression string, must contains "(" and ")" to capture matched ID // "" for default value: `^([^\s]+)\s?` // if record head does not match the idRegxp, whole name will be the id // // Please call reader.Close() afer using the records!!! func NewReaderFromIO(t *seq.Alphabet, ioReader io.Reader, idRegexp string) (*Reader, error) { var r *regexp.Regexp if idRegexp == "" { r = regexp.MustCompile(DefaultIDRegexp) isUsingDefaultIDRegexp = true } else { if !reCheckIDregexpStr.MatchString(idRegexp) { return nil, fmt.Errorf(`fastx: regular expression must contain "(" and ")" to capture matched ID. default: %s`, DefaultIDRegexp) } var err error r, err = regexp.Compile(idRegexp) if err != nil { return nil, fmt.Errorf("fastx: fail to compile regexp: %s", idRegexp) } if idRegexp == DefaultIDRegexp { isUsingDefaultIDRegexp = true } } fh, err := xopen.Buf(ioReader) if err != nil { panic(err) } fastxReader := poolReader.Get().(*Reader) fastxReader.Reset() fastxReader.fh = fh fastxReader.t = t fastxReader.IDRegexp = r return fastxReader, nil } // Close cleans up everything, the most important thing is recyling the reader. // Please do remember to calls this method!!! func (fastxReader *Reader) Close() { poolReader.Put(fastxReader) } // close closes the file handler func (fastxReader *Reader) close() { if fastxReader.fh != nil { fastxReader.fh.Close() } } // Read reads and return one FASTA/Q record. // Note that, similar to bytes.Buffer.Bytes() method, // the current record will change after your another call of this method. // So, you could use record.Clone() to make a copy. func (fastxReader *Reader) Read() (*Record, error) { if fastxReader.lastPart && fastxReader.finished { return nil, io.EOF } if fastxReader.Err != nil { // only for empty file err := fastxReader.Err return nil, err } var n int var err error var p []byte for { if !fastxReader.needMoreCheckOfBuf && !fastxReader.lastPart { n, err = fastxReader.fh.Read(fastxReader.buf) if err != nil { if err == io.EOF { fastxReader.lastPart = true } else { return nil, err } } else if n == 0 { fastxReader.close() return nil, io.ErrUnexpectedEOF } // last part of file OR just because reader not fulfill the buf, // like reading from stdin if n < len(fastxReader.buf) { // fastxReader.lastPart = true fastxReader.buf = fastxReader.buf[0:n] // very important! } fastxReader.r = 0 /// TO CHECK } // check seq type via the first non-empty charator, run for onece if fastxReader.checkSeqType { pn := 0 FORCHECK: for i := range fastxReader.buf { switch fastxReader.buf[i] { case '>': fastxReader.checkSeqType = false fastxReader.IsFastq = false fastxReader.delim = '>' fastxReader.r = i + 1 break FORCHECK case '@': fastxReader.IsFastq = true fastxReader.delim = '@' fastxReader.r = i + 1 break FORCHECK case '\n': // allow some lines pn++ if pn > 100 { if i > 10240 { // ErrNotFASTXFormat fastxReader.close() return nil, ErrNotFASTXFormat } } // break FORCHECK default: // not typical FASTA/Q // if i > 10240 || fastxReader.lastPart { // ErrNotFASTXFormat fastxReader.close() return nil, ErrNotFASTXFormat // } } } fastxReader.checkSeqType = false } var shorterQual bool var i int FORSEARCH: for { if i = bytes.IndexByte(fastxReader.buf[fastxReader.r:], fastxReader.delim); i >= 0 { if i > 0 { fastxReader.lastByte = fastxReader.buf[fastxReader.r+i-1] } else { p = fastxReader.buffer.Bytes() if len(p) == 0 { fastxReader.lastByte = '\x00' } else { fastxReader.lastByte = p[len(p)-1] } } if fastxReader.lastByte == '\n' { // yes! if i > 0 { fastxReader.buffer.Write(dropCR(fastxReader.buf[fastxReader.r : fastxReader.r+i-1])) } else { fastxReader.buffer.WriteByte('\n') } // we have to avoid the case of quality line starting with "@" shorterQual, err = fastxReader.parseRecord() if fastxReader.IsFastq && err != nil && err == ErrUnequalSeqAndQual { if shorterQual { fastxReader.buffer.WriteByte('\n') fastxReader.buffer.WriteByte(fastxReader.delim) fastxReader.needMoreCheckOfBuf = true fastxReader.r += i + 1 continue FORSEARCH } fastxReader.close() return nil, ErrBadFASTQFormat } fastxReader.buffer.Reset() fastxReader.needMoreCheckOfBuf = true fastxReader.r += i + 1 return fastxReader.record, nil } // inline >/@ fastxReader.buffer.Write(fastxReader.buf[fastxReader.r : fastxReader.r+i+1]) fastxReader.r += i + 1 fastxReader.needMoreCheckOfBuf = true continue FORSEARCH } fastxReader.buffer.Write(fastxReader.buf[fastxReader.r:]) if fastxReader.lastPart { _, err = fastxReader.parseRecord() if err != nil { // no any chance fastxReader.close() return nil, err } fastxReader.buffer.Reset() fastxReader.close() fastxReader.finished = true return fastxReader.record, nil } fastxReader.needMoreCheckOfBuf = false break FORSEARCH } } } // parseRecord parse a FASTA/Q record from the fastxReader.buffer func (fastxReader *Reader) parseRecord() (bool, error) { fastxReader.seqBuffer.Reset() if fastxReader.IsFastq { fastxReader.qualBuffer.Reset() } var p = fastxReader.buffer.Bytes() if j := bytes.IndexByte(p, '\n'); j > 0 { fastxReader.head = dropCR(p[0:j]) r := j + 1 if !fastxReader.IsFastq { // FASTA for { if k := bytes.IndexByte(p[r:], '\n'); k >= 0 { fastxReader.seqBuffer.Write(dropCR(p[r : r+k])) r += k + 1 continue } fastxReader.seqBuffer.Write(dropCR(p[r:])) break } fastxReader.seq = fastxReader.seqBuffer.Bytes() } else { // FASTQ var isQual bool for { if k := bytes.IndexByte(p[r:], '\n'); k >= 0 { if k > 0 && p[r] == '+' && !isQual { isQual = true } else if isQual { fastxReader.qualBuffer.Write(dropCR(p[r : r+k])) } else { fastxReader.seqBuffer.Write(dropCR(p[r : r+k])) } r += k + 1 continue } if isQual { fastxReader.qualBuffer.Write(dropCR(p[r:])) } break } // may be the case of quality line starts with "@" if fastxReader.seqBuffer.Len() != fastxReader.qualBuffer.Len() { return fastxReader.seqBuffer.Len() > fastxReader.qualBuffer.Len(), ErrUnequalSeqAndQual } fastxReader.seq = fastxReader.seqBuffer.Bytes() fastxReader.qual = fastxReader.qualBuffer.Bytes() } } else { fastxReader.head = dropCR(dropLF(p)) fastxReader.seq = []byte{} fastxReader.qual = []byte{} } // guess alphabet if fastxReader.firstseq { if fastxReader.t == nil { fastxReader.t = seq.GuessAlphabetLessConservatively(fastxReader.seq) } fastxReader.firstseq = false } if len(fastxReader.head) == 0 && len(fastxReader.seq) == 0 { return false, io.EOF } var err error // new record if fastxReader.IsFastq { // fastq fastxReader.record.Seq.Alphabet = fastxReader.t fastxReader.record.ID, fastxReader.record.Desc = parseHeadIDAndDesc(fastxReader.IDRegexp, fastxReader.head) fastxReader.record.Name = fastxReader.head fastxReader.record.Seq.Seq = fastxReader.seq fastxReader.record.Seq.Qual = fastxReader.qual if seq.ValidateSeq { err = fastxReader.t.IsValid(fastxReader.seq) } if len(fastxReader.seq) != len(fastxReader.qual) { err = fmt.Errorf("seq: unmatched length of sequence (%d) and quality (%d)", len(fastxReader.seq), len(fastxReader.qual)) } } else { // fasta fastxReader.record.Seq.Alphabet = fastxReader.t fastxReader.record.ID, fastxReader.record.Desc = parseHeadIDAndDesc(fastxReader.IDRegexp, fastxReader.head) fastxReader.record.Name = fastxReader.head fastxReader.record.Seq.Seq = fastxReader.seq if seq.ValidateSeq { err = fastxReader.t.IsValid(fastxReader.seq) } } return false, err } // ParseHeadID parse ID from head by IDRegexp. not used. func ParseHeadID(idRegexp *regexp.Regexp, head []byte) []byte { found := idRegexp.FindSubmatch(head) if found == nil { // not match return head } return found[1] } var emptyByteSlice = []byte{} func parseHeadIDAndDesc(idRegexp *regexp.Regexp, head []byte) ([]byte, []byte) { if isUsingDefaultIDRegexp { if i := bytes.IndexByte(head, ' '); i > 0 { e := len(head) j := i + 1 for ; j < e; j++ { if head[j] == ' ' || head[j] == '\t' { j++ } else { break } } if j >= e { return head[0:i], emptyByteSlice } return head[0:i], head[j:] } if i := bytes.IndexByte(head, '\t'); i > 0 { e := len(head) j := i + 1 for ; j < e; j++ { if head[j] == ' ' || head[j] == '\t' { j++ } else { break } } if j >= e { return head[0:i], emptyByteSlice } return head[0:i], head[j:] } return head, emptyByteSlice } found := idRegexp.FindSubmatch(head) if found == nil { // not match return head, emptyByteSlice } return found[1], emptyByteSlice } // Alphabet returns Alphabet of the file func (fastxReader *Reader) Alphabet() *seq.Alphabet { if fastxReader.t == nil { return seq.Unlimit } return fastxReader.t } func dropCR(data []byte) []byte { if len(data) > 0 && data[len(data)-1] == '\r' { return data[0 : len(data)-1] } return data } func dropLF(data []byte) []byte { if len(data) > 0 && data[len(data)-1] == '\n' { return data[0 : len(data)-1] } return data } // ------------------------------------------------- // RecordChunk is chunk for records type RecordChunk struct { ID uint64 Data []*Record Err error } // ChunkChan asynchronously reads FASTA/Q records, and returns a channel of // Record Chunk, from which you can easily access the records. // bufferSize is the number of buffered chunks, and chunkSize is the size // of records in a chunk. func (fastxReader *Reader) ChunkChan(bufferSize int, chunkSize int) chan RecordChunk { var ch chan RecordChunk if bufferSize <= 0 { ch = make(chan RecordChunk) } else { ch = make(chan RecordChunk, bufferSize) } if chunkSize < 1 { chunkSize = 1 } go func() { var i int var id uint64 chunkData := make([]*Record, chunkSize) for { record, err := fastxReader.Read() if err != nil { if err == io.EOF { if i == 0 { // no any seqs close(ch) return } break } ch <- RecordChunk{id, chunkData[0:i], err} close(ch) return } chunkData[i] = record.Clone() i++ if i == chunkSize { ch <- RecordChunk{id, chunkData[0:i], nil} id++ i = 0 chunkData = make([]*Record, chunkSize) } } ch <- RecordChunk{id, chunkData[0:i], nil} close(ch) }() return ch } bio-0.13.3/seqio/fastx/reader_test.go000077500000000000000000000067041457355065000174700ustar00rootroot00000000000000package fastx import ( // "fmt" "io" "testing" ) func TestFastaReader2(t *testing.T) { file := "test.fa" reader, err := NewDefaultReader(file) if err != nil { t.Error(err) } for { _, err := reader.Read() if err != nil { if err == io.EOF { break } t.Error(err) break } // fmt.Print(record) } } func TestFastaReader3(t *testing.T) { file := "test.fa" reader, err := NewDefaultReader(file) if err != nil { t.Error(err) } for chunk := range reader.ChunkChan(0, 1) { if chunk.Err != nil { t.Error(chunk.Err) } // for _, record := range chunk.Data { // // fmt.Print(record) // } } } func TestFastqReadern(t *testing.T) { file := "test.fq" reader, err := NewDefaultReader(file) if err != nil { t.Error(err) } for { _, err := reader.Read() if err != nil { if err == io.EOF { break } t.Error(err) break } // fmt.Print(record) } } // ----------------------------- func TestFastaReader(t *testing.T) { file := "test.fa" reader, err := NewDefaultReader(file) if err != nil { t.Error(err) } n := 0 for chunk := range reader.ChunkChan(0, 1) { if chunk.Err != nil { t.Error(chunk.Err) } n += len(chunk.Data) // for _, record := range chunk.Data { // fmt.Println(record) // } } if n != 6 { t.Errorf("seq number mismatch %d != %d", 6, n) } } func TestFastqReader(t *testing.T) { file := "test.fq" reader, err := NewDefaultReader(file) if err != nil { t.Error(err) } n := 0 for chunk := range reader.ChunkChan(0, 1) { if chunk.Err != nil { t.Error(chunk.Err) } n += len(chunk.Data) // for _, record := range chunk.Data { // fmt.Println(record) // } } if n != 8 { t.Errorf("seq number mismatch %d != %d", 8, n) } } func TestFastqReader2(t *testing.T) { file := "test2.fq" reader, err := NewDefaultReader(file) if err != nil { t.Error(err) } n := 0 for chunk := range reader.ChunkChan(0, 1) { if chunk.Err != nil { t.Error(chunk.Err) } n += len(chunk.Data) // for _, record := range chunk.Data { // fmt.Println(record) // } } if n != 5 { t.Errorf("seq number mismatch %d != %d", 5, n) } } func TestFastqReader3(t *testing.T) { file := "test3.fq" reader, err := NewDefaultReader(file) if err != nil { t.Error(err) } n := 0 l := -1 for chunk := range reader.ChunkChan(0, 1) { if chunk.Err != nil { t.Error(chunk.Err) } n += len(chunk.Data) for _, record := range chunk.Data { // fmt.Println(record) if l == -1 { l = len(record.Seq.Seq) } else { if l != len(record.Seq.Seq) { t.Errorf("parse error") return } } } } if n != 3 { t.Errorf("seq number mismatch %d != %d", 5, n) } } func TestBlankFile(t *testing.T) { file := "blank.fx" reader, err := NewDefaultReader(file) if err != nil { t.Error(err) } for chunk := range reader.ChunkChan(0, 1) { if chunk.Err != nil { if chunk.Err != ErrNotFASTXFormat { t.Error(chunk.Err) } } } } func TestBlankFile2(t *testing.T) { file := "blank1.fx" reader, err := NewDefaultReader(file) if err != nil { t.Error(err) } for chunk := range reader.ChunkChan(0, 1) { // should not reach here t.Errorf("should not reach here. error: %s", chunk.Err) return } } func TestEmptyFile(t *testing.T) { file := "empty.fx" reader, err := NewDefaultReader(file) if err != nil { t.Error(err) } for chunk := range reader.ChunkChan(0, 1) { // should not reach here t.Errorf("should not reach here. error: %s", chunk.Err) return } } bio-0.13.3/seqio/fastx/records.go000077500000000000000000000110251457355065000166200ustar00rootroot00000000000000package fastx import ( "bytes" "fmt" "sync" "github.com/shenwei356/bio/seq" "github.com/shenwei356/xopen" ) // Record is a struct for FASTA/Q type Record struct { ID []byte // id Name []byte // full name Desc []byte // Description Seq *seq.Seq // seq } // Clone of a Record func (record *Record) Clone() *Record { return &Record{ []byte(string(record.ID)), []byte(string(record.Name)), []byte(string(record.Desc)), record.Seq.Clone(), } } func (record *Record) String() string { return string(record.Format(60)) } // NewRecord is constructor of type Record for FASTA func NewRecord(t *seq.Alphabet, id, name, desc, s []byte) (*Record, error) { seq, err := seq.NewSeq(t, s) if err != nil { return nil, fmt.Errorf("error when parsing seq: %s (%s)", id, err) } return &Record{id, name, desc, seq}, nil } // NewRecordWithoutValidation is constructor of type Record for FASTA // without validation of the sequence func NewRecordWithoutValidation(t *seq.Alphabet, id, name, desc, s []byte) (*Record, error) { seq, err := seq.NewSeqWithoutValidation(t, s) if err != nil { return nil, err } return &Record{id, name, desc, seq}, nil } // NewRecordWithSeq is constructor of type Record // for FASTA with a existed seq.Seq object func NewRecordWithSeq(id, name, desc []byte, s *seq.Seq) (*Record, error) { return &Record{id, name, desc, s}, nil } // NewRecordWithQual is constructor of type Record for FASTQ func NewRecordWithQual(t *seq.Alphabet, id, name, desc, s, q []byte) (*Record, error) { seq, err := seq.NewSeqWithQual(t, s, q) if err != nil { return nil, fmt.Errorf("error when parsing seq: %s (%s)", id, err) } return &Record{id, name, desc, seq}, nil } // NewRecordWithQualWithoutValidation is constructor of type Record for FASTQ func NewRecordWithQualWithoutValidation(t *seq.Alphabet, id, name, desc, s, q []byte) (*Record, error) { seq, err := seq.NewSeqWithQualWithoutValidation(t, s, q) if err != nil { return nil, err } return &Record{id, name, desc, seq}, nil } // ForcelyOutputFastq means outputing record as fastq even if it has no quality (zero-length fastq) var ForcelyOutputFastq bool // Format returns formated (wrapped with fixed length of) sequence record func (record *Record) Format(width int) []byte { var buf bytes.Buffer if len(record.Seq.Qual) > 0 || ForcelyOutputFastq { buf.Write(_mark_fastq) buf.Write(record.Name) buf.Write(_mark_newline) buf.Write(record.Seq.Seq) buf.Write(_mark_newline_plus_newline) buf.Write(record.Seq.Qual) buf.Write(_mark_newline) return buf.Bytes() } buf.Write(_mark_fasta) buf.Write(record.Name) buf.Write(_mark_newline) if width < 1 { buf.Write(record.Seq.Seq) } else { var text []byte buffer := poolBuffer.Get().(*bytes.Buffer) text, buffer = wrapByteSlice(record.Seq.Seq, width, buffer) buf.Write(text) poolBuffer.Put(buffer) } buf.Write(_mark_newline) return buf.Bytes() } // FormatToWriter formats and directly writes to writer func (record *Record) FormatToWriter(outfh *xopen.Writer, width int) { if len(record.Seq.Qual) > 0 || ForcelyOutputFastq { outfh.Write(_mark_fastq) outfh.Write(record.Name) outfh.Write(_mark_newline) outfh.Write(record.Seq.Seq) outfh.Write(_mark_newline_plus_newline) outfh.Write(record.Seq.Qual) outfh.Write(_mark_newline) return } outfh.Write(_mark_fasta) outfh.Write(record.Name) outfh.Write(_mark_newline) if width < 1 { outfh.Write(record.Seq.Seq) } else { var text []byte buffer := poolBuffer.Get().(*bytes.Buffer) text, buffer = wrapByteSlice(record.Seq.Seq, width, buffer) outfh.Write(text) poolBuffer.Put(buffer) } outfh.Write(_mark_newline) } // It's unsafe for concurrency // var buffer *bytes.Buffer var poolBuffer = &sync.Pool{New: func() interface{} { return bytes.NewBuffer(make([]byte, 0, 1024)) }} var _mark_fasta = []byte{'>'} var _mark_fastq = []byte{'@'} var _mark_newline_plus_newline = []byte{'\n', '+', '\n'} var _mark_newline = []byte{'\n'} func wrapByteSlice(s []byte, width int, buffer *bytes.Buffer) ([]byte, *bytes.Buffer) { if width < 1 { return s, buffer } l := len(s) if l == 0 { return s, buffer } var lines int if l%width == 0 { lines = l/width - 1 } else { lines = int(l / width) } if buffer == nil { buffer = bytes.NewBuffer(make([]byte, 0, l+lines)) } else { buffer.Reset() } var start, end int for i := 0; i <= lines; i++ { start = i * width end = (i + 1) * width if end > l { end = l } buffer.Write(s[start:end]) if i < lines { buffer.Write(_mark_newline) } } return buffer.Bytes(), buffer } bio-0.13.3/seqio/fastx/test.fa000077500000000000000000000020571457355065000161240ustar00rootroot00000000000000>test ab > cd UACACUGUGGAUCCGGUGAGGUAGUAGGUUGUAUAGUUUGGAAUAUUACCACCGGUGAAC UAUGCAAUUUUCUACCUUACCGGAGACAGAACUCUUCGA >cel-lin-4 MI0000002 Caenorhabditis elegans lin-4 stem-loop AUGCUUCCGGCCUGUUCCCUGAGACCUCAAGUGUGAGUGUACUAUUGAUGCUUCACACCU GGGCUCUCCGGGUACCAGGACGGUUUGAGCAGAU >cel-mir-46 MI0000017 Caenorhabditis elegans miR-46 stem-loop CUGAGGUGAAGCUGAAGAGAGCCGUCUAUUGACAGUUCAAGACCACGAGUCGUUGUGUGC GGUAUCAGGAAGUGGGCAACCGCCUCCAGACGCCGAGAAGCUGGUCAAACUUGGUCAUUUAGAGGAAGUAAAAGUCGUAA CAAGGUUUCCGUAGGUGAACUGCGGA >record with no sequence >U51103.1.1480 Bacteria;Proteobacteria;Gammaproteobacteria;Xanthomonadales;Xanthomonadaceae;Thermomonas;denitrifying Fe-oxidizing bacterium UGGCGGCAGGCCUAACACAUGCAAGUCGAACGGCAGCACAGCAGAGCUUGCUCUGUGGGUGGCGAGUGGCGGACGGGUGA GGAAUACAUGGGAAUCUGCCCAGUCGUGGGGGAUAACGUAUGGAAACGUACGCUAAUACCGCAUGCGCCCUUUGGGGGAA >cel-mir-46 MI0000017 Caenorhabditis elegans miR-46 stem-loop CUGAGGUGAAGCUGAAGAGAGCCGUCUAUUGACAGUUCAAGACCACGAGUCGUUGUGUGC GGUAUCAGGAAGUGGGCAACCGCCUCCAGACGCCGAGAAGCUGGUCAAACUUGGUCAUUUAGAGGAAGUAAAAGUCGUAA CAAGGUUUCCGUAGGUGAACUGCGGA bio-0.13.3/seqio/fastx/test.fq000066400000000000000000000100301457355065000161270ustar00rootroot00000000000000@HWI-D00523:240:HF3WGBCXX:1:1101:2574:2226 1:N:0:CTGTAG TGAGGAATATTGGTCAATGGGCGCGAGCCTGAACCAGCCAAGTAGCGTGAAGGATGACTGCCCTACGGGTTGTAAACTTCTTTTATAAAGGAATAAAGTGAGGCACGTGTGCCTTTTTGTATGTACTTTATGAATAAGGATCGGCTAACTCCGTGCCAGCAGCCGCGGTAATACGGAGGATCCGAGCGTTATCCGGATTTATTGGGTTTAAAGGGTGCGCAGGCGGT + HIHIIIIIHIIHGHHIHHIIIIIIIIIIIIIIIHHIIIIIHHIHIIIIIGIHIIIIHHHHHHGHIHIIIIIIIIIIIGHIIIIIGHIIIIHIIHIHHIIIIHIHHIIIIIIIGIIIIIIIHIIIIIGHIIIIHIIIH?DGHEEGHIIIIIIIIIIIHIIHIIIHHIIHIHHIHCHHIIHGIHHHHHHHHHHHHHHDEH<@H-@CDD>:E?@GHEF-@E:@H+@-@@ @HWI-D00523:240:HF3WGBCXX:1:1101:15680:15180 1:N:0:CTGTAG TGAGGAATATTGGTCAATGGTCGGGAGACTGAACCAGCCAAGCCGCGTGAGGGAGGAAGGTACAGAGTATCGTAAACCTCTTTTGTCAGGGAACAAAGGCGGGGACGTGTCCCCGGATGAGTGTACCTGAAGAAAAAGCATCGGCTAACTCCGTGCCAGCAGCCGCGGTAATACGGAGGATGCGAGCGTTATCCGGATTTATTGGGTGTAAAGGGTGCGCAGGCGGT + @@EE@@HHIIIIICHHIIHIHHHHHHHHHDEH<@H-@CDD>:E?@GHEF-@E:@H+@-@@ @HWI-D00523:240:HF3WGBCXX:1:1101:3159:2162 1:N:0:CTGTAG TGAGGAATATTGGTCAATGGTCGGGAGACTGAACCAGCCAAGCCGCGTGAGGGAGGAAGG TACAGAGTATCGTAAACCTCTTTTGTCAGGGGACAAAGACTGGGACGCGTCCCCGGATGA GTTTACCTGAAGATAAAGCATCGGCTAACTCCGTGCCAGCAGCCGCGGTAATACGGAGGA TGCGAGCGTTATTCGGATTTCTTGGATTTAAAGGGTGCGCAGGCGGT + D1<DEHH>HHAHIIGGGII@CFFHGDFHI;:?FGB6@AGIIIGH=@?EEE3=B;?BCFEC>BB@?B8=B9>@@9?A3>@09<5>+8?><<)5@+@@=EA::??C6',-(5::,5350077<<&8(3+3(+++:::ABA@>?9@@<>BB######### @HWI-1KL131:72:C1BH9ACXX:1:1101:1364:2370 1:N:0:AGGCAGAACTCTCTAT GAGCAAAAGGCCAGCAAAAGGCCAGGAACCGTAAAAAGGCCGCGTTGCTGGCGGTTTTCCATAGGCTCCGCCCCCCTGACGAGCATCACAAAAATAGAAGG + =@@DD3B3A:2A;AFBBG9C;+28??;31?@HG6;@>?D88@7@-:ED>CE?>'3;:?C@;533:>>8?################################ bio-0.13.3/seqio/fastx/test4.fa000066400000000000000000000000521457355065000161760ustar00rootroot00000000000000>a ATC >b >123 ATCGN >abcdefg ATCGN GCCTN bio-0.13.3/seqio/fastx/util.go000077500000000000000000000043321457355065000161370ustar00rootroot00000000000000package fastx import ( "io" "github.com/shenwei356/bio/seq" ) // GetSeqNames returns the names of a fasta/q file func GetSeqNames(file string) ([]string, error) { names := []string{} seq.ValidateSeq = false reader, err := NewDefaultReader(file) if err != nil { return nil, nil } for { record, err := reader.Read() if err != nil { if err == io.EOF { break } return nil, err } names = append(names, string(record.Name)) } return names, nil } // GetSeqNumber returns the sequences number of FASTA/Q files func GetSeqNumber(file string) (int, error) { n := 0 seq.ValidateSeq = false reader, err := NewDefaultReader(file) if err != nil { return 0, nil } for { _, err := reader.Read() if err != nil { if err == io.EOF { break } return 0, err } n++ } return n, nil } // GetSeqs return fastx records of a file. // when alphabet is nil or seq.Unlimit, it will automaticlly detect the alphabet. // when idRegexp is "", default idRegexp ( ^([^\s]+)\s? ) will be used. func GetSeqs(file string, alphabet *seq.Alphabet, bufferSize int, chunkSize int, idRegexp string) ([]*Record, error) { records := []*Record{} reader, err := NewReader(alphabet, file, idRegexp) if err != nil { return records, err } for chunk := range reader.ChunkChan(bufferSize, chunkSize) { if err != nil { return records, err } records = append(records, chunk.Data...) } return records, nil } // GetSeqsMap returns all seqs as a map for fasta file func GetSeqsMap(file string, alphabet *seq.Alphabet, bufferSize int, chunkSize int, idRegexp string) (map[string]*Record, error) { m := make(map[string]*Record) records, err := GetSeqs(file, alphabet, bufferSize, chunkSize, idRegexp) if err != nil { return m, err } for _, record := range records { m[string(record.Name)] = record } return m, nil } // GuessAlphabet guess the alphabet of the file by the first maxLen bases func GuessAlphabet(file string) (*seq.Alphabet, bool, error) { reader, err := NewDefaultReader(file) if err != nil { return nil, false, err } _, err = reader.Read() if err != nil { if err == io.EOF { return reader.Alphabet(), false, io.EOF } return nil, false, err } return reader.Alphabet(), reader.IsFastq, nil } bio-0.13.3/sketches/000077500000000000000000000000001457355065000141725ustar00rootroot00000000000000bio-0.13.3/sketches/README.md000077500000000000000000000036311457355065000154570ustar00rootroot00000000000000# sketches [![Go Reference](https://pkg.go.dev/badge/github.com/shenwei356/bio/sketches.svg)](https://pkg.go.dev/github.com/shenwei356/bio/sketches) This package provides iterators for k-mer and k-mer sketches ([Minimizer](https://academic.oup.com/bioinformatics/article/20/18/3363/202143), [Scaled MinHash](https://f1000research.com/articles/8-1006), [Closed Syncmers](https://peerj.com/articles/10805/)). K-mers are either encoded (k<=32) or hashed (arbitrary k, using [ntHash](https://github.com/will-rowe/nthash)) into `uint64`. Related projects: - [kmers](https://github.com/shenwei356/kmers) provides manipulations for bit-packed k-mers (k<=32, encoded in `uint64`). - [kmcp](https://github.com/shenwei356/kmcp) uses this package. ## Benchmark CPU: AMD Ryzen 7 2700X Eight-Core Processor, 3.7 GHz $ go test . -bench=Bench* -benchmem \ | grep Bench \ | perl -pe 's/\s\s+/\t/g' \ | csvtk cut -Ht -f 1,3-5 \ | csvtk add-header -t -n test,time,memory,allocs \ | csvtk pretty -t -r test time memory allocs ------------------------------------------ ------------ -------- ----------- BenchmarkKmerIterator/1.00_KB-16 11445 ns/op 0 B/op 0 allocs/op BenchmarkHashIterator/1.00_KB-16 7974 ns/op 24 B/op 1 allocs/op BenchmarkSimHashIterator/1.00_KB-16 79477 ns/op 48 B/op 1 allocs/op BenchmarkProteinIterator/1.00_KB-16 17852 ns/op 432 B/op 2 allocs/op BenchmarkMinimizerSketch/1.00_KB-16 56071 ns/op 48 B/op 2 allocs/op BenchmarkSyncmerSketch/1.00_KB-16 101310 ns/op 977 B/op 7 allocs/op BenchmarkProteinMinimizerSketch/1.00_KB-16 29914 ns/op 736 B/op 5 allocs/op ## History This package was originally maintained in [unikmer](https://github.com/shenwei356/unikmer). bio-0.13.3/sketches/iterator-protein.go000066400000000000000000000051201457355065000200260ustar00rootroot00000000000000// Copyright © 2018-2021 Wei Shen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package sketches import ( "sync" "github.com/shenwei356/bio/seq" "github.com/zeebo/wyhash" ) var poolProteinIterator = &sync.Pool{New: func() interface{} { return &ProteinIterator{} }} // ProteinIterator is a iterator for protein sequence. type ProteinIterator struct { s0 *seq.Seq // only used for KmerProteinIterator s *seq.Seq // amino acid k int finished bool end int idx int } // NewProteinIterator returns an iterator for hash of amino acids func NewProteinIterator(s *seq.Seq, k int, codonTable int, frame int) (*ProteinIterator, error) { if k < 1 { return nil, ErrInvalidK } if len(s.Seq) < k*3 { return nil, ErrShortSeq } // iter := &ProteinIterator{s0: s, k: k} iter := poolProteinIterator.Get().(*ProteinIterator) iter.s0 = s iter.k = k iter.finished = false iter.idx = 0 var err error if s.Alphabet != seq.Protein { iter.s, err = s.Translate(codonTable, frame, false, false, true, false) if err != nil { return nil, err } } else { iter.s = s } iter.end = len(iter.s.Seq) - k return iter, nil } // Next return's a hash func (iter *ProteinIterator) Next() (code uint64, ok bool) { if iter.finished { return 0, false } if iter.idx > iter.end { iter.finished = true poolProteinIterator.Put(iter) return 0, false } code = wyhash.Hash(iter.s.Seq[iter.idx:iter.idx+iter.k], 1) iter.idx++ return code, true } // Index returns current 0-baesd index. func (iter *ProteinIterator) Index() int { return iter.idx - 1 } bio-0.13.3/sketches/iterator-protein_test.go000066400000000000000000000036331457355065000210740ustar00rootroot00000000000000// Copyright © 2018-2021 Wei Shen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package sketches import ( "testing" "github.com/shenwei356/bio/seq" ) func TestProteinIterator(t *testing.T) { _s := "AAGTTTGAATCATTCAACTATCTAGTTTTCAGAGAACAATGTTCTCTAAAGAATAGAAAAGAGTCATTGTGCGGTGATGATGGCGGGAAGGATCCACCTG" sequence, err := seq.NewSeq(seq.DNA, []byte(_s)) if err != nil { t.Errorf("fail to create sequence: %s", _s) } k := 10 iter, err := NewProteinIterator(sequence, k, 1, 1) if err != nil { t.Errorf("fail to create aa iter rator") } var code uint64 var ok bool // var idx int codes := make([]uint64, 0, 1024) for { code, ok = iter.Next() if !ok { break } // idx = iter.Index() // fmt.Printf("aa: %d-%s, %d\n", idx, iter.s.Seq[idx:idx+k], code) codes = append(codes, code) } if len(codes) != len(_s)/3-k+1 { t.Errorf("k-mer hashes number error") } } bio-0.13.3/sketches/iterator.go000066400000000000000000000665521457355065000163700ustar00rootroot00000000000000// Copyright © 2018-2021 Wei Shen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package sketches import ( "errors" "fmt" "math" "sync" "github.com/shenwei356/bio/seq" "github.com/shenwei356/kmers" "github.com/will-rowe/nthash" ) // ErrInvalidK means k < 1. var ErrInvalidK = fmt.Errorf("sketches: invalid k-mer size") // ErrEmptySeq sequence is empty. var ErrEmptySeq = fmt.Errorf("sketches: empty sequence") // ErrShortSeq means the sequence is shorter than k var ErrShortSeq = fmt.Errorf("sketches: sequence too short") // ErrIllegalBase means that base beyond IUPAC symbols are detected. var ErrIllegalBase = errors.New("sketches: illegal base") // ErrKTooLarge means that the k-mer size is too large. var ErrKTooLarge = fmt.Errorf("sketches: k-mer size is too large") // ErrInvalidM means that the m-mer size is too large or too small, should be in range of [4, k]. var ErrInvalidM = fmt.Errorf("sketches: invalid m-mer size, should be in range of [4, k]") // ErrInvalidScale means var ErrInvalidScale = fmt.Errorf("sketches: invalid scale, should be in range of [1, k-m+1]") var poolIterator = &sync.Pool{New: func() interface{} { return &Iterator{} }} // Iterator is a kmer code (k<=32) or hash iterator. type Iterator struct { s *seq.Seq // only used for KmerIterator k int kUint uint // uint(k) kP1 int // k -1 kP1Uint uint // uint(k-1) canonical bool circular bool hash bool finished bool revcomStrand bool idx int // for KmerIterator length int end, e int first bool kmer []byte codeBase uint64 preCode uint64 preCodeRC uint64 codeRC uint64 mask1 uint64 // (1<<(kP1Uint*2))-1 mask2 uint // iter.kP1Uint*2 // for HashIterator hasher *nthash.NTHi // for SimHash simhash bool m int // size of m-mer em int // end of idxJ in current k-mer hashes []uint64 // cycle buffer of previous hashes of m-mer sum [64]int16 // the vector nPosHash int16 // number of vector value > 0 nThsld int16 // the threshold to decide should a vector value be 1 (x >= nThsld) or not idxJ int // tmp index preHashI int // index of the m-mer just getting out of the current k-mer preHash uint64 // _hash uint64 // ntHash fracMinHash bool // scale > 1 maxHash uint64 // the max hash value for a scale, i.e. max uint64 / scale } // NewSimHashIterator returns a SimHash Iterator. Main parameters: // // k: k-mer size. // m: size of m-mer in a k-mer. range: [4, k] // scale: scale of FracMinHash of m-mers. range: [1, k-m+1] func NewSimHashIterator(s *seq.Seq, k int, m int, scale int, canonical bool, circular bool) (*Iterator, error) { if k < 1 { return nil, ErrInvalidK } if k >= 65535 { return nil, ErrKTooLarge } if m < 4 || m > k { return nil, ErrInvalidM } if scale < 1 || scale > k-m+1 { return nil, ErrInvalidScale } if len(s.Seq) < k { return nil, ErrShortSeq } var s2 *seq.Seq if circular { s2 = s.Clone() // do not edit original sequence s2.Seq = append(s2.Seq, s.Seq[0:k-1]...) } else { s2 = s } // iter := &Iterator{s: s2, k: k, canonical: canonical, circular: circular} iter := poolIterator.Get().(*Iterator) iter.s = s2 iter.k = k iter.canonical = canonical iter.circular = circular iter.finished = false iter.revcomStrand = false iter.idx = 0 iter.length = len(s2.Seq) iter.end = iter.length - k + 1 iter.kUint = uint(k) iter.kP1 = k - 1 iter.kP1Uint = uint(k - 1) iter.first = true var err error iter.hasher, err = nthash.NewHasher(&s2.Seq, uint(m)) if err != nil { return nil, err } for i := 0; i < 64; i++ { iter.sum[i] = 0 } iter.nPosHash = 0 iter.simhash = true iter.m = m iter.em = iter.k - iter.m iter.nThsld = 0 // int16((float64(iter.k-iter.m+1) + 1) / float64(2)) if len(iter.hashes) >= iter.k-iter.m+1 { // resuse objects iter.hashes = iter.hashes[0 : iter.k-iter.m+1] } else { iter.hashes = make([]uint64, iter.k-iter.m+1) } iter.preHashI = 0 iter.idxJ = 0 iter.fracMinHash = scale > 1 if iter.fracMinHash { iter.maxHash = math.MaxUint64 / uint64(scale) } else { iter.maxHash = math.MaxUint64 } return iter, nil } // NextSimHash returns next SimHash. func (iter *Iterator) NextSimHash() (code uint64, ok bool) { if iter.finished { return 0, false } if iter.idx == iter.end { iter.finished = true poolIterator.Put(iter) return 0, false } if !iter.first { // subtract the previous hash, which is out of the window of current k-mer iter.preHash = iter.hashes[iter.preHashI] // fmt.Printf("[%d] prevHashI: %d\n", iter.idx, iter.preHashI) if iter.preHash > 0 { iter.nPosHash-- iter.sum[0] -= int16(iter.preHash >> 63 & 1) iter.sum[1] -= int16(iter.preHash >> 62 & 1) iter.sum[2] -= int16(iter.preHash >> 61 & 1) iter.sum[3] -= int16(iter.preHash >> 60 & 1) iter.sum[4] -= int16(iter.preHash >> 59 & 1) iter.sum[5] -= int16(iter.preHash >> 58 & 1) iter.sum[6] -= int16(iter.preHash >> 57 & 1) iter.sum[7] -= int16(iter.preHash >> 56 & 1) iter.sum[8] -= int16(iter.preHash >> 55 & 1) iter.sum[9] -= int16(iter.preHash >> 54 & 1) iter.sum[10] -= int16(iter.preHash >> 53 & 1) iter.sum[11] -= int16(iter.preHash >> 52 & 1) iter.sum[12] -= int16(iter.preHash >> 51 & 1) iter.sum[13] -= int16(iter.preHash >> 50 & 1) iter.sum[14] -= int16(iter.preHash >> 49 & 1) iter.sum[15] -= int16(iter.preHash >> 48 & 1) iter.sum[16] -= int16(iter.preHash >> 47 & 1) iter.sum[17] -= int16(iter.preHash >> 46 & 1) iter.sum[18] -= int16(iter.preHash >> 45 & 1) iter.sum[19] -= int16(iter.preHash >> 44 & 1) iter.sum[20] -= int16(iter.preHash >> 43 & 1) iter.sum[21] -= int16(iter.preHash >> 42 & 1) iter.sum[22] -= int16(iter.preHash >> 41 & 1) iter.sum[23] -= int16(iter.preHash >> 40 & 1) iter.sum[24] -= int16(iter.preHash >> 39 & 1) iter.sum[25] -= int16(iter.preHash >> 38 & 1) iter.sum[26] -= int16(iter.preHash >> 37 & 1) iter.sum[27] -= int16(iter.preHash >> 36 & 1) iter.sum[28] -= int16(iter.preHash >> 35 & 1) iter.sum[29] -= int16(iter.preHash >> 34 & 1) iter.sum[30] -= int16(iter.preHash >> 33 & 1) iter.sum[31] -= int16(iter.preHash >> 32 & 1) iter.sum[32] -= int16(iter.preHash >> 31 & 1) iter.sum[33] -= int16(iter.preHash >> 30 & 1) iter.sum[34] -= int16(iter.preHash >> 29 & 1) iter.sum[35] -= int16(iter.preHash >> 28 & 1) iter.sum[36] -= int16(iter.preHash >> 27 & 1) iter.sum[37] -= int16(iter.preHash >> 26 & 1) iter.sum[38] -= int16(iter.preHash >> 25 & 1) iter.sum[39] -= int16(iter.preHash >> 24 & 1) iter.sum[40] -= int16(iter.preHash >> 23 & 1) iter.sum[41] -= int16(iter.preHash >> 22 & 1) iter.sum[42] -= int16(iter.preHash >> 21 & 1) iter.sum[43] -= int16(iter.preHash >> 20 & 1) iter.sum[44] -= int16(iter.preHash >> 19 & 1) iter.sum[45] -= int16(iter.preHash >> 18 & 1) iter.sum[46] -= int16(iter.preHash >> 17 & 1) iter.sum[47] -= int16(iter.preHash >> 16 & 1) iter.sum[48] -= int16(iter.preHash >> 15 & 1) iter.sum[49] -= int16(iter.preHash >> 14 & 1) iter.sum[50] -= int16(iter.preHash >> 13 & 1) iter.sum[51] -= int16(iter.preHash >> 12 & 1) iter.sum[52] -= int16(iter.preHash >> 11 & 1) iter.sum[53] -= int16(iter.preHash >> 10 & 1) iter.sum[54] -= int16(iter.preHash >> 9 & 1) iter.sum[55] -= int16(iter.preHash >> 8 & 1) iter.sum[56] -= int16(iter.preHash >> 7 & 1) iter.sum[57] -= int16(iter.preHash >> 6 & 1) iter.sum[58] -= int16(iter.preHash >> 5 & 1) iter.sum[59] -= int16(iter.preHash >> 4 & 1) iter.sum[60] -= int16(iter.preHash >> 3 & 1) iter.sum[61] -= int16(iter.preHash >> 2 & 1) iter.sum[62] -= int16(iter.preHash >> 1 & 1) iter.sum[63] -= int16(iter.preHash & 1) } // add newly added m-mer iter._hash, _ = iter.hasher.Next(iter.canonical) if iter.fracMinHash && iter._hash > iter.maxHash { // discard it, see sourmash paper for FacMinHash iter._hash = 0 } else if iter._hash > 0 { iter.nPosHash++ } // fmt.Printf("[%d] new: %064b\n", iter.idx, iter._hash) iter.hashes[iter.preHashI] = iter._hash // update the hash value if iter._hash > 0 { iter.sum[0] += int16(iter._hash >> 63 & 1) iter.sum[1] += int16(iter._hash >> 62 & 1) iter.sum[2] += int16(iter._hash >> 61 & 1) iter.sum[3] += int16(iter._hash >> 60 & 1) iter.sum[4] += int16(iter._hash >> 59 & 1) iter.sum[5] += int16(iter._hash >> 58 & 1) iter.sum[6] += int16(iter._hash >> 57 & 1) iter.sum[7] += int16(iter._hash >> 56 & 1) iter.sum[8] += int16(iter._hash >> 55 & 1) iter.sum[9] += int16(iter._hash >> 54 & 1) iter.sum[10] += int16(iter._hash >> 53 & 1) iter.sum[11] += int16(iter._hash >> 52 & 1) iter.sum[12] += int16(iter._hash >> 51 & 1) iter.sum[13] += int16(iter._hash >> 50 & 1) iter.sum[14] += int16(iter._hash >> 49 & 1) iter.sum[15] += int16(iter._hash >> 48 & 1) iter.sum[16] += int16(iter._hash >> 47 & 1) iter.sum[17] += int16(iter._hash >> 46 & 1) iter.sum[18] += int16(iter._hash >> 45 & 1) iter.sum[19] += int16(iter._hash >> 44 & 1) iter.sum[20] += int16(iter._hash >> 43 & 1) iter.sum[21] += int16(iter._hash >> 42 & 1) iter.sum[22] += int16(iter._hash >> 41 & 1) iter.sum[23] += int16(iter._hash >> 40 & 1) iter.sum[24] += int16(iter._hash >> 39 & 1) iter.sum[25] += int16(iter._hash >> 38 & 1) iter.sum[26] += int16(iter._hash >> 37 & 1) iter.sum[27] += int16(iter._hash >> 36 & 1) iter.sum[28] += int16(iter._hash >> 35 & 1) iter.sum[29] += int16(iter._hash >> 34 & 1) iter.sum[30] += int16(iter._hash >> 33 & 1) iter.sum[31] += int16(iter._hash >> 32 & 1) iter.sum[32] += int16(iter._hash >> 31 & 1) iter.sum[33] += int16(iter._hash >> 30 & 1) iter.sum[34] += int16(iter._hash >> 29 & 1) iter.sum[35] += int16(iter._hash >> 28 & 1) iter.sum[36] += int16(iter._hash >> 27 & 1) iter.sum[37] += int16(iter._hash >> 26 & 1) iter.sum[38] += int16(iter._hash >> 25 & 1) iter.sum[39] += int16(iter._hash >> 24 & 1) iter.sum[40] += int16(iter._hash >> 23 & 1) iter.sum[41] += int16(iter._hash >> 22 & 1) iter.sum[42] += int16(iter._hash >> 21 & 1) iter.sum[43] += int16(iter._hash >> 20 & 1) iter.sum[44] += int16(iter._hash >> 19 & 1) iter.sum[45] += int16(iter._hash >> 18 & 1) iter.sum[46] += int16(iter._hash >> 17 & 1) iter.sum[47] += int16(iter._hash >> 16 & 1) iter.sum[48] += int16(iter._hash >> 15 & 1) iter.sum[49] += int16(iter._hash >> 14 & 1) iter.sum[50] += int16(iter._hash >> 13 & 1) iter.sum[51] += int16(iter._hash >> 12 & 1) iter.sum[52] += int16(iter._hash >> 11 & 1) iter.sum[53] += int16(iter._hash >> 10 & 1) iter.sum[54] += int16(iter._hash >> 9 & 1) iter.sum[55] += int16(iter._hash >> 8 & 1) iter.sum[56] += int16(iter._hash >> 7 & 1) iter.sum[57] += int16(iter._hash >> 6 & 1) iter.sum[58] += int16(iter._hash >> 5 & 1) iter.sum[59] += int16(iter._hash >> 4 & 1) iter.sum[60] += int16(iter._hash >> 3 & 1) iter.sum[61] += int16(iter._hash >> 2 & 1) iter.sum[62] += int16(iter._hash >> 1 & 1) iter.sum[63] += int16(iter._hash & 1) } // decoding vector to hash code = 0 iter.nThsld = (iter.nPosHash + 1) / 2 // the threshold // fmt.Printf("nThsld: %d\n", iter.nThsld) if iter.nPosHash > 0 { // if n >= N/2 ? 1 : 0 code |= uint64((iter.sum[0]-iter.nThsld)>>15&1^1) << 63 code |= uint64((iter.sum[1]-iter.nThsld)>>15&1^1) << 62 code |= uint64((iter.sum[2]-iter.nThsld)>>15&1^1) << 61 code |= uint64((iter.sum[3]-iter.nThsld)>>15&1^1) << 60 code |= uint64((iter.sum[4]-iter.nThsld)>>15&1^1) << 59 code |= uint64((iter.sum[5]-iter.nThsld)>>15&1^1) << 58 code |= uint64((iter.sum[6]-iter.nThsld)>>15&1^1) << 57 code |= uint64((iter.sum[7]-iter.nThsld)>>15&1^1) << 56 code |= uint64((iter.sum[8]-iter.nThsld)>>15&1^1) << 55 code |= uint64((iter.sum[9]-iter.nThsld)>>15&1^1) << 54 code |= uint64((iter.sum[10]-iter.nThsld)>>15&1^1) << 53 code |= uint64((iter.sum[11]-iter.nThsld)>>15&1^1) << 52 code |= uint64((iter.sum[12]-iter.nThsld)>>15&1^1) << 51 code |= uint64((iter.sum[13]-iter.nThsld)>>15&1^1) << 50 code |= uint64((iter.sum[14]-iter.nThsld)>>15&1^1) << 49 code |= uint64((iter.sum[15]-iter.nThsld)>>15&1^1) << 48 code |= uint64((iter.sum[16]-iter.nThsld)>>15&1^1) << 47 code |= uint64((iter.sum[17]-iter.nThsld)>>15&1^1) << 46 code |= uint64((iter.sum[18]-iter.nThsld)>>15&1^1) << 45 code |= uint64((iter.sum[19]-iter.nThsld)>>15&1^1) << 44 code |= uint64((iter.sum[20]-iter.nThsld)>>15&1^1) << 43 code |= uint64((iter.sum[21]-iter.nThsld)>>15&1^1) << 42 code |= uint64((iter.sum[22]-iter.nThsld)>>15&1^1) << 41 code |= uint64((iter.sum[23]-iter.nThsld)>>15&1^1) << 40 code |= uint64((iter.sum[24]-iter.nThsld)>>15&1^1) << 39 code |= uint64((iter.sum[25]-iter.nThsld)>>15&1^1) << 38 code |= uint64((iter.sum[26]-iter.nThsld)>>15&1^1) << 37 code |= uint64((iter.sum[27]-iter.nThsld)>>15&1^1) << 36 code |= uint64((iter.sum[28]-iter.nThsld)>>15&1^1) << 35 code |= uint64((iter.sum[29]-iter.nThsld)>>15&1^1) << 34 code |= uint64((iter.sum[30]-iter.nThsld)>>15&1^1) << 33 code |= uint64((iter.sum[31]-iter.nThsld)>>15&1^1) << 32 code |= uint64((iter.sum[32]-iter.nThsld)>>15&1^1) << 31 code |= uint64((iter.sum[33]-iter.nThsld)>>15&1^1) << 30 code |= uint64((iter.sum[34]-iter.nThsld)>>15&1^1) << 29 code |= uint64((iter.sum[35]-iter.nThsld)>>15&1^1) << 28 code |= uint64((iter.sum[36]-iter.nThsld)>>15&1^1) << 27 code |= uint64((iter.sum[37]-iter.nThsld)>>15&1^1) << 26 code |= uint64((iter.sum[38]-iter.nThsld)>>15&1^1) << 25 code |= uint64((iter.sum[39]-iter.nThsld)>>15&1^1) << 24 code |= uint64((iter.sum[40]-iter.nThsld)>>15&1^1) << 23 code |= uint64((iter.sum[41]-iter.nThsld)>>15&1^1) << 22 code |= uint64((iter.sum[42]-iter.nThsld)>>15&1^1) << 21 code |= uint64((iter.sum[43]-iter.nThsld)>>15&1^1) << 20 code |= uint64((iter.sum[44]-iter.nThsld)>>15&1^1) << 19 code |= uint64((iter.sum[45]-iter.nThsld)>>15&1^1) << 18 code |= uint64((iter.sum[46]-iter.nThsld)>>15&1^1) << 17 code |= uint64((iter.sum[47]-iter.nThsld)>>15&1^1) << 16 code |= uint64((iter.sum[48]-iter.nThsld)>>15&1^1) << 15 code |= uint64((iter.sum[49]-iter.nThsld)>>15&1^1) << 14 code |= uint64((iter.sum[50]-iter.nThsld)>>15&1^1) << 13 code |= uint64((iter.sum[51]-iter.nThsld)>>15&1^1) << 12 code |= uint64((iter.sum[52]-iter.nThsld)>>15&1^1) << 11 code |= uint64((iter.sum[53]-iter.nThsld)>>15&1^1) << 10 code |= uint64((iter.sum[54]-iter.nThsld)>>15&1^1) << 9 code |= uint64((iter.sum[55]-iter.nThsld)>>15&1^1) << 8 code |= uint64((iter.sum[56]-iter.nThsld)>>15&1^1) << 7 code |= uint64((iter.sum[57]-iter.nThsld)>>15&1^1) << 6 code |= uint64((iter.sum[58]-iter.nThsld)>>15&1^1) << 5 code |= uint64((iter.sum[59]-iter.nThsld)>>15&1^1) << 4 code |= uint64((iter.sum[60]-iter.nThsld)>>15&1^1) << 3 code |= uint64((iter.sum[61]-iter.nThsld)>>15&1^1) << 2 code |= uint64((iter.sum[62]-iter.nThsld)>>15&1^1) << 1 code |= uint64((iter.sum[63]-iter.nThsld)>>15&1 ^ 1) } else { code = 0 } // update the preHashI if iter.preHashI == iter.k-iter.m { iter.preHashI = 0 } else { iter.preHashI++ } } else { iter.nPosHash = 0 for iter.idxJ = 0; iter.idxJ <= iter.em; iter.idxJ++ { iter._hash, _ = iter.hasher.Next(iter.canonical) // fmt.Println(iter.fracMinHash, iter._hash, iter.maxHash, iter._hash > iter.maxHash) if iter.fracMinHash && iter._hash > iter.maxHash { iter.hashes[iter.idxJ] = 0 continue } iter.hashes[iter.idxJ] = iter._hash if iter._hash == 0 { continue } iter.nPosHash++ // fmt.Printf("[%02d] %02d: %064b %d\n", iter.idx, iter.idxJ, iter._hash, iter._hash) iter.sum[0] += int16(iter._hash >> 63 & 1) iter.sum[1] += int16(iter._hash >> 62 & 1) iter.sum[2] += int16(iter._hash >> 61 & 1) iter.sum[3] += int16(iter._hash >> 60 & 1) iter.sum[4] += int16(iter._hash >> 59 & 1) iter.sum[5] += int16(iter._hash >> 58 & 1) iter.sum[6] += int16(iter._hash >> 57 & 1) iter.sum[7] += int16(iter._hash >> 56 & 1) iter.sum[8] += int16(iter._hash >> 55 & 1) iter.sum[9] += int16(iter._hash >> 54 & 1) iter.sum[10] += int16(iter._hash >> 53 & 1) iter.sum[11] += int16(iter._hash >> 52 & 1) iter.sum[12] += int16(iter._hash >> 51 & 1) iter.sum[13] += int16(iter._hash >> 50 & 1) iter.sum[14] += int16(iter._hash >> 49 & 1) iter.sum[15] += int16(iter._hash >> 48 & 1) iter.sum[16] += int16(iter._hash >> 47 & 1) iter.sum[17] += int16(iter._hash >> 46 & 1) iter.sum[18] += int16(iter._hash >> 45 & 1) iter.sum[19] += int16(iter._hash >> 44 & 1) iter.sum[20] += int16(iter._hash >> 43 & 1) iter.sum[21] += int16(iter._hash >> 42 & 1) iter.sum[22] += int16(iter._hash >> 41 & 1) iter.sum[23] += int16(iter._hash >> 40 & 1) iter.sum[24] += int16(iter._hash >> 39 & 1) iter.sum[25] += int16(iter._hash >> 38 & 1) iter.sum[26] += int16(iter._hash >> 37 & 1) iter.sum[27] += int16(iter._hash >> 36 & 1) iter.sum[28] += int16(iter._hash >> 35 & 1) iter.sum[29] += int16(iter._hash >> 34 & 1) iter.sum[30] += int16(iter._hash >> 33 & 1) iter.sum[31] += int16(iter._hash >> 32 & 1) iter.sum[32] += int16(iter._hash >> 31 & 1) iter.sum[33] += int16(iter._hash >> 30 & 1) iter.sum[34] += int16(iter._hash >> 29 & 1) iter.sum[35] += int16(iter._hash >> 28 & 1) iter.sum[36] += int16(iter._hash >> 27 & 1) iter.sum[37] += int16(iter._hash >> 26 & 1) iter.sum[38] += int16(iter._hash >> 25 & 1) iter.sum[39] += int16(iter._hash >> 24 & 1) iter.sum[40] += int16(iter._hash >> 23 & 1) iter.sum[41] += int16(iter._hash >> 22 & 1) iter.sum[42] += int16(iter._hash >> 21 & 1) iter.sum[43] += int16(iter._hash >> 20 & 1) iter.sum[44] += int16(iter._hash >> 19 & 1) iter.sum[45] += int16(iter._hash >> 18 & 1) iter.sum[46] += int16(iter._hash >> 17 & 1) iter.sum[47] += int16(iter._hash >> 16 & 1) iter.sum[48] += int16(iter._hash >> 15 & 1) iter.sum[49] += int16(iter._hash >> 14 & 1) iter.sum[50] += int16(iter._hash >> 13 & 1) iter.sum[51] += int16(iter._hash >> 12 & 1) iter.sum[52] += int16(iter._hash >> 11 & 1) iter.sum[53] += int16(iter._hash >> 10 & 1) iter.sum[54] += int16(iter._hash >> 9 & 1) iter.sum[55] += int16(iter._hash >> 8 & 1) iter.sum[56] += int16(iter._hash >> 7 & 1) iter.sum[57] += int16(iter._hash >> 6 & 1) iter.sum[58] += int16(iter._hash >> 5 & 1) iter.sum[59] += int16(iter._hash >> 4 & 1) iter.sum[60] += int16(iter._hash >> 3 & 1) iter.sum[61] += int16(iter._hash >> 2 & 1) iter.sum[62] += int16(iter._hash >> 1 & 1) iter.sum[63] += int16(iter._hash & 1) } // fmt.Printf(" sum: %v\n", iter.sum) code = 0 iter.nThsld = (iter.nPosHash + 1) / 2 // fmt.Printf("nThsld: %d\n", iter.nThsld) if iter.nPosHash > 0 { // if n >= N/2 ? 1 : 0 code |= uint64((iter.sum[0]-iter.nThsld)>>15&1^1) << 63 code |= uint64((iter.sum[1]-iter.nThsld)>>15&1^1) << 62 code |= uint64((iter.sum[2]-iter.nThsld)>>15&1^1) << 61 code |= uint64((iter.sum[3]-iter.nThsld)>>15&1^1) << 60 code |= uint64((iter.sum[4]-iter.nThsld)>>15&1^1) << 59 code |= uint64((iter.sum[5]-iter.nThsld)>>15&1^1) << 58 code |= uint64((iter.sum[6]-iter.nThsld)>>15&1^1) << 57 code |= uint64((iter.sum[7]-iter.nThsld)>>15&1^1) << 56 code |= uint64((iter.sum[8]-iter.nThsld)>>15&1^1) << 55 code |= uint64((iter.sum[9]-iter.nThsld)>>15&1^1) << 54 code |= uint64((iter.sum[10]-iter.nThsld)>>15&1^1) << 53 code |= uint64((iter.sum[11]-iter.nThsld)>>15&1^1) << 52 code |= uint64((iter.sum[12]-iter.nThsld)>>15&1^1) << 51 code |= uint64((iter.sum[13]-iter.nThsld)>>15&1^1) << 50 code |= uint64((iter.sum[14]-iter.nThsld)>>15&1^1) << 49 code |= uint64((iter.sum[15]-iter.nThsld)>>15&1^1) << 48 code |= uint64((iter.sum[16]-iter.nThsld)>>15&1^1) << 47 code |= uint64((iter.sum[17]-iter.nThsld)>>15&1^1) << 46 code |= uint64((iter.sum[18]-iter.nThsld)>>15&1^1) << 45 code |= uint64((iter.sum[19]-iter.nThsld)>>15&1^1) << 44 code |= uint64((iter.sum[20]-iter.nThsld)>>15&1^1) << 43 code |= uint64((iter.sum[21]-iter.nThsld)>>15&1^1) << 42 code |= uint64((iter.sum[22]-iter.nThsld)>>15&1^1) << 41 code |= uint64((iter.sum[23]-iter.nThsld)>>15&1^1) << 40 code |= uint64((iter.sum[24]-iter.nThsld)>>15&1^1) << 39 code |= uint64((iter.sum[25]-iter.nThsld)>>15&1^1) << 38 code |= uint64((iter.sum[26]-iter.nThsld)>>15&1^1) << 37 code |= uint64((iter.sum[27]-iter.nThsld)>>15&1^1) << 36 code |= uint64((iter.sum[28]-iter.nThsld)>>15&1^1) << 35 code |= uint64((iter.sum[29]-iter.nThsld)>>15&1^1) << 34 code |= uint64((iter.sum[30]-iter.nThsld)>>15&1^1) << 33 code |= uint64((iter.sum[31]-iter.nThsld)>>15&1^1) << 32 code |= uint64((iter.sum[32]-iter.nThsld)>>15&1^1) << 31 code |= uint64((iter.sum[33]-iter.nThsld)>>15&1^1) << 30 code |= uint64((iter.sum[34]-iter.nThsld)>>15&1^1) << 29 code |= uint64((iter.sum[35]-iter.nThsld)>>15&1^1) << 28 code |= uint64((iter.sum[36]-iter.nThsld)>>15&1^1) << 27 code |= uint64((iter.sum[37]-iter.nThsld)>>15&1^1) << 26 code |= uint64((iter.sum[38]-iter.nThsld)>>15&1^1) << 25 code |= uint64((iter.sum[39]-iter.nThsld)>>15&1^1) << 24 code |= uint64((iter.sum[40]-iter.nThsld)>>15&1^1) << 23 code |= uint64((iter.sum[41]-iter.nThsld)>>15&1^1) << 22 code |= uint64((iter.sum[42]-iter.nThsld)>>15&1^1) << 21 code |= uint64((iter.sum[43]-iter.nThsld)>>15&1^1) << 20 code |= uint64((iter.sum[44]-iter.nThsld)>>15&1^1) << 19 code |= uint64((iter.sum[45]-iter.nThsld)>>15&1^1) << 18 code |= uint64((iter.sum[46]-iter.nThsld)>>15&1^1) << 17 code |= uint64((iter.sum[47]-iter.nThsld)>>15&1^1) << 16 code |= uint64((iter.sum[48]-iter.nThsld)>>15&1^1) << 15 code |= uint64((iter.sum[49]-iter.nThsld)>>15&1^1) << 14 code |= uint64((iter.sum[50]-iter.nThsld)>>15&1^1) << 13 code |= uint64((iter.sum[51]-iter.nThsld)>>15&1^1) << 12 code |= uint64((iter.sum[52]-iter.nThsld)>>15&1^1) << 11 code |= uint64((iter.sum[53]-iter.nThsld)>>15&1^1) << 10 code |= uint64((iter.sum[54]-iter.nThsld)>>15&1^1) << 9 code |= uint64((iter.sum[55]-iter.nThsld)>>15&1^1) << 8 code |= uint64((iter.sum[56]-iter.nThsld)>>15&1^1) << 7 code |= uint64((iter.sum[57]-iter.nThsld)>>15&1^1) << 6 code |= uint64((iter.sum[58]-iter.nThsld)>>15&1^1) << 5 code |= uint64((iter.sum[59]-iter.nThsld)>>15&1^1) << 4 code |= uint64((iter.sum[60]-iter.nThsld)>>15&1^1) << 3 code |= uint64((iter.sum[61]-iter.nThsld)>>15&1^1) << 2 code |= uint64((iter.sum[62]-iter.nThsld)>>15&1^1) << 1 code |= uint64((iter.sum[63]-iter.nThsld)>>15&1 ^ 1) } else { code = 0 } iter.preHashI = 0 iter.first = false } // code &= 0xffffffff // fmt.Printf("kmer: %03d-%s, %064b, %d\n\n", iter.idx, iter.s.Seq[iter.idx:iter.idx+iter.k], code, code) iter.preCode = code iter.idx++ return code, true } // NewHashIterator returns ntHash Iterator. func NewHashIterator(s *seq.Seq, k int, canonical bool, circular bool) (*Iterator, error) { if k < 1 { return nil, ErrInvalidK } if len(s.Seq) < k { return nil, ErrShortSeq } // iter := &Iterator{s: s, k: k, canonical: canonical, circular: circular} iter := poolIterator.Get().(*Iterator) iter.s = s iter.k = k iter.canonical = canonical iter.circular = circular iter.finished = false iter.revcomStrand = false iter.idx = 0 iter.hash = true iter.kUint = uint(k) iter.kP1 = k - 1 iter.kP1Uint = uint(k - 1) // iter.mask1 = (1 << (iter.kP1Uint << 1)) - 1 // iter.mask2 = iter.kP1Uint << 1 var err error var seq2 []byte if circular { seq2 = make([]byte, len(s.Seq), len(s.Seq)+k-1) copy(seq2, s.Seq) // do not edit original sequence seq2 = append(seq2, s.Seq[0:k-1]...) } else { seq2 = s.Seq } iter.hasher, err = nthash.NewHasher(&seq2, uint(k)) if err != nil { return nil, err } return iter, nil } // NextHash returns next ntHash. func (iter *Iterator) NextHash() (code uint64, ok bool) { code, ok = iter.hasher.Next(iter.canonical) if !ok { poolIterator.Put(iter) } iter.idx++ return code, ok } // NewKmerIterator returns k-mer code iterator. func NewKmerIterator(s *seq.Seq, k int, canonical bool, circular bool) (*Iterator, error) { if k < 1 { return nil, ErrInvalidK } if len(s.Seq) < k { return nil, ErrShortSeq } var s2 *seq.Seq if circular { s2 = s.Clone() // do not edit original sequence s2.Seq = append(s2.Seq, s.Seq[0:k-1]...) } else { s2 = s } // iter := &Iterator{s: s2, k: k, canonical: canonical, circular: circular} iter := poolIterator.Get().(*Iterator) iter.s = s2 iter.k = k iter.canonical = canonical iter.circular = circular iter.finished = false iter.revcomStrand = false iter.idx = 0 iter.length = len(s2.Seq) iter.end = iter.length - k + 1 iter.kUint = uint(k) iter.kP1 = k - 1 iter.kP1Uint = uint(k - 1) iter.mask1 = (1 << (iter.kP1Uint << 1)) - 1 iter.mask2 = iter.kP1Uint << 1 iter.first = true return iter, nil } // NextKmer returns next k-mer code. func (iter *Iterator) NextKmer() (code uint64, ok bool, err error) { if iter.finished { return 0, false, nil } if iter.idx == iter.end { if iter.canonical || iter.revcomStrand { iter.finished = true poolIterator.Put(iter) return 0, false, nil } iter.s.RevComInplace() iter.idx = 0 iter.revcomStrand = true iter.first = true } iter.e = iter.idx + iter.k iter.kmer = iter.s.Seq[iter.idx:iter.e] if !iter.first { iter.codeBase = base2bit[iter.kmer[iter.kP1]] if iter.codeBase == 4 { err = ErrIllegalBase } // compute code from previous one // code = iter.preCode&((1<<(iter.kP1Uint<<1))-1)<<2 | iter.codeBase code = (iter.preCode&iter.mask1)<<2 | iter.codeBase // compute code of revcomp kmer from previous one // iter.codeRC = (iter.codeBase^3)<<(iter.kP1Uint<<1) | (iter.preCodeRC >> 2) iter.codeRC = (iter.codeBase^3)<<(iter.mask2) | (iter.preCodeRC >> 2) } else { code, err = kmers.Encode(iter.kmer) iter.codeRC = kmers.MustRevComp(code, iter.k) iter.first = false } if err != nil { return 0, false, fmt.Errorf("encode %s: %s", iter.kmer, err) } iter.preCode = code iter.preCodeRC = iter.codeRC iter.idx++ if iter.canonical && code > iter.codeRC { code = iter.codeRC } return code, true, nil } // Next is a wrapter for NextHash and NextKmer. func (iter *Iterator) Next() (code uint64, ok bool, err error) { if iter.hash { if iter.simhash { code, ok = iter.NextSimHash() return } code, ok = iter.NextHash() return } code, ok, err = iter.NextKmer() return } // Index returns current 0-baesd index. func (iter *Iterator) Index() int { return iter.idx - 1 } bio-0.13.3/sketches/iterator_test.go000066400000000000000000000147361457355065000174240ustar00rootroot00000000000000// Copyright © 2018-2021 Wei Shen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package sketches import ( "math/rand" "testing" "github.com/shenwei356/bio/seq" "github.com/shenwei356/util/bytesize" ) func TestKmerIterator(t *testing.T) { _s := "AAGTTTGAATCATTCAACTATCTAGTTTTCAGAGAACAATGTTCTCTAAAGAATAGAAAAGAGTCATTGTGCGGTGATGATGGCGGGAAGGATCCACCTG" sequence, err := seq.NewSeq(seq.DNA, []byte(_s)) if err != nil { t.Errorf("fail to create sequence: %s", _s) } k := 10 iter, err := NewKmerIterator(sequence, k, true, false) if err != nil { t.Errorf("fail to create aa iter rator") } var code uint64 var ok bool // var idx int codes := make([]uint64, 0, 1024) for { code, ok, err = iter.Next() if err != nil { t.Error(err) } if !ok { break } // idx = iter.Index() // fmt.Printf("kmer: %d-%s, %d\n", idx, iter.s.Seq[idx:idx+k], code) codes = append(codes, code) } if len(codes) != len(_s)-k+1 { t.Errorf("k-mers number error") } } func TestHashIterator(t *testing.T) { _s := "AAGTTTGAATCATTCAACTATCTAGTTTTCAGAGAACAATGTTCTCTAAAGAATAGAAAAGAGTCATTGTGCGGTGATGATGGCGGGAAGGATCCACCTG" sequence, err := seq.NewSeq(seq.DNA, []byte(_s)) if err != nil { t.Errorf("fail to create sequence: %s", _s) } k := 10 iter, err := NewHashIterator(sequence, k, true, false) if err != nil { t.Errorf("fail to create aa iter rator") } var code uint64 var ok bool // var idx int codes := make([]uint64, 0, 1024) for { code, ok, err = iter.Next() if err != nil { t.Error(err) } if !ok { break } // idx = iter.Index() // fmt.Printf("kmer: %d-%s, %d\n", idx, iter.s.Seq[idx:idx+k], code) codes = append(codes, code) } if len(codes) != len(_s)-k+1 { t.Errorf("k-mer hashes number error") } } func TestSimHashIterator(t *testing.T) { // _s := "AAGTTTGAATCATTCAACTATCTAGTTTTCAGAGAACAATGTTCTCTAAAGAATAGAAAAGAGTCATTGTGCGGTGATGATGGCGGGAAGGATCCACCTG" _ss := []string{ // "AAGTTTGAATCATTCAACTATCTAGTTTTCAGAGAACAATGTTCTCTAAAGAATAGAAAAGAGTCATTGTGCGGTGATGATGGCGGGAAGGATCCACCTG", "GAACAATGTTCTCTAAAATTG", "GcACAATGTTCTCTAAAATTG", } for _, _s := range _ss { sequence, err := seq.NewSeq(seq.DNA, []byte(_s)) if err != nil { t.Errorf("fail to create sequence: %s", _s) } k := 21 iter, err := NewSimHashIterator(sequence, k, 5, 5, true, false) if err != nil { t.Errorf("fail to create aa iter rator") } var code uint64 var ok bool // var idx int codes := make([]uint64, 0, 1024) for { code, ok = iter.NextSimHash() if !ok { break } // idx = iter.Index() // fmt.Printf("kmer: %03d-%s, %064b, %d\n", idx, iter.s.Seq[idx:idx+k], code, code) codes = append(codes, code) } if len(codes) != len(_s)-k+1 { t.Errorf("k-mer hashes number error") } } } var benchSeqs []*seq.Seq var _code uint64 func init() { rand.Seed(11) sizes := []int{1 << 10} //, 1 << 20, 10 << 20} benchSeqs = make([]*seq.Seq, len(sizes)) var err error for i, size := range sizes { sequence := make([]byte, size) // fmt.Printf("generating pseudo DNA with length of %s ...\n", bytesize.ByteSize(size)) for j := 0; j < size; j++ { sequence[j] = bit2base[rand.Intn(4)] } benchSeqs[i], err = seq.NewSeq(seq.DNA, sequence) if err != nil { panic("should not happen") } // fmt.Println(benchSeqs[i]) } // fmt.Printf("%d DNA sequences generated\n", len(sizes)) } func BenchmarkKmerIterator(b *testing.B) { for i := range benchSeqs { size := len(benchSeqs[i].Seq) b.Run(bytesize.ByteSize(size).String(), func(b *testing.B) { var code uint64 var ok bool for j := 0; j < b.N; j++ { iter, err := NewKmerIterator(benchSeqs[i], 31, true, false) if err != nil { b.Errorf("fail to create hash iterator. seq length: %d", size) } for { code, ok, err = iter.NextKmer() if err != nil { b.Errorf("fail to get kmer code: %d-%s", iter.Index(), benchSeqs[i].Seq[iter.Index():iter.Index()+31]) } if !ok { break } _code = code } } }) } } func BenchmarkHashIterator(b *testing.B) { for i := range benchSeqs { size := len(benchSeqs[i].Seq) b.Run(bytesize.ByteSize(size).String(), func(b *testing.B) { var code uint64 var ok bool for j := 0; j < b.N; j++ { iter, err := NewHashIterator(benchSeqs[i], 31, true, false) if err != nil { b.Errorf("fail to create hash iterator. seq length: %d", size) } for { code, ok = iter.NextHash() if !ok { break } _code = code } } }) } } func BenchmarkSimHashIterator(b *testing.B) { for i := range benchSeqs { size := len(benchSeqs[i].Seq) b.Run(bytesize.ByteSize(size).String(), func(b *testing.B) { var code uint64 var ok bool for j := 0; j < b.N; j++ { iter, err := NewSimHashIterator(benchSeqs[i], 31, 5, 5, true, false) if err != nil { b.Errorf("fail to create hash iterator. seq length: %d", size) } for { code, ok = iter.NextSimHash() if !ok { break } _code = code } } }) } } func BenchmarkProteinIterator(b *testing.B) { for i := range benchSeqs { size := len(benchSeqs[i].Seq) b.Run(bytesize.ByteSize(size).String(), func(b *testing.B) { var code uint64 var ok bool for j := 0; j < b.N; j++ { iter, err := NewProteinIterator(benchSeqs[i], 10, 1, 1) if err != nil { b.Errorf("fail to create hash iterator. seq length: %d", size) } for { code, ok = iter.Next() if !ok { break } _code = code } } }) } } bio-0.13.3/sketches/kmers.go000066400000000000000000000037321457355065000156470ustar00rootroot00000000000000// Copyright © 2018-2021 Wei Shen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package sketches var base2bit = [256]uint64{ 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, 0, 1, 1, 0, 4, 4, 2, 0, 4, 4, 2, 4, 0, 0, 4, 4, 4, 0, 1, 3, 3, 0, 0, 4, 1, 4, 4, 4, 4, 4, 4, 4, 0, 1, 1, 0, 4, 4, 2, 0, 4, 4, 2, 4, 0, 0, 4, 4, 4, 0, 1, 3, 3, 0, 0, 4, 1, 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, } var bit2base = [4]byte{'A', 'C', 'G', 'T'} bio-0.13.3/sketches/sketch-protein.go000066400000000000000000000112771457355065000174700ustar00rootroot00000000000000// Copyright © 2018-2021 Wei Shen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package sketches import ( "sync" "github.com/shenwei356/bio/seq" "github.com/twotwotwo/sorts" "github.com/zeebo/wyhash" ) // ProteinMinimizerSketch is a protein k-mer minimizer iterator type ProteinMinimizerSketch struct { s *seq.Seq // amino acid k int end0 int idx int // ---------------------- skip bool w int end int r int // L-s i, mI int mV uint64 preMinIdx int buf []IdxValue i2v IdxValue flag bool t, b, e int } var poolProteinMinimizerSketch = &sync.Pool{New: func() interface{} { return &ProteinMinimizerSketch{} }} // NewProteinMinimizerSketch returns a ProteinMinimizerSketch func NewProteinMinimizerSketch(S *seq.Seq, k int, codonTable int, frame int, w int) (*ProteinMinimizerSketch, error) { if k < 1 { return nil, ErrInvalidK } if len(S.Seq) < k*3 { return nil, ErrShortSeq } if w < 1 || w > (1<<31)-1 { return nil, ErrInvalidW } if len(S.Seq) < k*3+w-1 { return nil, ErrShortSeq } // s := &ProteinMinimizerSketch{s0: S, k: k, w: w} s := poolProteinMinimizerSketch.Get().(*ProteinMinimizerSketch) s.k = k s.w = w var err error if S.Alphabet != seq.Protein { s.s, err = S.Translate(codonTable, frame, false, false, true, false) if err != nil { return nil, err } } else { s.s = S } s.idx = 0 s.end0 = len(s.s.Seq) - k s.skip = w == 1 s.end = len(s.s.Seq) - 1 s.r = w - 1 // L-k s.buf = make([]IdxValue, 0, w) s.preMinIdx = -1 return s, nil } // Next returns next hash value func (s *ProteinMinimizerSketch) Next() (code uint64, ok bool) { for { // if s.idx > s.end { // return 0, false // } if s.idx > s.end0 { poolProteinIterator.Put(s) return 0, false } code = wyhash.Hash(s.s.Seq[s.idx:s.idx+s.k], 1) if s.skip { s.mI = s.idx s.idx++ return code, true } // in window if s.idx < s.r { s.buf = append(s.buf, IdxValue{Idx: s.idx, Val: code}) s.idx++ continue } // end of w if s.idx == s.r { s.buf = append(s.buf, IdxValue{Idx: s.idx, Val: code}) // sort.Sort(idxValues(s.buf)) // sort sorts.Quicksort(idxValues(s.buf)) // sort s.i2v = s.buf[0] s.mI, s.mV = s.i2v.Idx, s.i2v.Val s.preMinIdx = s.mI s.idx++ return s.i2v.Val, true } // find min k-mer // remove k-mer not in this window. // have to check position/index one by one for s.i, s.i2v = range s.buf { if s.i2v.Idx == s.idx-s.w { if s.i < s.r { copy(s.buf[s.i:s.r], s.buf[s.i+1:]) } // happen to be at the end s.buf = s.buf[:s.r] break } } // add new k-mer s.flag = false // using binary search, faster han linear search s.b, s.e = 0, s.r-1 for { s.t = s.b + (s.e-s.b)/2 if code < s.buf[s.t].Val { s.e = s.t - 1 // end search here if s.e <= s.b { s.flag = true s.i = s.b break } } else { s.b = s.t + 1 // start here if s.b >= s.r { s.flag = false break } if s.b >= s.e { s.flag = true s.i = s.e // right here break } } } if !s.flag { // it's the biggest one, append to the end s.buf = append(s.buf, IdxValue{s.idx, code}) } else { if code >= s.buf[s.i].Val { // have to check again s.i++ } s.buf = append(s.buf, blankI2V) // append one element copy(s.buf[s.i+1:], s.buf[s.i:s.r]) // move right s.buf[s.i] = IdxValue{s.idx, code} } s.i2v = s.buf[0] if s.i2v.Idx == s.preMinIdx { // deduplicate s.idx++ continue } s.mI, s.mV = s.i2v.Idx, s.i2v.Val s.preMinIdx = s.mI s.idx++ return s.i2v.Val, true } } // Index returns current 0-baesd index. func (s *ProteinMinimizerSketch) Index() int { return s.mI } bio-0.13.3/sketches/sketch-protein_test.go000066400000000000000000000035521457355065000205240ustar00rootroot00000000000000// Copyright © 2018-2021 Wei Shen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package sketches import ( "testing" "github.com/shenwei356/bio/seq" ) func TestProteinMinimizer(t *testing.T) { _s := "AAGTTTGAATCATTCAACTATCTAGTTTTCAGAGAACAATGTTCTCTAAAGAATAGAAAAGAGTCATTGTGCGGTGATGATGGCGGGAAGGATCCACCTG" sequence, err := seq.NewSeq(seq.DNA, []byte(_s)) if err != nil { t.Errorf("fail to create sequence: %s", _s) } k := 10 w := 3 sketch, err := NewProteinMinimizerSketch(sequence, k, 1, 1, w) if err != nil { t.Errorf("fail to create minizimer sketch") } var code uint64 var ok bool // var idx int codes := make([]uint64, 0, 1024) for { code, ok = sketch.Next() if !ok { break } // idx = sketch.Index() // fmt.Printf("aa: %d-%s, %d\n", idx, sketch.s.Seq[idx:idx+k], code) codes = append(codes, code) } } bio-0.13.3/sketches/sketch.go000066400000000000000000000271641457355065000160140ustar00rootroot00000000000000// Copyright © 2018-2021 Wei Shen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package sketches import ( "fmt" "sync" "github.com/shenwei356/bio/seq" "github.com/twotwotwo/sorts" "github.com/will-rowe/nthash" ) // ErrInvalidS means s >= k. var ErrInvalidS = fmt.Errorf("kmers: invalid s-mer size") // ErrInvalidW means w < 2 or w > (1<<32)-1 var ErrInvalidW = fmt.Errorf("kmers: invalid minimimzer window") // ErrBufNil means the buffer is nil var ErrBufNil = fmt.Errorf("kmers: buffer slice is nil") // ErrBufNotEmpty means the buffer has some elements var ErrBufNotEmpty = fmt.Errorf("kmers: buffer has elements") // Sketch is a k-mer sketch iterator type Sketch struct { S []byte k int s int circular bool hasher *nthash.NTHi kMs int // k-s, just for syncmer r int // L-s idx int // current location, 0-based end int i, mI int v, mV uint64 preMinIdx int buf []IdxValue i2v IdxValue flag bool t, b, e int // ------ just for syncmer ------- hasherS *nthash.NTHi bsyncmerIdx int lateOutputThisOne bool preMinIdxs []int // ------ just for minimizer ----- skip bool minimizer bool w int } var poolSketch = &sync.Pool{New: func() interface{} { return &Sketch{} }} // NewMinimizerSketch returns a SyncmerSketch Iterator. // It returns the minHashes in all windows of w (w>=1) bp. func NewMinimizerSketch(S *seq.Seq, k int, w int, circular bool) (*Sketch, error) { if k < 1 { return nil, ErrInvalidK } if w < 1 || w > (1<<31)-1 { return nil, ErrInvalidW } if len(S.Seq) < k+w-1 { return nil, ErrShortSeq } // sketch := &Sketch{S: S.Seq, w: w, k: k, circular: circular} sketch := poolSketch.Get().(*Sketch) sketch.minimizer = true sketch.k = k sketch.w = w sketch.circular = circular sketch.skip = w == 1 var seq2 []byte if circular { seq2 = make([]byte, len(S.Seq), len(S.Seq)+k-1) copy(seq2, S.Seq) // do not edit original sequence seq2 = append(seq2, S.Seq[0:k-1]...) sketch.S = seq2 } else { seq2 = S.Seq } sketch.idx = 0 sketch.end = len(seq2) - 1 sketch.r = w - 1 // L-k var err error sketch.hasher, err = nthash.NewHasher(&seq2, uint(k)) if err != nil { return nil, err } if sketch.buf == nil { sketch.buf = make([]IdxValue, 0, w) } else { sketch.buf = sketch.buf[:0] } if sketch.preMinIdxs == nil { sketch.preMinIdxs = make([]int, 0, 8) } else { sketch.preMinIdxs = sketch.preMinIdxs[:0] } sketch.preMinIdx = -1 return sketch, nil } // NewSyncmerSketch returns a SyncmerSketch Iterator. // 1<=s<=k. func NewSyncmerSketch(S *seq.Seq, k int, s int, circular bool) (*Sketch, error) { if k < 1 { return nil, ErrInvalidK } if s > k || s == 0 { return nil, ErrInvalidS } if len(S.Seq) < k*2-s-1 { return nil, ErrShortSeq } // sketch := &Sketch{S: S.Seq, s: s, k: k, circular: circular} sketch := poolSketch.Get().(*Sketch) sketch.minimizer = false sketch.k = k sketch.s = s sketch.circular = circular sketch.skip = s == k var seq2 []byte if circular { seq2 = make([]byte, len(S.Seq), len(S.Seq)+k-1) copy(seq2, S.Seq) // do not edit original sequence seq2 = append(seq2, S.Seq[0:k-1]...) sketch.S = seq2 } else { seq2 = S.Seq } sketch.idx = 0 sketch.end = len(seq2) - 2*k + s + 1 // len(sequence) - L (2*k - s - 1) sketch.r = 2*k - s - 1 - s // L-s sketch.kMs = k - s // k-s sketch.w = k - s var err error sketch.hasher, err = nthash.NewHasher(&seq2, uint(k)) if err != nil { return nil, err } sketch.hasherS, err = nthash.NewHasher(&seq2, uint(s)) if err != nil { return nil, err } if sketch.buf == nil { sketch.buf = make([]IdxValue, 0, (k-s)<<1) } else { sketch.buf = sketch.buf[:0] } if sketch.preMinIdxs == nil { sketch.preMinIdxs = make([]int, 0, 8) } else { sketch.preMinIdxs = sketch.preMinIdxs[:0] } sketch.preMinIdx = -1 return sketch, nil } // NextMinimizer returns next minimizer. func (s *Sketch) NextMinimizer() (code uint64, ok bool) { for { if s.idx > s.end { return 0, false } // nthash of current k-mer code, ok = s.hasher.Next(true) if !ok { poolSketch.Put(s) return code, false } if s.skip { s.mI = s.idx s.idx++ return code, true } // in window if s.idx < s.r { s.buf = append(s.buf, IdxValue{Idx: s.idx, Val: code}) s.idx++ continue } // end of w if s.idx == s.r { s.buf = append(s.buf, IdxValue{Idx: s.idx, Val: code}) // sort.Sort(idxValues(s.buf)) // sort sorts.Quicksort(idxValues(s.buf)) // sort s.i2v = s.buf[0] s.mI, s.mV = s.i2v.Idx, s.i2v.Val s.preMinIdx = s.mI s.idx++ return s.i2v.Val, true } // find min k-mer // remove k-mer not in this window. // have to check position/index one by one for s.i, s.i2v = range s.buf { if s.i2v.Idx == s.idx-s.w { if s.i < s.r { copy(s.buf[s.i:s.r], s.buf[s.i+1:]) } // happen to be at the end s.buf = s.buf[:s.r] break } } // add new k-mer s.flag = false // using binary search, faster han linear search s.b, s.e = 0, s.r-1 for { s.t = s.b + (s.e-s.b)/2 if code < s.buf[s.t].Val { s.e = s.t - 1 // end search here if s.e <= s.b { s.flag = true s.i = s.b break } } else { s.b = s.t + 1 // start here if s.b >= s.r { s.flag = false break } if s.b >= s.e { s.flag = true s.i = s.e // right here break } } } if !s.flag { // it's the biggest one, append to the end s.buf = append(s.buf, IdxValue{s.idx, code}) } else { if code >= s.buf[s.i].Val { // have to check again s.i++ } s.buf = append(s.buf, blankI2V) // append one element copy(s.buf[s.i+1:], s.buf[s.i:s.r]) // move right s.buf[s.i] = IdxValue{s.idx, code} } s.i2v = s.buf[0] if s.i2v.Idx == s.preMinIdx { // deduplicate s.idx++ continue } s.mI, s.mV = s.i2v.Idx, s.i2v.Val s.preMinIdx = s.mI s.idx++ return s.i2v.Val, true } } // NextSyncmer returns next syncmer. func (s *Sketch) NextSyncmer() (code uint64, ok bool) { for { if s.idx > s.end { return 0, false } // nthash of current k-mer code, ok = s.hasher.Next(true) if !ok { poolSketch.Put(s) return code, false } // fmt.Printf("\nidx: %d, %s, %d\n", s.idx, s.S[s.idx:s.idx+s.s], code) // fmt.Printf("idx: %d, pres: %v, pre: %d\n", s.idx, s.preMinIdxs, s.preMinIdx) if s.skip { s.idx++ return code, true } if len(s.preMinIdxs) > 0 && s.idx == s.preMinIdxs[0] { // we will output this one in this round s.lateOutputThisOne = true } else { s.lateOutputThisOne = false } // find min s-mer if s.idx == 0 { for s.i = s.idx; s.i <= s.idx+s.r; s.i++ { // fmt.Printf("s: %d\n", s.i) s.v, ok = s.hasherS.Next(true) if !ok { return code, false } s.buf = append(s.buf, IdxValue{Idx: s.i, Val: s.v}) } // sort.Sort(idxValues(s.buf)) sorts.Quicksort(idxValues(s.buf)) // sort } else { // remove s-mer not in this window. // have to check position/index one by one for s.i, s.i2v = range s.buf { if s.i2v.Idx == s.idx-1 { if s.i < s.r { copy(s.buf[s.i:s.r], s.buf[s.i+1:]) } // happen to be at the end s.buf = s.buf[:s.r] break } } // add new s-mer // fmt.Printf("s: %d\n", s.idx+s.r) s.v, ok = s.hasherS.Next(true) if !ok { return code, false } s.flag = false // using binary search, faster han linear search s.b, s.e = 0, s.r-1 for { s.t = s.b + (s.e-s.b)/2 if s.v < s.buf[s.t].Val { s.e = s.t - 1 // end search here if s.e <= s.b { s.flag = true s.i = s.b break } } else { s.b = s.t + 1 // start here if s.b >= s.r { s.flag = false break } if s.b >= s.e { s.flag = true s.i = s.e // right here break } } } if !s.flag { // it's the biggest one, append to the end s.buf = append(s.buf, IdxValue{s.idx + s.r, s.v}) } else { if s.v >= s.buf[s.i].Val { // have to check again s.i++ } s.buf = append(s.buf, blankI2V) // append one element copy(s.buf[s.i+1:], s.buf[s.i:s.r]) // move right s.buf[s.i] = IdxValue{s.idx + s.r, s.v} } } s.i2v = s.buf[0] s.mI, s.mV = s.i2v.Idx, s.i2v.Val // fmt.Printf(" smer: %d: %d\n", s.mI, s.mV) // find the location of bounded syncmer if s.mI-s.idx < s.w { // syncmer at the beginning of kmer s.bsyncmerIdx = s.mI // fmt.Printf(" bIdx: start: %d\n", s.bsyncmerIdx) } else { // at the end s.bsyncmerIdx = s.mI - s.kMs // fmt.Printf(" bIdx: end: %d\n", s.bsyncmerIdx) } // ---------------------------------- // duplicated if len(s.preMinIdxs) > 0 && s.bsyncmerIdx == s.preMinIdxs[0] { // fmt.Printf(" duplicated: %d\n", s.bsyncmerIdx) if s.lateOutputThisOne { // remove the first element copy(s.preMinIdxs[0:len(s.preMinIdxs)-1], s.preMinIdxs[1:]) s.preMinIdxs = s.preMinIdxs[0 : len(s.preMinIdxs)-1] s.idx++ s.preMinIdx = s.bsyncmerIdx return code, true } s.idx++ // s.preMinIdx = s.bsyncmerIdx continue } if s.lateOutputThisOne { // remove the first element copy(s.preMinIdxs[0:len(s.preMinIdxs)-1], s.preMinIdxs[1:]) s.preMinIdxs = s.preMinIdxs[0 : len(s.preMinIdxs)-1] if s.preMinIdx != s.bsyncmerIdx { s.preMinIdxs = append(s.preMinIdxs, s.bsyncmerIdx) } // fmt.Printf(" late2: %d\n", s.preMinIdxs[0]) s.idx++ s.preMinIdx = s.bsyncmerIdx return code, true } // is it current kmer? if s.bsyncmerIdx == s.idx { // fmt.Printf(" current: %d\n", s.bsyncmerIdx) if len(s.preMinIdxs) > 0 { // remove the first element copy(s.preMinIdxs[0:len(s.preMinIdxs)-1], s.preMinIdxs[1:]) s.preMinIdxs = s.preMinIdxs[0 : len(s.preMinIdxs)-1] } s.idx++ s.preMinIdx = s.bsyncmerIdx return code, true } if s.preMinIdx != s.bsyncmerIdx { s.preMinIdxs = append(s.preMinIdxs, s.bsyncmerIdx) } // fmt.Printf(" return it later: %d\n", s.bsyncmerIdx) s.idx++ s.preMinIdx = s.bsyncmerIdx } } // Next returns next sketch func (s *Sketch) Next() (uint64, bool) { if s.minimizer { return s.NextMinimizer() } return s.NextSyncmer() } // Index returns current 0-baesd index func (s *Sketch) Index() int { if s.minimizer { return s.mI } return s.idx - 1 } // IdxValue is for storing k-mer hash and it's location when computing k-mer sketches. type IdxValue struct { Idx int // index Val uint64 // hash } var blankI2V = IdxValue{0, 0} type idxValues []IdxValue func (l idxValues) Len() int { return len(l) } func (l idxValues) Less(i int, j int) bool { return l[i].Val < l[j].Val } func (l idxValues) Swap(i int, j int) { l[i], l[j] = l[j], l[i] } bio-0.13.3/sketches/sketch_test.go000066400000000000000000000123541457355065000170460ustar00rootroot00000000000000// Copyright © 2018-2021 Wei Shen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package sketches import ( "testing" "github.com/shenwei356/bio/seq" "github.com/shenwei356/util/bytesize" ) var _syncmer uint64 var _syncmerIdx int func TestMinimizer(t *testing.T) { _s := "GGCAAGTTCGTCA" // _s := "GGCAAGTTC" sequence, err := seq.NewSeq(seq.DNA, []byte(_s)) if err != nil { t.Errorf("fail to create sequence: %s", _s) } k := 5 w := 3 sketch, err := NewMinimizerSketch(sequence, k, w, false) if err != nil { t.Errorf("fail to create minizimer sketch") } var code uint64 var ok bool var idx int codes := make([]uint64, 0, 1024) for { code, ok = sketch.NextMinimizer() if !ok { break } idx = sketch.Index() _syncmerIdx = idx _syncmer = code codes = append(codes, code) // fmt.Printf("minizimer: %d-%s, %d\n", idx, _s[idx:idx+k], code) } if len(codes) == 5 && codes[0] == 973456138564179607 && codes[1] == 2645801399420473919 && codes[2] == 1099502864234245338 && codes[3] == 6763474888237448943 && codes[4] == 2737971715116251183 { } else { t.Errorf("minizimer error") } } func TestSyncmer(t *testing.T) { _s := "GGCAAGTTCGTCATCGATC" // _s := "GGCAAGTTC" sequence, err := seq.NewSeq(seq.DNA, []byte(_s)) if err != nil { t.Errorf("fail to create sequence: %s", _s) } k := 5 s := 2 sketch, err := NewSyncmerSketch(sequence, k, s, false) if err != nil { t.Errorf("fail to create syncmer sketch") } var code uint64 var ok bool var idx int codes := make([]uint64, 0, 1024) for { code, ok = sketch.NextSyncmer() // fmt.Println(sketch.Index(), code, ok) if !ok { break } idx = sketch.Index() _syncmerIdx = idx _syncmer = code codes = append(codes, code) // fmt.Printf("syncmer: %d-%s, %d\n", idx, _s[idx:idx+k], code) } // if len(codes) == 5 && // codes[0] == 7385093395039290540 && // codes[1] == 1099502864234245338 { // } else { // t.Errorf("syncmer error") // } } func BenchmarkMinimizerSketch(b *testing.B) { for i := range benchSeqs { size := len(benchSeqs[i].Seq) b.Run(bytesize.ByteSize(size).String(), func(b *testing.B) { var code uint64 var ok bool // var n int for j := 0; j < b.N; j++ { iter, err := NewMinimizerSketch(benchSeqs[i], 31, 15, false) if err != nil { b.Errorf("fail to create minizimer sketch. seq length: %d", size) } // n = 0 for { code, ok = iter.NextMinimizer() if !ok { break } // fmt.Printf("minizimer: %d-%d\n", iter.Index(), code) _code = code // n++ } } // fmt.Printf("minizimer for %s DNA, c=%.6f\n", bytesize.ByteSize(size).String(), float64(size)/float64(n)) }) } } // go test -v -test.bench=BenchmarkSyncmerSketch -cpuprofile profile.out -test.run=damnit // go tool pprof -http=:8080 profile.out func BenchmarkSyncmerSketch(b *testing.B) { for i := range benchSeqs { size := len(benchSeqs[i].Seq) b.Run(bytesize.ByteSize(size).String(), func(b *testing.B) { var code uint64 var ok bool // var n int for j := 0; j < b.N; j++ { iter, err := NewSyncmerSketch(benchSeqs[i], 31, 16, false) if err != nil { b.Errorf("fail to create syncmer sketch. seq length: %d", size) } // n = 0 for { code, ok = iter.NextSyncmer() if !ok { break } // fmt.Printf("syncmer: %d-%d\n", iter.Index(), code) _code = code // n++ } } // fmt.Printf("syncmer for %s DNA, c=%.6f\n", bytesize.ByteSize(size).String(), float64(size)/float64(n)) }) } } func BenchmarkProteinMinimizerSketch(b *testing.B) { for i := range benchSeqs { size := len(benchSeqs[i].Seq) b.Run(bytesize.ByteSize(size).String(), func(b *testing.B) { var code uint64 var ok bool // var n int for j := 0; j < b.N; j++ { iter, err := NewProteinMinimizerSketch(benchSeqs[i], 10, 1, 1, 5) if err != nil { b.Errorf("fail to create minizimer sketch. seq length: %d", size) } // n = 0 for { code, ok = iter.Next() if !ok { break } // fmt.Printf("minizimer: %d-%d\n", iter.Index(), code) _code = code // n++ } } // fmt.Printf("minizimer for %s Protein, c=%.6f\n", bytesize.ByteSize(size).String(), float64(size)/float64(n)) }) } } bio-0.13.3/taxdump/000077500000000000000000000000001457355065000140435ustar00rootroot00000000000000bio-0.13.3/taxdump/README.md000077500000000000000000000005141457355065000153250ustar00rootroot00000000000000# taxdump [![Go Reference](https://pkg.go.dev/badge/github.com/shenwei356/bio/taxdump.svg)](https://pkg.go.dev/github.com/shenwei356/bio/taxdump) This package provides querying manipulations from NCBI Taxonomy taxdump files. ## History This package was originally maintained in [unikmer](https://github.com/shenwei356/unikmer). bio-0.13.3/taxdump/taxonomy.go000066400000000000000000000373731457355065000162650ustar00rootroot00000000000000// Copyright © 2018-2021 Wei Shen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package taxdump import ( "bufio" "errors" "fmt" "strconv" "strings" "sync" "github.com/shenwei356/xopen" ) // Taxonomy holds relationship of taxon in a taxonomy. type Taxonomy struct { file string rootNode uint32 Nodes map[uint32]uint32 // child -> parent DelNodes map[uint32]struct{} MergeNodes map[uint32]uint32 // from -> to Names map[uint32]string taxid2rankid map[uint32]uint8 // taxid -> rank id ranks []string // rank id -> rank Ranks map[string]interface{} hasRanks bool hasDelNodes bool hasMergeNodes bool hasNames bool cacheLCA bool lcaCache sync.Map maxTaxid uint32 } // ErrIllegalColumnIndex means column index is 0 or negative. var ErrIllegalColumnIndex = errors.New("taxdump: illegal column index, positive integer needed") // ErrRankNotLoaded means you should reate load Taxonomy with NewTaxonomyWithRank before calling some methods. var ErrRankNotLoaded = errors.New("taxdump: taxonomic ranks not loaded, please call: NewTaxonomyWithRank") // ErrNamesNotLoaded means you should call LoadNames before using taxonomy names. var ErrNamesNotLoaded = errors.New("taxdump: taxonomy names not loaded, please call: LoadNames") // ErrTooManyRanks means number of ranks exceed limit of 255 var ErrTooManyRanks = errors.New("taxdump: number of ranks exceed limit of 255") // ErrUnkownRank indicate an unknown rank var ErrUnkownRank = errors.New("taxdump: unknown rank") // NewTaxonomyFromNCBI parses nodes relationship from nodes.dmp // from ftp://ftp.ncbi.nih.gov/pub/taxonomy/taxdump.tar.gz . func NewTaxonomyFromNCBI(file string) (*Taxonomy, error) { return NewTaxonomy(file, 1, 3) } // NewTaxonomy only loads nodes from nodes.dmp file. func NewTaxonomy(file string, childColumn int, parentColumn int) (*Taxonomy, error) { if childColumn < 1 || parentColumn < 1 { return nil, ErrIllegalColumnIndex } maxColumns := maxInt(childColumn, parentColumn) fh, err := xopen.Ropen(file) if err != nil { return nil, fmt.Errorf("taxdump: %s", err) } defer func() { fh.Close() }() nodes := make(map[uint32]uint32, 1024) n := maxColumns + 1 childColumn-- parentColumn-- items := make([]string, n) scanner := bufio.NewScanner(fh) var _child, _parent int var child, parent uint32 var maxTaxid uint32 var root uint32 for scanner.Scan() { stringSplitN(scanner.Text(), "\t", n, &items) if len(items) < maxColumns { continue } _child, err = strconv.Atoi(items[childColumn]) if err != nil { continue } _parent, err = strconv.Atoi(items[parentColumn]) if err != nil { continue } child, parent = uint32(_child), uint32(_parent) // ---------------------------------- nodes[child] = parent if child == parent { root = child } if child > maxTaxid { maxTaxid = child } } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("taxdump: %s", err) } return &Taxonomy{file: file, Nodes: nodes, rootNode: root, maxTaxid: maxTaxid}, nil } // NewTaxonomyWithRankFromNCBI parses Taxonomy from nodes.dmp // from ftp://ftp.ncbi.nih.gov/pub/taxonomy/taxdump.tar.gz . func NewTaxonomyWithRankFromNCBI(file string) (*Taxonomy, error) { return NewTaxonomyWithRank(file, 1, 3, 5) } // NewTaxonomyWithRank loads nodes and ranks from nodes.dmp file. func NewTaxonomyWithRank(file string, childColumn int, parentColumn int, rankColumn int) (*Taxonomy, error) { if childColumn < 1 || parentColumn < 1 || rankColumn < 1 { return nil, ErrIllegalColumnIndex } maxColumns := maxInt(childColumn, parentColumn, rankColumn) taxid2rankid := make(map[uint32]uint8, 1024) ranks := make([]string, 0, 128) rank2rankid := make(map[string]int, 128) ranksMap := make(map[string]interface{}, 128) fh, err := xopen.Ropen(file) if err != nil { return nil, fmt.Errorf("taxdump: %s", err) } defer func() { fh.Close() }() nodes := make(map[uint32]uint32, 1024) n := maxColumns + 1 childColumn-- parentColumn-- rankColumn-- items := make([]string, n) scanner := bufio.NewScanner(fh) var _child, _parent int var child, parent uint32 var maxTaxid uint32 var rank string var ok bool var rankid int var root uint32 for scanner.Scan() { stringSplitN(scanner.Text(), "\t", n, &items) if len(items) < maxColumns { continue } _child, err = strconv.Atoi(items[childColumn]) if err != nil { continue } _parent, err = strconv.Atoi(items[parentColumn]) if err != nil { continue } child, parent, rank = uint32(_child), uint32(_parent), items[rankColumn] // ---------------------------------- nodes[child] = parent if child == parent { root = child } if child > maxTaxid { maxTaxid = child } if rankid, ok = rank2rankid[rank]; ok { taxid2rankid[child] = uint8(rankid) } else { ranks = append(ranks, rank) if len(ranks) > 255 { return nil, ErrTooManyRanks } rank2rankid[rank] = len(ranks) - 1 taxid2rankid[child] = uint8(len(ranks) - 1) ranksMap[rank] = struct{}{} } } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("taxdump: %s", err) } return &Taxonomy{file: file, Nodes: nodes, rootNode: root, maxTaxid: maxTaxid, taxid2rankid: taxid2rankid, ranks: ranks, hasRanks: true, Ranks: ranksMap}, nil } // TaxId checks if a TaxId is valid in the database. // If being merged, the new TaxId will be returned. // If the TaxId is not found or deleted, false will be returned. func (t *Taxonomy) TaxId(taxid uint32) (uint32, bool) { _, ok := t.Nodes[taxid] if ok { return taxid, true } // check if it was merged var newtaxid uint32 if newtaxid, ok = t.MergeNodes[taxid]; ok { return newtaxid, true } // // taxid not found, check if it was deleted // if _, ok = t.DelNodes[taxid]; ok { // return taxid, false // } // not found return taxid, false } // Name returns the name of a TaxId. // If being merged, the name of the new TaxId will be returned. // If the TaxId is not found or deleted, empty will be returned. func (t *Taxonomy) Name(taxid uint32) string { name, ok := t.Names[taxid] if ok { return name } // check if it was merged var newtaxid uint32 if newtaxid, ok = t.MergeNodes[taxid]; ok { return t.Names[newtaxid] } return "" } // Rank returns the rank of a taxid. // If being merged, the rank of the new TaxId will be returned. // If the TaxId is not found or deleted, empty will be returned. func (t *Taxonomy) Rank(taxid uint32) string { if !t.hasRanks { panic(ErrRankNotLoaded) } if i, ok := t.taxid2rankid[taxid]; ok { return t.ranks[int(i)] } if newTaxid, ok := t.MergeNodes[taxid]; ok { // merged if i, ok := t.taxid2rankid[newTaxid]; ok { return t.ranks[int(i)] } } return "" // taxid not found or deleted } // AtOrBelowRank returns whether a taxid is at or below one rank. func (t *Taxonomy) AtOrBelowRank(taxid uint32, rank string) bool { if !t.hasRanks { panic(ErrRankNotLoaded) } var ok bool var i uint8 rank = strings.ToLower(rank) if _, ok = t.Ranks[rank]; !ok { return false } if i, ok = t.taxid2rankid[taxid]; ok { if rank == t.ranks[int(i)] { return true } } // continue searching towards to root node var child, parent, newtaxid uint32 child = taxid for { parent, ok = t.Nodes[child] if !ok { // taxid not found // // check if it was deleted // if _, ok = t.DelNodes[child]; ok { // return false // } // check if it was merged if newtaxid, ok = t.MergeNodes[child]; ok { child = newtaxid if rank == t.ranks[t.taxid2rankid[child]] { return true } parent = t.Nodes[child] } else { // not found return false } } if parent == 1 { break } if rank == t.ranks[t.taxid2rankid[parent]] { return true } child = parent } return false } // LoadNamesFromNCBI loads scientific names from NCBI names.dmp func (t *Taxonomy) LoadNamesFromNCBI(file string) error { return t.LoadNames(file, 1, 3, 7, "scientific name") } // LoadNames loads names. func (t *Taxonomy) LoadNames(file string, taxidColumn int, nameColumn int, typeColumn int, _type string) error { if taxidColumn < 1 || nameColumn < 1 || typeColumn < 1 { return ErrIllegalColumnIndex } maxColumns := maxInt(nameColumn, nameColumn, typeColumn) fh, err := xopen.Ropen(file) if err != nil { return fmt.Errorf("taxdump: %s", err) } defer func() { fh.Close() }() m := make(map[uint32]string, 1024) n := maxColumns + 1 taxidColumn-- nameColumn-- typeColumn-- filterByType := _type != "" items := make([]string, n) scanner := bufio.NewScanner(fh) var taxid uint64 for scanner.Scan() { stringSplitN(scanner.Text(), "\t", n, &items) if len(items) < maxColumns { continue } if filterByType && items[typeColumn] != _type { continue } taxid, err = strconv.ParseUint(items[taxidColumn], 10, 32) if err != nil { continue } m[uint32(taxid)] = items[nameColumn] } if err := scanner.Err(); err != nil { return fmt.Errorf("taxdump: %s", err) } t.Names = m t.hasNames = true return nil } // LoadMergedNodesFromNCBI loads merged nodes from NCBI merged.dmp. func (t *Taxonomy) LoadMergedNodesFromNCBI(file string) error { return t.LoadMergedNodes(file, 1, 3) } // LoadMergedNodes loads merged nodes. func (t *Taxonomy) LoadMergedNodes(file string, oldColumn int, newColumn int) error { if oldColumn < 1 || newColumn < 1 { return ErrIllegalColumnIndex } maxColumns := maxInt(oldColumn, newColumn) fh, err := xopen.Ropen(file) if err != nil { if err == xopen.ErrNoContent { return nil } return fmt.Errorf("taxdump: %s", err) } defer func() { fh.Close() }() m := make(map[uint32]uint32, 1024) n := maxColumns + 1 oldColumn-- newColumn-- items := make([]string, n) scanner := bufio.NewScanner(fh) var from, to int for scanner.Scan() { stringSplitN(scanner.Text(), "\t", n, &items) if len(items) < maxColumns { continue } from, err = strconv.Atoi(items[oldColumn]) if err != nil { continue } to, err = strconv.Atoi(items[newColumn]) if err != nil { continue } m[uint32(from)] = uint32(to) } if err := scanner.Err(); err != nil { return fmt.Errorf("taxdump: %s", err) } t.MergeNodes = m t.hasMergeNodes = true return nil } // LoadDeletedNodesFromNCBI loads deleted nodes from NCBI delnodes.dmp. func (t *Taxonomy) LoadDeletedNodesFromNCBI(file string) error { return t.LoadDeletedNodes(file, 1) } // LoadDeletedNodes loads deleted nodes. func (t *Taxonomy) LoadDeletedNodes(file string, column int) error { if column < 1 { return ErrIllegalColumnIndex } fh, err := xopen.Ropen(file) if err != nil { if err == xopen.ErrNoContent { return nil } return fmt.Errorf("taxdump: %s", err) } defer func() { fh.Close() }() m := make(map[uint32]struct{}, 1024) maxColumns := column n := maxColumns + 1 column-- items := make([]string, n) scanner := bufio.NewScanner(fh) var id int for scanner.Scan() { stringSplitN(scanner.Text(), "\t", n, &items) if len(items) < maxColumns { continue } id, err = strconv.Atoi(items[column]) if err != nil { continue } m[uint32(id)] = struct{}{} } if err := scanner.Err(); err != nil { return fmt.Errorf("taxdump: %s", err) } t.DelNodes = m t.hasDelNodes = true return nil } // MaxTaxid returns maximum taxid func (t *Taxonomy) MaxTaxid() uint32 { return t.maxTaxid } // CacheLCA tells to cache every LCA query result func (t *Taxonomy) CacheLCA() { t.cacheLCA = true } // LCA returns the Lowest Common Ancestor of two nodes, 0 for unknown taxid. func (t *Taxonomy) LCA(a uint32, b uint32) uint32 { if a == 0 || b == 0 { return 0 } if a == b { return a } // check cache var ok bool var query uint64 var tmp interface{} if t.cacheLCA { query = pack2uint32(a, b) tmp, ok = t.lcaCache.Load(query) if ok { return tmp.(uint32) } } mA := make(map[uint32]struct{}, 16) var child, parent, newTaxid uint32 var flag bool child = a for { parent, ok = t.Nodes[child] if !ok { flag = false if newTaxid, ok = t.MergeNodes[child]; ok { // merged child = newTaxid // update child parent, ok = t.Nodes[child] if ok { flag = true } } if !flag { if t.cacheLCA { t.lcaCache.Store(query, uint32(0)) } return 0 } } if parent == child { // root mA[parent] = struct{}{} break } if parent == b { // b is ancestor of a if t.cacheLCA { t.lcaCache.Store(query, b) } return b } mA[parent] = struct{}{} child = parent } child = b for { parent, ok = t.Nodes[child] if !ok { flag = false if newTaxid, ok = t.MergeNodes[child]; ok { // merged child = newTaxid // update child parent, ok = t.Nodes[child] if ok { flag = true } } if !flag { if t.cacheLCA { t.lcaCache.Store(query, uint32(0)) } return 0 } } if parent == child { // root break } if parent == a { // a is ancestor of b if t.cacheLCA { t.lcaCache.Store(query, a) } return a } if _, ok = mA[parent]; ok { if t.cacheLCA { t.lcaCache.Store(query, parent) } return parent } child = parent } return t.rootNode } // LineageNames returns nodes' names of the the complete lineage. func (t *Taxonomy) LineageNames(taxid uint32) []string { taxids := t.LineageTaxIds(taxid) if taxids == nil { return nil } if !t.hasNames { panic(ErrNamesNotLoaded) } names := make([]string, len(taxids)) for i, tax := range taxids { names[i] = t.Names[tax] } return names } // LineageTaxIds returns nodes' taxid of the the complete lineage. func (t *Taxonomy) LineageTaxIds(taxid uint32) []uint32 { var child, parent, newtaxid uint32 var ok bool child = taxid list := make([]uint32, 0, 16) for { parent, ok = t.Nodes[child] if !ok { // taxid not found // // check if it was deleted // if _, ok = t.DelNodes[child]; ok { // return nil // } // check if it was merged if newtaxid, ok = t.MergeNodes[child]; ok { child = newtaxid parent = t.Nodes[child] } else { // not found return nil } } list = append(list, child) if parent == 1 { break } child = parent } // reversing for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 { list[i], list[j] = list[j], list[i] } return list } func pack2uint32(a uint32, b uint32) uint64 { if a < b { return (uint64(a) << 32) | uint64(b) } return (uint64(b) << 32) | uint64(a) } func minInt(a int, vals ...int) int { min := a for _, v := range vals { if v < min { min = v } } return min } func maxInt(a int, vals ...int) int { min := a for _, v := range vals { if v > min { min = v } } return min } func stringSplitN(s string, sep string, n int, a *[]string) { if a == nil { tmp := make([]string, n) a = &tmp } n-- i := 0 for i < n { m := strings.Index(s, sep) if m < 0 { break } (*a)[i] = s[:m] s = s[m+len(sep):] i++ } (*a)[i] = s (*a) = (*a)[:i+1] } bio-0.13.3/taxdump/taxonomy_test.go000066400000000000000000000027211457355065000173110ustar00rootroot00000000000000// Copyright © 2018-2021 Wei Shen // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package taxdump import ( "testing" ) func TestPackTwoTaxids(t *testing.T) { type Test struct { a, b uint32 c uint64 } tests := []Test{ {0, 0, 0}, {1, 1, 1<<32 + 1}, {2, 1, 1<<32 + 2}, } for _, test := range tests { c := pack2uint32(test.a, test.b) if c != test.c { t.Errorf("pack2uint32 error: %d != %d ", c, test.c) } } } bio-0.13.3/util/000077500000000000000000000000001457355065000133365ustar00rootroot00000000000000bio-0.13.3/util/README.md000066400000000000000000000002211457355065000146100ustar00rootroot00000000000000# util Some utilities - LengthStats: caculating statistics of sequence length, including, Min, Max, Mean, Median, N50, L50, Q1, Q2, Q3, et al. bio-0.13.3/util/length-stats.go000066400000000000000000000164361457355065000163140ustar00rootroot00000000000000package util import ( "math" "github.com/twotwotwo/sorts" ) // LengthStats accepts sequence lengths and calculate N50 et al.. type LengthStats struct { lens map[uint64]uint64 sum uint64 count uint64 min, max uint64 counts [][2]uint64 // length and it's number accCounts [][2]uint64 // accumulative count // accLens [][2]uint64 // accumulative length sorted bool n50Calculated bool l50 int nXCalculated bool lX int } // NewLengthStats initializes a LengthStats func NewLengthStats() *LengthStats { return &LengthStats{lens: make(map[uint64]uint64, 256), min: 1<<64 - 1} } // Add adds a new length func (stats *LengthStats) Add(length uint64) { stats.count++ stats.sum += length stats.lens[length]++ if length > stats.max { stats.max = length } if length < stats.min { stats.min = length } stats.sorted = false } type lengthCount [][2]uint64 func (lc lengthCount) Len() int { return len(lc) } func (lc lengthCount) Less(i, j int) bool { return lc[i][0] < lc[j][0] } func (lc lengthCount) Swap(i, j int) { lc[i], lc[j] = lc[j], lc[i] } func (stats *LengthStats) sort() { if len(stats.lens) == 0 { stats.sorted = true return } stats.counts = make([][2]uint64, 0, len(stats.lens)) for length, count := range stats.lens { stats.counts = append(stats.counts, [2]uint64{length, count}) } // sort.Slice(stats.counts, func(i, j int) bool { return stats.counts[i][0] < stats.counts[j][0] }) sorts.Quicksort(lengthCount(stats.counts)) stats.accCounts = make([][2]uint64, len(stats.lens)) stats.accCounts[0] = [2]uint64{stats.counts[0][0], stats.counts[0][1]} if len(stats.counts) > 1 { for i, data := range stats.counts[1:] { stats.accCounts[i+1] = [2]uint64{data[0], data[1] + stats.accCounts[i][1]} } } // stats.accLens = make([][2]uint64, len(stats.lens)) // for i, data := range stats.counts { // if i == 0 { // stats.accLens[i] = [2]uint64{data[0], data[0] * data[1]} // } else { // stats.accLens[i] = [2]uint64{data[0], data[0]*data[1] + stats.accLens[i-1][1]} // } // } stats.sorted = true } // Count returns number of elements func (stats *LengthStats) Count() uint64 { return stats.count } // Min returns the minimum length func (stats *LengthStats) Min() uint64 { if stats.count == 0 { return 0 } return stats.min } // Max returns the maxinimum length func (stats *LengthStats) Max() uint64 { return stats.max } // Sum returns the length sum func (stats *LengthStats) Sum() uint64 { return stats.sum } // Mean returns mean func (stats *LengthStats) Mean() float64 { return float64(stats.sum) / float64(stats.count) } // Q2 returns Q2 func (stats *LengthStats) Q2() float64 { return stats.Median() } // Median returns median func (stats *LengthStats) Median() float64 { if !stats.sorted { stats.sort() } if len(stats.counts) == 0 { return 0 } if len(stats.counts) == 1 { return float64(stats.counts[0][0]) } even := stats.count&1 == 0 // %2 == 0 var iMedianL, iMedianR uint64 // 0-based if even { iMedianL = uint64(stats.count/2) - 1 // 3 iMedianR = uint64(stats.count / 2) // 4 } else { iMedianL = uint64(stats.count / 2) } return stats.getValue(even, iMedianL, iMedianR) } // Q1 returns Q1 func (stats *LengthStats) Q1() float64 { if !stats.sorted { stats.sort() } if len(stats.counts) == 0 { return 0 } if len(stats.counts) == 1 { return float64(stats.counts[0][0]) } even := stats.count&1 == 0 // %2 == 0 var iMedianL, iMedianR uint64 // 0-based var n uint64 if even { n = stats.count / 2 } else { n = (stats.count + 1) / 2 } even = n%2 == 0 if even { iMedianL = uint64(n/2) - 1 iMedianR = uint64(n / 2) } else { iMedianL = uint64(n / 2) } return stats.getValue(even, iMedianL, iMedianR) } // Q3 returns Q3 func (stats *LengthStats) Q3() float64 { if !stats.sorted { stats.sort() } if len(stats.counts) == 0 { return 0 } if len(stats.counts) == 1 { return float64(stats.counts[0][0]) } even := stats.count&1 == 0 // %2 == 0 var iMedianL, iMedianR uint64 // 0-based var mean, n uint64 if even { n = stats.count / 2 mean = n } else { n = (stats.count + 1) / 2 mean = stats.count / 2 } even = n%2 == 0 if even { iMedianL = uint64(n/2) - 1 + mean iMedianR = uint64(n/2) + mean } else { iMedianL = uint64(n/2) + mean } return stats.getValue(even, iMedianL, iMedianR) } func (stats *LengthStats) getValue(even bool, iMedianL uint64, iMedianR uint64) float64 { var accCount uint64 var flag bool var prev uint64 for _, data := range stats.accCounts { accCount = data[1] if flag { // the middle two having different length. // example: 1, 2, 3, 4 or 1, 2 return float64(data[0]+prev) / 2 } if accCount >= iMedianL+1 { if even { if accCount >= iMedianR+1 { // having >=2 of same length in the middle. // example: 2, 2, 2, 3, 3, 4, 8, 8 return float64(data[0]) } flag = true prev = data[0] } else { // right here return float64(data[0]) } } } // never happen // panic("bio/util: should never happen") return 0 } func (stats *LengthStats) Percentile(percent float64) float64 { if percent <= 0 || percent > 100 { panic("invalid percentile") } if !stats.sorted { stats.sort() } if len(stats.counts) == 0 { return 0 } if len(stats.counts) == 1 { return float64(stats.counts[0][0]) } i0 := float64(stats.count) * percent / 100 i := math.Floor(i0) even := math.Abs(i0-i) > 0.001 var iMedianL, iMedianR uint64 // 0-based if even { iMedianL = uint64(i) - 1 iMedianR = uint64(i) } else { iMedianL = uint64(i - 1) } return stats.getValue(even, iMedianL, iMedianR) } // N50 returns N50 func (stats *LengthStats) N50() uint64 { if !stats.sorted { stats.sort() } if len(stats.counts) == 0 { return 0 } if len(stats.counts) == 1 { stats.l50 = 1 return stats.counts[0][0] } var sumLen float64 var data [2]uint64 var halfSum = float64(stats.sum) / 2 for i := len(stats.counts) - 1; i >= 0; i-- { data = stats.counts[i] sumLen += float64(data[0] * data[1]) if sumLen >= halfSum { stats.l50 = len(stats.counts) - i stats.n50Calculated = true return data[0] } } // never happen // panic("bio/util: should never happen") return 0 } // NX returns something like N50, where X could be a number in the range of [0, 100] func (stats *LengthStats) NX(n float64) uint64 { if n < 0 || n > 100 { panic("NX: where X should be in range of [0, 100]") } if !stats.sorted { stats.sort() } if len(stats.counts) == 0 { return 0 } if len(stats.counts) == 1 { return stats.counts[0][0] } var sumLen float64 var data [2]uint64 var boundary = float64(stats.sum) * n / 100 for i := len(stats.counts) - 1; i >= 0; i-- { data = stats.counts[i] sumLen += float64(data[0] * data[1]) if sumLen >= boundary { stats.lX = i + 1 stats.nXCalculated = true return data[0] } } // never happen // panic("bio/util: should never happen") return 0 } // L50 returns L50 func (stats *LengthStats) L50() int { if !stats.sorted { stats.N50() } if !stats.n50Calculated { stats.N50() } return stats.l50 } // LX returns something like L50, where X could be a number in the range of [0, 100] func (stats *LengthStats) LX(n float64) int { if !stats.sorted { stats.NX(n) } if !stats.nXCalculated { stats.NX(n) } return stats.lX } bio-0.13.3/util/length-stats_test.go000066400000000000000000000043761457355065000173530ustar00rootroot00000000000000package util import ( "math/rand" "testing" ) type testCaseLengthStats struct { data []uint64 q1, median, q3 float64 n50 uint64 l50 int min, max uint64 } var cases = []testCaseLengthStats{ testCaseLengthStats{ data: []uint64{}, median: 0, q1: 0, q3: 0, n50: 0, l50: 0, min: 0, max: 0, }, testCaseLengthStats{ data: []uint64{2}, median: 2, q1: 2, q3: 2, n50: 2, l50: 1, min: 2, max: 2, }, testCaseLengthStats{ data: []uint64{1, 2}, median: 1.5, q1: 1, q3: 2, n50: 2, l50: 1, min: 1, max: 2, }, testCaseLengthStats{ data: []uint64{1, 2, 3}, median: 2, q1: 1.5, q3: 2.5, n50: 3, l50: 1, min: 1, max: 3, }, testCaseLengthStats{ data: []uint64{1, 2, 3, 4}, median: 2.5, q1: 1.5, q3: 3.5, n50: 3, l50: 2, min: 1, max: 4, }, testCaseLengthStats{ data: []uint64{2, 3, 4, 5, 6, 7, 8, 9}, median: 5.5, q1: 3.5, q3: 7.5, n50: 7, l50: 3, min: 2, max: 9, }, } func Test(t *testing.T) { for i, _case := range cases { rand.Shuffle(len(_case.data), func(i, j int) { _case.data[i], _case.data[j] = _case.data[j], _case.data[i] }) stats := NewLengthStats() for _, l := range _case.data { stats.Add(l) } if stats.Count() != uint64(len(_case.data)) { t.Errorf("case %d: count mismatch", i) } min := stats.Min() if min != _case.min { t.Errorf("case %d: min mismatch: %d != %d", i, min, _case.min) } max := stats.Max() if max != _case.max { t.Errorf("case %d: max mismatch: %d != %d", i, max, _case.max) } median := stats.Median() if median != _case.median { t.Errorf("case %d: median mismatch: %f != %f", i, median, _case.median) } q1 := stats.Q1() if q1 != _case.q1 { t.Errorf("case %d: q1 mismatch: %f != %f", i, q1, _case.q1) } q3 := stats.Q3() if q1 != _case.q1 { t.Errorf("case %d: q3 mismatch: %f != %f", i, q3, _case.q3) } n50 := stats.N50() if n50 != _case.n50 { t.Errorf("case %d: n50 mismatch: %d != %d", i, n50, _case.n50) } l50 := stats.L50() if l50 != _case.l50 { t.Errorf("case %d: l50 mismatch: %d != %d", i, l50, _case.l50) } } }