IsoSpecR/0000755000176200001440000000000013747131146011750 5ustar liggesusersIsoSpecR/NAMESPACE0000644000176200001440000000022013507710304013152 0ustar liggesusers# Generated by roxygen2: do not edit by hand export(IsoSpecify) export(custom_isotopes_example) importFrom(Rcpp,sourceCpp) useDynLib(IsoSpecR) IsoSpecR/LICENCE0000644000176200001440000000010413507710304012721 0ustar liggesusersYEAR: 2015-2018 COPYRIGHT HOLDER: Michał Startek, Mateusz Łącki IsoSpecR/data/0000755000176200001440000000000013507710304012652 5ustar liggesusersIsoSpecR/data/isotopicData.rda0000644000176200001440000003071013507710304015766 0ustar liggesusersBZh91AY&SY4U/ (}_P( RzS!zh7mv|}tZTC筻[e tWEwU^(Ibݵd톻4]vhI4 AL=25Dɀ4 '$ Oښd1=?AiSi& hfѓ&L&bi҈&`11LCF&LLhFL5Si0#&D)22hi @ҟOѠ4hT44Q&M!1OJyF5O52z=&y?I HmǝIjy@mG&lMGB; kJzHoXPuS 5 [hΡďWYONHm399Q_rVEGCkSۣ O9)25Q 0p,@6|!U,!!4hj JfB$i, ͷٗ3 dž$=Srd%<4u(N3z)/LaX\ץCަ e.ZX!E`w.'vb"v,ZE9IY`qȤ0bɐ5s \WWWL`ԋ𤈭JԪ*RdMMN `ڑ9$B&a\DI3Ui(CU*P&CNB±1bѴFF H eUUe dK˅X+\+*]B]^N {UUUb P*`k0g*V5RCUSTi"fY,uujUR"4ZX*v$]NXR.^dLȹfbP!++\/Ed Y`/B.jzTHjݎo478AF @ L4WT" E|1Pto,PS1p"8 5>;cP" "/à;XLD uDEPEC"i({,uEt_j5,Ry k0Z*^*‡WyA-44]vx縰! Ɂv< KI|W!96lyA.\=o;{lz'^PsQEKn%SeQz)AQ*!]W=M H}_ypB-1߶a\;)+S`t2HV(2~_:a<"`R̆JfPvA!eMCv3o9vh_.M;y9/6۾I&\ ݋m*U /\:6V؈&+^&YAcyc'D*,̔t"K7Td2E8f yG-j&kUec Fe vH#4Z\H6fzR0Oàv #/7 coc;CJsb8&H| t|шㅘ|q@V<95Xuvg:x!Hw\::)4Ck&>^=e`J!FT;56PG鸊%2S@$m̚%0:SnyJoBm^t(v{oq/.ZVDA;|ұև\Udp~ ]밮oOm&)Ơ ktt8}I-30AS74:R<¶mͳF \`;PwE^V>&N8,N]̫+:aj2F4ck >zk+|N6|oAp txX'ӉNeh[%al.;cu>?I/LQ4:ȋ4_{Dh08e|Dh"}(.7JkQ')72(h{ 0"v8>ma>5Ekܱjʀ2C1}ovk:ҬsXS|,Iid\pb?#b.+<+qJZ%qv!/^'}ߡd?..K$I-2?`.&Rfp13yU&}刚p(R@30Yin:sgN_2)ބ8r[oxz G?S7-&pT9 8*p2Y1 ͅTQ^;\8wals pD{@m1*aJųLQ޹b7v9lຣEovq>_rDͻR+9L ;J^~p=[;Q )~5"n>VjIagYFKI34 @-%5HF4G"kχTSDz*XWV,8RF ]EԔ0U44b` D!$c0cm?Caqx''}GIek(D:їqdO=A Q8Eur <3*,.c鴹ֲ^_A|`ņ BHBHB,`>+4T̖e! F+1a3Y NiHF `<]k:66&bǢ d&Dkduf4Nbcbk&;EqcADQht F٢Bbņ4󨁎jtE^Gߙ!}.=/;=W>uUES*QTzJˋ˨TX$(+2H)&&*,Ws͘ڱ4b]y3M}ۏL7 ʻ-IJɑieE"U[AxWYS,xxh4ccOBQjaF1`$$'nH H{+K0L|P#x,eW L#" *kwt˒N#2aT7 A51`z4 Ǭ*r(`1_FLTc+8}|2|s)\K! P$(!ZmUYM.&)#fФ!(ь> 4i$x{7sN~^qF T|9N*d)Fa8ҜaJ4Ў _?ud{LYdTy?\70n.o %jw) Hcf ighaodE0"" "7L&c6xꪗ9vy5\qNէ,XG/~NbߧbGw;7[8X^=#-xC"FC(d X$"ޛ_&r'ge[R,,pVQ]:ߗx٧ '&aP4[67l=lҭ|}{mosOklw3n7kkҪܷ 5m2o]Yqi+݅7IڧaSUψ{ovP7q׵)[YԌm̽,EϖKP^łlTv]IQ<\~Ό!ƍüWb7ŚHbw {ﹳ+iz H18|ӌ=S4.R'ͅ\O)^_~-: '`nJ*`cS8cȬ+ѷ }SĪN@( A)L"MleyGrx xGkc<I>nvǒYnR.݊ tXɟDi@VI8[=3|"ݕ#Gm_z{.Pux\|,?=.oB"2PRJ;">VOOr|SQB8: $J Zډ'n ϲ@7'N^Mv't &élo1Rz]uLn0"ZխW*f,B&,C4:Ҝ܊0OFitn s $ȑ]uO:Sʺ>Ò qWԚ~8O1]w|qc!DRsb/PІ5V+,"qz$ؙ%k` Uuޏz@`B0!f|D3ҁU/I9{'Ȅ7SxL&'=Fx91!Kptߵ\׸\xi zȁ:"|?aFT372e, A-q `"Vj"#ķ/fHY~7SC1 싲YjR|Igi1I ٺ@~~~af$PN?ȿ91)Arm.DPg<֞xiu)-I2{![wŲll4K j%sl0H&fb'd'!wf/ Aso׮-; .o5810T&|m-slnlG\gcj^Pyca٢(<雎>6<$H6*t0_A7;20|ޤD9.Ah<ա[!g y`@rn_bs T9^KTD(E[((i~Hwher8dD#m{8=|"hR%1ENGɌ||EU 8.%nZ_`#eCCV4=4p|@>D?DtiQ\6KA`IԚEM66D#:5~>Q#"#}x.-e̒-4|*4P"1+To-W|[T ^>J  />Wo.VUExzE :PNW#:Pk4!-%9c P!DD? d=&8լG77륶wkXldatzZ͆A ]<skϋ6cxuܫI au4~;-6䆔R&a,l0Rx~n|%{3h;OIvOz"-@*b $rYM,clM+V~^է|E~eӕ^9FT @?)%0#}>>(s=p?ȚOwPë1dyy)y,ah8K/IzJ9’Jځ8qg"0T{w2nO)H6ĭR=c !mV6ÊKٍ]\A ,b1z+#kG Q3-gw8΄9j4zK%A6΂@;S]+d f#)^M_>+=#4ޙ'%Y*1ΟbYM;u \t|"E/ *wKPa5u*q[]ͤVdhxIEP`tdр'/JT}![Aв$m7:ma낮?sXtD뽪oO$J#yi*Н筊"~$}O;č.6珱-1ʎa5iM! eGHp3 0j< z>\([E<0pBڪ7ghgV+)tҼUA(7;H@pR0y.a=M-cϒ8j_C4__Ÿ4P>S[p.{|茉XDKo(S@. ũ=wk\?Lfmr(QDL/@}3({xWR\~~:VI IjBIg"LJ?Bʦ:-7HDQ%԰E^g [BIԦҸjNNk>ipv$ d5 I 7fUܞ4lo~("Neu2YKl%XչP Bb69)A (5cr6t[E#Kڌ edW5$3` `sq<l:Q%l5Y*R:~F At$=f[3îPcVf{>?OozN]DYA@X S̪%2o* 'fES<Ǐ`̰ clr!p3D#0٘d40T9y,K˲!҄\6zȀ$ mbG۸B) i:=N1X\1t1tEgZ_"awifi s9YX`' ɞ{}vk=r9./?w3Uoil1"mZޟ}\S“5 #۝lSYCO2AX,ZR&؋kW{rǜanU=j!+. VNP_Ėҋx(g&јZ6k+e|hTA2&Vp[ysX;RN8'I 9꯰n#h2dkc>7^EТbFgϧ K8+I+ sv=dX Iue/AGȾb䠼`rGRl, PUDsq4G`$@W(FB-ۂ Q1v T|w 9W#B;xV>t^v'!kJnh aUYEv_eCIe5h\AmڟΎǂ@8oiL*M1ӛs @\FRټq.e- 肇x^^~WLB!0Ҝx7DA㜘lW=M?~wƌ8L4᧱2 V&-f`oneP$\6hkR*6bjtJ..p0|/g|C\wnl.q!ɕ$?&!8#|٫%NA0)"ߌ4$^ZEQ6xJ>;1PIh.GhR6:0hniuP Sf?PRk$j|.$c4݆3h`~4gïs( Yك39Nu: ]qYng^s۰ivJ}dLVI6&73+-HKE0_x#^r&i{{‰z]B@/TIsoSpecR/man/0000755000176200001440000000000013747057215012527 5ustar liggesusersIsoSpecR/man/custom_isotopes_example.Rd0000644000176200001440000000052113507710304017753 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/add_custom_isotopes.R \name{custom_isotopes_example} \alias{custom_isotopes_example} \title{An example of how to add your own elements.} \usage{ custom_isotopes_example() } \description{ This can be used, for instance, with isotopically labelled molecules. } IsoSpecR/man/IsoSpecify.Rd0000644000176200001440000000517213507710304015065 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/IsoSpecR.R \name{IsoSpecify} \alias{IsoSpecify} \title{Calculate the isotopic fine structure peaks.} \usage{ IsoSpecify(molecule, stopCondition, isotopes = NULL, showCounts = FALSE, trim = TRUE, algo = 0, step = 0.25) } \arguments{ \item{molecule}{A named integer vector, e.g. \code{c(C=2,H=6,O=1)}, containing the chemical formula of the substance of interest.} \item{stopCondition}{A numeric value between 0 and 1.} \item{isotopes}{A named list of isotopic information required for IsoSpec. The names must be valid element symbols, see \code{isotopicData} for examples. Each enlisted object should be a \code{data.frame} containing columns \code{element} (specifying the symbol of the element), \code{mass} (specifying the mass of the isotope), \code{abundance} (specyfying the assumed frequency of finding that isotope).} \item{showCounts}{Logical. If \code{TRUE}, then we output matrix contains additionally counts of isotopes for each isotopologue.} \item{trim}{Logical. If \code{FALSE}, then we output matrix contains additionally isotopologues that otherwise would get trimmed in order to find the smalles possible p-set. Therefore, switching to \code{FALSE} results in a slightly larger set then the optimal p-set.} \item{algo}{An integer: 0 - use standard IsoSpec algoritm, where \code{stopCondition} specifies the probability of the optimal p-set, 1 - use a version of algorithm that uses priority queue. Slower than 0, but does not require sorting. 2 - use a threshold version of the algorithm, where \code{stopCondition} specifies the height of the pruned peaks. 3 - for the threshold version of IsoSpec with \code{stopCondition} being the percentage of the highest peak below which isotopologues get pruned.} \item{step}{The percent of the the percentile of isotopologues in the current isolayer, specyfying the cutoff for the next isolayer. It has been optimised and better not change the default value.} } \value{ A numeric matrix containing the masses, the logarithms of probability, and, optionally, counts of isotopologues. Attention: this matrix does not have to be sorted. Sorting it would also compromise the linear complexity of our algorithm. } \description{ \code{IsoSpecify} is a wrapper around \code{Rinterface} that calls the C++ implementation of the IsoSpec algorithm. Given a molecular formula, it will calculate the smallest set of infinitely resolved peaks (isotopologues) that jointly is \code{p} probable, where \code{p} is provided by the user. } \examples{ library(IsoSpecR) res <- IsoSpecify( molecule = c(C=10,H=22,O=1), stopCondition = .9999 ) print(res) } IsoSpecR/man/isotopicData.Rd0000644000176200001440000000205313747057215015441 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/data_description.R \docType{data} \name{isotopicData} \alias{isotopicData} \title{Data on isotope masses, abundances and other.} \format{A list of 6 tbl_df's or data frames, each constaining: \describe{ \item{element}{The symbol of an element from Mendeleev's periodic table.} \item{isotope}{String composed of the nucleon number and the symbol of element.} \item{mass}{Isotope's Mass in Daltons.} \item{abundance}{The abundance of the isotopes. In case of enviPat data abundances do not sum to one. In case of all other, they do.} \item{ratioC}{As in enviPat reference manual: "Maximum number of atoms of an element for one C-atom in a molecule, based on 99.99 \% of case molecules".} }} \source{ R Package enviPat and Commission on Isotopic Abundances and Atomic Weights, CIAAW, \url{https://www.ciaaw.org/index.htm}. } \usage{ isotopicData } \description{ A list of data frames or table data frames (dplyr like), containing different information on isotopes. } \keyword{datasets} IsoSpecR/DESCRIPTION0000644000176200001440000000246713747131146013467 0ustar liggesusersEncoding: UTF-8 Package: IsoSpecR Type: Package Title: The IsoSpec Algorithm Version: 2.1.3 Date: 2020-10-29 Author: Mateusz Krzysztof Lacki and Michal Startek Maintainer: Matteo Lacki Description: IsoSpec is a fine structure calculator used for obtaining the most probable masses of a chemical compound given the frequencies of the composing isotopes and their masses. It finds the smallest set of isotopologues with a given probability. The probability is assumed to be that of the product of multinomial distributions, each corresponding to one particular element and parametrized by the frequencies of finding these elements in nature. These numbers are supplied by IUPAC - the International Union of Pure and Applied Chemistry. See: Lacki, Valkenborg, Startek (2020) and Lacki, Startek, Valkenborg, Gambin (2017) for the description of the algorithms used. License: BSD_2_clause + file LICENCE URL: http://matteolacki.github.io/IsoSpec/ Depends: R (>= 3.0.0) Imports: Rcpp (>= 0.12.0) Suggests: testthat LazyData: no LinkingTo: Rcpp NeedsCompilation: yes SystemRequirements: C++14 RoxygenNote: 6.1.1 Packaged: 2020-10-30 19:22:56 UTC; mist Repository: CRAN Date/Publication: 2020-10-31 00:40:06 UTC IsoSpecR/tests/0000755000176200001440000000000013507710304013103 5ustar liggesusersIsoSpecR/tests/testthat/0000755000176200001440000000000013747131146014752 5ustar liggesusersIsoSpecR/tests/testthat/test-numerical-stability.R0000644000176200001440000000164213507710304022027 0ustar liggesusers# context("Numerical stability") # # test_that("IsoSpecR calculates correctly full isotopic distributions.", { # expect_equal(nrow(IsoSpecify(c(C=100, H=202), 1)), 101*203) # expect_equal(nrow(IsoSpecify(c(C=100, H=202, S=5), 1)), 101*203*choose(4+5-1, 5)) # expect_equal(nrow(IsoSpecify(c(O=100, N=10, S=6), 1)), choose(100+3-1,100)*choose(10+2-1, 10)*choose(4+6-1, 6)) # }) # # test_that("IsoSpecR provides results similar to envipat.", { # load("envipat.Rd") # isospec = lapply(mols, IsoSpecify, stopCondition=1) # expect_equal(sapply(isospec, nrow), sapply(envipat, nrow)) # isospec = lapply(mols, IsoSpecify, stopCondition=1) # # for(i in 1:3){ # envi_1_probs = envipat[[i]][,"abundance"] / sum(envipat[[i]][,"abundance"]) # envi_1_probs = sort(envi_1_probs) # isos_1_probs = sort(exp(isospec[[i]][,2])) # expect_that(max(abs(isos_1_probs - envi_1_probs)) < 1.0e-10, is_true()) # } # }) IsoSpecR/tests/testthat/envipat.Rd0000644000176200001440000100063313507710304016703 0ustar liggesusers\y80~cEQdXR `XRB(%BBX$e"JEBd*=Ș)zs?8-uu<{zVJV,,,l,o9_XY8XNg/z:G=/|@Q@xyfBQHI3 2iڎKSc1c,H+Uy>ǧ@b3}:{@̚7u)hE _fS:0'[a 2⧲(Y,J+k'5Aξ*`]:z*ecL4,.xl(e~=eU2rRڎo7<lC\&7Cp!oE9L۱Vm {ʽ,qۿ&v)zVQ:eqѯ\'yۯxvH/[j _VF:r>FUuym"Xh'o' Ҳ%"CIHk13ΠOdUqЗe<:j :wrR,5A翡y!ӑ`Ly3x;a?h>gӥ.yr-b ΢/h`t,o^4~Dg9QC9žB^WSŧuW֏`]mOtu بTXە%,GCC^{@rγ)߀_{V;cK!6v92!徂Bc)[]aYBO]m{|4{A>-RSѬSOAr;^Gc*l5^!w? YyH]4>{ ~LYZ o@?\f4 0MeS|w,x% +*cU3B~o6&)Ohvr`o" ;z4$Cgywr+0!>0?00xsXMRW'FР).|J?$QFפgUK~785m`)`ϾT7-\z6KpH0jgR_ ^J5^ֽvt'jw9j Ŀx c*'EW^gy/QIs5mi.A'fS:*gEO^e9|Q[!/stѥuƐ?Syֹ@ ;3$b6_jJt1Xg!Url^ ] ((No3#BQh~k݃: ̆&ڙ鸂ekM4S|B5{ :f\Tǟ$Q/|ղ?[C %d28\/b`=%<^B듄@W4_SBiwsJ\uQ4^4~CHзZ4m +{ׅAk+j[xCK{/7_M`E|O/~ :Po geEς?|Ti1<: ^?o5{ , 杍QG y}{Ḍ_rɷ"M}`9J *k" >iֿ8 vx.ђ>ҩksVJouo} yݨy5̓:le:5_ юރ8Tݹ~`29_t[}X {~\{_Lh~ MOŹuP_WiEhћW%'M+֖, 3C?_l(GИ֊~x[WZP,K-C(s6A"!t'ϟ}uGط&"ouNsp W37R~`oAA'G{g'?~۟ mUqnvIf a{Hz٠^2bi1ХAQs?su{C=6з ϣcP5݄Z<6Fn6|}7P=:Z qgߚu/ [fq;8;Vџ<A9#"Q|_hyПWMs`մ4fr`&J!/M@\9l2= _}q jz5 Aa?QT}!q +ہ_hEį+v04"w MoBΔ :͐5PBm.IDzro~ Uw7!C cmO: 旐b]G` -} fZ^OBZ_KD?Rˠn Ό:2b~m!pT| y0>xQA))K:̍ZOTCVVB=8sÃq/vhW-aN})6c7@>s-(ɬi |oF3܂uz?㡺[̎>< W:3qRٚ oxx *'?Y@>Uxd}-G-!sf=/[QPު_ @N@={{dSҨɿ$: W8G3>tveg u(yOu^RU3rה'[9-)Z|aC#eoqfRonBdPZϤvD ?>v|]N8Mʬ)Gƞ R er(4ZK<-:B.+Zlh4y>ʏ3dZ<Ljbu&)=3L9|vτ=s^:!kfdd #ZsFV|^[ Y]1~]3 {6njO9ngVŬp^~ P3zES s}^qiU/ܢЯUʿmují?PJq ?F;12"c壘e߮&똎`Pn+x\(%U#o=!m4cB&e[A?c T])b/وcdw?ȭkn[|7qfI\'-n۪}ELepcQ-+u5{ۊ^&EHyJկȵEW-2% '#(/w5dr#) N)Pn+d^ j ȷv7)b?r_$ֹ~7i`6..~#C܆^|ZPv/3/lS#D0.[k9 {{hzGC۸*?NaYݿesoqBכI~GT"?p}|$3XD߻SV*=kdy"+p'M"M.VyM 7>BʱV=@KTS" mg}p̙"|;yE`RVZSF_MES8"N㣐n[o (G^thCNekH^od!Y[DYq)(*0Xw3dΆ Y1ֻKܮg%%)HdTw }عXbzJ V]%8ezSJϸ ~%d˷5Qlbg7ЇY^d;kb\HRoIdU> _#7aBzVQ36Ee6'^|a;{aa8t,aYzfRPyލݚdI#Ftlgv&oJ7@F\{ՎAy2]>;ĔˍKOd_@9үR_HZ_~Id8}r$]O8Jr۟kF[(\&ؓSə[=7$qבk̞˕]j%s!}*p؁4=2HyȍT#8RO،Oep>8}zgv"SِA7[s(ILϋ92Zۈv/xː9Z:)7GkۨFrW(KD_Mjo/H_.*'vyR2`5*9ҿ?̉M nm$٦P.X^ zM:=$(NkحC-11"[I7;&=1":'%Iz&a%]Ƃ{n!f4.RޗmDœ?=82$gj'ȊSSL:iek~w}˃77ם ;/-R:cpDs9_WbuyJ_EOo}xS.ޟh\P@z[ÚMA4`{M-ɓ=Rma^tEfw:k]Hߋx>E\idfSNɑDYMs c/."ZCicG})ѷY_k5Q_i2QU"hTWLqabN뗈 -,%r4IS%$E wv\<(za/Is]7ʸ}J$3:$^ɬۯERO]I"Y;+$~sqQ}p! "-*}ͫD> iSr |ud7g #@RH0~c6KҚbeqZbVi;' ![ڈ{hKrڪh*wNG|~Yk?>XD*Ι5I*4kuL8}Լ&/r X=![Pi:4xkٝsD}9,D=rJ^8Jj[ih(9pEUf$)JEaɡ}u?i>Y>A fȯЍ z7(%[yE'HM&.jƗ.Giׯ$C*f_&4gR̭Ѽ(Qɉ{NQ 1bݍSuD٤u>DgBT=n6Qy}>yf$+??_,:]-oT{!bS̴JN=Sbnktз&ڌǾ=^miݙv+mD꿫iΨe$N֬6 i:H [!q7^#jml`I<2zk@F3 \IM^zu@̈]in"&(4ݾqǷW4ߩhWҪ&vj_ϻzy;k6fܢ2Z+2:ρ>Do)dh>c͞GyVJj3mk>#V|%Mk/ POHD):_dQI͊ .YmPTᬹ)ahwi6jdE.>6:\Jot1.}OH q7C kL,5 R-k4Uׅͩ,43>2+v"J̖L?f|e|嬃y,m<߿rp=Kw8Ks5D۹RAzmAiۭ yiC`Jy69Yۅp`h5:s8I~_jRC~ #ަѹJZ`5`yN`]`NhV,4L?W~ "Aqta+3[v[`iG9|6L8=l<v* y=z{ X7UpR5+ lk8{`tw mm4A[yBSگ.<%ыRAПa \c ]> 8t^FxƩpӤP5}`u4 G7:$uIC}Sp#}Ve~  p g7Bpfqz|7 {Dp'x18mD8y8[Z7Zۃ?\v\tR\ q)C0hmu4VmK{X_:JKȝwރA[x!:>bu&@zA|rcn?L>3wB߫*A^$SsѤD􀻾$@ߣn%wf O ¼ Ta yMS5/!6 U]=P_hFϝ@?l/X}_kBҐaDk&ԏ0CJOaY5@ K-OǼO(zThD'.胾wTR7)P‚[^'ZEbvnk3?ÿq9瀰c-ס`#Tut;愰Q}ҰNa~Ϡ;\U6BKK85G<\!Ø?B /܇Ba9uзrv$s0ĭu +t7X/WN {} u֥PJxLy߼?Xr^b7a"=an>wR2le~NË*?՟u/j:Ol2k<<qcz?r{Tx;CuXi>gq{PºcvvzW(7GZ?礗{XXTt5ߗc;zSa=BGas1FN>3g@= u0ܟZ_imj)Je ό:?PA Aiu72Kx 榰E4 (O64-Kؒʃh{afDUp][ݱgr_b~ڡVhgX"ܗQA 'Sցv <:c4 \ K n跡02}c2W~e ]760˄>$uo mcX+ !5Sa{J4mR'E2v 1o' b;Duwds BԱUPһzOS|Ч5wof3c| uu\!1.-Bn\_,3PIP[PP* I!9 P|?;Jv|/걽I009Y̹ ;^3L~g@2|-Bg].)-TBBad_s>eO ':xa=%8j/m>{'sO[ k2.pX-8z }[vlS ̍{_Ȼtr?zuGҏe1gq/sӽ9(䃗vR{ˡ_{a W`#^_KE<~=wcz\p/1[<]ߕOxJOޡ-hÿ֙sECd-VNnSznoȏ jX0?^:"=O*`3Q(|9KM1" |g+sF"Kys }^~ > ?LgzڥNiCK,3ϋ||ֶZ_p3%6|g})k/K- oӛZ^lX]V̠unO qR_8z-~>q jza|;Iw|_&Gɷ.`1kzt:Yp0Sht8? ȏoLh5ȓCFӿ_' `MKKEe>:MK@e:y y>h1Epv= 4zȯ[]HD8Goe7.L:*Z?/Z{ΕueF;@^TCI_5. ۷I9LY&Ӆ.=A3'̵n{N@ ޠ%a}׍'&#<=VL^&1I4!02 ŝFsgS:+S!p.5ЁG3q7 A~ѴnL[gŒab.}NTδi+t" Hpz(y^džuoF`Nb:JEs0vtW}s~.Wیi[mݓls+g=#yē03/H{]  3o8%`a^ 3v?zU]hZC ׼&fadƼh?>-_3'NCf&i|Hxw'̴̉a`Ÿz4sBn^0?0*s,h<ާ10%f[8/2WCW`yZ):t h;_ln YuV}|KlsHRU.mM.!09.m!yUK>P?^B;ǫ$U2ak iUU<7e)0T=t鏮r~ǿQv:&j}*[5ԑܥ`#s?|z͋q,O]{#U $z1q/U1NRռ#\ ߫%ⷪՓ)]{!󪎤EheU ߶ `nt{+]OVBH{t⢪ccz[/TCW}uixQC+?3hzwt@㛿&:»{B iԫjjT^th?EUfyS5#aZtG藺T:UӬOq=NM w f}Z~:9snw׮@*\ʪE֡}u=n#؎g :]:Y_\rgSjjVS-:L W[gTs2B^Q zΰu)^ם7EcNi@mz tu`yA=V_FԴanZ&S4ޯX]R4_h8ZɌyZX4P_|w -QuZ![6waV-#^!"Ey\-6K⦾;?AG aIEP{K q#]'JBUK;Skԥj5hʘX:9H<ߪXp_L9evss20ATW+.x;mתeϔڝaP7)9Vk3}Sei~AwժGT5xZ@?V~9ZCjMh _;їZl_դ?_w1~(;wsP$UuA:jCٸ?:BP7uy7Ϗui GmTBWPՍv`&:kԧjHRmHGCՇ..YOWx03I8ƝJX#iǪrBߜ>s`yê-m~P}g_1ގld-ݜ@kH:c '^}9v> (q'/Ў[x)ꞵ~8&B߮yqx5}>7NO}8=Usd4Ϫ FUUyjwN%\op>[ܱp!4hƵ׮q=axgZye}yq=} Nt?10. 5vuc>`o=QpΫauؚ:cSa)U3"<:b@ֱ+8-lSxE`ޫ^ ά\ulw^q^7IϢ#~W'ĿӔ sCuu9ǔ l!0Uyfa4?QN fLYZ{fѝO|DzT='A>OȩM:7/݆fKsեEW74moFjAa^]܍u] T7V'-pnnM'#^ ݯ)9`euqP \0:Fۦfl ԭa~ AV@7<9 9`:Թ>XkEpZ[O`MU1\~i0<)pC>S1W3`cYNyJ᧻  9|jdv\2|/,]׎zg)7 (t86SNsTngEimy~F2:ҭ`=זK`',Oczȧ?.Al+ogUmB}^az'j(v6 A͝ӳВ9ploa{{ahwѥV 8(֭칉?%cuepshhĉl<N䞇 s5V]s`G(uK8zpF ̕<48﯃;P~.@]0] :hpcDݟkJķpն[[]Vz32s0/XeeSOC݆ QК<q*<܅*w@4ĥW;Rf}{f9L݀~ڰm4=ϛp=0g#Iv[KV &)vA~ $y: ɉUKR>$d-~4e8*V˦TixMH)G 28 Za@ H~V_w4$~ ,`uHbY!ɰp"/Ev/`-V'/jhgޑh)C:G Ym8IC{nsnސ̮2A־DE, Wt䆍-q_e $?%>׽ hLY4 ŐdGtORS9?!'\8) zaJ?ZD҇0P?9qCnhn{!F >~ j y2-z}Y{* NXH=!]6ԃεCTlQ.nc8 鵯pY]$ہS_qͯ^VM!^ Ll/7gg =>!cS1m<$}JZ4)4C:1ᐹ U| ! JM8t0^g\djiջw ޗMj: sЉJgPRtlp{kOiPLJNnK-ߋR5\тh":xlsG=lgD׊hX+!zFIq$<i,e雩П\Bb~oB>9zvM;70ky{#}uڿ-e9kf~\}rqu-H א1Ƴot?;|N->Íڀ&Aк8_~I_ep_m?@O^|h9e( S4cՠoYQ6wP.8O9{B Q:u.C/C7{NF*`b^+9]W6r[4}"* ԉΗ+[}Cqnp~}7|G~.sˣ蝕'ptr´/:vi ɂpē9uGRWZ+>օsPQ3C 0I`̓xS9a.ٿl^aǕfn`>O4睡Y؟\8y+|3u=9zh!R 돰EǫOcOJ?q:̖-WoxM0s)~5G!a59,r!+%)I0~aPw~C]6e_\/U ^Z\r`ܞSlୱ`07{c0%#L553>u9[=)a`ny:U*P$qXA7CqɮknNA}`Obi_} .F/N:0tL#osf^u}pT߳K|8xrrSCgvBf1O[uԏ{܎ޞ}?sD},g(ȴt}٘˼nZעɠ!PqLzܬ`Z"'T|A&!N"J֦̎ݿx8jl\n0xNjmY6Z?JFc{9^c是ޑYǪ, j kG, \[QLOP_aCΟ`?Q]Ͽ -@N}Y؋k}`w^kCsjǏ~!--t;oaj>% cY.u=*ȗQi*+ %0xm]w׊ W7EJV`|_pe;Хѽ~sp =ļ|S[s SM#˂/#7`xvs7A/&~:v :6x3bXrZ?'<I|%YĞW]¸4b>}45ؙ0qK"C??1i꟰d|ĸ̘ t2zX3(;gQ| (U%|>0Uoyǹ5^{^Z-Qt?Q炧 ϨwuϦu_-?>O}}XE 5Vb}},mIPxx1ߛa?{eG^x-v֙I.J^K%fm̷ *wzGz ܟL{mԇqn}m:XA@Fx}Sx Cu9?TzíaX<Bp;:haL"~2ڄ|~^uk@KНp]{_{I}׽QXz[.,H.mEb*}|pV uKX֩qog#d&}!J{c>Oxe;w z'kwgз8-zϙ}ۆp.”YӦ(QU{5ʂG @.%5R3/xOLpx/>6f:>7~!B: sv&+9}0pḢJs}c=9vrœ!G*%vGPIv*2SU_rhHYk85X&e&lOp:a+9ޏqj:Qu#hZ>o j$g>y%SϱsJOzIfZ}̧,Kxn,o }U%W;V [: ̑uSjpΐl< 6j5tEq#$[_4އul3h!]+);$:)~죦 noa$CHl7< \Jz-9!z 8 Ж`.XF\A" ¾8"l#@.S` l#γ>"[4Q'Ӿz iEl=a;XT_ĖM>B$ G FKYz@)3E\Eo! 6B5 79-U ppjB3 2s 2ơnd߆!HC&[yO о';`%|vPqDq9p!$3|CD! -xwz1?+6rETe5s=Ec>MѶRg rޏ.L,;\,kŢ|Amiz\g=zP }EM>r>P3cIN\'}M;n!#hѓh'Y^0/f&{1|ؠ\ٸ+_G0~v`Y(% _ GG:7A?'W)A ńW^[d8O->ECI~ ~Z)a3‰[6,Qxk-3&S;I& C_}@+x{a%Z`cObAKYMc>>Y\OMцp>%кC^{:)utd^F[uB%87:. /.S[aOQ1\ܞ@]%9lw5j4Y=YӀqd SoMg*ow=0o[jl}n¸)(w:\ 9Vpq Q q|.۟NV4Mu*,R[B:B C_Iu{G$S6XDỊXSu5^,P赏C0.VDI_(Gҕɿgч}-Y LUxE|LEt>sVuU4EvT9ւN79?d1oQSg?J{uK >َu+ )-QP4u1?oCq ,0^HBoF( ۊ?= ށ%xh5^xWLz$ku'#|_Y`g8\,+1l ,8/v]0Z=JOǕu1*:f0?E5zk,Yec6aAH\B67aօzVv?g7`&(<׿{~~'s%ęQ$qTXpUJb))?%xwn!9?/񧽂s3]zV2NV.[o)OΌ]0޻Z0>\xB`)@_^s5ÐRBJq9Ci:h ǏġOR3`Γ 8sRM5 Jh/PߤH5 o{RcuaޖT?PJXZhںڷ(t؎f e)qE)GRW6 VOVB\J~]}gwz4s.'X/ݜE NvRJvl\М.R~CRl,sLT`n0I4$z4Hץ\#EFJKQʻ i! 8|C;UHg49Bʌ7RGH0HY 3Q)|>Tץl펱BJzv~f=)*}UyUmĩkSR)c ARp09Xʯ7rT7ՠvUR7bU>Ivץx \-̨}njes~4nj9n[Z8': ou{?u;; 'b`M}ԝùMwj3֩ԝ"¿bwu.aS]||Q+*kݠnT6zSBGXurQ禮{uyXo*4G73- nT11' c Uw3/UũM8}udx-U€^nnY UtSm YC U\]@>P#\UWۂ.U_xJ(g8/Rh=>qKU qBOZVc^P姅 Aa8Pwt{2[˨*WwdY.>nFc}'y)eۡRUeAUvPYԸ+v*P"T J0ރDדr:tjci\EUkG*UԠ;ZhAi>E=Wwqz*OU)zD=FW([z@"h`aF* 8Xfqh^_a'T3WpZcp>eqˎ9j ?7_N~jQ&5qBTKP-2Ehi>b@ߢ'͆]ч1m{a\T ZaRkjajcENu4L@' Sp:KP=L\I='p%VBsw>Azqι lrޭq|x`ÃK }zD ¥L}'U}?5?3;b 1yz35j mjr~/02^dw1(zg W s薙 4>x]A7?֋PC.= /6˗p" 1"NA^&sʡU/WW=ƠS,mpay8S}6~|-;=<)u2wo8֝m2[cq`ޔ)c- ޲72. Bޖ62zC̟cRk¥Op&700nxߊnj,crE'5sߘ: sq-w/yaܼX׿1 9Þs_z[jIc:g݋-X 75='ʞ~vEI0. >;lG `ŏi* ^W2|ܴ G}jIC]>50gQɥzEDmԭ7ur'u>uqm̀΢-IVX}bMnIG0~' 1-1&yKp !W(6%mCov3.s;V0?;VKu^=ta*U{pvq`}/m1g؄}|[sݹ|֘b8oP9cYqcS p.R K~E|LnP0ֳxe߯2hrF :e?y Fz,2hG {=*KASϡqܲBK4 5 zk>~!^Xжa3z7P6Wiȡ<w/A(lq:h uNc w 쫞@j!~Y91Z$|=ڛ깞}4BF[o̬>Z#=BQ6{W |qBz$ bz7bIXlVJ7@Փ}~K+]˙0GI}? }\o7=@iXT{rQCW'iPw}n-ZWOa&O=ekmsTm3ujr.[k8Ӡ**@zw31o܀~gYhh-kSC3ܩgZ4}꫞١czGM<Gz'0W{#ƥVshW]87xƳVZ4RS89zi%^tr&S=ty_z0E{p=F=Og??kCn#=sE#|]? U9}Es^TԾ|\7L1ol/m-6$?z)'0_RAO5zp~pnޡV][zϻEt\4^@OS;̿z]t_0X9V~|&ݱ3Pq\sI&Ļ X')>?X>9N@r\LI}rMj(.Acw}͞ڂK= M(7:.o?KNm.mc'68/MG=!hB[=aqOuR -EeB;Уۃai{.0 i.Ӆ|)񱬃 m`:1Oet1|Tׄ=!e'#9+,z#fX.<&'9.mhz5p (0&qoߌϟ }QKk⠎ZtCk1֟9Xw1}4?'7Yֻ'\X'lpE3gB{ -s$sopp =n'Wisd\G0[c=y^l~4P{ܗnNZʂqq7h%wkwkd5_o\_a/^6S Zent2*CEL̛)fnX_>ڹi8WЪ(;[1Nk6n] {ǒ^4`q%0Cms>Z}" L]|wAt zCi3Օc`C6Fb/9֠i.h7Y懿p}[T؄aj{.c\|L:vW?ovzvZM9CS3+ic5"Oqz~yN_k/xk̷ՆNߞq [`e = k8о 9gv/Ta.Ofk ︾-mwj963!ڋkCz5[,6 gWa?ι6jTjԄW|UUǢTݱ^O\$yp&o}rCci?ԸB'3d>h>39sZ 3W#b\Kù6+zEp:\`s ^8I8xoI̗Ehq'K2aKc K _Ka O8->-}b;~Zw9?]P6yᅱa觡48oYTQQClΟ>˟?tB)[cRi8FB}; v?O9l QB݆8oଣL bv :o#" }c6]q=>&&4_Z|gt]tݪ_у-pofyV튞 r̄x ܎,G3f? po#mq+ .byXA_jf ̈́a~pNb P⼿Ǜߌ '@ 籧hl,< /Zq ~;ѥ^9I>849=M: 8}؉JT!: }nyA5|M2"ɪ~-N;Ch`y<]tE)6b/{XQ07Е'<@{4l, 5_t:-tU2m]}]] :r ԘL6fծvI9}n(c2E YkZYhƆ+.N?'9LB3?\Cl̎8|%|\=rSO};^9^üxC6z¥|k6^< F8o窫9 s:G/--CwhD@A_vv')&vS'|~MЄ&( Be>z8y @K.Y"臺w W|WԱ:㫐 \S b7 ֣eB \n02VEOc< kR%43.Gg_ ؋6B giZق:6)@\sptC 眫8\sRﷶ Q8hԐ4>lA#'2<^ho;ݗu<|CB4w8hޏs-|A>&ԂDGtj7_[O ݲdm-|@F9J>nx6G7}7g?,gm˳g϶hq :& 0z4 ])93\t~^st{'%[? %,ph?pwΏ\+"|jwP)<5 :|u-2j[L!NetBs^hPV[O W6ip9v學r2j辦˕}' o3;ЪJyWgPÕBhs;\FՕ8R)ۗ u?|Nw\5:OG-CPJmo4\u5Ws"p&pb-+ȗpo&P(o{o86Rz6B~k0qrm`9q9e&ϛaS1oA:H03saIrPh&xP4AeGykp9Jݟ:x_ChE/cqUGhbְqa|}MKB?LC<;<@OOCM3ڮ~C\5o|u=Ҿ"<3u24qn>o W'2b^(ya{-"CL=Eu'%&1N㺝ɛC lBuzЎqy!zxH͏EknY\T6>x=3qʛ *5u Lw7a|^UFp3x;y#7 rƱ#M7b\z?.<'{w#̓ᾍA;0/ķ5>/}}U%2пH!kᗸuNDtű]z0%jyT:S[WS~0+߄?όJR[g^jk-4z}tw MJR>B6`?tmDPϙwDGogy4/Ѕf)ᾤ9C||O:y˄]z i]YN>7T[&nz>V0}fD8:]g|/\j曽afꕿF?%0],7=mhYk2 b\ن/G3_fS,KzPAX, Y ,ZDAO A`V:^' Vuv??wFk9u}P7R_t̆ /F7m9։>Il~̍ע{ J߷峥m4:󣻕$sf-\ ex]zVOD=Qz\w ˽ Ǔwn/P= %02{Of `ї9 ;,NZ-zG}9z_ 1I{*kdX?þful#r tܛsJ!?2֛C](7Nc3x(/@ۣgouظrqTMdԷMqB28e*'o +|WH/8dlm`a B/I(~9ўKP3μߏ6sdEg+Aƶ64zL ѫ=zCt;딙~un!jsR9N^|)x_QP*x~5c; '{/dn\5cX'10f\r1{Ҵ &{G+g5VP:+02e5a^PPcy\+4}%^Ĩ7*y9cGg1LrޤIBP_2}zIa>w2 Uh}\UI(B6F0.ԼD@8<}!*A? ]8_QMYtBE\ pPB/I@4u~ax{1o{l1ϧ> \E>qa % BͅzňYa4|Ã? - ou^ޥ`ES!uq0 o=p˸,+Sj.Zt+~:m!xxF+`YL%oЗ2|" ^_u߲mc]jP]{zR/~x3;V4jql|~* _0$o|G<h vQ -;l`&Nb9NἘ(}OTƺ$A |R%<鳕A*!8d0tjSpS;Ncܧ.LJyVQ?[ >Vӥt`^%cb^D8؜w 9ۃYp޵y1։t5ySlm׿p_ K$х?H~4?-i2<Ѷf-m)bX#rFly}ʿghO~}ed| 9Rw+ݲpY^uZZLpW.|Yg>`?n?ɉu[87!#XFc)B7֩}6cSl|i/w34@qS \7hfniٝ3a>l\y87dr݇bznw/37I&x+/3"2E>< d 嚜~͢4LӐbD0dJ:ml@RgJ5m~Ha32D2m>RHg"*ӡfϵׇ8T 2SsYSaWüw i*}"̲'2v.xgn#GgOޅqB$QF(ǐc% E!!QdT 92g*22pBɻtޗvV$t3 RT]ЯuۿO܇|թkCꈛ94^zJ҂<N[|%+W^3Sݓk7uiB`_K{K~,˩l˕Vي|GϕlZW1-]XX QWvw Y }QW.X[z'u/9C/9uUUtNu/j|J/t'Qf6kgAiW^ȸ?PuͭGAֵTȂnԵ ̂ϯ{_SL9 󯮃6fw'uBǺ=[1=ŕV^* =5)d@$9~>ƋnA&_ق7i'Zarb+X#\u:ѝo֍q]~ϥ sn|RCM[ kbD| `aeϿU8Nct<(|'UޘZݥ; x=ǐгz%/k7Gٛkݍox:P1j? -?bo" HеzI?a1zAsLЏB SN~]/zRe(%AT)%gd!7Qk`hC^{y, |U̺yC,^VQ931c(Piu[RifUR52&ŰWT)4}Tk] ko''RL~"ϑxt^c XaW*O+}}|j:I+*t##[j]ݰ7ԫ[ļ/3W'&nB~ XOZZ"^R\c2Pr(0F^njzIuSWw_Oda@j̏z@7xՀ6 nLd!\*rnHAϻm q,O:.(b;(as@x|\Y_̋iYЕz3gXfEd+Z5sOs-J-YCzO#rՀ½ b@/ c`|=Asl_,?~h3ֻFTC`'Op]^Lh"z;Ï9ҟ\rquر :YW%ցSůuG0ΟG#؂own|{1?nI7'cٷ`]t?W0*M a՟Un62B% NC\1I?}yAtIַׇ%̿7ɋs%5G3.tK}YoO͞X>{{}ܯ].Y']xy9f]o=_?ނo~BE'#i.|ido0h#;֡ub{cޮ-GHn>p))Ûu\Q.(usa>Ԅ-vca(Y~id5A|nWDNj&7<|="mR#>`]D.0sN|p}0v6c[G猰EFwC揮:?_j7žX.|NQc7bV<:q"#X' :/wJݩ~&Z#kcQ筳>/T{~zf%5+$JuWV#ϣ˙5TSO'G}O^q.>`Us '_ .þWf"dP%\NO߉?]49;EqNgz\¾ZQ}e FHO;`Njk%$ޗ'2ςӉC{6ߌPpܵ|n*oUlaʼn)8gK\\y%f?ұ>Jnq.8y٘y{$%[`e>KU*GܩZNj;滺Um5Zo._y8<_c 40tU8龥 kGPoa5<~Yl9:QF>no%΍҂?7IϣF-ą1m:aXmOWw>Pq.6wwZ8޹J\o2K);]g;WC=_1n/`b7({Ƅ' oTW'!O.go-#PJ>|} !cF>oso'/T~BFj6T4w|3>8^0o8_D(N]-gF=.P*#fsOob~*~h?itd X%vjy'!6^X{Wƃjrֳ\ruu)< ugYyY.c[ ?Lƿ~*K:~- L/<\@We"g~_Hg< K_s5}H4YOU ty :@s2{];pC\r6x?oJ/˻P GTaH#K&7< Q2 "ETq# NלD΋C}?ooѿ;zD| (iuI^XƩ Q S/M eziAw~=.{t;eUx)n) hq+wb }OW/qH~._C\(-_Cln+%]5L0 |*ݠ7A`J7tjNtmtv#-@ '0*VЏ\^xKos\mM +~gG<`/{' r4kzp(=^8=&i)2/AG@}XrA_X[)}V 퀹1rn zpghHr>H:H {dw#"@FM&}DCG;ہm|<1@V>_σ<]o$.)m$߰u6Rq#eVe~i4ϑB.6%YG/vC<y{VsE&~,C^F6TJ#v ;9>"oXm/BN цy$Z1AB@|H+Dg?8$Ș/j>Ӑ|[<2y6$# [->l~khI?CA7OG]y`GPˡF<>YY%(8'(jdg+L{y]-lz?{xѵs7KF@p:# %yYw{n}lrF~1gahP`$QA֩0G}A_=]oiycu=*.n,=\3-&rQiЧRٯ`~Js~H+b\_BG/ amȏYNMWWYfx}:K+ϭ\:=dR*ˣ WC.+Ns_<'d2Q?@e=铍ʹ4+F(77zG<\}q m0$C'jhCj}Ǹ跿ݗ\y (Q]*[dxB-䂆~F C*dqO$娡f| @ 0G 5`oIW1QLz |T.7BNcBD`=utc?.H=?HfkF-)?86"# ?1V*K1g/ (>G?ςq,wz8B_7qxBs_ǡx1.'ǎ:8ρuL<.bI=WsqɹxE ͣnZ~8e6в ~WlAFϰ ou=e\ʗH "Y 9AZs=߾ | \7nd07w9' ;z+Ψs ̯OϣC-K*#.#[cM@F/~tAVrkE׎Gp_1 JiMxBW.8c%ꀿ Re6dvX6_ [؝#BfϳBK:+KXaGON,2]rC`4\~d1Qrok17]_lGC}0v|G_9a"Ϲ]u}|/0~-SqD#Nk1]za&pU'AR^I}A8svqʧb\I޳:H&]Ca4_8DB Q<#[.)){()-[~^dG8O RW;*Mn=~Y`XgZDp f 6{)uQd?&`/L_ٳXYw`\w\zV 3z+w ϝԷ#'1y:_>o}"dcw5d@U1ԷbJ_ηU8>^rӦ$zT_bz].m"?|n?'sOU| | Ҟ/VW?BXs+XGjΟ6֬NێsFAw-)a[{ gyBqw3`]vd1X?oCn=wvBvÞ1ڭꩋOGWGٟ'7M<҆uppa ۿnĺǏΓO]xO6llo lQ?3=ס0-˨âB|X8G?J2NW5:AoL 5?O,(ǫ4>/.k&-ϛ.iu0rV9;_0:9_NgmJ#5O!͝X?ĈagUGbΊ7`ϝ¾+?}}B! L ?0nIQXPB*F=e |m$CbcK0 ' ?! ΠP]XOs.g2w^|$c䅱#q=)C+c'2 |JDf~nfj>t8qR70/ &VxB*)=ϵ{Z_c> ;΀2/2~,]dyC1mkwNAIȠnyQМ/I#? U1uڀOd&A/ui. Y>aq5vE!cH߃W8taz±aIkÅúC o'/#Ozc٘X {(únqi6G|3ωU°'11.Nn%S w:SbJnqI}/rhW(+g/6'!X?t\Ys˂nQ7Fdj#a^3b6a_+9 TA, {#GR[V˝:>em+0қ=H8cCQ0DA0§2`Y|пa sUiC]WjÕy;'~#7! w<vw{eߏ~];;Rwp ~(pBA?{Ⱦwia]=5R5"Wj1S@&p!.鸖;+s @IH#޷f9 k,QVMߑ^1=*+W*'.x_ې|1\EjEj&j4dRBpKZAs@~ω@)~SKxBxߥ#'|"|MwszH9{ P!*P1޳ͺG+flnqyyfE 9~T$ªY{u}mrH47V HYuU02" pWmG:y+ҷQH+ZdMSarQ,~aB~S}K+4? C@Td2L;*Lx#~=G $rNm\vbGH ZȂہz[XOzuE$8M~;@oHvHXЇJFpdӀk./u_wY5 KRrq#0w.. U_0_"対3A&}j$~8 5wr8Z9&`BlV'׃,0Z͘HC, p۞UQWf+[2dLEV`sSk̬Y'ro`+/bbUqV|D\8L0 b~yd{ɾX= db,'i-0ǽn} ԋ7FDR0 Q :N2⿀yM\4(|ϑqL>"yMk|8̆ &7fK<>ᖦ |*X rw?g,dwC]C}zhvIE=U.Oy'D3dV^cz;V-P ĭC#/=/K%1~%j J72˚+"rfۨXuJ*է/œ Tð*{_/r D@5f6 딾faZ_!7 2GJy}i$ΑYkM Q}Q':1M+[-׭ZcŸhe8CV,mO[߁U_#6b= $5v`ùE οω/os̩gC^ߡq=qԷi!#s55Qu&A>@ƆCW2/YF85xPƝK_/<~_I7zGkj,DLbpR>5uS;dNٌ,N|`]Ή X?|.~')!+/b&FlA[=M"s~6a}N~םŹ`y:e!1':*؇6eŁM?߄NR s/z_]!tIu4?; i 9~tn 9}tK˗X.l,AFan6Bu;BRl9ίB]rV9R_Qy mf dL{dpt& g:Sr>Y ꜍K\Q9$ `[_$Vucu'Cx>^ŧO?y#۟7nLg{J iw_Ǎ@~aϐa.e؄qgE mDqt;Ϭy +g=cڄ ۗ _orͶY"t6:ۖge=v9{UbS ŦdMEo5jHlj⦱HJ_ l꫞5ظ> ڑz:FoۀuL4 $ڙҗs2xGSV u̘IMLO(+&*Go~MLXVvcS`#d0_oic*Jwodvͽy^γźW~6@5O6;Z0Dr OEX'1uC co&fN*`޲HG^E `E^_Ҋt1-?JSk@]JƟ>no7e{aޱ%s}tF;Sq;וrlr'=˓)sUfy!KlIl2q 釪З[ Ano#rtskOo~N]s[8o^ gI];Y}Uuȿnj<IAK4!2cFY~SVJ JCLJZ{g0SQj)C]ËN!}]aH)TJYڊyBF)A )uo,7HfLHzԑŪrײ7i uV]x"nUj1u.eévI\O"oz2s}P dəߟFg`o;l3߱ltˡOlYn[ANNOfh&h 4:@I9qAT ~$_9xVZ8:/n�/gxy!i0ΪoN+o;?LZu+9;w =@dW+Rx@&0A#J! _fNILYr T gRW'eA TLGMRץkJc#N`2l>Qڒ֖c[핟aߐVXFH*u[0zrX YBߓKy,HOJCR47!_IO:Zú~8p7)$qd6#6H%{X1)aozCIѣ'~2cWW}{S.zyw$-S'0lO2mrT6ْ02żIo;M]m+#ϡ#23|zgd]SPrp|Tqa8(HTVs`E]C…Rk /٤rgrz(Y/&R'K̑|+д?HRuUV)'jk]X6 : Y'n>nWb׷Cj8xU`~ 'q9̭|-~AoYt u)i%-2_ BJ,8s?K`IClUɌ׵ٌl8E0 FDaz3XC9dm:}',F3:̖P:k0ݔ4_Rl!~]ow@W1ּ-PW?X,A)"'+A%/J}4yeF9}ov{v;)|gmP>?6 ub(YVLR#vOQGfӬQGSQG#h[~Ɓx#Ove44;pso *'_yHM(|@haMNCNDՋu0%BjZ,{:b|5 kf"0z0?VbNw>yǾ9vssV՘yFcdH^6ӅVŀ6/ 0Caoqu<_~7KLxG˄0u}FwX+üfY{|*:AIL)i󒠭A*)iVoH?2 i6}윊){yMU/(B:{#[|? $y)JsʥYg!h0Ϥ򠾥LFrȂ|p/i~=dl i3T7lB {FM6Ȯ0XSLn?sAߤeEǜ~+-(}:y1>Gp"{(%q5wJ?f9ۥ"g]F[/#}N>rM\Ji3KN"g=}`|x49|XzM/)#;E`NHK' ނƩKT ;LF)R:yޑإϑx]:Ott.˲W<0hz Yv7 ,%9dg> ,z(羠dUJ^T]UEOV"iJP>OFѭGJ0]=R^-|zOތ93GúĠ̟*jΆ@_Jk}-}~{}.#\u \c&p}*o*i}V>]?qy4+k@/]h4u?Yp {`>ox߁) f}i~jT?伞9$#[KPg2oiui%҇c˪aH >ȇHiZ=ݿxtTIt\^xsxu; ̳5O!r"7U+67Sl@['\glüEܙc%.=A%IXE d?5 L٘sb|T">%U8XƵ.T渴{wo`,3=o3xsyױ.O`]ztüy6:z彽2\ / ̿/8sI1߷*Iȅw.71?~7."cꯃKK!J2QM1OAkuAEJs/HH៯Yb^Bw.ݙBnyu?bVP;ZgWA.h~Uj3 u.%OmХ0Pzz6umJ{ u5{ 565}0}kj4)? y=bg9^怮eΑjcȞ!ve ^'tcY0uwI˸\\WsX]",f3Fدt?\ P`wA6p^5KX!xnxs1DXs`1 )HEjK>{ҕL?S.ALudPF>܄(KDļ$A=Iw!inx[|c/Vx]-X/O5}p# *fU^Ǣ^F)}- yO#60muwrtEsAuK"Bhc6O S0^"L"lft_"0/Mza_S[5|# t\L 'RʟqU琋BS>qO; Hms؉ t[lڍ׿d rm[%˶[UC6/֢Hاu|j?o)n}+}6%^3J֥m`1=yqt\W3Vt!a7c?TpE޼g~ȭyia,pGE'dp8>UC"l&RѺ#K@uGw0$G$2R8.pt݉g`<'e&1Ok&0>SAOaȠ7!3AjRn|ܓ=E)+'ۖ"P]O! edG`eC=T ~Yd#~h [^] i/Fk!dЕR@[H+,ȾQϜݣLE@'a/O]]=@ϩ"'kS<>N爛%mygE=w5DS<-5L{037·`FLH}i:9@ԛ5X7s옗hnըJOڰ^&`ߦܜ:?5C_jܠkԣ#?ާnOw<qwHCw9mw1}z 5 GGbUx_2D'6XONU=AT:68Gj෨Y&g!GcEq>f} {25Q ?<g7W%W~c?y8}җPYf<; (5yfÞHsu.O/s=s\ ݐG钡b V'GgkA/IZl<[ZyἎQ>zd 1.WQݑ9ݭ)ؿ> cV`?uspuH{y3$G șJq%AP)F]}G>4-}ߑ`{SYI<ө7[Y;.hHڨ]z a+cќ rZ.du@O_^29|-,x+˫OA,~՞AMok3UYڣ7MW!]Ȝ?8 cZuv|cGfoow*Bvp+̅}±˯ͱXб3=M~wR!p\JrC SIJGǁMO\ng8 }ݮ|a>6Hץ^355Z/ҿg9Aͼc(齊D8xM::mB/9 \_O1s4{_)6]:.WQKpJORK:.k9gH#2&*dB;RP<JdiNVy"! "ga/pZ=dωM;qhǹ}kоgȬٝwa9;q%!trvi'C'ރc)HŐ_7B6p9=ډTwDX$vyX3PpW'aN"=Nb@} 1bF ꑞ9+ !NtMv! yrPՇ$)\.U \Sqmcd (sh葓 y73w.^tI$Y D]q<_2CT5#No{+W \a?wR_褨nƁ.紇31=>'yC).Vu2'Q| q!%sRrW/?qL|an9-[o;QXZ[Бc/4\vҖ5~9$5<9j$q'ݫ#`{޻DPyj)Gf4H>E贓#UgpJ!|udq_@5~{*<;c^9;xscݛ4g;ZI_ sɌ,%|5lq2?mNG~ d8Y7" zs֯͂}MȂ%@NׅBf]9dkQy?u*悓i7k{2׹>̳E_yW#y`- sy9|ur>e3 9b x]'W& N6aKQ?NuKb2:>5tf1_>Mav:W'led詜ݏqbS]~V:R׎k uJe /֋c^/Nɢ>dݗލ}^̟8̺_ z ҟqN5G R;Ƽ^-4_yx #(:zتv7aQ^, }߇\R焷~Q>scA>ȈoQ;>ߟD}AFxߕ"ڠ}[yN쫘=GKMGb>cٌ:X}5.[?xO M;AM-0+A=W{eԋPTzJ)^N#beR2^\z(6'K$G"0Eh墳p!ι ﬈βŗ l0s5aa_t4q;9 3kVgP j:-vT"9r19a=ev\gyCM-LcQc~3*,́)LXuy8U28[v\iΟFH`GO˚{:G؇Π_`s@}%1 nAu7/; e8f09X|M*1qޠgP+߯vZ |'9{ƙYb~ybF&WP'}f]Зm!Ƕbxfz 9cx ;!'L![ BBBTL7kaޅDOG~Y#Kȝ$k!sBwg()CҴ_H!{^oyvC])8 O!%[R4 R~c S gJ f R-8!̙74?xYinly zqRz & ƺ6`זMo$ ;!HSHC*Ç'C>܋Vs2}c2cFpYs1Δ b'dtBrlbk$O{^2RX8>"=5d&`&^A)߯O-xݼ4</^CdS^~JDΈ,X /dF=P&퇬nȞBYoI wF&Yk[ *t"_:U?do'\Mg]Co 啘G^7ܰɬ3{A ȳ`Ԭ: R802l8e*&" U˄iCXuu+6#Za~R>l^(g37}d̝PO@B K!=-@lЃ0BKpC<ݍmRsB)~OBYL7`^J=rjЉP ٫m,cGc!hh"',0~&ABm2!{_%F"1~Sg/ Ǻ߃y |B )EaE<@(aSPGNoz>w]rc\\ڬ'ײ u?n"}>pM?z*Tk_02J̙3@BnR~zz.:J|*Ge,Cˇx >ЭP7ю@S6WBRa^^Qw‰ X{rh1~ 5[W!fw8Ga ga{8nD"xF>zs_B[A\Pw;eQb|<)z60VXlWi_+-]cBhm9p{vm{?"_Tb߽ nNea>dPM 1G[ijP?n>R<&G}NِOÍuNK- W"MqϷ_^Cɪwc+wܠQ <~'/т1d9K^j8 _>ZtlMm9RZ!2x]Ż@ eSRZSx}I.oZc}mߛ86%b=4ܿ0 *{BhэI>a-,5v 7vn=n؛C:/ |;/ ׹Y'ؽG쇡Ա?8B(eFǹ5pCm 8Wz;_<`s!;/"0C< v* ׿h΀_5ƾYlЇ:Ts~_.s):%0f[WO +C?q'q ۨ^_?g6Sa"70nz&7h :uNM._!EU-LԴ4 Ҟ=_qjZyl8qi/O<zVmfAq|5MxHs] ( KA}w{kЈaF<_M66_GgeWi(rV%i?́=.5%OiBjc;9;pY*"]?4-$IcUy2J Ʊu#L[9fE(>YP Vnߥt_5GfjùhB`DZrJhJ^w1m2IS_psd۝Sch[OU(DI {3m[1cx?vC~gNOGۑ9 ;W 3Ȟ2##&[]<;G?&vGyMX ե8oЎ76Zgm`| `mz\8yѼ0W/c<.DeJ@ ȭFVv |"5b*_5$U@T 7.Ї>ҿ}&aҮʞZn N&bw` ZE S}^a}`F,-G{,0Q #n}F@Aup 2Sv#XXOl|-e]H" `%< *ޱ{wN|ϻ_v%nY(VzD{j:6ZRS#G37nc=Q LI5dW8N_rCK >qOiO`f>5Gp~K22f;카&cymCx|)<2-P+\$vJ+0:{-tz6Kg 쳊'UyC/^6e+8l[3)V:W{u?DN-iـ ZV5oqo@.]+ިwQ/9`ƸO󶿙<6uW/6l>oF=>:~xCOa5{]mz7, /z7|3.SE:.軜Z|oKuԽWI^};uosɽKl?f`M| U:3S˕]ȄV]alf>y&'Au.t/~7]$4@ |D&ŖqdRPc_\%寓 /+5d{nC38Wu~W'3p~Zߵyxn Ƚ0wۄ`%} D={ğ:lp^}AO<γ 9ރu}'|51=K+I<[M Yzw(kZ@zӍ3ȮSF!gro'h 9R vȊF|ܹW]7qzd%@_ej)>d:.^-PZ+X34w:VEJVId}r2ƣH\_^}z>Lt_6*i߿1.:riXzȖ Gͥ^xΤ3m 7zl2}cnm(pA7k¼gxdחy57݄3(9zP{5C[ =C]X^hP1?%!Y)a4i& f8z8Vd{>XG+@OC\j8b&,qJM73ɿϯ /^0;b=@+ǑPҶb߁gO]z:ypiہaV?o2KtR xmɝyx#d4ܑǡ.3sW3r۠ńܜ^~0']qww盗さCȃt1)`0$st|>/~#(Vd #j gVU|޸owԡCmi5 Gj<%YGs,Ǡ֩mLa퇀lݷ󀜌d@<Бm%bk ڠ^7w+\ү/.eo}q̻t|Qi+=cB?3+5Mm5z[87W߱|ou9@(2{A8p*Ҁ8 ci 8'Y}KG,:%a\KO& \':*Aw;$`5C1` ' .9ukhzaQS]pǙGP yǃoN?+] v=wEin$meu1OcCS#G"#>+,2-vQeg@e#B;XxH~S0p!\3'nUf'㠭4ͼr-V4]꽟k?!C󃙣E%#,g^|bUMᰄEwu (4#?֔P JG(?廦=_wݡ#4eV='RW;v;4cM޽}9)#wDK?Yͫw2cI=p(S!O$ /0WF縿#l!1)%eGcݚ(.OܩpzxsoΛi/U?,YLiwzm~[-fcN!á* aI0Uc(whmZW!v; 5^{pJmmԑaRb4%5RY-J/;H]xw1ɅB}8sPSވ样*sO)]gڝF?[jv5KǯRO,N}6{K R:^+6Pnq3ƳQ)1Wl(^tޑ/_ W3S(m\}ؗԃ*i7J(׻_;|f՜IE&}}F(^qaE6 ¯LUnU R[R:s)FF6 P.HHP4S\>?+dr"-bglɎ]"7};6RT|iM~3FP c_Gzqy&-VdSTj'" Ǝrl'Yӿ`+FR P 9bKWVsd= }6ܟǣwD?K۔C oX($<;A/fuYǔΟoZd)s ZC't-R]|ڪɱ w$ID!, 3u.QQY ʮ0S m9)˳5N˒]k~PTuOY2giO. ݣԽc(u"_4)H|[ndMp߯.%Fʯ .l.'mkV]X˵kP^:]ԊKvߙbܰ- TrMOv:G-59DjbGjz2(ݷqyn2e'ccTc{ )صO꟤QfT!|;zBٹ}g+x~䷔]'l  Z/= Iʐ+d)u"xZPfi%qO:)oǵ4\e]sȉ+I^MRXMǧC|V/2 ~i`ddpd{S<=DnmW^iG#t6W eǓ*֛^KZQ,݈}Re-6Q6yE|5T y{;Rs([ ʵ$)R%{l ]:CQg_AWS$J9w!I6(;sK"~ :?Wù҈M/*STP.^/%GB%,)zctBDUvdq>rkI 6gΑ"]F63Q @߲JOJZe׫;JƾI'd?ʛ&,]&]?ox;`تD6ě~E 2x-/Em:MIPTuG/R{wh&nj݄37ƉFv ȒdXXҘjrkRH;DCJ63"v>e2?H1SȮ$?S$jX:(wm4Qˬ1;)YݡDeDQ?e9G€uv2{-5m&0&JpރHs{oJ=L%[ezf%S3C-3)m(.ߠ>5RD?󧧐>v\eM>-&?Z ?bm܎$'CxH4Ż:KTcu:lk)G˟]1XN1u*A:M\3+C16I/~xeMwɻ/N&{FoYS:LexoyR]mǚ%xR8LyHi#yzGv?{Rox2g3(RD!eC߱>qkNQ+&xO?Ŭv>W:IoU;Y92 Ud->FbCʥĿdAgH3]/Te'(&RFzͤٴdgE,adqujy?!KA]hk۞H~uFP!Zsx-G1/y PWs{[yMK9<Տb, ݎ'L^wNSCU(ڳ}&$|M)±rq{-W~'g+hL.yD7[)ro\Kf7t'4}Ҳ5/_l:#pc&xS=a/ӓ86}i0y;uvO5dj _CbHF;E%bb{FLMSa~?)S!z~ڞXGcNa:?mB;CZt!9 ةux{+sA{O%+]1uĭH~6Ù)gkaft2%;u%-ψQ9@ZwD'GKr;Bt2-AlMa } w٧ 23=JRAԊU ASDi Vo9h44؜y_)ߡȍoL\]S1s2iSokBV߳3E@>HL '%u?!I}ioo$ֲ\֟@=Vz{GY텛}$֍a+$qz `UYUDX|g;9IXdWImyE.};@mq/rH4$DGV9=:G7h;zQ"OyO5kqb]x ["Hjk§߅w ﷌{Q>ࢳƧh1= WڜZՑJ#d$qfbԛKOsZ0Fߟ&-߲4B:h.6]C(l4o$niyQv[{݅qrdZ}R%Ϩc ^Zh&9 M2gK(i>ȃF)WcGRXNs&o>RFVo0;~[0<m6 t$tϓ\pAO& K?8~l)wOTS$ɯS%,Wl!ݷ?sѽ"ϽWE 4bm~%MQXk2/;^ WVk-qfmw!SLS/),$hgb xɽN/vsvbw(6˝T;Vʴ {!Oq;U_RN*jʛ?YdNƁK"}7L=wBdG ]af|JDEcė=FA+C>Jad]z{HAUcNWo\$:}`HT2Lږ-2E'6ET'k=G*nK:"%צ9N]g̯SJ1$oQ$ dŷWɹY_ Kߑ7%"mw"l_:n#mמi^H!eGD`2Գc.UjdE@S#'1C.@?16rrE8]=;y]B*V݄1/dN{ϖN"Y?' zOXGp8MT*GLP,ZOم.sk.$^ixl1[5g'v+Uܦ7=X=5їP4#mJq锩uQDh$z~B,?b~F&7, ߎxI5K/$U? `׼u˙ppec(yq6||?K gs<4z_kMyξpʣbg6q"Pz eA<w>UJ-d."@w-+F tmSΑLkd*%G=K"b%]>nBBCEbg5UH*G~>u7g"G1'b#-4L8}FRALѸ$\G 1{ijt\3ŷ%2Sqy7^MǺS-D|UzHl$V!HܾA;g +ݛ;B O>93]LƩr-p]Dyk} a}i[?~+|EKoyX0t3e[ƃdg 5 57W$K#w''bAeyG]ߪ[QFD%{\J ⼚&4Fz{zjv%}il"Dcj,h h\qk(HHS.E8^ U:% [$MCp ׈m~jT.ШQ5Jv R~NhJx{Q;04s#N0_3@"pi,ij~ӨVS`[[, Dn%vW)qDt̮P\W*omMɛLtq [r#co q1Ŕ\FCԋb/ioIBegxxw#niؿ`z}PvWKY5bf_&Tt4i%ogLf! _}V"7Oxh̫'.qe!`fzYƋ%[Vy1o|X5baz\cvG0#5=!m4}>R ۦ.8Ov̈́}":D h~~; [=5;w<:p9 ]o4e:& 弢Q ez3t㺡D=/Pꔂ<>B~[,|h*|KjA0:g9N ܯeru `{_ȴ6!4uFfomTCSW xcg5 !*w5EFl]fx8U<ה5R+&Y{FUy*hKO}]-"jؕEc p:`rbU;| (0G D!+TF{c-JȊ$YQFd̔M2Wʖ=/u`dS1NTkO>7Z?ljl8)nM^pZi;fJC}h,k4fuz?ڑ@i;)1_7x)RAO *26ma0^Taf;F 2?$/rim/SLv0'WB{ -}1uR*}9МuVm\2b?_5Ոeɡ\?[EKfvI J{oPincWPZ(u0dޣBnQE2:0_.ޛ4CrScV^U}NG% w?R9 f4hdѱ1Y ߛDo բ>Gt,Q9.gZ?-uݺ?zٍ@Fh)jwAŤI;AW¤\0?V9~/J,S gQ87%| eM*]^G ?F$}>_| G 3\m/#7 _bl wj_$j0ƫr `?p( eYm'}t띺W u\M9H1ΨR}$1Ӫ1Xտh2Lo}dX?`jљv] ԿR//]Qlg*W'V`eI(ocFyTRqirãJM%) y߱N[)5"ftaȪ)f%b[ʲَǰt;L&!z@nG=l)RrfڼC#V#,WshVb?rM^^2zJ<7Av- KtIqrG4^b2>U= 33 ]pb|Wƥ$˳c*b7|--_w-z"H Ҝѿd4QX)>/ژ?!e*stʈc)M,UA$V|#@}=fT}"6~!9 :uݗc{-ÇwFKQ,Ȫj"3PuGLqeRq.y4037K~{] 3,|^F/.eJ~26)T3t邋g%p޷&ܔi)wb{/IB#rRQ ,0zn ]tY7j*RSz}yk4|(Dxzq1ӌR}W[~%(,îw4wMh0>E5=cS-) N>QĐp{Qcm5!.-mڟ+",rkۅ-nq9A09')+Jhn{r]쓰ٱ7AdRz~u* MH* l~'uثm}u8m·kEv b񎨃Ƴ&+C?=iM5]* 7;û.b5"S}lxz? =ߗ)t$,cPNFӭi%9/AIxmי1tt>lƇ^ih194ck>., ZxuET⇌B;`AL!L-N[e //,&aՔN">poV fIq2v=GcjSҲh &Ӯ $W&[RTTuP-*mSte~ zWjҟ豓fm4VW=f);wxG&kTA%Yx3_F-~dj%nHT=Ɩ*WOj$.2$gIJCP~s_WcR~!ѫ bRULE?Q-*-kk&Wq$xr^]aihY̱YoLj$Cg&O ߋ(I]SҖOwE.ӄ/dbѦg?\̨,G[gg"m'NlP!D?nƀPk*@{*8GNOGGJGRFbTI52d^%40_κ  h Wcy QFw$ZH8Q΋ij;y@sX_TmLG/x(y.{iz%m<ڐܡKeGI R8{G' pK`.SrO BEZs44b(rD"aԪ_5U2{~}zϐp`zsjOBsTQ*H ]G|l.ՂKxp Pab$}#)gE0Uk؝md B\!:Hi~"ziX9/&$"P1+8u5!n$d%pWRBWD8Kq*=+)~0OZ~}t95PUcb:T)Iw1_SR~eYˢԹqOKXlT/+X_fEEGOꦼY?8( uuu4jq!5 ;1OS Uֻ 8$Gc5m˓>S^o?lkqwK˃|ѼBXCno{QNi0wdkX^)`zt됐6HqxFk $;c\t1ZL_f QRAtD51VWr9 ,l RY oOKjT]Fe'Rs1昃u6hiϣ$s)%"]QtIfZD_ 6gDɺbxsc,-'vRd+7R1~hk.\`4EݣQe)}<4 c$2H$ j(cIߖJ7~wYGTW#'d{!kΕZ?Cx8(,$2~h`]X"=#хr k,{⺋R}|"RdWbZj(% F㛵h1S\/Rf{;9r 9V6F0BLtN4 l(S a deWk1&ú)Fl-\ O`\Ygwsc' Osk?MM罞Ƣ}Ӫ菊엧_pΑy4]vo^¼G[#')1?p"4V,?t/=?I{]^,!k >qI}֐K\/q}"<,0T!f@eG+S?( 6aW{ 05jؾXs\?ڨ<3A01tdS @b3p!e1Λ:F/an?D=T'AkЗ`Q"G-qG(Oɹ}dTLP=W˜h?@O; .ޅV2/6:iKTKx&9CJ|- 5\`& |SV^̿Ŵw"BԾOG?OOQ덷(岺w~YS5Rk{J>j/Tu!k<#60Ŝ*S3< Ү` +vhߴK* fe([F[w1p>ŲvM@_?CTĮ2 G EHEׯ_@3-^WβlzM1F[6Uau>F73ƿ=AcW G/a%'#'! tw0Jwd!d5ǃjjf<K~)RBW۾^5{mΡ(cMם+`16\ I vDONG6Y1G7PҁKݿQ5ObPyl]xwh,])^~A'>fG-^;F_L(Oфǁ_hYü@4zG}-Zo-r˹r? NQRw7QC&miE7kU**>ƀu "Fs["y,LN?176Hԇ@g#5c\\ѓ9ttrm8<Յm2^[7wO:l~aY0^U&%ȐǢam- (I3zcoB}=AwEZgPm;s%Ri$:驟Y;cQ,)PK'H^zBڌG-$Ywy]uGM* {["C9n &2B+ז?4Q݀-ec\Nz-Y,D۳)Itd&Oj=\ ~CgRHy oX fW#eq|C=wi FΒ+xn#"eOQ,R=@n.3.P{;*C`]Sb*JWV78d-!%|Ԙ3J{%A7CPd}A]J,4mHbUkл)D7QWV(emL';ְ0],Eq(?(KNy9\W:zavEEDF%%mIDeKpSx>zq@ l>oBEeЩ n8*P`|m)gϞB qhx#́ʟwt\UE  FN {*_Q?~V/Z %hĭ1 DQf#REb(Oo5@N?p`#X rE]'M]d"XhN^ep[ j'6r`_h͡IGzD5eOu-lۘe;&L<>'jdrd\qW?o="^$Oo\#k? ﮜɂF֚gdhuYZ6= bjo+g&Zm,>ɀbZw{?ŸS!mn˵(ߝH/93be kT{y]w}>8WV._"9M;8Hذ{im9x8]{uu ۊ7/sYoBiɳ)gT[%z`=&HR&.1,3NW予l>w/n*M_>KP{k ǃBeO ը//s8>%+g𕿾C S?d+<&v!נixڜ0n%)~{<'As,mYPP>mB3(O@Lq YǤR2k䝀Fm執o\RX(l h\!֌cK}i|o/zu2wb NeKt1fKUM3O_Cß ק*0ҺK_ hEh~9|Z`ւ_=|Uy宋1g_.o7XKtoѕ L{b]øYXϣ|fZBWb3pjUxJḲn)ZC)J0m?bϝ*֍-D`DI8 0ɍYGz6fv0OYrޮ {s9y{w4rQo_˙So n`K/c,ܰBZ;Šh}NE!]F_uѲ '7a,d^΁ƨj̊&,\Q|=SK@j@Rg.jm5:}&fa#O_` .<ٗ}Yʏ)rG&z 68U'yy}#^j}ݹso.ƥa[5f|_?V ٪ġ[ O7Hܔ@~kA&Tjz/WT{39_l'FLyB08zkNWĈv,ՠ %ܭPOhcxXF@@0ŁJ2BYJ)_,+|"uFSͯ&~NׁFCwSI?87%J"(*Zݨ- XGyu^znVtA4 Dd20p v.ޏ ;PO*AhrN +:}CirПE 哪{Ƞ~X~2ӂM'a`Nk jJQAFPr];'F,ENL:u0sgϕ/F?BBGT:a^QꉢYQ?6+[N?*d@rg{m9GI[% jk6'$T_,lsۢo%x2aOJhY6:m*Zr95^وEHϣWUúGL =G^֠?*QT( npPRSv5F9Gdvs"P 7+5T~"*L@U p.9ZMְzV_܃F>lLF1+~@}@*] kRVf!dWQR>'܄Wp3w_ O YcK7"Ё Z/l5h3eҤ:ljjǪIS JpXl/b&$zV04-B_@[`1;|nCq疷[SYEKVXTT~:w7c;[A'hd%pyC'\)6$ $#KQen:oH#Bõ(\ǪTթB֠_EI uc\QUs$^1;*bR,f'@ލB84iMzjDbb%=eVDT=#i6xv4*H^Gr".K>3@usC6%<'2Sya-輪 QMR9vQFqn-(Afo' $BjfwL*=$aZ>M, r [F_qE=T wuޞ?r܆kw cW\Z}>؀yUV 4p O^,~FiMwyÔd7G8 R<0s5^{X;mH#Er"*C4i j*L땐ۄ|X= ш"SW+op_.EnXLf<߈f~>><,wCX:9:>1Zu28gv$ w:r@!&9iA7i@/m0Kť*tQۆxrO"Gxp6@0t•=3.)Ȝ}ġM}8xnmܒT5w 2E$uV uda.x&)>N㶫€jT$~;;;her߲$&2L.}{mwy+bԎ\wj')g;Dyya_OLl_EvVcp)KN/fv9B%$,gH` ^qzۉjG_RבGDuJOn=\۶w4l/ 6s/X(3Ab ཱ7a*׷F/g#?Vp]رl$j7VGjd+롿[qSlm-c8p-qRl^(^U)tGn.TG|Xn&7 lknmqYB_SlJ aۥ Ķ9J=0VaDv9;VT23JlU|MZm.wOm|UƃkpdWyw^<)r5Ceefpg<)XԴyA~1ZӬޗCOI,^f·ЊY)?hfV&j#^Pra~\]W9,(XMeEt@B5+-t|bP6N6`-=%G,;X>3? FZ-).ԅhIZTQ35bi%c t4\bRL^n/Eo }K WQ?dY+GWBz'YsjˠzbTE0&ִ&<`_ӌ~Xk ;5&nW7"QcQncaqIuۉ JĞ!/~ZD嫿y} Q_+10ʢk^bMXݟQYDȾ1[huHF^nXRqbj6qK3W U 6C=_>{HPܐ4o9"NC|v;k RLwуY졄)$W B]&=/';ڏDPbˬ;gj5%hrq9!$'w 4Y T``=S0M^_蹭%haY+XimI%Qn1Iv8I΍ަkƛїUR:M&**Ư?qH./ϺAɭ_> IQO`Rxa+ksRx\W7/NѢ`['yoN})9ؽ܂ : 8ɳn/PY'W<5kF}?2]OPfCc:4p('GmSQ%$pˆl @ jF?d? ըnїWAEܻRPᢈh/`nBɔ室{SRϺ>^/G{6{6((W7!G e8i}=ۿkbD"$h"6FOdB )[n%90d-pǴh i!ߍyn>|Zz~uQ\S.|*O~"_M v74foh Z{#A =̾æA'⤆}(>s_$_d{5:0td|H̱~ "ldEPD,zgT"9wjJ.3^>^`ݶ腽+uΥPW瓓O"`(_aeS$ Ez\gGzvԆ 32˂ y{5 g ̤:ªp+E-C2xs&xvǺ=玴CFomF*/Hr7Yt :qnOY)#_gA 9dlFtH"o~yh9N#ໃ8*  G ="^5 (;qn<竑/-x'DGíLƕ-v+dap;:KD/U p 6xȉO/=fYmRg:R zXDƮ?A\*d8!YHwo&~od܉tFwy4DC"77j W']CEa:J^|-r;ݛl5)5&'Xza"b/}-B=4"|nB} ,Ux}fZ 𡣬Epŷ`ktI^!$RV0;!D e=Ӌ{OGϤ߿!IG̈́ 8lL`.c kem Néj!_G"dcؠ "+~EE ]OQD<ɻ<9dۯ PV{}oOxO d#@X7?^)6Kdv q| 4RzNDnF7Z^]B8o~[e,L|\h|\W* n̹”3ƈPÐhJ};#gcwvV||q~K } Bj|v`,j|+C};DDek(Ú·YjF\.)ZœK `|i[k>kǷ䈮JeDkwՄ`4AK&舽 ij@.o[CƹB HV,</H`Qg ο3^h!%ۺv!Ηz 9D>/62BD6a]k)U.(AМo'?s{@1~2bIx/ƧrӿbqMa$ M%8 MiFkh摒`G0L.[f DPCޮ78_:Ab/ ]F_QM)K7Uhw;20E]?/s]d9bF= P 23AFt. L|bUE6_u7<%} %t+䌻6|* gІO4bk qݟHTE*]B%^4-*9j[}kҡ3/J\3/t6ml>+,}t-42U d+'H Dә#tψj72g|nȝ&'ocVSAo/p|׈\u#<ȫ\A<{F;oA[DvDJ_L|uarjl$~hز'z4_-JZm xxXAգ3K>H мMk"ꃪGK0=ŋnN1d/A݅{Bj^w4@^'x֤`jbI& Jc@|^FX}4x&빣F{&:{b6P }a.J'ջ(^t|P{ LG54zQ\D'h1{>,UPݾ[vk;X O"~s l$ڠX4ZC`3fW=jaFzܣ$bDn ϫDǏt=0.RRL@NT =bRHv0xR,U$q;N{$ 5fnQα" ˆr-;F6U\Qp Ők<,HXVHfN H ϰu-`H '%l\$'LXEb[*QK@KP ʚH!z]֧<mf+@4]C+-~/<lX: #rؖ( Eyeo+=;j7]%#a6!o\@҇0nxmq]-+3|X,[{@WnHQ? \ 7&P-KWNQQ] r*^wH"]t́O =E9S-WvE hO^G.xްygI뿣$?qwNe5RV4i j0~_@vnltG*>I#.rS=FXlxYɜ_ww+9[%:pCT|]b$FDSOd15@nJvdԎxiskYl_F2X BKU{/q1?v>#޿X#L%Nu9<%8&:ՈyM*H>Ey[M8%.ۤd B?*[Iyj&$FǏcR5[G`1ȗxxg1#@∷}J!I9C:7Sg2IJ.x'j-ⵟ #C|Zg`LaЀ  "P%Z%. (ɓ8I(&I( #iG'*}%I7JwWDvc`jFh]rERg="wCS4)!5L>FY7|( aWﰀܸo`y!f1B)E# ,eZ8#: 䇞w}\նPc3 `TYV f!.}uCB`~فL_cڴ2Ҟ4CÔv} p͕l7{ ǽOD.yF JT} =հ#dxOƤbX!7 HD{ؤfy&c,Za"۟.ƲH]afC¬WMn|۷YbȊMxy(pRd!6pE᭽ې䵘 8;*\50wT1!#ʨC Ye{t{x8R}XfڄV߽bvvO[eDF)B'`ت^";X'mgg漜%o[t:S(ְh ibHZ;ˌoA[5ޥ!q[JLs76ބ0>?yl^c Y\s̀S_QW"ѽg=Es/UFꯒ^ {@ⳁ)Pztt=pṵ"ĭKE}G"W|f3:Ыa9풲HV(&O A-ٱ$Lv#_EpjNWs%"~|.LiOԜ` 7)u` >c2 $8)0MW+6l}KvDMZB 4O  $MYxrcIt&)H{XA^77!|zɏŒO1Q&{C32nDj_t+'\d 鵯]>-02xu P#uNW ~FtE^YCQw_,'[K-E֙z \Hʮ?eۖmX{8M4Mjm-"TyeţT߳t, K H`_B> = 3ؘtR٫:\2m ̩(`+4_\67%435ڜ_ z_CHws89Cٷ!a*K>GU.jQ ]rw(jR{m:sfTbO6C?9{~s# ]4J~H8^8j O؇qĺkM.a<7^j($3f]_Q6{t7^EưB0d~MF/ğXv]*T;*s m&k޾'6nsT"xMf s$a"GcӴMlK+%>)٢Ȉ,/ePG Ep+MP{{`[;E)>vY箵|*c v9W D R/:R.E`?(oJy z0%H0àF|>y+ xbO3w@W.NdzB?URk((Ú=kvMȟԷ[m`).*RqUO}5fͤQ{u`vCDt 1R]vJ8Gi6ˑ>['πd>P^~y*Ix\`r1x !~OΞg&-KS,# }_ ^|{v>W"Wug#,*hf?Ǭv[9zx ]iB>w.)iGl{V`úGGĝ;ks/dKp91BlYt_ , g+[iMj$4l׵$@>m۲Z){"-Buk=d%PE#YX` R&_cGyk>$)"^l(@q>.O]_!oqWۧ\4 Ǫ>BUq:<*.?T1 ^53wxv/!S&_qϽ g,UTLRyg|Ӿ"G]>^K@s9Y_`'QU<}<1*~ébo`FT"n]UPq|CR r9r(S@2/y˸[YktPOmZJ&ױ>b(iGSL_8RэQ fOC% RZyDq-<\kJPGyԬa@&UO \l*BɞȨAO7;-wdӹ8kRKϕ#jX~ 9|sVqWdI'V}CzǗHj昤>4VaN͔#+`+䐎T"m*샑4"Ab~mt )uκ, BbCᆂ3֐Tl]I4[~~{d2':`}>j&.lQJ{,9م?,mL#$4ߌo~!/ IƖMNvl{6D鳗&J꾹b&& Y!]87)Js/GB$.Fs8\sj˃Uz:LQ![/(ɼuփh5Ɍ<쨚,{r?|;K/AAt%p̙{~ξ!4Uև<]z3ۗ5X;~!Z.%}Fd"A؇ٷwbq" Z*BhSM5#5 3Urŕ»aK3t:{GW:zwpn9NgCkg@Wh c>RRlwqtWE*¾r 8O!ғe{0 ;LtƔ]t{ľfJKP oaG;q 3RBsDeBﲷ0e>şRoK³Gtx/R~8ۇV>Xj;PMIC{*H>1QBbE \c٘{>]518xn؅ EnZdlnmrZ`;]o_M~ \fw 9n蚕ė9qjD/Vr}KfE,ͼwJa(6H>p ~<†<4⛶ OPcUh^jJ my**瓤oޥ#$C?d)l׶U"ńcv8$k(*BFĠ -:x{<-|p~.+4r)qru%+4E /*|($e OOOk^(+j2z:fB>Vo@nQbo=We!I8WA_/́Tc?r|IvtXqSX,~|٣5i9zSE\_MIQgy\eyZCo\ ;`=H ?_QD5Q/^L|96sS"×z7.N{ qWsaN4W~M^XƭWfsxN q*r#ۄWeEWgK  iKI DW/V[M!m>ή]E2_m-c?m1O>Q=thkzZ^-˿8qZd7b>qǜZ⻐77- t}pܜUo jDջA8mKD{ |Xnt Ȯ֟:gYHv3 X1EZå|004X-u5=L(5;Vx7Lso%b%(R!?Ҹ6 o_= 9uGІ/]o1!tO4/x<ĸo։.^LA p/ J ]ꖜѥҒO5c9{F$k1cZũ[%%tDmQRVk,Jm]1{傤!Ý#;URH6_k b-4y_!+[ Xx`Ds=Bw(竚_GH>>tORHljYHx,*@/乀o kWG"ǟX)p,r5)IEYRqG§눛&kE^gK 4#f1\|W*7a,;?@Bvh^Z8{s _yU7/1EUM!(>3z!MT xŰaH=L@=VB΍s ;5 nW !eJΠ:tWX2{h8q\K ItޯW݃G^ Z@OXO9Wg/?0xB% =+p&Dyp,NdfP8~wDϐ_ܗZO6Xu50y8"`/j- Oh}yISU&6s֜{Ƅu~ekP+ZWW].Uî/@OlA#_(\'Cy%r3qQ]GH+*cAP9~t>/@E.a i!ːoW7_/yoB7Lv^-u4С0Z\y^ Ũ g_1880gN+6q:%=w4AT-ީ+xIWYm2&Ij^[$% {B֭@i up~(˶P0na]86#A %*4r+E Iy) v{~Ih7'z-xMc<*Mw}ptW.BPf`]3h\5| GCGmjZ;Eh(fV(ZI0I"ke&j>jOLZQ!=X5vN@ uON2!]ϾBcoō8HZM.ZC|jKq9X:l)#LA/ą>Ér]Lŧ5HfM~l0JG_g8[/hfW p< ώ{;tƐz[-ؓF{9{TH/¼G=jz?h0)^{RRn҅~,EzR622_)=}zh^s?_sUR `\K@2rJ<5f[* n w^֐.7ka|J%Ӎ?X5n3c'4!8 Y%0=|3Y;:SI|}\W\ZѵwpH-ygy8/U(WLPr Q߀gzyK_x!_8u t ==^CJz?/%w|xu ;i;Py*"ٿ]K%WVW?&HBQ?LÄa1A#Wq :K.@iw>@l1`g0!X-&*bDqu/$mZsl8\ )xfyͨGgf\P4|;=?<}&jdK q8RyF奔4SIA1eFB*dh9㲤T]qĥw ]\w7@B_VުCDM$Z l- #5a#Ӳ߅\v} Pwm)޲vx V}{1 BX;phO6);?hBvy_="Ts dHPt}h@I$! ~Vc1㹪 u/|?qK'Sb3-qiWTG#L4,>/A`ͳT~|oέ O<`*NɝTM$hZRj#5ݢfǫ;+㒟^Ixn]9kmgkkٸ>srq/w&nK !Jp^k5э'0;Z7Oq^r?}ݺ"7%%mLMR$q"\Ca HG\Ec6§Eu8]ߔW@u8-yeANOd+r»ۂ8o=u!.ɔ]Kxqd&)x1.x/,I2"f(q'{:ߛ7őkHwX k&D4&y%?W-CY&!O qTA,Uƅ8ZE8:H2.Hwy/4S4U^$KY.u,Q *YK `Af;_% [5%LE# -J?xAȝ N 9gS^A}ܟI#ρ?lj^z2NhI":"a/@(g-!ƔdL@OR#@:CD4Pj=]EH"'c.SlnhdE~ۥPz p!Ĕp?pk?WH킾SqsH/6ׅG8qqoj]( K$>ÏBê+WX-$JBg?lYgʻ#`n,Azـ "n^l~aDPG#pbp*N^~[\7}!?}|[IQ3S/<|)^",DdkGg`9za osgkpIUx<}H'iVS_$P y[* nCΗ"(<8zUo: IaQg+7+qBreGnS.G"k<3ிH[pһ{~¾Cik9aRItM+Q$;n܊C]o䎈D8U:/9s"kco{l\}̠GN!|ҥ6PںƋœ%k">tT^X H0Jf71%7ǧC 5+dꣵѶEp> +4 d"D FN ȵǓ7^BJyixƦ|81z|}}a)t ㏚fGH^O# ɠF懤\`96'qy7?Յq ߊ+0]:M: qO5|"ٟU au72n~) \04s*TQ,_xmޜ7ˬp@QZnhog^߈T;l¦}8 4p4c͑:H;4ɚpvw n[}zi6/"ٓ _`40B0Ӊ~nk3h({Tʴ+6oįZ;; eJ9b_X=Ces{gI'A~!9D.\V| GvIP7PolP i*a_nzL4w@C((֨Y+u`8P]NrW! Ծe!?9 PpP"7_194) Qܑ#G"~ؒL/L[ ׻:}uTGʱxUY>D?q /A*n6p\Οu>ӊSWI@b٫M~ ㏠B ?*5-ڼ#V?< wn0']a] a)۩Wìc/ .B}}?jLj HP _-"v2xV/APD8d _,`}y60V\v>JI#OU39~s>db'"|#n zнMWwլXu>,I0F#DpcvsvCO^pcG5m}j7 7ϡrf:x8: kZu_٣vv*{nE.s7E*r4ZOS8#(Kyܢ.DԘAEc!H,IzDXtyLIVL>=o}+9B'7'Ygo`gG, wjqꪧ <%%bL|.‰: S$o_iq(zLCMtHM+' Y5.ЗmtsΝ_ĉOsTԼ1@(.Cj9NQp3QOa?Wl󄬜3;DQ pKgg8oA<} _-g<~,}R$;w)t1B6;E?=mwM|؀rsPG0|ƚ>)% e7.'=2;Ķr}SH8^琌RS)[F2gYd˖"}~xs^uu;䯑"7O$3.ɏ>Umu# DҁŴ5s/ .Z}f761 l+Hߟݚ$蔩kt${:Y))yHu+h]&mF^)k)#_%0R1qHc5h$Jw?BԪUi{8Rx=T )~Y˼s@(Zp6FnKxﯺ(g- YӶ!cx QF \b#_8U M7>/1AvK5ETp֔ ]m'\dNRىCrՈ%/`RoK H @hZ38}k\Pe?WRD(I^% [eJ;ҙ\"p1ZV ᥎w"Lʬ8/)zʿ o v 90^1zxĠE{kYGyuyBRo[Q֧o$.H=TgIGYoV7䂝$ܮ!mDNYtz(ziExx%zfUl*4P{B7 xwH㌈PmPs{4r_:f! gJixTJopA&m8jWKM$% *Fb_"6Bc.oCm'{;1-ý fy.l\ȕ~#+E 'M?eNgh܃6RTQp"k=BnATBˌR.NpN딄;/#_!£;yskk&<3;ݦ'`ݱNW1kqg 4jjv}prvf^D4JXB/U^5U>P4m BӲ:M/ɣh ]1Vǟ_#JS05Y9EDG\%Hq(OV޽~0 lآD^.!LG;@7_|U WܦӡteDS2_z8/Jp&Mms&:w(`zbuQvf}j|ءs>r8cSaF54>Ȫ9}g v0IEn' ld MO-]IXn^)vNnYܵކٴUOLN"RELK,)F@Kp(XPfΔ.EM)ź/k Y>)P+?:Kqm͒'ĈPX(C%25pl*qH^ٜs/@Y'kx;n,Xxj.-JDeL]%|ˎIMjRDY/9x'><,B~TO?!QoTV;z@[py# VJ #@ji[ύxJr c#L Zo_w8 M)1H]dpNc,(W6%pUQDdG`}P95M2g=mv~:b*qr<p9'}yF+()J^ڴZ7 Uȥs?_]w؜\|HCtc.V)L11 S{a1 9X',:"IN~`w.Nr-BMQ#֖,@rAd-FnݐXp6]JHxT 9NnK SԼ;-ۗtJh.Y=J\T[4gJԚT>!!"dS˅DMe:_I1y)K,_&⤼[rQ(ѻt9ڳ"dUq? COȭXOT^Js+ l  ?1ȉ u!_}yQl}-#9Q["_`ya955nG)TDI~7[BQ_E׊*3zNݭ]O1c*k=H=n (fX#X!e$|r.H/Fk(G$_FRsŨ0Lf`>_KEaeEzq] q.(؂ KqDF9ʙ:"W%3^Ѕ_$#g*e?"9%S4 VUߛ eݼpbéGś sω >{FބpL|ds7m5oṠ 7rK!YWx8.m4AF\O]rH#TOe%^)J$KkͿr]JKH&H1*8J(Xm'6Ǖ+c}idy4 {@b[>Q$;8Y'0'iBNwlMo|"ȥԨNkX?߾GN\bn&*XJ䐖&TLJiI*%|&=xZ!JDߡ 1'5"WA j|9X;$U+ 5G.9_G  V2 섻_đ+iLǀi<_'/%2c^^$S͋6͈캷k͔S݁ x)?K?Dr9&Og;%销 zF0wFb,>#M}z 21UB\3 E!W(z:"_ٜz |1ڐLF^7_~ֳ]C䀫"V=s#HrJv@sz6F+F V;r 62".\a4v'9F$wx,f r˧TLnnԢJeCޚ*QM"a{0X'Ί% \S~TI n[V,V$!,JVxXZX ֲ頌KA5vn_E$Dfmdb KT>AѬu# 牕HW` o?7v0-jEdP-IrU'G:D2Jrg`n:J_J`X>By)[y{1y)DŌ 2W2 il8}xfU~$a&.r itp}|-cVVTbr7.hfuBxJeS3a!6m!?UP٩Z"m!C@rp&2a%9q? f*C:˳G ns~'+" /ŚZa,DpMߐ0v|C"D()eପ8ajOvS[*7!ۦl)CZ7"Z77h#iRbx /lFIAfOGY^x+kKLxVNAe6Xv Gk%qEkx$ȃaw$u#5lM]]L0ɴ.^_@~p`j/O.Rl Ru> -w} *%\xɷw,4@KeoC%_N]W.~#[)=; X6]n?bنA~P?2_Qs85G)J!w:Xx0,>Z1Y.! e8e̚:аx3|p.ʄec4lm'{H͝-fZR>f =_y<')>=x M}kˇ6PuS'*s2Y(.{V|¨ҹ_ry,B Yg)}.AիG.^S[Yu36Йkζӈ8X%_P($|H^{&lTsQm7{ lXGυ/R,JKK_ Y><<9i!q8 jKv9\c4E~&|ʗli $@gͳ |-߃X[{8ϭdސ˂+%aƠԸ-v0$DvI*Ej Q7M)~s0-9vd <蠉{n8F{4uKe\|5&Zj9m\J+ÜmI ^^ȋ<!T {:yR,j\K@j0^p"m1x4BtLM;e#! /_:+O$C`vȈh'DTdvy[Ixeڷ Gxw5{+`usT!fQ7Q8ᩮі8]Wߡge""zF\ U!.yH7ac%$c']w9pfL0j&6*JQay_Y<;P@^x2AunPDK5"!ӫBGdG%GiaO: f܆5 fs|EZyGx{mвXUQU̧"nVS[4^!Fg.'v)P]t o&~ Yf--ĆעEH0sa-N(>NM$Iio""b0ܼ@#FtĴƮz"ާ+g`b~bU, c+"O^! SԿ/s6H(#+/o#EȑO5up[$@%H+k.ւR'=Lt&gp')!7ڕ|D:7m>{l eR_HՉMcXAR [ObGo&*>$"/]?w}!pI+Wi3Cr?̞RE8ttiw˿jHsn H[*RBM=j^US'y eJMC˙5EZM-&RdvT}}D)'pۗ.ڱ;ŅK7|?ܟ̲f r?G[qC<şͯNG,xFD?`A[ 4ŒA;WDLCh3x·`_ENT0ZgfW:px5 G`_rT<;Fe~B%Uʍ;'o_<ۧތ OBnz7I ƦR@x9QP BBL ؾq -IfV?x9x('H5PrꊍM0luiJ!:8]=q/|]!=Y.JST0p&LAutk?KCVtTWe}C/: =`0) ؞՟|!MNp1y77yκxsdHmJ n4B:=(Z~[˟`4m5$ ]:L3gjuWl'U>^Å汯yLpj>x1.tV$ K5k*fo IwV2eY*#MCإ" UA軜C9((pn?*."|=JdiVaka%2a 6(56v<| )Ɂհ:~9*;nKl1З3 L'ĄVQd AlUSJ2Vwx@?RےWƸ~:c ';P.+n[rMX/`^3߀.*+YBսK@ ˹/!e` Hœ \q{zN-0_< sUW t x7$C. SCϡiqhjńԭ4U w;ܞ[/9X-vYmpB_< >ܐH46Z;֬c *bybK/Ԥ{2nE!$_|Pğ쁝/Kv_B| xg־J lW-AAM eUH3џeŵu'pD`ڽy=E˛7G%bs @Uc$t<u""O2^b9EX Ѩswl5@Ղ^VX|uءb\ Yw̆>˃+jΘ{& P@VAcBHvv]{>Eu?{.d@C  65 [7#.-ダy}g`&^REgY0 խ;Om@?mFMW@KhcwȖ|R$ #pc{iV0'io!(D)S>Κ8\@> {kZqfZ,ji>BB:wO Sc-0y0^xj@Tgֲ/~-9 'R6?zz(fq)Np0]R3i((CO#>D}K<(,eĆPkH&ljVʅ_ ! f=iR5hgړMje+k7LUeIi"lm!<.H=$|B&o2)ʼ%xp`IPA3dL9y@/\9,@2Hyi{ (8QO,ԹH j6$&%znIAf9oůQ_5 ~iJmS pB<$[g"bĔ b$2~]M M^|TV\z: ☹h,umqt5bFO8CΕkfE$P"p8zF DLCܟSo!:}#pG_DzwRFs|)pӕˈIu6}8^o6$wʺl ~v%/)9SG Nw@v30:+LLt@UnKP0?s/fGt*%]pYGw)n* )NScRO.4\i,Wku=LsOvW۠߆Q):Uo֍QXG{|3'$purV#FloQ< {F)H HXv6>03-͘NsmA]Pz!<L}r.޹Y 6 ¥/݂oj3QsT~"icfWNj`5GvWn:HkEKHkPd-Ga d$I >i͌6E_h)!bM od*BbB>}X;aa8wRt$8~8 QWO H0q݋M&~;;| g|^Lލ-~^\$$k5xYCuݍ|~Q)wJ~|,]FA &GzS: hvIl?`#)}zʾoD_0w x;`&&==K [u* H&fO+~f1lXVppˊ'K뇈cA[dF@&Li|;8@gof8M +޴asȝr#`I5? cWk r>;|y/gBlZ9CC[=SJ\“|算B峷?V痮V8C =nLV,CmC}Bi7B“846S#9,)<߸pCcpg3rڶl_u,D7nCڽЈj)i?=;PLfG\nL< w8L\]c $J=IWgbuw3$ ܲ, ^3)&>rX$]cgjW[Z g9 (f@U R:3zqki¹Bε! +ߠkw |pq Ѳ\yXL߄d6prкse--P3Y.@N✇;^\F62FճaN0[MsdqDO*ߵ8?i&T1Ñߪ[OV_s?J.7@UAd*OBGJC˃J^2ȴ5a rs;%P'M?-@@IX6toR^\Z tCr1:P$׾ [I9A\ڂdJyX /7X]J/ksRK:V1e'½ΰQ(}eQLIb $KOil)y&$U(n iOuIWE2mwG١?A*2@5MAܐX4t-Q޲0nVPA9i|;IIXom;@K!) x&avȩw3m͔z6^}'^R_ z pք0Z+-XsZ:h2/'H8ۀq8FC!K\xs (} |ܘ&Zָ5=/3$ :jrg :P3n^ MR:dUV?Ƀ <|8gYq#7%d4P64ysxb"z@MA)Mwľ d+d[ 0GiJ.zT|n^05];g4ZVdZ.EMupKdx^k/me|מwn ,< ͍7qȮY_vX&@uW l74ɣ(@ M)`о,q =WTMt/s媃o ${>l=M5O$_/tN>/2sc:|ؙa}g~JOŗ'˗/zp.âecBڬ=|cQcdI3b$$+O6a`0ay-) %A_gӈ,$OAŏqin f?~牶vFpqde_ "oWxMN92ɥ?_ -%(:tb=Q2< u i3y?,snnm:yvu!|d2}#Ɲ!1I +K[_D]MpTƛڊݙ˓uI.^Z%{2XuOn;U6̳|+ʆ55a N*{KƪQpx6 { ZݧuJkGWa*= ޽ҞUJ?ײi @tϥgǃԫPnd1іg~[c)m>xة!gu`l7FK]lz:@X А@}6 ْk z*S\SeKc.<0Ĩ\K?e~e+0 FG4|\8K88}gc/Z#> ,CX*L[ ~z>{Zě&4DIB2?3t~a l0V^`yX!vGgF@M}Z59_s1SE$&yUM~?Eو8j#6BTP>īt:G9Zp E#L4(6 /Uŵ%>UXvGEȋA^qKgӷ)Uɼc;Ox=4Ѯ/L*2oAnB՛oN>rնeByF!Ch>~y ^3=t5j/uƲ6@ "@+7L+C/'SE–- kC][_MLA/z}Í]c`u}nfXre!'ڴZ~4B}H>+8qc-d(! :})[!VioWgUMIs)/ͮ"tT|dlQ| oF.ɩ# u.وܴ-Vq%N 0Y'Qf̺FS#f=?3vJj$Mߑot'{/XyƜz Z Y }a ^sMo:C?Ɣ&[pI6@Ït`\AfIj+8GPNȹDgtB}ɮ2ЀNU^[ ˪TMAyc|i5|)^\L]/oLiKIBo9QӛV ipȧ@_̅@c]Jp?<20Qy Ys =bbeu8sd߿yk> \9JEȂE舑<`ŕz_5@ xܜvm#He LS?xངz+rkؠ(ƛZobwG˝.l0̠݃ɃPlD۸$&t1b_QgJ5d~'9^tV܀[ʃt[藞C/xI_f/jH&M0Xh=tӬ6HUMC#v}.0M=pP$eJ~8)C[W @ʠ .@Gc^D@da)(G1<^qfV#}1 |td_p݁r^ V }WnN4%w Yj@|f1v"ٛhGrkYC>FŻ?x:$"C\6:pu7]]^ktGHZMn8o1t~C[*/[;w]:NLZm{o0H@O"v~E@V yBǬ> %O{QES0&Y]5 +{d:HpgkYI?S>} H6xVXߩruemWG9;%Am|BY4!NXZ=I`Y-9.& ݝ% ̹y<4);3\o2mqfmwWl2hE&];pbM7 idp½#D6?/GD@=V7~.'T[\#fHv}0ZtmR(\Pj::&|ԦiGՋg>r&tL3Wm!D !zt҉M3IoxL)>j)}2}0[D<JB'a.]>_IDN5YuP2{ZI xx( vj;*$2c9ĵ>j`?GUA@19!l=\p[=PIP3S}o^codo_x2ZkCęa@JڗtOOK)xFڵQ3柯R7^mW@R+i F@yz=O\b?E!f_,\Dj1};G"_y0D*eoyyҚ`xY $.ODǏ%!G9v'54HV}r]e\4lD7YZ cT@߄hϷ4 0_]TX /R8_O3/ƪ'O%q =sp#IT[G.ovx pb. Hׂ!6b> [NgzUAOBmM$-7)U1!J_!73-+Q?#QqHGރѡd !BXwWS3@8?qoOiDJX} 1k 1qzweEi[exjK0 l|IBhr ѓB7߿G[,u Mv7?"-#QL:7Ta_1RP,x V{Bdi6Tn-uUZҸiҦy;B I_gԛ;ODQ!z/R~Ou^1єtd1zg\%.Bw8tk5E_fZxl;XP:k3~)l2t."I9r=׭4N=\ 11s7.թlUﶜ)[Y(H 9#ϟjf]?ɐ4P#)pPH"xzѨ0%:p4 qh<qAo*oM-!:4GAEm{^l.SXEAT%!]MܑS/ɘ {W[!nG ݷ ݵfϏ XkBdS!O<q5/ 6\\0 آfMTmXrC>\|D&]a!,H̊f^_o;/Φ3QÄ~ ž-ۉI;VKb{%AJ 0يKf\YRӺN_;.+s^JP3|(սKлyfP\S˓׀WWu聡,yYPDŽ&f$HPJLzۡ 9 ^SԂ77O{s\g/A#u*m "} АV͓0И\H_[2}7/ ʿ4BOB7橮C\Q󓚯GK)Hil]݀YT;3R4 Yь,-:S$OFAs ݃,^ C:"z jŊv x9K{ 2P@@(_E5a+ وKI~&{7z'5Ll4ǒ3Y໒?L%UU 0l}*qH/v\=z.yWgU='oNL1u)36/G{en]@|> vk 5tjTK_[_۾h= 9k$1wZX@o)P$Վd48N#%1EOn ;:{+;w?UVu |CAӆ 7M8Σu4Kڿ`V/p}m~T}7͢MSe͠Z`#H^ʢP +tY Nw͍,Hz9C=~֟`m&,,t)k\FtF AZ6>-0ǶA$ٝ{h2%0OtF%ȤB#\E749xWmCwg~v%n)lAߘ#NSřAF@'b 6@HpWC4F\,TΖp3wʷplz{@D.ݥ@j-聯i[ٺ*r#UPbyA|wfUٿ}GVww_smOdbu*V p14hJp:+ X%z>)3qdg\ ($>ި/@|BJ@I.к0>pЪKЊH/N2ipTl%.$G+>N-EϢQ j_Ѧyk\E />@̬9ørŕ*-ekd|uXRꡧߕ/E9lZ=H0ϤɗGV lk[s$[o8n(5sy.;1{ O pZR*xϝ:律x3%컈wPm֩`"~>xꎬD `bV"EwmKx`_+)99,(#}.\ˍ*]ދ-8>#v>-/M@p> 6t*)q?QE#@W\K~NG .LdcfďȞX%)OeFSz UjOMi6ԪWmG7aۯxhuGT7Rs|AJWkU|W{hԣ2]@__M/\h8q]gYlcK-ς -FϷ*Cz#Xp$S@e07+ "^hHga5/ZA kVrŠ PG0R =g~CqR"Mj4D ؟ IIs݄?߄e F 8fuhSaz}osR:ҏx h3{,)Q íf2>g`om"#H[X1*o*XQW΂9=Wz'`N?-B6{ُvDTĜ`f\rS2w2 i TZ3+9 ~ާ}Ԯ@,[>pAk#3BH_ TGPBތ󊨜80qWjʍ~3M}v@BDŽnȗ8C䤚#?zw/*Q -[zb lǶ;^+-W[R7T! _-` R1~h}q!xpO-M3P?TJ?xՁdHmɨ:SiI18tU{u'[އ>t~>M_NZMͷYPs 灢jp~] XT2uK@] Gdn=ۙb > ξ^^L1^Rp-nZԁ? ry@/E(8/33R޴ߜ9 pzs0Xqv06'=ȕN!M܏oF["cYq,(Wsw,LI>:Ú˚ZP_ey$a!,m</x%*m[ߚV?3{j>n$ R^z?9<~^{x%JYYav/47?OP3 _Gst 8:+4[5W~+&or Ja4@= gZ%=@وg5W ۯz܇o\9p$jKjs0} }üZ<Ͽ! 2^ J)L]N([֛iHw){0 `ј(cԳ@3^YԔ;)?gj?V i|{|B'W:q! 8-O aKoG)/;5 OzrbZvx̓)3 Hg:w ~< jO"ƯP?z7!^! ~#)Ǔhy讌 ~S[_hR1wqFNzb2@céHV̷:6u`LeRq׮\ԗ}@^?Z̺>FJ;unǙ*^ůH]Kpk1GQ)}*Z"*?¯V4LN :˽z|zԵl7o ~'K7w ۃDCM#T!RJ1N= Ȋb.`|*+ ה\sCffzk>#r>zc ffqJ*A \K_1E&%>uAާ$rw2{M1orZk&Lb:@{> 4(}^"pW@)>C~QX^986"ˢ?q GW (:Je'/X kaӎ5Bc=Q׊c~*;ꧠd#Zw_YFWU&C`b~U[`H_N|=`:s6w-n48 6&?qrd]/,8zR(^#̃n}V ]D'M>NBZ=p]r0/Wxkxf:G.^dPa=xYO$d_n*>#J\Uhn155e\i /{m7?x-xE'q4/Ϟms(晞3wFxVûB[d5)"D͖_PY}r 6d_vğj{2GT_: ˙CS#ƍͅ=['@KG}@N.h)^`nQYو%@^q/ߟE{_WD_Q[#~5o-L'#iǀa)xyuENSS#c}!+s)@ٴYPI"p3뿧ixID;{C@RkB-o9˟xAiO{/V|o/0A3L?7EJm10p5<_wdUi&Cx%|i8T+^c]<|[ԂaS\2ɝv[@Gikv<5[<멆+ T $ V/hj'n8b U 1;?o˥Y6 4M#\5ҏWmO[*8⟼7V@zDũOzJQ >kJ_>ȓ^Am^*H^u+ PtPc4~6~uKg{V:XN /Hy$?`ފx6u#4Gs8t%EpŌB6gϮANn $]@[aRWfEoIynI|=>2rwh/"wy ߌܱ@E.ӏૺEOmVu+@dT56/R:Ild[WOD:Bs;̳F r-TP8{q.5ƩBL]&G|&]Ǯ;׮B2nQvPvU*#jA7p@&tY!q~! >G? ?GQgr<;Ef~ڗv['\MMbe3!.2/(wj ~F`@KM^'o:,8<>OMxXL8՟ F7gt)אUۼF@9-A$b P$ ? ;|STK("Ez"xN_3߁ׯ1pGXWud1oS+ Y8e] ~AT/S3`Xȿ8J+Iû~ھjTPojqԐ!5p~qğc";.H*V ;7pϒ 9n3Y@%Hin~HX8-ƹG"X__iO# #|4 “+xJ'MxA\Q\Qڲr##unLx #ORޗ*m oÙrM0,|_?q9_~ Pn kj  U5w5NWYfqs,\5/(H:puc;)ǹ?w]9Aj=⑳U@f#27\Zx4nTgky/iPH,AjO5xArkMETw"xgUGXS-!0 &Va pzD&B꓎/|g'bssbҵ^$OWly㪛poڔ^7m_Ծ6w﯋d ^\h'3|Z&I^ opzw gףS M2әNi|`wrC kuW<m'6YM( t`R8 ޫ'Gǁsҗ4ew} M0kc|2=Ë_JW>=O]l'gP /F?q }Հ2g\=>:l+P_ aBxADOo NKW낻?DJ ^Jkl_tr$iq U\ctg(:~kG8q"ªQ )MSTPbO\?ʉ Hh_;s|W-rs4.(gb?!&pD=(8rPMİ)?~ڙZ$ vIz಴: pCT2ʻ,ql63K^5Ko;$Ln$e^fOĕp|%l/mZ.yK| OGޟ3u\%]6,ߝDq ^Nsq61S8W {Փڟp!foq$|#oἡ%2_o&jҶrmq`˝(Dӏc)\s.Wq/jrf mf1[z.P+M@*0; qX5oX?u/*&g .1FWEsp͜ | Z,z/ z "V+Od x +l=_K~:s*S$=j[`Cփg~Fص,.'C&'S"뇻zU>N|;ȗq(*xKK ]ĽBï  Q֛U,8 x*{֨C _œޥއ<}15wjWgLU ,NRAˇ߄ot%2S)>u|/^z&l/l2[¥u  23ow~ 9JTxGHBrK]^]gNޗCxS HmZPqGge8LwD6PYO@)3.*<T~UfCRTyoCTeju-oou}lEUs>'k޲Yk Mf?¬7Ai]B8KQ zcP%2z*1J}^>EIW>q4v>gc#B4G]Q}qR&+*WG.zK{Lè{E烓Q  g0ATɝ($OLfE$ ]F7H|FGޣʇ,(z3knQUdWQ5Ng6aȂ?L\LA[}m>}T}j7QC땶Q DzQ|ڱ5u滈ƵP  5h>*l{$8:[ ڠ'earg84!~ڷs吔 5 GQA][vJ[ ez4/-kL1\QzTEmLxfY }7kWo?jc }St\v8TwkWS`> -@A@cT}vb0@YG6ϱu=fC57Hyj(G ۷i\f"O|[t|Q-;|[Rj]ljl=5nqQx9SעuTAՃ=[K9E*C}O\:h+@:h™a>3TqZCLZG퉧k垡į4a˥(GuXİTUl<*1B%,J^խbD F۰=w7¡7oSz̾ͩ )u<Z,DU85f僪!;PMsc$v7y}O=O/3߆ u7@k> ˃ H7hZw8zAQ##lFMKMGPZӖ͠ZR\xu51цT}x[e6Z V0ӡvADETTQz)%g->MT~\Pݜ41K(gz)cpy[ʿ{g!aoŨ~n1;m: )y~ [Wz :'ç?sZFwclRAx8ʌ<5F4jČ=/a jh+~SRDw8sz_YG}SS6·O~3\ @tA{*t' R h]'t*ۇhA/Ώ4a[4lAނ$AU_4NUJjE˂їf(3|&Z snMQ!Q7 y`MSjDdIMv 3 &":Lh^0 >ۆj҃G‚e(lIs`tPBY`ۈ q(L7+%h._IeΛj7GQ TG t,8am%jB7f-Q-ݢ^##*0 PuX>^Jr!Fd"ћk,2W aOؽ>˅4kg;s,z2q!o f.dtcːDBAl.a,,oo)}/hUrt+jY$  qlU!oMQk,ePX_boH./h?[Ḱ}ϝ4  Ju\FҴ:vo6N$W4^_NP5Geʮ?Z19{J K7sy=c55ҹ)3;jW&7ocst7Gߵsɥifu^kc)3>Y]k+>Su=?vkܶ͘{>}nzu]Ei  -u v}0 4}UTƿ"DǘeDt.zybn=ۋ;^4 gT:zOq=w?9.o(`>b[DnHR&)z`AcWԥ0c }h@ <ۉ9/wCKP[{nGx4fz]7Ԩ X^tbZ4}\Bs&#OXbw '`_ B:cɪUb=4>о!G9nqlݙ2k1hͺ蜅nW+oV=Fn.#z>p5X'dloC {a63jΌ*Ovoq g{vGsasHugۖJ=Tk; {S;#aTCSe_N5K\2.ku ]aod*}soQXT]\u[{&ᨏ*7Th#mTkb&祜U1MPMYZIZ/Yڀ|1-zLXiTiWPIb~d}ŷqj3.wM.o~TMݏe1E E:OzV3aڝG @2"&סŪDaeРkbq PH0|:bt6 %FWE~} A{sS !i}KckA >x5<3jd]QEDIَad0Zb_Hp fG&iD#m ԸӼծq)>sX0ݰ&?OQ%k% :ԡ//}-?XQ?GKdT/QPoc뚢{q{9vHlO:,f&6QOb[& ue/xum}%G7,?`3s'˷gu 'nF.=QT#=y&T=:<&Ag[87vĺQ_>DuxmBFf9_γRTa'׶fP=Q5C'b8P| T?Cbj뮼G \y5.NC}Q kq#ڱ`VI;;NS̆x["ڟXLjg0~%u5hDƀyG?4,xŐrad^Gmys~lj젎^G°0.2bmcu9NqrbZ15yXܾ 97e:k/ 0;jPg X' ;+XͿL2F]B!ˢ{!(esM+=cYkPBk,>XmҡeSr\bho[[6|2A8-=vGO}@*ztԾߎRy'ta%-Ds!`l<4frʐ-+-f.YT}3yo\/}^qԐu3=6SC!پT=3TN~n`.>\ۨ&O0~79{ /|/dbR Q<QyNѢ*z-GA/>^K9)V2bشČ0F,_c[=1,hbHV1s0wbM8Q:1#dbQ /Q\cӵs3PW1ŏK; +|{b=/_F26meG0מ~@z  LN2M@yH .Mmӛ1OSa9'^_`T?Z|ocMmQ_VF-&B뒯$s[I"漒<:ʜ5jĉP_M :OF}@r).F~ROۺ&uF11ˌΣ *hy^Ǽwq4~f9ԼM} Qayi}whUeԒ#;r̮h]e cq+<#Ō~ `KhK|vpSV f@:9)p@flU28UxX~jG*P_y%Ԡ?83@DSys- ˯@.Z0w1o~A :H&ţu:zڄ2z1C C2$3X5=?@+jnY{cmX)U^V'L'}q#/0Ϯs~S4j”5Ck*OߨF&z.~a& x1bvGvrd:hm(ٯUL gygn?~ ?a`S އpL8X{u9 S&؀{`D.,_sh& r\l }Ra_-8N_wD:Y}ǘ)*0/ya Lt3 w1EvaQ,Ey ,ՆxavbZim2m?0/BUCGX~è/]l0;)abzyy ִ\E<ZKZA.38`&:N\0G(P(("Ee-Dv(kQTDEQQ*iQQ (,!tWBqQPC/ȑNßaΕUƥ洣/Wڦ[i~Lݒ~+KM|d1֜0p9d0lw-c^o*5 n.;Pv>gKT׌Y Si2fg;gZ`垵\-#|;WQs bw1Q4Ѽa UŌp rڳ 4_|~e<\{Cʧzb/eHa ~XS1G)to0ȷ$eTG Y{h.0 R0sҾ`Ȫo<%;WJ".WBqAn2/2U9_NR2NYl=XFh!kcX?G'7^`?<042ϡrޥD;@Պo, sYϴ}u‚9pЕ :qJ69`2)Υ-4/tL] h yZn6Ru*ͥw΍T: {^»>Hç_5-jV]B8Fx|S?):wSBu ~=qOntI{~{CRMɹT!\ 8]_9S;8rN>i\,mZwWOm"TUܫnUܚMQ5u:|.:lYS-OB*>y{;bN4A;o aȼm-ӫgR|ʱD>$J:?Oi:$~h:ڊ>x=׶g2!_sC"4:@wΗpd;긓E( WqwNWv.EC]w5'/9G}h| 9 q;ڄ}MRo=gra躟4_tzJ D<Zt?%V zu~=p":_Kj̇4u` mJH0?N Bokޠ#|qcpyO}4.5x{Lg{ t?ы;EN ~o&#Id3:kbk:ޟ4lBMm_2__ݬa)Erތ;LwŭŞ$WP']nO_@U'tyZ/&9)ú:wH>g^;sRF ҆OfD};Wb\Eow!#yF'Rt9=w<j7[~Y<+U>|{h- 6NQ7g7Ҟ]$pGtg]qNn =tJ ?8e _~]^|u]iGCkg"nνote~=>-|Z %0!tel#tW_G 9:tcQTfFSct [o,[yPcM]*wp0{6PYs{uxl<|W ;rB]k6+GL\/}CMНd>!gSVC ]c$G"朌Lya땗OsnP"S 6Q` vYX+V n-'Nʷ=w`ix~ ̌ॹk>Waf1T; TNW_ iʧ"3|ԺG)GscS̅rK5)>peERn|04EE`B'TLDs|:g[0ReNݱ2gF`.P`XⵄomR2}M3XV̦g4!"7̧́G̥ Wqx`W;\V<_q]ܐ j }n;|yc'ݩrNn0zE V,F|Vr.g"  K4WV^ny)i+ig{|Ptscy n:t ]Bm̛[z3JwOȓ tW+/nLߙȍNη`xW 3sMw b ԳbrS{,У=hdwiKFܚ^,ԧީP0З5omOԯ2~={hOo q_:3̻sQo'OsMe:67[`+'Is ­I粴ͭ2#?wD泹M9K#ݎ,;NxUj;"/sT[Cn%]›Ҟ2wsv }>{31]*d |I #&H;Ϻ=pujN^v}!7yyO`&wg{?{yvO$7Q;O|t7_oFu0~FKTUJ?aR0-Շ хԟ#14xwP8ԧnM}aO-W!2qi6=;l䡺ph [ƞLPϗToJ%R$)P9뷿DqsxϡGsȡ9y1\lc~as L~h+VI3P+I ^lzr,x YgqzʊʡY!K/g1,?]qc_W&*^6-~r 5g9Wڇ=+7jokᑥw°4X .Eۜj[iwd(ozOBCpqz4,He` 4~;A PWOYڰ5, @p~ <8=90ʡ]x YP:j:ܣOI@,11џp$ho"M߿4&Cn_CO,6ygasӶyЫ!z:@u,LXI}֪8 (/z =>m_ui49T)Hޓh;.齀\ydG2u`3Rg~}tW?,`](m3k?tA7sP:;9/7Yzuu gZ3"?>wڳ_w|rvS.㊦47W.tBj"olP34Lw] mi9*wk4{q MYPL0tf\=帶9xYsTYL##5زv1U뙦r\xI[t֟+N477HE?m2 2=1m}})1unNRh28MæO>ǦK+v=K G3䖠]Gwd ExI3M 7*Qt@Ik$·^/5';lE_A k:&i:wd@|M[>r4Sc?G}m Y4{um3@ѲK]eAy¿?I?pܛs2I3G}$h@ twL"vkIe?yM".>Μw%LXI{2f zbc[Ghԋ=!QG= ]~;V7q<6\*> y=[J{1dɭ~t W\@=1c=t8^xJfB% mwf/O.ӹާAf s>ͧ ˻i>T~yfVQT=-?S}<RCֶɥS_Is畓yKiԽ +yAs~GPiգ;v8feX-z`#l `q>VB4hS[9FS|*Ds3 Iu>rU2IsK]Y,<%_oKa0MҧuI8Jsܵnu*2L*G+;qꓷ@~hNd]3S4 N Cw`x`D,LL31jq_ GCktOGi^JXNvP#>E\4ݑy3&*g<5XcIRW>)mJG`>Ӻ׺Q?N_vC0^Ҁw̼Cp}Vg1o+^T ={e 5ze5-j+iά?#lir-Ic+X7i0nCqPfGQ~|4, gl5Q߹=rPg \u7CgzzK}UDn;T>|?a4lV09;_~_A" Xrua_<[O=lIQ3>h;tx4$ UtCPgjb'̄?Af{W"|,[J4rV\]s%)%N>dפ5C]ױiJ+ʫBWnCKIYYP8T?j2Ǡ կrUStu|YPjq _:Q*_QyNo]iS]c4L}竐Ұ术霼^rUvA>ezz.,K}pz:BP+! ӽ6o0XeG #LPt/#s#CV0 :{`r'Or.j]-((,b5Cg47}5*5%7s@}G^&vSڰhv/ij%"nZ&`yKZK=({Lj{rU#~$3BZ2G6 -ChZE D+ȇUOէ_M%K0:I{wooq>e7Ë_f34x^B>0N v}{8ۏ9G:4;"Ύ c^:qG;T"ΡdP~lI=؏> ÛT"dfv^ oZxc  Pn;?zJsP]Wo<MxXZhzq_4t/=;n.D{";REP!)KX5 'x5LaF-4qkќ|qVvuzfR#4G7C!o:P#"ϕ`RrʄwV{S;"NKS= Ns=|2ͫi;vJwx ބP]<ު-[N֧g`/3/< Vݹ>D+:M<;u&멏"fi.Q%T'6.g ݃4(s9ܮ@P}In41L{8|*BsJh*b^L0-3l8v$ Gt\}83#PfR"t(OuC?`߽_ݔLoI"oIҟY?a}R =ߥR3h~F,@\ l6 ȟ̠qYPUHm1nڛ_g~N[ %2 )mOyVBZ8_IwvJ3B/a042XE(]ق*zl0YDs3bCu_ї[+v~<~2{_ <~5OG3b?~>ygNS&|;V$B3W#L *bTi&_iii!C4П]wQTKV%5Yvx|Nx:nA2QV7VU>'讎cq۳DZyv=rOfψ}җ͠Q_zFh/WG ہ<ޔ# ^K/>Ü1/lmQ94Z~#=8Ģ.o\yvu}%4Ey]`jY1!cioGMφ/7EGJ% "C}X~{ (G 'ݼGbtDxk\8xoD>mdHAQS~'P d\Hm {/3爠7'Aˊ1/cy8f0|,d#`3D [(ED"B{!'TW@ݟlᅩF9))*L變8#ک̦t'D(Qvq1JA=DlIw[DlGԙU\.8w =D I^HD}^86k!21N>Pag_1I_jzOE\~>?/-ВLlLk{0'ҷs=w'FI"oh\xn=7S~ !ob;Ϸ~нI;c}yWuZ ϋWXfT,\YNw]DIĺ){#)#e/-g2/e^&/'͋Ζ3;Oѝ\لF}ik,/ i/%jm>B=ӷ;>K2h^$UN{<]N ^o;"M1 =ji&M}]tTyB/ф>'/]xCaQ_MmCs:aX闱)L%GsӞK\h%)Ra1뫩|swy’Cԇ ͗vsi%L[ӜM!WnLx&r4DS9y6WBqfk36|OiOx5Dn0e+PPwv1`F%C5D>-%s%΂*D]OaO 8-D6}go_5 ]sNMٕB/2sٻa ;:!¦[caʳ~Dq-oFfѾIUiyH4TLXE8l?|wL|KH-f wq%˝u{~@W2W]}(1;$edB&R'.hG{&qDi6Kq/%%t`ʥkD94b\aɳĕZ]E020VqϦrb/⥨5|)*\`lK'z/VjƺH?Jr%gÏǿ@7Vqv؝ \Ȉ3f‚4ǐu ?%ש-*ֶ"0슰"<7_"HD5ZyT3hޙoE{3D|mDģPcgbho&/gL42k+d"EȃeG-#З%{4'; `L5%;`tC=o=ꄾ*zp}'`5}b>)<}ϵ,Dvz% ]ҥ}G(7,@\t@[_@>F[a7|u(-A:c:fYjJн,|d1o&# 0\Kx ~|R>=d_R˄f+g?85jS <1/ &=П9ƌgc gtr,wlP.ۚ"/b]xjgzI0\y-gI &?쯏=+aLMB/E1.]p~%;1]}Lfibh g]]qC`q's-}!Ver/F=gP@f]Z8furߕSsXqz}AyyϼB7ysȻ<}Pwwz; bY_V Yx'qaK.zW&oͬM?G:P$ݕow>ڮu xgRa?Pb]e~Ī]϶pǾ|;Z{{Ad_bQymw;НC[#֕unFG w=K^蝚#a4242Æ2nꏌ_'*cxk$ 2\96Y-=҅'2CD?4g2.S3W)Z +3wh~+=93)֯=J^ Kk .?.ߡFͅLgAI{ڿs i/dw]cyqBaƍ=LA=d¶W0w:,jA) Qh568&A}`ӧެvQgо\8w)LVn)8q#3S"JfXlGs5s5E0AO`$8O!? t#idnk> 95hejtEzҜԼRqUY1ȋ{ԹVܸ>yhUΘr#0w-8`L,SgL]ѕȓsm64j0*{$SG6GR/ _[y]f@ma۶y!~ML WÆq)n0u8Lv43w[ZOv:Fà\amqߓ DS}cy3ԻfbN2Ӵmrq4<زKm؉jY9)A2Ӽi L7˴d~73lk_V!-0qQ3*=ϡNduJ 3W0j+yorgoLoOn#;p\9VMs>sywXفzrA:}މ¨yuؼ?<24@;_sfC:X-0]{C8ypN1%yHX'T=0Big4}x|m? ~C_ԥ}y)6:NY[|l\@̰eЇ""h/gF0/Όta_@<g<~y>Lڏ}ԆIrm"$2I2t/e&6O"K<ͨK=KIʼ\0s,:{r0ʬY+RFR =B_fc^B%+ҍ^aަcސ,@?X нԿs5cz33\Rwqode8d|dլrB?eM]m9avNJ9Km齟yԦ2O/ixz+zn}rWvOdMw`f~菂ۖ`NUzbQ E +%_poYWp_3wߏVfHhzܥ[#G#6Gwj0WDJI"˒/e||+y~Mılk6%7%?x٨g%_oG=, 겚gtw@M}&tgg,='=_Ud*8R9GL7ӻ!)'^ND=ؿ]eFO /^|G>hN+e{DKh5^y{$靓;O?Ob?]Қ< Hy:lBkrOfLW;(+άѝQԑ{);E?z/V藡r{+wLXSg\.;{%wpdr71gɽϓ8LR7Ɯne'&TM1,3w OOLW%|UI_.h3JuN7`u} L) ;;~&Ksmz1檺 V2Kuh0})}T=`CZ!cڢG{6{ڿuY;*%Gs&s*T5B?h0YZ^kk] (>VDs|FV+j4OK ڳM%/Z1ߏk%-*-E0 YLs+$>&쌆6&u㖂5UR5̭{eP.(WE/m}4NgWCW3v`= oIT@}4|>W-,<4Ǚ&C^%OP&j·ӴHdFnMTi2wݾ %;ivspft1@~̦zh1-lU12ee2n∛UPK0UB=iG=i3qԽ?\Yz'3_mi^ !O6#|g[Qݴgx~|o#Iy"ovi'ޠ8y0?7Ǽ%}07,EU175Y@*Щx>OQJ8l.ݡL iqD߸uڧÉ7W紏nIGL[34۶dkL-qH ;O{-0O @3[>K摾;梿|vSW#` 6̯~[}3c=sf@\i3 eW_ޏ K{[h>gu%rE|"~@^Cx;!@dOq3^K9oq|W܅;!m,݇̓ Ÿ Z^ܲ~cpr&{QfcgxB,7<-27Yr]6BF}ƌVnA|OYO_^d Gkě HSX'Џg8bϜ2퀊w<ӷ3)B_mr/;:+gG-3>DJ|lQ5>ͳ}Vcy أ r DӷNmt0/_܂LoD2or{2ybaK +ChW尪'n0Wy\u)rK(g)3#~)ߐϔd˰U iUk0$^ N 6L0ož/BfFW^0L5vPb ̬݃kW”cVՒalV}-;|7`v;*cdoy$x>m/+kga^Oݺ}x/& .Y$o~k-\8Us1f^VX::uYcF'z0K"WQb]Qٝ/ſ`?;㞸o/RB>Xy*1Z DbE}yy{M_Cuw!nMUb5fw{/z~.k]fNzıhx e#~(Csi [IvJ28rqf焑&H^}:|w+oq?g=pA%һӖz24ov|5rU_Ww9ӟnR5rzG3^]H1g3b徴y(żoE-(;+IwY^Y ūxA}(?b8%5x;hNѝAWs0͇'!mNөٓ7eqf%ѿǚe- w[A!hhGw;,A.Yb ,'b-]E%z]ї;|fI,XbI (>|ྋT7QtﰖieI_ɇᠦ8M2)ڧv~XRV5}kU~;CXU>XJ,^Xk56k\`oxk5oXj3Qj#OX0rcPAȩU}?l9M3-t#Lslz.L{7tpm.q!C,y:ԃKE+lBeq$"W5,ZX|ߑC,A{Yv{`8[(&ʯ@ ~fiD2s!0[V0oV2l!;>8d=1D~ pW+m̍X'xbKP'ĭb,~ux%Blc}+>Qֳ hqt ,wYi" Hr{~& =+#4r=:uZ.[H: 1*%B1WlpN8(Ov51,>@92._[P&I,>n2i>/iK%y]irzIw>u4ąqzt߶xj`o7%y5t /'?HF_w6a\T9xrs妹m`o=q#n92冼$lpGR!/ĺqLx*!Wm4#-`¯ߵsFvt5DZһo),,9[*(GveAcQw}q9{+wQX bkņ??C^(X>,\,iZ҆x>6_?Z<>lF>ٽ `oYoaYN rXd_s͠UcMa=-I6Tπ.;U*"/Y5>|*vKQ 5A} #}7@>T{P5 +o5׽ ӣ-Vx 48V*o OG&^HEڅe㹬+qO4$h B֫ 5[7OKgoZvJ.b9ޠ.v`6mS|}v{ u.{6ؒCd1,uhf01oVV_Zj\DB=G/yp|,Qƽ:~9,Xz>lA۵ {=S2_Ɲٵ0%\4ˁs JH칯|p_|cHw LbC}Z4L#;rO gGz߰8:Wۻ qO@=pES9X^ʳw wEyY>vsMLhƝ0!_كxLl10)179pSALhb= ,m#Ӭd8e}Hpe.-!RiݢξWBeȆJqoԃ{`B;Ȳ[Pq=.Nm럤7ψj-ؗs)kߵ0ԑ_3) =q /n94؁r2SYPʲ{C%ᅞgb `X߱˟b[P!ءO'f|8LD?,{Y^`oz̙z Ob4ّK&=m$?6 c[G{-q q,L|d6>-8x3;۷{-|\肸vu΅cEhOe٣4TAݟԶ&aWnf' ADBkC7ĊO]˾[l2}N*:݌8&3eʟQT1GR~&SKhL2t,82Fw%*]/Pi^%^]( *0;6&wT (W }v˔G mCѧwD;}{q.W5WWkZ;/Z?6#7IPo4{]迺W@浗 +򅯐E"Z_{0]u̜ڿʎ,̸>--Ůto\ۆxޟٍ>ȋJsɠa,G)BtϳW"Eʑ2e;x`ItFGQv[7=ʶc~Ťct+F_"+E@_Vj= T(vG87 )c Sm٬K^vժ|ag_B\I5#65Nϡ?kCzA%Z狏 y `|c 긮ޜ6^ Yvu\']ofX{l# }o \&ߍMw1Y5?gS_(:~g2*yc1 Fߖ۱4GykV>u۬'_]f~}&26Fr6c Ak!Ri[9Oa?I{z*r/B?֊p%m|آo "+q#>ݟ _݃n軯hz+"{ ߯WKB}Fg}w{t=~Sr6Oqi@V{~жYMVGGbJ|@?zՃc(_KG {6m-0GƜ#cE>bOpEL^֬W/WޛT_;c9Rnqjށ^cE4wܿʃt_4q>n̑'lmeIePU 4ЦYqTMpһwX6?znw9Pw5 4r]"Ak hw&OQHJչY(Sc9mh>mQv8i U;w}&Ac4;WO~4eiv|0~SW'ڛ`i\d hBssoN@[:큹ayNcv`JANCt0Lt}qjɹT`O;]X4:A2rΕѾt7 }Owg0#Yǰ[:lT huz~ 9<@cNg~+W^ ?))ea*{u\tmhkyԷh+o%@y%4:Y(}<joߐ89sOh?udrN>q WyOfk_}v3GD8è"{Ψ΁;׉=4:OWnwVס9[粊PܖA}۝o>?2`\|p{꼅tHs |Ȩ how&6B\[O{ܿ{4:6s#I .dUzOu^(?xz/B?rc:*|tn.TA^5Q7.^^L\*^j}ݙ=qJo*wkue4';ofӝbsi?ufzYCܲRb_ordӨl~]4ƽ%]S ?zHzhz낹la_+>wġx(~U\OμGW^%+m*pGλ~?UaA2h8-0{M2Ul }Rdsv&~ˍz,G{X< uXd/5}&7Agt_vg-]7j{ðj5!zWu>@?\N>}sԎq-(Ĝ*KAU&-6?wÐ:B@ JFᅫP? ;Gwn a)w>?ލ>}*+NV4*G^Y *{/OϜҫ`< 32&>=Y.yI𧻡V{_B0hՍ:7Hz>!PRpWÁ9X{khQu9<6?D^_NE<s+ ;:u<쉆7ˆ gmUcoR\II1wyh{ԍG_uGz(b~6s.:n^,c-ՔF<l 뗮8*dJ_' ,Oiga~N0XA+G\۽@ۣ,_#JП_4jπ\c.ї]?@YM;!YcПɆp"&@_oIC;e JԊЧWbɷbUFrQB<\yCܞ?lП?T@Xwူ)݀\[46Fhg!o<ߑ ~rs( 6S<̗,e݈G9}7%;⏚䘿*6bNX{o!m{%.A跩nM+EeB|%Jhb/)5.!Ow7]A4f*S<$<<̮ÛQ+f-~Z7/z4:rwRלWaAG4|7.~F UyNwFwͷyr!N".!s [pyw8N.?d= ?<;k\g^/xF'cs}%!߽ئ>%)̹ޝ]R]S3V~Եg3ݡ]RUnJdК OҌ}=YE8жw V~ڱo!s2r! 3CUEWBw^!ڈMt L:dQqmEs}A d}wr:/=r[wUfuwyP}9tD7c%N*><򻑗Dy [@sƏP0ηCNJ-g8to?wC!7 sB>cH.ڃC#/"D"ŧAw.3Cr[@T3Z-*4̾E4tvG8lGiVO_C1d*T9|ẅD ͩlh {#S΋@~ΛjMN/.Mߧu(an= +XDsj(طќJ8KMFƑK*;i]Q4}urwq*lg Y _Aqe҄$bšiá+I{[P]Zg`1sC76oîzWOC|O¸3Kioe[ȓv'8Cx75n)ZZy9힂飼\ޡPy6w"XTT`u./S Cw#D\&Y-4׆ 6vQiOvh Y[~F܋9DP{ʡڋbLz' o?[}0 T}PI̶f{fȮȜz7 =:qz3ٍ9C_UJ+?li'۽ d)yvA^2C#W#{aN5vMB) C7ZjQ-?FZ>j?AB iC, "賏GaMuHlȦ`Ӽ'hUOQOej";#y/j/dgi_Gc)\82Tse@vCiE2JW>797[oaL_sgXޏ>C﨡}ߒ^Yoo6 В}w͵r>Sߙd??SB S\WlCҍ_%Os1?[x.[>v?~s$:4dwE؉zzh=4T{tļګ #\ӑ?#u%11ݱ)nuBM^jG>6;KФ-穂uuh }96km ;x0Z_F˓(XhZW%)M{{_&3/s639^i(B#4,#Iy}cqػl{^-vq5%Gٰ5v88@u; W4h +66p93ކWg( ab?"ʙ':ɵ  |Zuo]yˡ0|0~XCyqg(R6y=0^;Uw FX,ݡT]t? o~߿iPoltonJ{}XKKzkGtvH&u+Otޚ| 5}0br,yU)e`~-/M`fc2AX{)<;:a ƁX0>, K.$3^lϴމ̭7H$o=0 * \嗡X1~2/?|ꭂ|5lIZ=]+&xXNj,q#W`I≮7h9dacT[8ݯ;$Ycד%Ǎ7P%Օ,dDcf":|sT֐:Kݥ&wdXAakdcd&\9t6H2(qfQI XWRg¾ŪBH5k8j365-KlZzɂpY-7A!a?+ZC!y0=Kϳasr?:}u6=is I”=ʘc=QM=v,JNq/ǻfRf %?꽥Y!֕ E6SƛAǂ3`sti}kh=u)LL_ )#F ٦0jS >|A?MG/s윺{. EvgoCtW,P}_ߢ>W'rvǿUG^OCy7h0:[o} DzePwӏ p_`&/)*3}[ iz_1aӟ*`31̭PC"_Ci킰f]4YىW1.)A06LؑĊ?6럍c*6c ߅qq=_$d\yP ȡJcGa}# lc@>cl&0ͭr11ΌyP+'tyŽlɅBnRLe̺P5RWo$Wg\<>Rk_Y?q.$oH;Xa>lE8s0#h,ꙋ_TNTs ȇv3%n0{K9y$m8 n6iԇ\+R~S~$,Եڻ\BE掐ŸOQ/!7?h\U`t1LY *W_'⠲WwhUG`k;m. M@q>Q$:~Z1%[tjVK#d[F9>Cm`s-rw;U"ھrީlIq9r)d!qjn>+Pr)< Ntwq*Kz5PB]f߂~+RylvEB*Aw?jMu0&\?Sm/f!g԰KD?i= >4ǵI~hlyB 7L?ͣ&-9&'-r[;&eWH:O\zɭ~+aA<Է>}1u.uq޾gTrG{^|玟n 9:+/2J~ q7갌DNJhMjD B277F}ۖ0 YNh-~eʨg5TI6֗WKi|y@y2e-tu;n2uioGts\>StljEsHs8}3;lG~>w4pHzgp4=t 2 'v|NM̺g j^N/uEaozð-—VߜrtaԵ#4g.XK{X9uk;7͘2&|xE1Vk/\z5n C?5wC]"$wߝWa۲ҾwYV,:EJ`BccO'ݓ;A9-]ڋKN7솬{D*{]\ OnE<χP .P64 à>dA?/sn"\1ZB׺0+*5z2h ?s>sAEŹ4hW*V%ӿ4W_IW ެOCԯd #/JS|佁PE2B, qn>oy"jWxd݁&vqk4WƤr'[ oMEK~o9&{{ޯ2h_Խ8ے~NK>&G}r*›( ǎQE~JJ:0cM{Z/-2.60AV=pj9Sd\JYTm#2ao lmЃךDMRE]K$Y^> |eAY93Y>hAyYht))Z dpRLQUHjqA'?ݥ"/5%C IUǠw.Urk;M=ԗ"KV@D2P2XOTۖR*Er#o{ADF7|ԶXJ2s~-ZRS zz0ϻ|ʋ^eeAtg3GWԇ9<OEV.xF{UD_[ U >QdYrBq]PC>5|W5B 0j6#wV3e0D+NsB:U£'QHEZhӥ56CmTg y@S4:[;F^@a/qfA4TvRġj{=QV+"Nz> Stg4 6 'qzu )G\3+}cV(ݟ"Gʤ5/OtDaE˫~7뫈G!ϖ;AO|Q5 AwH0s.x<"s6q2I^B'iܸ2K&S9 T|ä+7@}9{w/OsR`Yw%5La )2VEB $2kҞ)U=@yd6QL+x7L~x[^L"2Ogn nO2=M壽%ʘ@g;B/$d6ށ~OiԼ̋%ǝ'?KjE"ɗ}]!p[k0Ȋ-Ö^PuG<ٺ{ɒT2MM>4oe.x[ iȼ+>.*dދi)2N{A'*O~sa zWȴTTKO"_m*ӑx[w_5gYP|c8qr_ZJv9/-&Yr^sCzU,!ョ:h|.O|^@m2}fÏ/oBǫe~ޭr_ܥQ'*@~p 9xcs#$.sDaVшwn>k7? ަ*76\nyZHyМ+3P_ceiɌY:@I27".LEc2dlsn{2SWnH"mq"d9C!kt^DsVv&`W@3.mӇ>ޓ]6'.ҽ&͹:K.'sۧ Gi6ey/ܽMs^oU79RGad"/y1Ԑ]M/0߲&>ǿD*h2 6Lv~me# q<4eٮJY3f K`V%wdE #—]-F{BVtӫB?~Pebf@c4eWFJp@!PnԸd7AnXoKZ]"+'hSmD%Ϋ%h.7 ƵZ=,,li;S"2v;Bk?e㪇 l<#tZ;DTVwƋl8JKVoRUT+.T̙VQ`NEޕUNE<sWla(?* U{wUTqcOd|],l}  8 Zܠx4!*-P* %izpPqUdUc. DVKش>8<=.{>s}&&Y^])Θz S}&{Qayp2Ajku/7Gn_ u+!/ty|Tg9Ag0/;]Q }]7B=kz_#:~N\ 4Ԇ>թ*нE;g^6I}o`ps^q1vwGk8*ޓܩlu(w>Y FN<^0PFu`7e۲Aȧq\ Ը TI!jY.J}zȚ-C<͵UBȣE iɷn-CnU>ݛtVB_~{Z{09 h]z#cXTn:ٽNv{]ǽQY˽eVtڈ'/E#/kk}FW ߿n#_0ЇAuhqv 1*Ke$,81PN[ȃ6/-aH`M/d]G\l~ܸ"Nu /_gw쑺'#-V/9`ridE#%aY7|B+OF|i2 sgCeg,ՎtWOE#G5N߳%ˏ~%YV_Ѭ]S t?9?^iEP/{"J 'dOrJyFldF;#{ѻL6 eXgGgPgg!guPgqDZɋKc/'|G=\4-y;itfX"7[;qK.K.K;zh0nξGwnO&i38 9RVP&)WFSOtZ7~̅c!}Zi>1j&/6yĨ<#vz}=h8y)'-( ˛4_t>oj{(;mW>MM9hoC{ѡ .^_J}:֮ce^N~Zt9$ EKJ΋t3? Vci25R?2~ Rk'G`\Cƀb0e{PСAAnj}wprt`YG)uxcc!v _cKh1ŅS1&x#7ad iu:1wS^|Uئg^R6>EyW)ywT yU},aa )^[o9*Bp4|k~T蚘3&a=Չ*X^q$EuՍAUՄ;:PS]orV] z5^)sSaNDXk-Uu:3QTyTwWr<}F{Ku8-Սaioj,} CU7!tjinP]x|ۀz->Gv́X ]ݲK^9 ԫS4t=Թ=g LG0WzTۘ3G%ҾP:rݦ}*v-D| BKKlD^wMEh5# WFr_(9kkgT Xw^|Oh>kmZXŽ_1OK{aGTMU ZC[_Sdio)zNՌ\k#g<,noV w$-'`ӽu-7+H 2T`iaz=?ա}ծOZ0|(='x?jl~;QտQCjxiU bdpIQ^p't׫돾:~I68lJ5TS*u}FIOR3 iKI׷t?` ]{A559n邸GQ'OEuaր~SU+CX~n݌w](o)NcTͻpҾm[]N#{SfR~qZB}e] ,h vQ_K7u79G_COjhV{(Ϻ<:N?4g 7ݑ{h᷒x=X=WIqNwDҩe}Qwr]Zb/E};60~`Kz˟97[9b=?fOؑ[4xx,`~֑ ݃_ozix2]v ePoY/AOB'zBQA w>96Q{ Ow'ȭ3pOi-. Ta)z!{774KMy"6tf> ѓx]yVS4!bU]T/zќғ{湞hsEыzA4WȘYOw$< ?&~2+\hZrk&/]Vz@fLwo k;Eo5 z:MsLOQ۟T3]}!?zO4<`*z N Wu}v3:WCaV6ήHEF~6ڕ̥Q>bGs\O׿ NCOoS1mskY&P|~ C}i7zGs^O)E* <z׉|,O{COctyҳj1ΦaВzf\C C-k=G?xوp3Íhmϝ;M:aϼ0(dFI2au6m|n'!%HB=4)APο }cjri]Fz58qa@'3C}tgkrG,n7 -otE\w׋_Ew}DZclx´c}S HIcU~؂>qyul=^vb y/gomCB@vh|8Oާzv]aH+Oj_}=~soNcA#S߃>CC}`:%C_6.rȫC4jw̢;[ϵg}8ruu.G_i/CNB}޺z7"GJ] _#^N*KW`_hb'Y&{QϷq~ O}駦! Oo8{5#WO5hMV>w^`\s6S􂫽ߒm2ЗCrfNnBMnCm<n0.QTddl"2B wFef]I콲HF=T2B<< 7p_|, 3ꆺ N0"BB_FE%0, kzhi:Rq*`L8 |m&y7ry ( ̻sk azCW+dTbFg~Gۡq}rg\1ҹqtkXw]PT$O>7d]07l OzS-28k~Sn 8B @*~ssȰ d36cyF]ben@c /c;yjt ~ 1H|Kv@.5«d1 y@ ,%Ffm;k*a̙oAGBh 民E-!eZr4(0:m]y K@kJ6XGjadFu엍|>o!MmĜ@>5c:>;y'D7+ip7UC.|jt $[]WJ9`ca/C?9ab{ȉm Y kF7wqTK.ɱo$oAi}yU.Bcc1H$/rNA**4rɆz,e3Ͽ27C<W8 z; QmȼnqyX__!cE$ ]<9]]eP)twL?!9Tn#5FwhýpS*`pFg cp|ߌ%zOPm Gwa ;3 QD( +c 5jt.w&A +lȗ9(_" Xέ"4K~ ;qS㾧Xb=T cB3JTb iw0ΉT+R gѫOC5t֜Lo-JχK_O K^pxP` ܻ%bp/eܕ{7SqQǏQX"7!g_Qzr.zi:?z>K: {[FrщrxR s@~1(zX/p#˪aJkN 2ob>& =?)gA]z־~y3~^oz~src@7ӑ4^@ b*t+܀_w]$ c/{2DN?Ph@Zwm:Ss E=$WCYQ9Y /g9:Q}~?RuTz$˘ h`6cTmHK҃ *İ=i3CY&'La,Ky U;*%h)i. 0F=Av.R@*SYJ $UvW؋T |rꯝO%0p*P<[!PB0XOGIHUF0ߨXA7ȅy-;@'T")_G 3P#&*v:!}}XYwi`.0tz|_꬯~Bb9EH? 9:i B^> L;tvSTmZKF@ Cd`VCڏP.An:0`|X )?!aϏJsCñWA^1eTLu0wj zPw +A`PдNB]ڣ IXV9l{k4wyp#"bռP%;Iȉ:kub`>Q\̏:tlZV w6b?SHeXw Qx>]9gy R9\ ^|A^!2I[m,m@.ƥFu`ur ܔ|2TgI2 Hl`Q]T)|a4sC^t_vkz`_D\Ggz\9ԛo*^O(2O^ gYB/~ra'V0?@:%o_@?c_)(WI c\0%@dqWMƢt w].z"5vȄl ۬]Qʞ QCHWQАO(O8]p xP#Gd)u"Q@j+>FOgAۓ)C0yM;&؉=7 מ LCqvF|%iw*?CЎ$jDc> чw UQ`OPYvB! GԤ=ɷA^+Lf~KR r95M8jzv+}%{b@u rY}{PX^A? C^updx(*1c[n?ӓ QsxANd;UrڄO}Z4,v|m5.xF1؏_諂'XԢ]>)}MXRƸes++CTtYC˺w{sooYZ;0G<)K_za<;=yb:(#܂@j`{0}y}渇 a+Qb/0=(ҳF{'{CSVPU=C9d^+ %oŋd" x yQs%c@7akf|c%sנ*C} xPOs(菇:SI%`h)бFA0<4i6^8kVN}6ϧmPW'ƿCc}| $97 ێ z'N߀s1b dOe`>xkӃ76n6V{ qZ qo\{Yg$_{2W權@yK _9IC.{\n2𽇵F`n^>Q4? |a?uR" dr>`y\gd Ƃ Eȹ6<ԟs})A QmȎ,swh%w[LCPGE7B~*I3rW^Ȃ<+:Er`xPRCAZ}?rw3 90/ lKu*U 9|! J dkprY`x*:BW_ \6kk}#pN$1~؟;`^{D|#RrG߬7;eX迦 yİ㚇z}uEWr{C r9M'0.4T2x7{\^$>V_!tC'xwu= SzpI$Dz;q_*=>FI {$  {pq }p"_րs1#a&3=2y,`2Fv`2V7kax9yot|ny#f-7dtY)r]dd(b%[z-ro;W[/=_y֣GxGx6(úmVdB|rGUM]c+;l:!q@Xptz-V G#Kg'؏: '2{z2ȩ;7i -h\Puoka:ܻ a2A^h=#ֳ"S/i)5̖/@4:o㧇ؕiVg9?G)~ 'z$ ^܂zuոV?v~gp_ nxz`b[{;=:`= OL݄{zv[ Bo:pi}> ^Mds?]6}!2S^"9{ V`߇`cC`!f@k(I?s/:pƻ`.u0\hd.t.t "D@V"6ka -^C̦C,b -^,BJ<rid!ևq Ⲿ)%M5^xk \!׀C%A>qϡQa'뵼א^۰N|ROFUf'[y `[: qev5&9 8Ye/?^U!;ǪB|.rF@:wcm wtѽyȾ = Gz|%_RRௐg@ wSVG¯-" 7iBںAz/A>/$lJ\ φzW~ itB"{G+$R~7'!Q˦A!Sc5-fQ`8rPGqz)!S kc@IB[*WS &rA}%RY.Bi䋐ۯ`]VG`Qm9GCRǪE~7BTu`=A=P/Jf^ i,܈BRwBƎt9h4`&iGbr 'ځ@H _):_irHTSZاM%2"9"F}>Av0tH|0Cj: S'nySFT;xgs:Տ\>{v$ GدFe&o XZ߻/+/0@&09!RUGg b됶ɷ 8:DHrC\ la=^(P{:W1}vd6ҷب Ož=2R Ձ7_zYbĠB3pO I{^>rVȇٺ`7X0&sp|t퐏̡>9Kz|SzܭeȌƭ [ bVvȍ!c;+J'{(dUp!j1D+Ҁߒ M"NeLh?t6 I*12 /dWE-yUGϾAFNgm~k9 ;A]K@=$y$I6I1`&9K>Kr| |waR2+ɳ$+=~$>>O cA?n A:n~H~HN >Ho~.S٣T t`okR(z/e+dH#yR8OznHi[aXp$$W6PA !yoU ;) ;){ 0.tk7dA.yo؟HqoY݈J_YW9 6LWav쇤~) GAHJ zOJ|2ArIaN`3,Q!=LMd*M u)vҰǕǐν܎|[IYhFAnIή|~5 /I9xAܕ8rYS,MȘ翚 +L T֛/ L6BrTK4e7"!;O{*ːdUfEJ,A7$GCJ3~Bs_,7XCy."TGʥu/dH` Odg 'MIwvO,Iu&NxV>C^s1>A(dN>C"TrgL@NjH2ivq}iX\~ ۤ-#Ҕ\u)F .#ViH퐗'.%G2`{ P\!Wnv23mG}͏';_+yԥ726|ɤnE2XgJ4llR߶O+w?[sH1*" R/,4RH{<>#~p<!t2ʤGaFk`?f خק 9RRL/ ߄u_g d';%E[Ͱ2^A]%;!_:G1iRt. "'IL{-m'z@Lyx8%~Bzj7!im+kaq7G 3 ?%ӆ]H%ۦ@92s21xyH -A0rtATB%@}rl=s5?,}T>qEҁr|>ɹ͠~60Gs|gk/rr=̎B[|`,dQsrNxȗ0sZxwMN<A5p61OQOb)~E tW 唰vw@NȑÐ t _5ܚCޯ% SyӜJU*s5M^BjŔ:~?^)Rbǝei rYRr{3F/9 AF.C=4TX:9W ٰ쥜 ~NX7^̩/ō 'LJ\30=# _sNʣPsϓ7 Y난Ψ [039kp,jtz-^N:]> 4~out 9k=X39,y i舴CFk@m͙=u-q[ri^y86;t\:m=7@S|'@bȹ,|<=6: _}5sڕڭxWq[P~/))Qu8pyWr݆\}[{ou">I {Ik9NɸԋBΎ}U@ƧFA>btU! \D yVoxNj`TgќzN>oA~z Tuf|2y@anL窋KgὋuCnjy^]hAn.W z:Zu?<_b`l<` \nư~.A.6^2}2ֳ!X1d?5I:] +`U|r/wKV^mGbXܯ~`z =COܐ=XCfrhum !jn0ˁsuvP[9j0`"5~awda'z$a?r9sa!HJ:(O#,z}G2zE)C}vv%zg܀:@# BTy>0?P7b+ kZ;HEp 0zH'do /TO28ޓ:Ve`TOdzQK+syܷ} |\. u-4Jpעqz;$uK`WQ-yze'W c=WgE`2_ !tzkM(2{ןR)X? GT͊ SC{5 t|[#ːvPjW!5vٛ'i0\X @ c/5́3 հX|dcd [>, WÑpz~5Gi@k#yu~kw{ݿ?]{T[@IcGӀ>S{=_%@7kϮ>lϹ,Rt=oW 袽̽R2{ y@f@{ş"+Օ! k~N) ^tlp4)ᄬI=d+柩AoopCjߍym9@c;{w,ya{Jr$ w+,PT B{ϧ\xEjYZl4yce.X$IF!4RRbP<)>"eN )99>:9#8m/#yC6~2X)E?'VB0>MqANrf~8)$G>x$Xi> rE׶I3mle-YE^oEm aG7Ӆ\#`:K5xwet`/{}שmQ2GX_3kr`o*|zTQ _o%HBH60P=zms>j.9y〴Aꠓ7dl蠟qcωA`u쭙:b ԣiB\GoaV lBX]p61 @~|0:ׂ>wpXoBz9>wp+qR䏎GC0G:3'L1[j#@];dg(V~J 60<g4 0']BA^e!=2A_"ʀy1dǴP ˛% =m rPgG&ȍC]'/uY|0͐{ Pá 7@Nzx CicZ24}'Nx!!=H 0OFho@jrh)šNր64_'0O&'Χ1CZSAyC*#=4uRr!rLY`/EHw7ۿ?|Zdiʃ[L0~_>qs?/`@}@CKi߅AN9IWC+1R8{ҭAn~ r崱_?G;]߁9=eU`l< _L~o#:pEKi<^[{!+"Ȑi> r"?DA \yiz +_;! ل}?7uC uB %Lvrcqhx/~&$({}(xPtp& JN4-O**M2U2I :71@rM }zȞErh ߣHJ08v{P6 ϑwE%Z|Ϻ_<W( (}=T|RHXrP:<܁<ר'֠_ ,({`VgnRJW2@6kQg^H׹,1hq硟ʃ-yT|=9T6/rؚ:rpw ^ȃ2Zo@=9DfjRP,/^㐓h8(\9=8 yiSutAv{q\hPIS& ρZ@Q7J.'/*{r:2N PL,NoAdeOhAPw6e@eu}:QNge5Mi(uuJUR&{5)+eW*70f,$sM7P8WIol|Tޛ ~ 2s7#U⣔DFIdS)uV'[J RE{9/7(VJ߻;{,ñ{xyCJ\O*(}D5 N7hQ'U)q9;J'š^G0GґVJ^CvOHQ%^f%x)O'/)1LHSq~r$12]͢ ?kGYMs[aBtB̫me)4$)Wk E1j|%iL;Ώ<[Q߀%0ٶJMմ`!eHVn~xnD\4/n `JvFT.Rl->OI7|E)J k+nJ6cA_.9Q &OܚidxbBr2$;#3򵃯;mJ݋{MSɶ`/lHvK2ȎZQVԳ>| ^sR"6ҔR]WR<[CT)'\)g^G~W:[bRyp)%aq[⟔ iG@WEH(k!ibw)K{'s\)/X&n0ϟ7Mqk]GQѧ^^oVZ2WԹuJ 8G}9 κx Mw$VQ]+I{,~eWt&.Ad饧iSU.3*a#lijEWg Ԑs%DәZ%Nf|o1^ W͗4JutG J \v`;d.=)jx:C5pCɼ#|rjb&_eogK|uwh7eխ KAG-bz]ĐsK[ P*_F J]{zy"1U }q)G%>)㻒):T7)ȏ36 ћOʈ8R{Z費&QtޯJA(Ϯݡ8d >٤p؟"N;_戀3$%ċ{QwQ/?cŊO.G}ܶ"E3#˦_7Y|$}|ȶkx GU%V4?H2WU 7k)1 W)4lQN֊T#bKG.JkMu kל5N*0?"GjRR &FlVW\/(l.E<_Tw3)ÇeJM(G~ل|Mҏ:|S|xѕ3ilt¿zT?BռA_wot(]džIҙk+)=}O#Z'\޾곩a"˭_^ws_<- xf~ϒCoure>C>BE xvFem8:'H^~&@dE5GH x: *v A_^x{gFooGƛoxo85-0 . SuFEJ)1؀Ğ+±Z |?z(^cTۮ 'QB8>^AJ%9FnmP{8J83>=Fyq>?;Qs{)EA̖?betāJ)!VצqpY?FX9Dqz-|ͮ?uAٿ?N$q"Vj|3%<ܻzX }p7?&# )GʲV_υOxӕ/[! =cE.=#WZ&>Ls QurRK6؛mK>73?5WŇtJ/TµtTc C`I2fy'X= 3fC&+_{QYAOm/ֽ=~бqӥzyeaE$I;Eٶ.t?$N;*71WX_T&iM K]+{#\4['nk :=īK[$KDX3bq3kW?X2t}MDp`"͞UB.g)B;L)13Õc/1ûySϜ[\%/8!O^kV~og7 ~'v`wF#B`]=;!q_F.9UyZF!Wx51.S'QvP3L"4~ʟtS%dp١·,Yf3K_waIKbo[/寘8_UBɧupe7MkXOTfw^5K5&@<|[&%hpqm*A".U>&Y`q=lCvOVTWF ǻhD&^kl27%gv'tv̰ 5Z4ʉ;LzUv1S';Ѝ.,p^ZӋ{Iu*=y :?ӭ| 9-V*sH()~ylyXglǗ\[L3s-,h1.;7Se8ͰOőqXg6'5Np~-Tp>݉bc-/a;"\N]*Yas 6L|r!)tύ~8x-*Y[bɨ坥aXzvaEjgw v%f +?QOVOQ)X97R,g  #4Q׳zv,7{,ɷo;^s\05*cĎEE8p@r-UE VNa,ytG=VOeۈP|*|M zATu yaܳg1i{ [S+2{B]N.\ ݌- rMbCKpyWxN:g5pUK3^7xKz9ydҭ/)},b^ffonYKe,&,~cO'NؾSYKpkN3iߥJ8“1&璃xwB[nnofԚf֚Sq%:-n"[v-N)̗CqfupFA3@}#Nz!.1c?n@][8$ta*EUĶ WjבU-',w6m8qik6N +s!,o/׭r&ݲ4ZYmϰ+qOx.Al?u(OQ lgn 5͎˂x&SSzC%!_ttI6wZg Q/ZMoWQNUjhgU~EkO7ph>΂(̈H;YD+:n+_`BO~sqy;YV˒DhZJJ 7cw19Y^A)!YS\9T:{e JlVl(7y1i# ^WèWS1p؏1VtLu;畫<Դihq0j[@%rD&]A}X$ZsdžA^X935\"O|{ =&8)(x,:쉌D,&ytw 3bI'3#K%.F\iIZ^%mE!gw !֗SڲL7\Wۨ ^m״.>D;*zysnya\/8vsJm>rT A<:٢u,1cфZZ+>]L]ES^G{[5uF# x%Bq<ޥd/HQ _d8Wゅ'I<4ۤW4\s09}$ ~7^[/P5# ?<ގ75՟|o'8׶6D?zc0ǰg*K\?rsžA1)W׺`ܦȩxհ7k Ɩ|bN5vѓ>9TW?_#ER6b;S`un>+͙j\ }R,On-" ԇK@5o_N>O|fZ>\=Y|l)3OI55T:W6S[\ӟeWgpzcX&@+ɟ?jeJθ.?Mdƍ$TAs,såv aOHC_q]͍n>Aq.= =63K˚:x `\}_hX3O|WAW q4 yL-5\/Jb.{6TVxڠ_!Ҷx (-)]Ux?a?Fw3>}yFprBcPfB~+&4bŊeJ충3Ťi\CVp]2rtJ \_f&4J$3b;7Fjz2f[,!]=a%3S­!qjP o-Ba=Fl2یZŸ> [F.۳8k]خ'зU81[L7 ",X$csYT6KL-jD퓘n=R=g۷%Δ(cn0i=P5 AdBmS{^Fy-UnhlQ3Nf+++XPJfo~5 @Ku)y.gP$$\4އ n^@uwf"UjvGR }i^4kAgGHdj'52js`LWV2x?3Fr<ڈ!NQklB|ӯ!_ܺ^Ka\>g9QjgpbuM+tQ`!t/kT'񦸛S9 `mo"gOc!?ͿL۱WK~cq[{U>h0hV1:#۩߇ݐm'H6ÈԬMn%tdurr=͕4! w9<ޝ)ƞ+{ɬ|1+CB9ӓs$L!"#ә^ЫBh].g̑9= :Ñc|gWHߕ0[ %U-tK ꠌ%4}˭oe>wt'vZaU+9˜*t l~{Z_V{1:>Eue3J{ҭ!z𵯅ӏ>`ar"\veKvMr2[3q\*JEa l.c(sda|XS!JM[Kk*Cpbvը ,jw~(Kcgl`qO`6lEk3:fcEұz?i~Ν^<闱A캇y{`5? NHc'Ϝ{>gPmy} =Ur W}א%sIB3M%Þ~ղxMտccG_Þ2YOFNJ3ݏZ{aǍOgi= XY˰/Eq֘P' 1V^c; 8~/梩c_%;׍l׳؛bX⹷cC7wDK\;z l/CYOk[ cTkc7?Ǚܲ2vb6%)&~.̯Q`}[JAytF27^E:1߆H-6^e0怵:mt~҈z)U-rO Ա6,P |V>u|$;yo "0Ri|_tʖ>6p|Zb/t_gSnة9&R7Q:wbGVvy4ߟ a3g~u=&i*iIQFϹ%,jQ{!4YMbC/&+.Ԡfqב d~o 2 c+]:vM=?QQ?^YeGcUխqmpt|mM7Χ&@Wηx^)#dx#&*' 2]{*;~[5B4y?vUͬ9^PmUٌ؅nj-KYk /ъCo5sKѢ򵴓>zCZboP[OZ`{WC\ \l9G$*7})ș/MoJelhhŦXbXf9Sn׊0Kۻ- M!$E~_OD-OqN*,*u{|]-828f19os+&=惺?2Pir[p^kY]TR҈9ǙmCZǻϵAĸ}G:Eţ l jD87$m3X~1cy1)E]'s 3f f?x!mpΞXdY*R|3o0=@%ت}ciν V $=LBB"i.0K[yyε`Jl䆄w:^@ yϦDE{‡DO!7L\jIf/IDȦCS 3H}֗rozjHCdo$ 1,u箋#g+ýVEܡd-₻]K7x zX"=iqI ϛcC<3UIe,ܐv X vS RNrεRx k]}Y_7}aמ6I|IӗbLH,Neyx,Jz0|hOT}1Vg0:EM)˿_^z[ݓiJ;6qOs!Iya+̑8Lέw^}ob {R2YF"v Wd dSYô/kkݻHw'/K`GwÉt~k`R懳0kΠ3 Ϡ9WlJ~cvo&{ׯ$$c̃~Kk!'^56}0;u9#xoNԈogu"ߧV4Q]>@1v#Z{Onv&,]fCjxտIXMRZq^[V@$,T3A{V6|7~| C~}SI;=업qu!}nx}>/i]7VYƋs²w<L&[ՌkB_{w`ZcG7NX X>lGvߓ=ѭ(r VV퇨uSNf̐y`/p@47M;mn>"]Bsv-.TٰS&!~Rܽ6Jzo^5&p8I:s&$ơt)/-O9ʍz&4@.dRKxyi07V)YxihOOw>/0]~Xu'5?Iydb h!߿~hRqGv>X_αn}/9G߫B3 =XدWv<{Ew,wpыη!~%]h 9ۛ )=|H Zm/~){ )y|ZDTx?#o-8Q>DЛύJP'OaSЏt'7&!R2 *Ps(J6!R[ݴ+{@hfuM"2oeܠ#!.3qH߼Žgod!{fu%%P#bwD  ȊScxd77QYkEH Oi{#{dL86";cS {6{GKN G?'-!&]t9]@T\Gg+ME/{yҒ!Oxq.pndV }nDg roj_ Zm}lI 9wLL~%'!N{^i { l@ћaCO4]]c&++z #c?W%ѵ<)IܯEEݣpbҾXW8|w%UG$uCb/"W}(KFmޠQ&- :׹3@moNY MIg""i;d˕+{z8^،]z<#D9%+"W}TϰG%{{o4 _:CX/KA SgI U=DߓVΝ6j%}*syp[9}Kz3zUOǒ;댒~t9M|o'?'bY]El sNN-[$̰1+ q\#'E)#xQ<0h+7dUhV9rurWb ]#2]iȼU\:$}KS\,wWĒni|lB`|sds˅zRҩP6R*fΥ_zI3:ݻLg۹Rߒ m<ɬ1UOHqY-jhEy 7NE+ǿ*ui,M[>&sy.c+쌄p@dc5#.aamg>`P?D{{a|9ף^W?/~'H;|RI N\8Ur?tV`Z'\^1Yyfr>k[mXgFYegU!UBhiz aօ% [Y `J]fH|Nw|(f'F&rfUl2S)ڱeXt0ߵNo1ݻ"'Řc/[15X@;Ԕ-txf2{{:C{X~A_ ]oa%UGU@CS+)z}=ak!+W كIwI֌Q:͏jտ1 mCTO =OO,זҨ##l˷&LX:cwr wAWå{] 9'4yMpI_([igkA\aNucS;Tȍ%xsޅ >r$ Bݵ^Bl^6xuӉɥ~;/|:R;ɉg@}UI'i/!;z'Hӧmɸ[hc|mԏgl#XyoQk ׎t|FWNV젏wgx9'wYWo碮>_U7ߛTx݋>*Gt/KޕE-X<_BO2Fv\IZ5'(" þu)y{AP[ZAaj p$D2ꉀY'먏V4fLJBIsQ~Wϣ)_>]@ctl_O*<` 5(QPbB%"쩨Ka1sQ7V 2pa9tڔ?2!bJ=wLCY5Z~HPg_ kĢwĉ5?֛E/ƁqW[[jk@hԣ s&d'aT|Ud**^Wwzp4!γMwo&GԄUh]/&H,J}K->iPyn,=ogrv#bM[ քϹnzW'lUAȠw?Q9ϑ*?,wx9p5F:0zFIS_nnc xeO9#d/ Ry-z>,B&$T#G5ޓ龎l}9xX{EuG{['o#U~;Ǒ¤@bT<~T@1ήosQ݈`|/*5nH#~u/|/}F{O3&ފĽȦF6rݽkC_:֝yY :'!CeIO^^yaݍ3shC{Y#43ɋg֜1dO(C˖ɯ\kx)}f Za&6HΏAz̛|ÙQv^*R;!t[#2Cn/jZoY4%*o;ޟR{<1H HFBE_|%$>}e4=æH YO3ՕRafbbJ8)FIlf羫$aOq?5dOrL\%mhpL"+_~ВiZ#S.0g)"רcHUm8^&^@&\%չIig/&x(HR/؝lc2ͺŨdRYQc$Zű%^ CRbT\-{Ĥ<9Mq|脥9rSɇWH#/" oao<6 $]ߣ-AQ-ͤ/}>FW?;{ҏT缢Kz(۳,R?I\~GQI_XREI}ωK`QRf4WDcEV|$¸Hg^,t]4TjHM}Bdh%gV˥o*֑86S<2\"gKTߝ cc]DlR%d ҧHXȆ_1)(+},J=hQ=}>s֞/(:~yTjVzIa'Һ:c\5e&+?39c-֞軟BtII-,WutКz]yP wGխad3GFoB?7񥾧{_~ݢ6IAu>Ds4c~_@n[=ňNyI>hĒ8>@/q'87 aĭ5kO/s\D .~Foϋ6BڸOBY"[ ;#H Aw5_AhV1c~h#fh ȗrC&*ahqYLe+@/{8yw]somKs=4 ">N{u9{:#%ͧ7aw klߥ`BkK' EPWFe!+aSyC^U^(dt_+dIC:ՄQ.` FE$cJ<(6GtAdęWmHY~fz}j!K䰶2rFt9 zІZ웊(dSFA@Il?IBQޅFȢڜy+9dGhww#!V{D##qUm1fQFQEڸ94G g r nLC䮱16Rmy O!#1g]>4$! 672A Y㠤z\X0*ၞ1bȘE.W9j6Ŏ$ {ҜK$P€<dqU4m9޸w,DV6IL^{N#^ qC I޸x_)ܧW7"OCCOkK0W|Hgu2~1U{ veE Z7  6Қh2kX|/eTKȧIBPϳ鮣}gB2N"oɈyd/zEbo"$$=V+r (?BXs G"F|v&ψsDVhC] v5=ac2(S2+Hem:fd2 "YvLD>Sc59ꩫ/+>{v GLU #=;#ڻ~_Gm\&2G'{}Yk2 #<$]&Yeɸo0^brfta_vYC"1܇l^&Ys5G`gV]T[~G..FWٻ? [R1d΍E=VHuN1raxڒQ B % 6?&/E6|j%y%rZ@HxORdB5ݓӷϐzy@^i\Tr8L)Tt'Kn$E/&e_zA=lt| M.nO[;8-MGg?r' jOwۮE# 5ť4OZo [J$:C rwhV1co.]IpFY?wFwgNRzl%rFDߟ$K9Ǚ-Tt_ @MDAiSli``C܈mm^3y~T8@9yceǺ [)y}wȆ!U:]?%3 Zd&v{kRйVAnR G$IlgR6&Ƀ{ۻE4I>²l#Hg v wْdҭKH:Ú1!JDG~YBt7GmK/u6It:9=ϊ};Qv$Necr$V$uC8AVwGx)N;ս?RHXpEKݞҤk_6J%ҞҔ"#H|!vr7+T YSLY]|ɒn,/s"-33[QD\Tb. DѐAgt~ ([^ifC s1#2s%̽n+dK*?8Ld+̚X=&0JuUTx}y+S#\i(l>"/Ro[%H8z2$CN;ÕlQe4R:7 )n\^#XX(ᢢU`KEA+fn+Xc!pjF8iilIfG+TXѿEV=$ ?~0t:ә=d$vUoN)L|}dPfS.:?"* 9&RJS _ Oi"lc߇`{>lӊ\~9z]8~I _+45OTڏ3ō @SӘR'zƵQ2!ӴwV~Xk{/l߯1hB<!Jj'~T(EADRgP+O5oѬfa!*cnOk<4, ͉ єLTef4.=hA {J"c#|w]פN7=M. 132(Oՠ?d^hn^3E8<%o#PmR_QV~B a]S9Q*/y|EY$}K f_> Dm^Λ.s{5_C3x`\bHkf )EhjSgFmv\+9g zۘC%"fw2\ :l= \T7?Ɠ6ڱ+zi݁he>=Hq21 kFlPrCܪѤ[Q1Ki>vZgO&Ꝍ znd/}S3ߝ4M8BaypM*IޫD`̅,^ k5E%g\< D_:ٹe$˼Gdq ̢F]Ka- GeO]\a]|q1\Nv2K`eY9Lיݒ(GM:NF̒W3Wk|}Of~䑿 )D릏%dDžQowG\f<$#2S7 {ĮO9t)C=q{<*_;חhi&#d뮆kf抈S dmkg"x#d?YwL͇"2\ESC"$FVtDsdRHpюg䤥W@5I?4[M*/$GŭY! P}\Qdy?EٞJF<",'5ޘ?$#y׍/PS>WؾniDD&!NJ2~xdNcbCոG9oh]5rhNJ[&_䗆²R@}ӮW~?ߑۿTi7Ad "{CU9kpiK^'PI0s&ɜkf"=^N_vٶuNANICyߥAFfQWٱd&M;KL"(FÛIœV*ZtVDV6JI%U=~B*ܭA(mB*4"~f|'Ofx@m֎O&+1:DiLNkQ@y;o~)yY_vmIgknv=]Q ]IKrߡ5Ϥp,Feӈxڈ6DL}Lime7a#vi^_0oz?p#Jei}"%k]!O֨2F#;qIGo]9M",CSOzZYk1Jѕx # e2CҐۇMDF23˦앬l%#Gedfez?p?عuXэ Q]Y $7w bhދ/`0xT2SP0I[3¯c.CG5)0焜h7ᶙR l)ZE:&ϴ߱Y\uz;ކW\Lhr˯(oꁙ;z 1,x*XTVh+Zo'}!w gmfO:Av{hnbI{WT.۰Gmu_s# WrEˆ;\#n];}žp0*qBGE('߭a_teOIy6'+ K/kl:e&0)E :_'c׈u3f;Ey]oo;zZ!5+kFI 0#zq &VpV":<ӤpU'dWVsDˡȉs쉻יcH%k1 w7wcD͛K_mesj;\PVYSrh~pqkbͥ|rNlq+VmbtѶ#5E ~]-,:hW1H^Mt2D8aeΡM4 k*]=Nt6q /z ~``H|[R;Ě:6QDu"%Qgg֝XW#ShEsOqs1ƇQҏħ7⯽'*~a V&$-+Ry.$*=J q"̄X2JݾLS\]`0SnzEfL|6ys"2pd\o6NxKw:!m+G-Mꧏ ;iX>KX&qgZD7Q'D sEbouΎ;z q3X,Dz!RDZ Nը;c(R$'Ƅúyz?l+6_++؈Mxm, ޮ=CLtmKEP G<#<٪>>I1m8tbě^M Z_6"ӋrWXVr?Nb%]f;{%z,a%G8]yI,:k94_#JiISˡVb̷;D,줓?1ː_/{?A[%EkŊ6 p*jƂ+tw2rT;fD]Je|l8(De I+g ;!-?o"Re4.DnڟSڞG,Tc〯K투œ3%0qOq;::5>]'}DL3is9󼟊vXI ֗"?f%?NajOVbVa-#B8'1skLDU_If,x鮱t,'fH]M{)`DgY<[W~۵`qlVŬd>{K^Xs=~vFQOTDƾꟉMbU]ZDQf UU`]a~L}-A n؜vjifB[G-v>s+/h垳R`&;9]!`mFm!jKhY[VY[|ꓤv6 jV+D-`NBqG1%hSݨIO:{htY2 r%%hcw_Hio;m?$vhG\t+' V~e`wUmK>^%DM_勽=?·0$ aT)zq~ ^ICϥ` >!i,4]7,:Pј(oQhOjWhN &i@-~ʷLynANO6|C$f^4dWZQN7*b`= 28\N?V?_b=VA'-%5 YkŒ> {JwP'uGmzr󤳯Pؽk1%Ad-=7jC^_wGI?B N@f͑ "1.2m]\di^R/^pߌkSw[?}k$gᕵѕC SF7_(~S-bLG]#_ݍ:L9]'?_!@_+|*5R t 8:"0{; hzqj$ˢ:F맨҈f3DX P@u"=a%n~(l68,P5E'v|'T$<js }P\ ¥{?ObN:*ܬ +JxlH]9CrMݪwEHe\)eKCdW`4 CnF{2RuήQ&poOFn\_nB/Y c F_7腩S#>FJ))Ǟacǥ1ɉCLNٺz=ةAL)-rU5h~JZ<5_.AO)>!4_qY@7~F@M^)"*ƻgLlWGC(] /u3[GxAhJ#p48Oz|MIseHlwj\@wV^T"},C7"=}<`tN5iW*`w@3GH!˿<,sRy #%kҍd7o&ZB `Z6I7V [)m@Z9~5Z58$a=;ӣ `Ҷܞ׻ /7F@5twn i{+2׃?=71<0sPT(Y@d\v.DopiTe߯NR$ Z>UUk\TE-:&n e"yW7~F!BDʼn'wS3DI =3I;oo 8~5fK%?#6Htn'=v/ XŚ-/D;QzF( 6*mcstPm6;pPSCMē'j wH_^eM쟍 1 T4p8Ed& S - } ꁨ630l5]QNdr" k=+ OGff?ڕ?D=,ґ)N3Q %z=D`Ɏo>WŒ;x8`0S(fg=ogL7Vd(M7} %z#ڍEm1'M`74gGAyuYW{=ac 9L ?TLrܺ#]j9q3"tS |;uIi0}ߴmh kL. bSZC5qA"˅Wo1tư(+>Wɲ929\*2RJЂXbeA>̭0!*~aQP"Cɽ{!lX}E]̼ cWEO0/n~#߭< Mr,CKEG"ԡ:l FN.%f)6,C:ZԽ=tG@s *K -> !lslpΰOͬ_c4d,΢1mIϹQTi1tZbIJB*|!c(Cc-u/KaWlzjѵC֦I!fZhG~lpEeV-.u)1utcۚ3ʿ<8LUQU;Xَ}tL4Gu؇{TfZ|x '^A {hP9QuN%RQy"dsˇ#l _/?WۍREPUIC;3j\L6XNȝqYm GgE~#aNYy!T0]^ .F[ NN:\xK#B. YWDQVn[R>e?J^o4v?ࢊ< '!&o#Y9 B6 <9E9j3m+I=?JN[#nofÜS.6n |:D/aӴ_HoӼUn߳s1JD6C}A֊]x#DZX,x OH. {kwnrIjVݟ[ O|E$KF̢Y)x6U'z^5vq>&cB侒 9_! ׺2WԌ{b}˗8%fE>M*YU}4wY:\-\P\ ZsZlQw=z~Ȟ1T =55yB8OjXF[K}i\]N?;?gK2*fw~R "ak=YqpʴPKĀ;`i/ޣ?sr[9E`'s(n9(+zm>ӱ zU1q#_2#Pv@)qc׏*0DY;︉H8h9GWWl#rFJkȯwV2Bޚh\ +nBNc(jbEG n3X@H-V+$GpD/ðrד:aA=DgD Ė0GzEGJaCOawxrbk1/?鰂+DJdņ%%˒e]'_,+$5zk`aދ_gb*Ln,o|tFǘ`W.?Qpy>6d'{\$)Ff bZ,HB$s8ޥ5Y ~Uo᥻?r݃z%|B{҅GD 1SWib'}yoLXLd̪3laV-=Fz8f@(.8Vm0bTdV`\O.rOqɷKS_&.)G?s fk׊xHuF_]hJ)+w Ú ZFyrKSqW})IvݿɊ_*?-QnnQRhV>:K!{ӯ1 !C3mJ6( `-Yqy',qPXׇ=_PQqR0 ʐ5*bĴQ<ŗyIwa8C0m|R`}.,Bm8psE"͒T6x4h~רϵ{ۨC}b7;)mbbN }KZ:w=3~S1a A5~DO (ahMJoإ)PidUIHBլu1R'?o"RċW~>Z2X죧 Ѷgp/-ҋnh˘G%zhj[Z-Nq6 Z š;hҭnJdL~Mt7,jiJ;QH椈[; Yc7w55AnnRkP=<J;Őy$Nf&01">ij7&cEԯ+ahZ ըԕ4zG]VC5 kD~GwtSd+#2l#ۿ$2ٯZcjOPJڡ4ndhi陿-фtyM|CnAodF%\<Q_jwɛ EH8W˔ӳMʻ}C//hގi䝗(\f I]\]e%䲽p>'^רZ9#Wu"}٨OO8u$eq1I>\V@{=Vh@gǕY'ke'S!t9 Yߐ[Қn#<Ìh`JsH+HI-G 7bR/ BߑnsLshjT£m2"_m/={[ 5Db~=m蔁:l]2TX_V0I@ Tcy? !I RhS}'qvey$)9+IS %H.6:Br+;Ɔ ?P8 # xydS@V”pa`ٵEp&OdZ4HYy7,j>OmrCίle^WBVonj z[ϤNWԐo-o7|&ĝG\tUmsO` $,a:|sfKl{pRBH^~'Xt7x5_DS`#ة$!!#!FudCa7R/8ޱAk}a֕4C/%M9IׅF7%T|tU2}@!XI6,s2k ӀއGAO+gvOX\4~(u~PC?W.L*HC P4ԓ A^ &/f?-קNjG]nG̀l*sFłaD9s. 8~= !4c :10n,gept&Fu(% [R3vp!3.uB`yzs'-_V3M~=]cy~ႄVuaJ}k.n *7TOx g @V#q 0&+<,L*Zʨll}"u6j~XK/E\ޗ<})QlM6Ū}]FR E$%5>*EY*\&– i9/" }Pka:! %*DZWǢ槴wtOXO·:0]&i v2.Y41γwXƂ` Q纱'm)XyGKL{VP(.`$^9T}UzXzyq5ڬ \U{,ƛ2`$?lrhb'g~FһU{aZ6 bE-C[1sQ5'a k6Ve^{shtP+|֢ .~{ՍYun}gC2Հ~0 Zr#ۂ=&âjۚhЁœʌNnOtВ+rcXImz[],fH;o߳v=|r4Ny=4CF^D3wqNIaz+yq&̤ErjjB_!e8b zV{/h38' t뎽hB>!f# TZ<"|1 (G!I/͚A#c[[amn*g(qTr}O> =bP4@: fċq]Fl?)E/ a8BtD#aF&hG&{u|o߷rVvS%Y~a mc_뭼y&ߞrG(=3yO>:gypP3CGyYXZN!<ָéο?#Eڔ" cqI}zċ;JLNaHa X4E&p*u Oo]|Cca`c^ Gʖz9&#DV݂-%Lu]l:Be&&\?l=m<,JkF(\K[DdB jG}QYnyy )Җ} ?p}v?к. qsf}KԺ(7h zY͒GaCCYo5JwmAcr;m;`.B8h'妔>7a A*0Ee-N/:p麽5nzwq9}nP- F.>}#A'\trjh[gBWχB;nLS1{ (ݧL")~M`* >N(NJJ5Az (DT ev^}8ciP[grκ~,*c)!&UU I-O3LA]جn8~SBf>/C#QslqHϜ>%Cb;sQSX\dw@Q||PaxE Q KZӅ_Kh3^el٥"  4Wd=X*AQy-r۩ )lqP#!B| ~l/b_So*|X[יQ99"WXͬM0.u( 2 YnBi|߿=N@Fg73ӏOH_Q \l+@ St^χ46'9t~/k;]@*qׁ7^P{w~C~-?*LTV9Y!.c3vC &ɂuX @]C&yTpoSsY9 < 後{@TXi|c!jyra87JG7{r+!\sٮHb9H`';pPs>#I0;-pDH;nM" Y8Op%02E8+0\ ๊{z@)6z@0^f7''4bH0qzڡDx6۪vfbqt+r349:~7˾/?/ݓkx7lǻuѡk'RN)֎C_>w/)Kyz&`1mH-]`ڄKypŠ/h8g$faoɞ*lIs{tj9r CYp'cGe>t]pQXlnO V1{e{ MoJ"\B~u72 yr5 ISew,@ch;r:_ GG̊=?GoǗih($":yT(B/˯6CIlQw=>F)]EJ;. Q",# .΀;bE`ƣ@4>QZާ6,]mA)R{^EeID֊1:;2>-+3Զ٨1_E ^5%Vz#v&k@JFs |^!S-l4TЊ\#[d؎6qnI}eO,?Co{7!7Y^n 9'x_45 D0"YԖ3:_ǨqckkG}f(0cF(,9OkdpO8v}ÆT% IEB`&\4+b_$n2HOQYIA"`dn[2I[XxaNz[ um1TM6BuWl}'46$',Bٌ fV{/7*$:RĒ6o7Fܖ2*9Ӛ/;ܻXORm@"%|3Uo@Mxr y:+}(ն?(2i1L>D^z{ury{fs(^ x6ߛ= ?" l"C\}k ' +H?\~)"Pp0 |qp&! [6FfPg8=[dH0 8OV][xgo_l Xyưw){/Ubk4Pwա$VXgZ0CkzޏgIax)yT?[Zny56Fa+Zq&i; 倇m>&c*1YS$vsY1!Vk)aIEn-;9ütUᯍ4h4(˱Mƽ|8U. LGg V+8;m GSlucRNFOtqN s÷Ė<6ѝpFS,fH o "9>]h² K 8;l=r+:>İ3WKs'<4L*i g( =4b|9 ߼r W:Ux N!Mm|G%rPL}FMn7C+|u&4߼y4G@gRVw)X(SDja>.s66˜]c0h Slw |[ghzϖ|tݟ)'dgOTJ}T7u()ހ*^>9(D%QArP6ceKHN@N9MT0 QB5޺k:lpN~0$)yfT?Ϻ_I@Px5K{UcePN_[ ٔM lED 뭽s'S^AHinXЋ(z^h))do0I?aHb29 lU * ß8 vLJ\UT\Ypn9I=D$JRq` ~ik EKn 8! EٚHkOUR9^p]gtDY<lf_o|U#|Sf 8뚙p֛ @{Wx}>wdsԃ$@Fc/Ks E 0*YuHЙqk>ܧ-֗8T >uN3JVO??Cp(0Wv?1 ?!]2yŬGnQy0J6f}8@i6`c6Xu@x5o4S+x{J ?&Bwpe_o+/2jTArC Bێ`y?;xDx1mhGPb3 =@T,BLNQ*-;0HB~FnF3,*epX&ʇ {^6y} v^hӉU2)c:gd/]"9|a*@4ݦ~ oLK*vnV8ՀrDf,xJ'"ή/8qmG)k-|o. <8p!Rܐ3GI/nM N4i* ~ׇ5mj̊Wp!|e~27EAd_!{ɯIV7"IfymZ%ɒaKfT⸢$10w}x>iIh1*vjc]FZ1 mhVbSggA=.}ޥ"WF\`[) M uF\SKs]p4+M $+ТO ;/;bkJ$ﺙ uJ nt".).<{AzOŞ~;F>ɣ&UV Q K/!_B#OP}_dkל/c{XEuv\-H2q [e+^ َǿfY,^ IGgr1m*aO$5M;$Nj@kO7 ژ湕UXa9TYr*^>sX~h+_93"sNT6Tg/Zs[ 4yjm@2wVx8TT=xok+4w.sAH.@yG C̈cLt@?qfl78" /j]%̑c־ܪ(Dr6M¬q4@ohg#ȏDgL/vKP1cl`E[IWwDL  @yims`RW:9*!RT{وyATr5BQB q[wgn{$O\NǼ.ܙ ΧWs (%/' Fp6 %Blqs,o./Z͒"3NJ.XM ]-#c y&{K;Wktn}e(1t <DŽ,.{"A&LhD\_)j-M:9l̟g`$ffaF+‘DD@]4} >k߼5;E/mV4:#+e^|ΏVZ𻾠}8 % *F)r==͞V\4N  3HWW'D|[/$j х1ӬFx9pƯ{vNrG9lDᚚ]l\pbq~ve3 2y=_>\sx{DaXG`%mupBņrO$9XO* U+4o\m dmåY#;Ni>oW<1; )W,_ OhuK# ̆d)k `Jl Z**[B/+߾HNAtzpҞJPm2,l]2>ԍ@ϯ\#;KL O7=)p;|E) f*aCb_kjh€&cXJٔ?Xk3O"cauNж!Jy]`M3 52p.V;@w SCerXLhJmo!A/ IXNB|Lv _oՔHU[pakF*C"X 3PF΂IgI Uo=5'd.̕7F4#vE3GR6K20ߌVql,CK~ޅzj :f~qbs'@+ Tsa WSk'սGYs1[{O!H7I9(=JxLxs`Po:_#ez׭=Ft)P\ /l.#`ckh0:u3zmB鷬"].dx~sz֓|rqJW4~:/9 q>Ӏɡ=\sS檭5![SC '{Yd@ ?EMw"3g'|Js (QAA;|m +K"gCȆCW/*&r&6P2[Nl 4J {@Œav v  *H8Jd3L ,h| (N/ cP_m ׻T$mRL2-T͇ O8/7f1 <ru}mKN/?ݘu|,.˯'Pj~2bH=F]@k@D9}#h\6)o.jfZ)N_&n6B3 O`uCs FOC9:(nF</)Kxpq5r*+:u s0gOgϤ#&{znE<"z*C;;V3&oeA ;q8&(~"R%xLM,Nv5-ߚwbtCHၟ3KK"M%#nݫ 4TN*p GA!:/P֤u7lkE:J\7!Ar]o&mCɣ uެiKR?v<ǝw ;.?#w+/3w:*o~uc1U{}}m᭷ I89@!EmS$I+R)| kBiX٨\Oѐn *8 \0^Cwx#z[TfҐ+ Q$Ytjz?^T){n@Rm㲃3HMO¥~V8ͯY<% S/*ɻ#$soDb|a(׷vIPbDنH|?a[\"9h1nsF2?ztspaZOӯ/ O-S]-ƍ2 K-3>Iz޲ME\׮EY8-"NĄd=\ј;2;yF^xx2sk!ȱDm_nALZ/^p  ]7QIZWE+HWgص0p͈YY#^aOO^] B}썼e]kBwg& Zg,88B ']di# 2۷H%j}ak-^ʅ z˿pw<ubX4.p:^m܋XKi7${'F,IBiT  BT/54QJ#;]piK^$Yw~Qںl ` d&sbZgiSyM\kv*},r.rNIh*'<3 I ԋ8/5il iBMUO783;kiô,}T~aI& ck][e5ͷ̦a/{pvjWĬ$ol Q|16v‰> OqY q!lFFIf³9=Dp t6GgSiGv`ec6@/_Ʌɵ0]!\`eK;בohΙkz7jRS,O+8݃Sn aYM\]hg#Kt7rJ`lCq:ubRÞwP'm W4=<>\%ǥ 5j({]uGnb+ [/Jغ~)aWTȨak.N!f(.ҫdFg&3X*(r 0RrͺپBh龲7X9`"M C y_ )ʈw8hِoQfMJrXξ/פּw,qϿѕ- K9O~Frg[ {$P}Dc/8u~HQw|w/KҼYp xzr $Kun3oXH! g is&.[1%R KӁxG*0wɟ1XV>tf<#4#%R~UN4~%vp,W~䢚@v%eXG|]I-Z),oP닦l12M+0VOŢ6LzQ"-d;WoR2+2] ưO'Jg'NifC *НBI4}tE.*>PvRhݛ;AmVkul5#H"үOpk(g@5TCn,HB]?` 0=fs s ,aFVUT*+8? ` T}c:$<\gU\@1;Rp:w@K켘!r*EpwnnVW>ɑ!JA-t PD>Vu;E'm/*8f XO*D2F7y&* AApN-0fܡEpK;*)l0$a|h}s@=iL F֞m ) b\ u5K%>DEu *}'&Fhe3LXyϥ֖Iqm^y11jBpUP5
O1aq©\+Wq0l>[ȿ@JAC4'|00JII~K7h`s>Fe?$Qi1W8IܥyG_Uw9 iB4׈F7G Ū\4 U G>MˉS~N8q:G bO,Y_ԏ9{;({t>95%mfQP]bnJ\+KUXƫG)}5]H/\ t z&Z5DhvY _GZ]ćhk)z2bkltY#ǃ%xVkl/{zzX-Tmgw ,$7*eoqY~`׳9^<)'ϐ%'<*\‡ zf6EIC Rg\A{!_G$nCe3˅"m?\p$fS eǯ=L3 s:e8Wz4"tv@c7&5I wK"{uVto:?3 rf8n94ȭB]kpG;\ѶiẢphN/2\0s 1UڧR2~ь a TO">ϰIr8ST5PZ ^t[q8()<x ][g*?c?rXW\ٝ1wkX="f{b8Z*<|s^uE@+*$hX=A.Vr#=_%%:.ǩ`KRjmYˊz!,RL;7oHuV-vG5ch ־8+PC=)UpuB-<0 <_!ܶǺq0oC׆csGwaI|Ӽ MMope0go_ $S 1Ζm(qxh>}2[W^3,M 1 _9MR働>x4&MJf(:71>ZBHťRǓ6SXSR?U`#7Uz07DIG)h.7GS/QƲlW EeڒCe ,ЈSXY _Ҵ|\5fgg/ zȿr| : ;N~6D+N[0e85stP>Te#ْFa8D9I;]Fa(lAUe.t(<ztCb3>2( Ѿ˴ vOM_m_Ou  ֺg?K S zat07|XK6*Ӌ}?7VEZf~6// 6p'{4bI],ztk+? :-!0!+4lgx ӏ4)W큿DGi)R^i:%ZV*r V1l^ :[[j"ߠ/q;R#Pmrvy G34np(ғ~]e;?kΔ xZ񁝛F6P$zI{mr mo ԁBO@ $AAm9c@Wr?3䨾nx*}dJ2zb3tOo}p- U|I>=K <|/+CUU|'i[% >{ds"S_#GFvk!-ъVƊ 67N!p%΂?COw@%XuKJBvCrRKFLހFImv #嚀O\R]!Hz-Eʋƚ+,i# \h-y|AAqdq@ظ1u>3ӀCL`IO 0[ڲc0b2/`vk B7]SgsJ@idLo;$o@`k}I?:ޔ,kIپ̬T&7f@M b%(50<PϿQ)|jd1l)0В3by\@fE]s*٫o&h´ﷴY)˫ GALM@+qF5}=IoX.&1ASw 3V]@`Ǜ oFLExk%W)ջu&KF _%?+>|4H=lwZh ڌ!i`D',6]TCzX谸FG.mX巀w*䜅= oٸb?xAVmye^rC? &nvOޡ̞pNPE@_[`ۜ Hg`W޻>}K*%C_H=ӏ/LRWf@ +B 4#˵G VSVG@f-B(>%<>W$ & ḡ!}& <_ɉ~M*c_+Y>͟ŸxL( `szk 8HZO&E/[vQm@5>]UX=M}&i@k ?0pG6Y^hNuuQPZq |x(7_a_/̼×_\f^mN>)૙AP{q~WxM裹k}@7'~)z Y1`;lT?;{g+n423etO7|uJOn >3Oۉ\{ÕPVD|_cҦל]^qfzX6kx)OgߊAKDok\ɒ̓n'%O c+ K3,p*WS$0}fϙapk" R  ݜ?)b'jS$'S6Rҹ,"T'gCpoc+6`a]&B&k':,Du^%ڗ&9G.q|GC-șG.r=rI7 >wGx &01 dJ]{eg9'.bPᓅ*-_&f%Xhx* ^0bD~y=˶! Sv,݅ZHxM>U,|^˯`o CWKz%(:#ANV%S t).28Th"'oC2x^X:վ,`~r7)NVA߸ӫϔ@6 jX5`@ [z IfPXn1fh0gCݥ2"н˪M{]j^P-\?G/ތ뫥<<_3h-Pr5ZPlP'/ SC7w>8?H}UFg}/-@&7 %۹GU#^K=pdehXU~ڀbt v_Jp(xei~btU-nCy)Pw#AFaxb6II0_R7oT3Ar{.M\fg}/Ԋ |h ,(l ޚDz>ɚ}H}jŘvZҝEX楮zKl0ߗSAy-(T;HV~vM Yod&.?tg~ SہGMŻ]#$|pl'Xjޞ^PΎ_u֌ebK 0u;I}?)8/Nt's鵟zWh +,{:w_U eOyt 0eýj'tsg/9 @v1z-R[qc*MVi6 Umת!ޜ5=oP:PԘ9hCДȾ թArIh$[|0>*;Hb2}K@ҍzK(o4y1}pod?bIٳ"Py;iZ &VXt4gcU¶;(a3c~GșPv1B&pRY֎ 0Ҥ@O|h x<*_D{l3/]`@rD nHh{&ޝK4Gڶ%:tf#.3Zާf4."drgܳ g}!|x&R ?\'UX<$KiDfڹk}SH*|;3krO Ǩ$e'`= _,]BLqR|?7/ϧv<U A2c";O)#jx\ӛYipE-Œpe꽆~x?1'PwsD༙S,}y-1p?Kk@u^uд~}d? ShJqWA)"L=;6lIxdyT/xA> ax/EM=,`JꙖ,  s'{d__*U?f-ȾHK`>Z)Կ~䣁-[1_V5'dXO_5o.5>*KF`!CsOs&KshbuÆͯwċȲ*Rθ"`R Hb|ǝWI 7'h#fD0ˎ^xT kאV8D^}D,7𚻙͞ʸ oі"):s!D+5"ZQ/2j>*܏ȿNag_>п"f7OWGʔx۽ڦo ']r3kg^g%ϲ^ME`R+6la:tAX'd;L4L ʝ WO,&,́> R*UB+<30+ri.O9w @|3_RX ash%yaz#g}ŒaK]B1ƫ+;k'GpWO7a'5dX_] ShZ,kEXWnrN jرZ zK&gf-eHg.&*^RWMouJ}2LvaV=F@wOoy}2 $~B՟|iA}js;}d8<3dCI>An)L$fWѩBnu(4}5 1.Ǔ@Ʀ0c(KpЋo=6]x2 0 LkāEBk/߁CAdy M崻 HfH%UoZ,n/)@{E U6̀B?!O+70U<RsW7o/bm&fΪÇvWIGh+!ߓ=9 ^|mU*]v80^#@5uAk_.Pc-ԏ_hpvUv@wpơi Vt0 .Fpisb&m.ta P!;r02%۝//<܄ל+*Jsݬo\5X(V"gw]{r\oO(d7ƿ< m @frlj55q-@P䔩 xCO'퇖)єK'ʐC @fm!zk HkmM4\)B8hb0½ $^yKȸ(ݾs='YP  Wb.' ?eB3ˏ%C 4Wp[DzJs ,'QzW#d]>·BZb_.u@?i~Bƻyަ@jQ@$|yk yzQs".c#U?B#@gxʎe/]rs ~eW9CBإFC5BD=|BBwN_M$ɂ" 8\3=H |U=ků|-avQMՌB?x;' ۱d_ G7.)8Odh ?>s$ܱ 'B jntxX՞ A0_co +*p pL%HٶSM6mu\xsȊ2~M8k}aoɿcܿeMx VLf~LSſoOЊ4WP!p5.\yS&K%p gy [J i48+YMzQyۮFݿzUq4 F㣇|x;!AwHYG?{]!z[. #| 4)^2f3SoD*?,:n1 >[ w}}h"<((_ Ɨe.d^p9+mۥ9?Q#"l HWEp !J"=SbOL&RB34#B*'\?m"B"w~*pݔ{‰`NOg)]Հ^ \ne\m,-+<-=e}hpnp*moi:O/Nd, R | a{L!̠% jHS<ߋߣWv<+Mq^a^rSj/?Hulװp~J{m5SҨ[q#>ֲNb;SE.H{<-I"2wB܅2ąW%.RԜ̸-)T4G<=/gzI 1v!B8uc@,/[J\.~ Z<ťO!=X%8Qd|_iF͕<#PDΊn4\4-fhR0$aZ 8Whr$Mχ\EN˖wJ Wd0A-%_nYQ\^<3y2m/KC®Q])[E`vXJ=u!n9T Cl#aV(x*R]Wc:~I52no> #-@ŶԾ^o@O!.C8[pB7ښtU8xc }}*Wyn0 f?|z: ,/>-Q fDv=8@5m8YsٗHQV5#tk-a 4G+1pDHPj'qT Jkdsr/#<Ձ:jTmem/n#vRxH{\:i^q}Q? S@!||^H˘aDfOxGNz=bG ۢno |q+,'|${kX[&jʀGsj[k}RRn| OGhοwۥ8M`t< T(O3>/R%J}"n5oZ"˘c կ-huZ*k% (H A`'$,dD QAQHT$HAd(9 ,(Wͩ9xW={Ӆpg},qb%CU"Ņy<=nK$8KL)9"nM%3H>% 2V fhML`[d`AoB[='2nYc3rCc+>’tXY46tHئV."6ȃ$BUXݗ o W]Ey:KBX+{Bo [ex ׂX8)9Χ;cO{Ǫb.|.jebOtnϜo ^` IWz$d1o֤S{& 0Dmf3BE*A7ҁ3HpbgŌɓ-|Oz7CE4$& +{ժj$(nŮ--<&4X #2,Gtl WXi-yk0zg\40nsa}z~lmfjJ\VOP~iT~)O}7i)6n -F_csJhœ=q=?>\Cd+wJ!hiaa(:jp]=_IeAJfbhHI6CNM_iQ%7[MDozw06N>$~AKϜUND K :2aPƺHĮ9 S?7}>Oc?ڣF0>[W-`۾ckM[a(\J;SYJKMDC doaEMy3Ա-nbo6XFvL-N(wbzw>-^;o蟔$UQ-?WVHbs]8-tiȭtoar+бW=wZϙa]s2/Һ4R4/ʳ}[ 5$8˙tf^_-_Έj@Ӷc7D˚Ms\}&Т!TO?QG9̍25'F9~JQ4>RcTsց=_뚨*Bٻ_uw>n@$EKPes(Bp &:=Ȇ!OIWVH}vo۞Ƶ3#YBf91Jmq|i@)1mT|+Y5xtvܺ4bU&dUg?.8rV@؝PL 9}tb_U%U(`X/ bjX]t5;0S!`;$qO$[&u󲇘4i1}ӵߢ~b5N%FbGH[]#}fğs 9vOcK>I+6s;+hSP>nk"F읽AAB_K=J{8>c$VHT#{I"&pY6!4SՌcLzRږ =9RL[ElNb 7䧘sÈ"Kgz΄aQ}bi9y>!Ƴܡp[ ijQ/w3'F{y/mϭh]8&+ wx戽m߿#zkr0PDܗ/>$IIMJdN4_"|NKXx_\ZXVlf~Gctv{ng+/r}~ O ?+rda:GDǢ {Y /l+B=XK(V(ZmJq|F γJ\!>LhΣ!Q _ {mOE O~}DJ5SKiO<ϴ:y6#*q~!FSX7AcM AR&U$t5v|i~\~`2CP[۵j` VQE]ʸ ٖ1ޒI+ 9HC =4RjY,Yp" SM}EfvU!a%dE/~. #.? 9.4w~Ɵ`D6Q~OG0w%fu(4~{v? ݚ-~'3?cw \F*Yx]kV( kLb2SŹX.ҖNSJ5Scz~)lx*g56Z B0yXw8>2Y["N=S1j/5V-FW(瀼fYDc|v'4k>)EGꭻ+!TY$;hOM*C4{m nxjk/=Ӹ@W>6ГJ樟#Lp>{UG[0wZ1WQlLɮ4Z ]Ё.׏9-+ @袑x7$ھp9c EOnz_C>wƁXC׎zfnس;H#Xp DcSW^ Go^ε@^>xrm0ʲ+5./#$^,:7ވx].\o|aU؆ Wn^@/$ݽnm~ ᆶR(I\x(Rg`G\y"2Ra;GR9z: ׬N1 >A0"-ŰS)xFSHdXх =º2 2vŦI$ ;:N{\ 7%H!4& ,j&ZPrM;J!qkB,%_C?ֆAl1e~Ӑ]ACU0"im| T||gY#"*CclӬQYZb5|eG =)p.,,bw. ?}%'Á;5󰁞:5h9@r7N$cʖL2c+oaFnwU[Sտӓ cZG=Hܓ9P;:ZjNo(+4rk^Եbr '?Df+hXz7n|PP"^D~؊I(u!?w*#fx:էqS b51„ DLҪd}# yHH$J\u{*DzMGxji4ے .`!65N7fw_2RVOT%: oO3 ]%, lI|!TU %\20]&l Hv|3Ns j%5`%>5&<F;(Ot!t?cJ#{F(ҌUxNCuu1A$],`W@g${Rk۲H㦠k0ᬫ [sg؁ǓAMC$=nΊ# a@?˷6O?$T&ɖFg O۫$_MWBGd|簄O~L`'"d/ibas>u]66raF2A2λD8z&~?uT:gի3Ys!gh/&`\=_Zz>ŒF*5l)r0/r37F|j0Jyy5#sANx[e4 '=y 6h8|9VpN-)kأIwu)ca`|t/b̔$In}ca~Ʊ\y|D<ݷ)AImdוh89,{jrM86щw~:!:~p9~,{A{Hm?tkE`}v(z=n,Z!v s-j/oEճ}ݶ;0kGo:z}C]S' }aݽv,aȉ)&!cEf)k.~o4wG6g_ yr vh}~p3V} *|ӹ(Vqk֦G.x ;4J}φǞ3Ar Q({gΗ;Qz Ē^!A2ɀф~fdyYJ:_E4>Ǻj԰/ =꧲uM{Ò\xV3c?Ԡ4F>`ޮzY#x$Yr3)6{Ո$Ս ק(]r`.tTKuVTdkܘ:ѻSD>^Ƿh۶C)rkB Y?&SF?5 C[]yk(Fd22 $N/TˮF=ք4! ߣͪEM,sѻ r`TӏQ9jfѳpҵ6I$zo*,P%22mKM8]LlDfμ@R#[U680D7::]&ѷ\@|)Z+MCCAVD:v;lCE ηF~3<9͸Z*LEiCb"¯-#nF;#XJake=sKэsJ9z'gݞ| !Zj Dqq~*!7Dt$fl|#%5B_n &̫{ =x֦hNt0P/sT+?_ՆhV GZ͛?k|ܑ{jw^q\ hAV.̀ ] nq:A8tpŏ YC=ְ[t~Ѿ[@gcU q;6dt{as1Yzt+H79 `Oe,"a)>0<^9|ܕuE8@?m`佳/ܧ$eFwo }\&{$ MLG:AGGd3R]n/x]zE1Vzl0N+nXw}XC1sd.sp%P(c&t~,LU9/L)BtR4g ݌}9g M3m긎~嬼 p͗/"O}`w @-m Xjlji3qVz+Xr6?.A LpP,)=q&?<:;(C cEwAA~'^i.*d:lvl ܫt_7IDq3u¸EjVBs@WD~ХnBy}JnyfK({&M*kk"<$docBGhq C)q)(NpZ2e"\+TR3j7r+T!jؙ߭EuK{3øȬr2*h7m_=&_l~\B&I1l1bs\jo85eA$oTRc e3^QADALҭv%<_b_o]vq i %51gxdva^Ξic@g<i OB1˝Ұ'W: cҧlE[lE|ũ؜:,o/$I8cЃ`ls~_:6n:cJr qc.$]K47S5"}sXS0⯚`=~3䰐8L~df 6ɻvka-ș9먦zlf̡.$Dկ] z3OՄ9{!k3Y+<@E2[~aTRgmWWg1Y%U`V]g{ =2ްsD4|G 8&A/H}(?Di̐lWO\6B2Zv7Wϔ ^3螁A:֤:A8SQ׮[FmsHP:Vj;k~Bb".ڣUa \AT{9Ҋ\\29犎[FZw 'lEWPw9^w Ęl=ߝ,< MV~dMC˦?=De)cyȯ%lݬR>ȄuٯD+w]hrIC\^ F7m7rZOfXVj{|p&8ms(ET*H;CW#ɞ#ND2E~҃]ɜ>#6;')b`xʎRU@h|b2M-!DcD|TDٔ>D>|Xkª4HKbNy7 HJA0)|`"B$!Zb` ioYC_$ YL ܲd8[%gj>eO8D,:qNB+dq}"DY4̵FY!BH~Ћ3A$MeZmȌ“hq9WAy~Ӂy)#E5Bq ueq.s>p)uxI@YzB6;7rADa8`$/wa+|׃XN GI? u& #&)rst5vX]5Fa l&AHK±WWExAX@ͅ J<< ЃY3k>EA8 # {C$vJ f ֍/ɺfSeyF MC[WhC>8o$;^9B;onf`XVB`ZN\/Q~QVʕT-GiX>RrLs-47Z:{@}.W~[ &l\ȇ&̡}6>,M_3Ujؠ*r*cy~ӡylA%.n8$.4c錓3b;Sz8@!RR`9HB-d]®0X 4lG1;`7Z>5O LxE]m;˛(iuN.H!,2(wo;V|C'.q||f ׈DYF>! rh[~"qRZ紬hgjx\P*f(}:&ܹhj'"7:4i iwf^NëцXBPmB^}EݻuT_X. NdR 9[qp" _"-t6 㭕H xsڐ_<|96]oa D\fU[/ͤՒS6)-o,ÒtKbKȳsl~GM_aDDn2 6ZG(Cbඳ=v%EsոweXH R[=Oa߫vH%+4%M^֔sêU.ATL p7룓 ,]2Fe}: 69K*wRt bnj薥p eANnA/~AlTHh@R%_U] W$ք{wdi Yy~ _9z ֩d G/(J(? Vb~0F\}*#MAfv4{k?] .Z1́ ~e{82y:bLUlShzt< uEh,AZ3*fl}Aa[o{VFT $6Tq̏_(\rS w/W&>0>j&xM?T~:HN$PWԥ1fm;1G&Wo{LB&|:9ٗ!t1 ޡ1DJ7hWY}#" ˯-E|;!v_d7:/_7cBEm._dBf ͌SyQu8}-ղ"*ǗEti~yC tX|i!P)OÊ1>y䫟3AЇU/l9]]ʈ UweH݈Xg~𖬼'l;e8Kcx"zEY ^ ܇1YөPqEНG~pJ ks@܃28"XNiWA^I\)t$w$4G 6=<1hvϗ?lJz\:z"`9q&x-pu¤S6F_nT-kWFQЙ,@EXbץUHy #ފtA ld4vTaS7Ipe֘gcW |Nl| :$NB1PEsgL=zT`~[{ ik3Bcĺ68o 8}kjaQ̢0l)? bzg qv@d'b˗wPϛ ُe= {95A|iꐰ3/Sme*|άw yp=q~w|2̹+#98bLih*:?oq$WF<\Vܵm'0bƏBƂϱ}E'>w<uN$PCQ&-:Z vj9ϻ >L\HǯK@'7,P,WC W7ւ.gN-+֬n(pP]=C= 9#\fȩ80wlp;ѩ:qs-<% T.](ǣOIGrҀ~qsJ`ᔀ)yj/X.kW1fI( phhrmt@K6۽D8>PLx.[ ;@3r>rF%ׂ?o޵ZETqÌ_t%_.Sf*vDJ{ W)iC2jLg%[eE=()2^ucأ?nL@goĘ\ui33 Q>󻠆RP(dǰZǢ1d2]Y. ܳ+ڵ/{Z9e9\ 9m{7g9?9hmX<G6"(n]Az63y&\N"n{m!$!M_x`pM 9b+vɆn}w+`^cG*v+#?瑯EXUTrT HX!|XRጐܭV J>ʜdFpEG Z!+ׂ¦yr6+PJdOm&X.RUl '^0 mݑ^~蠐'[ZA]wݠZ<9%w33&Oq_Y -Gv[vS-H)A-Y+ BWk6O!0O00hq 6,>U̝n3E V&ʟĝ36}e .q@7m_J10pqZ@ߦVp`c:㷆 |C<~%A^R"u(Qf&;֚81{@&{ v1[` nyPqpw$hHṕrM.$U_\M{5PU37e>/ b{Co" YOE`%s{4  tN5oAd`^4Bl[p~foq1E9mJVm"ntx?"GN˅ȿ@~rɚ57{v^v`LxˬQРSޖ\X RXKywlFk#@Y\BoQ 0!© &Oc^,~V3:mrL&Px#z y'8HVY+np$t~~DVǟ&ľ1<%R!?xAxs՘;HG~jh#qZ̎_H.bhxf;+-#ݤy6<ˣDdLa$ݪFG-Om^P㊁<.!%]rig"Uw6^?|/E\Ż/>O&<7V̓B.P9'˅okY,ݹrnA:w7%־j4-SU*Ѿojrwmt~R1sCKrK;gܮAign)e;z=|`8W JSq(z/&,z } 5?cTȆTO&/McFW}1L8kcY4%-+=v? -16'C7ĕu~Aºw8WBaX-jn=tHE;OH$[ G;\~5/M A/Ό$o/]$?r5% i(N(~S?|\{kɾ w`YU݃U΁GmwNO@RBr Yn*~tLV@1{BKeʷ 3kܿ!+!bM]8/\ q1Ɂ/3mvB1b<@:wT\YE ĭt%&;mdY[A6w3@3H6lzw8~M92z:rG:sHTze[mR~l|#k:@(NZRH:um,3h ՠg5Rgׯ oѧJO{`HRE/<*PIoSIŧ G<2l;R]nXVr^g2&Ve޸{vKb+wGUAgj~)#P{PY_l_q#? kMhl'j*.,U=sש~Կn<{V:{>xIyޙVÔ0P ckSӯLH=b N  lLE+p\V?0zQLp]h+V8eyU"ZAe= ]>=%!࠿ݸ2Ȫ?sn&l t1:ΰqZXFyKv)4\C,|JQ=h}}H.T!&/rb'qRk |i( R&Y]h߬i}* 5RJYd{5 @0ܭUp4A(L宅'޽mWsne&ÀuPWW0eKlߑCNDKn1 Vj&AnSEyЀ ?#VҐf +܏J ~O/?-Aa*:t.c<:isgT]0ؚ[xVЂ '^jPNgT1G)lKJltE pARb{0hw#A("=>:m5 ATӫ`|X6n"W9=ʙId{o9`~#=&(;f-ӽ  [ΖAǹ 街Lt/s<vU24jhx Q*&ByGy͠0/'ʼh xօڃe!D0ϥ1/Xw9Eg~a! )&FqW{}qkZ>8t% 4P jGIfd^'KHs['~i&-7;vāN:xx5w<` d<$yv ?MC=%O]&nwS`|(pJoDKf翤3/T1v@OΑO \}W@pXvto졐$@O/vݙb_b/:oZHn~xPēXZ\uKJ /r.Ț/H` ʈ#k bd41[Q-RN6/Z~#:_dŖ⏤o4-wjz tqU[˼scEl*$g5 { p dK9bTfK -gApQSp? ?ȏ~¯.Y| d߿`G39y4@=9"#ˎ|Ľ)ȦgnoVzT3ݥ D_-?<=3)17ǡ3nSe OV2G!XY-AP;ٶrb .ԥM^ r.T(.J+!lWK]]P(2M3=$Zuy<Vp8Gv6'z! ]rv;yD!:٩g-'h&QYҒ-^tW5P?sU>xR]9!>+ ѯso2gVeuiş.qKAq //ۑVh3oQ̪][zcٗy^Z~%y;3$Q@YQ S`t/GOWh@PS<mN^~d4U|kwTKAC6n PR,[Zm[AO#2iը${|ąۤJ@{Pj3Xq`" 9iГ3;8\r=l-cMiݳ+LU[ȳ%Be$AԄx2farlJ(P.`Q?|8+~H|#oLG{wom*M?{%He:ӇY ; ټ`aBT]E ꕰ3[;d| 6t9+x6f']^nćr^2K0O  bxaCܮ%OY[kn6*t2-8}==2)eT1R m<1%}hW B{K tl|jUJe?&iWT@?qOj^tf̞c^=I ׷II^y _D/ҮLi%ȶ`(~r O?7xz7ǛEun;}}@ eg`7=< P&;|df,+CdSv~ =ۉ4 CrՉ |]Q{ `4+Nе;`U ߻mzi0ljۿV=c"u2 Ȋ7oj䫲.{xܹ>0n 0< p/xPW\vꯆZq{O!;3܏NPe3= ~ |r?m}# TO;)8z?&tBlǀ*}o<͝Oo~ 9q3x`:zE Q* 7@E`^<6r=/ڒ/Ue05l9Wi/o;YtQĹP.: D6pYsWǟĹ1#F .ҥm#Εv%.Y$H 'ᮝ%[i-;T}iz#"_=Xv>xZj來E8%vğFƮSl#qo솇|$*p.s18T2{8t1`XWt^7Q ha?n;㷋! vҚIy..}o 5@o3p$ كVk d}3+~`Ev4FgE]@<}IǗy9;5H‰TpN_?sſ&)R3(#O@)p&>5C0!lia8`2v@xtlND|NHB[8ڷ=f ܩPǏO(.%!WanjkwZ`^O NQ5>fV> gh#P.݋.x Vx][g+fy8dp?Un_򟜐KK4~ x)oMf#?)Y]L;SFY/o(d\X['uxO_WUqkƟEDbek_w;Πř̗0E }lSb@-!&5jz<ӱǹ((~Q7*ɘXl_k.UäL0E;sg/DMk]J/f[z].jk0b:G̘(ӚKwD>kD5 $?3+Z]FvDћoDω8N_`mg\M? 8,+o~i /qͺR8I%<+Y{BM=A|Le|@-gԧZoFQE`Es̓NRڢDBqGD9ywRV\~Mݲ}),˻QQ⣥ہ)si"\Ņ[pNEi[SDW$=t#Tiʎ+ہR^*T('O;DK"GRt4徆E-|I0 לmhH}/5h^~6]°G2+8ܸSqѱM7EW;nrஊvVu5Mo)j~۹CQƇ%8 ;zg DVȹm~ەe'EFOfAqǚꢺ3 ♢KDK֢"ٴ9!KھǬq՜uo*ffQ…[F,ѓ" |"5&J5=D|b[=%E^8r$֊\t6!wNTJdsa^jO}*V~uDX޲HFm+ "w$$d$$d$$$ti?<+'9Oc0 "俐񾹣?cIHKUzI!ASLSKZDMD#hhS88=4%MBF?~cGg(N;5j*o_@Shב#va@4_X|HN?GgIsoSpecR/tests/testthat.R0000644000176200001440000000007413507710304015067 0ustar liggesuserslibrary(testthat) library(IsoSpecR) test_check("IsoSpecR") IsoSpecR/src/0000755000176200001440000000000013747064020012533 5ustar liggesusersIsoSpecR/src/isoMath.h0000644000176200001440000000526013656513443014322 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once #include #include #if !defined(ISOSPEC_G_FACT_TABLE_SIZE) // 10M should be enough for anyone, right? // Actually, yes. If anyone tries to input a molecule that has more than 10M atoms, // he deserves to get an exception thrown in his face. OpenMS guys don't want to alloc // a table of 10M to memoize the necessary values though, use something smaller for them. #if ISOSPEC_BUILDING_OPENMS #define ISOSPEC_G_FACT_TABLE_SIZE 1024 #else #define ISOSPEC_G_FACT_TABLE_SIZE 1024*1024*10 #endif #endif namespace IsoSpec { extern double* g_lfact_table; static inline double minuslogFactorial(int n) { if (n < 2) return 0.0; #if ISOSPEC_BUILDING_OPENMS if (n >= ISOSPEC_G_FACT_TABLE_SIZE) return -lgamma(n+1); #endif if (g_lfact_table[n] == 0.0) g_lfact_table[n] = -lgamma(n+1); return g_lfact_table[n]; } const double pi = 3.14159265358979323846264338328; const double logpi = 1.144729885849400174143427351353058711647294812915311571513623071472137769884826079783623270275489708; double NormalCDFInverse(double p); double NormalCDFInverse(double p, double mean, double stdev); double NormalCDF(double x, double mean, double stdev); double NormalPDF(double x, double mean = 0.0, double stdev = 1.0); // Returns lower incomplete gamma function of a/2, x, where a is int and > 0. double LowerIncompleteGamma2(int a, double x); // Returns y such that LowerIncompleteGamma2(a, y) == x. Approximately. double InverseLowerIncompleteGamma2(int a, double x); // Computes the inverse Cumulative Distribution Funcion of the Chi-Square distribution with k degrees of freedom inline double InverseChiSquareCDF2(int k, double x) { return InverseLowerIncompleteGamma2(k, x*tgamma(static_cast(k)/2.0)) * 2.0; } extern std::mt19937 random_gen; extern std::uniform_real_distribution stdunif; inline double rdvariate_beta_1_b(double b, std::mt19937& rgen = random_gen) { return 1.0 - pow(stdunif(rgen), 1.0/b); } size_t rdvariate_binom(size_t tries, double succ_prob, std::mt19937& rgen = random_gen); } // namespace IsoSpec IsoSpecR/src/mman.h0000644000176200001440000000322413651127333013636 0ustar liggesusers/* * NOLINT(legal/copyright) - the original authors did not slap a (C) notice in here, * for whatever reason, and I'm in no position to do that for them. * * sys/mman.h * mman-win32 * * This file has been included as a part of IsoSpec project, under a MIT licence. It * comes from the repository: * * https://github.com/witwall/mman-win32 * * which itself is a mirror of: * * https://code.google.com/archive/p/mman-win32/ */ #pragma once #ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. #define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows. #endif /* All the headers include this file. */ #ifndef _MSC_VER #include <_mingw.h> #endif /* Determine offset type */ #include #if defined(_WIN64) typedef int64_t OffsetType; #else typedef uint32_t OffsetType; #endif #include #define PROT_NONE 0 #define PROT_READ 1 #define PROT_WRITE 2 #define PROT_EXEC 4 #define MAP_FILE 0 #define MAP_SHARED 1 #define MAP_PRIVATE 2 #define MAP_TYPE 0xf #define MAP_FIXED 0x10 #define MAP_ANONYMOUS 0x20 #define MAP_ANON MAP_ANONYMOUS #define MAP_FAILED ((void *)-1) /* Flags for msync. */ #define MS_ASYNC 1 #define MS_SYNC 2 #define MS_INVALIDATE 4 void* mmap(void *addr, size_t len, int prot, int flags, int fildes, OffsetType off); int munmap(void *addr, size_t len); int _mprotect(void *addr, size_t len, int prot); int msync(void *addr, size_t len, int flags); int mlock(const void *addr, size_t len); int munlock(const void *addr, size_t len); IsoSpecR/src/platform_incl.h0000644000176200001440000000164513734347575015562 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once #if !defined(ISOSPEC_BUILDING_R) #define ISOSPEC_BUILDING_R true #endif #if !defined(ISOSPEC_BUILDING_CPP) #define ISOSPEC_BUILDING_CPP true #endif #if !defined(ISOSPEC_BUILDING_PYTHON) #define ISOSPEC_BUILDING_PYTHON false #endif #if !defined(ISOSPEC_BUILDING_OPENMS) #define ISOSPEC_BUILDING_OPENMS false #endif IsoSpecR/src/mman.cpp0000644000176200001440000001077613651127333014203 0ustar liggesusers/* * NOLINT(legal/copyright) - the original authors did not slap a (C) notice in here, * for whatever reason, and I'm in no position to do that for them. * * This file has been included as a part of IsoSpec project, under a MIT licence. It * comes from the repository: * * https://github.com/witwall/mman-win32 * * which itself is a mirror of: * * https://code.google.com/archive/p/mman-win32/ */ #include "platform.h" #if ISOSPEC_GOT_MMAN && !ISOSPEC_GOT_SYSTEM_MMAN #include #include #include #include "mman.h" #ifndef FILE_MAP_EXECUTE #define FILE_MAP_EXECUTE 0x0020 #endif /* FILE_MAP_EXECUTE */ static int __map_mman_error(const DWORD err, const int deferr) { if (err == 0) return 0; // TODO: implement NOLINT(readability/todo) - well, should be assigned to the original authors return err; } static DWORD __map_mmap_prot_page(const int prot) { DWORD protect = 0; if (prot == PROT_NONE) return protect; if ((prot & PROT_EXEC) != 0) { protect = ((prot & PROT_WRITE) != 0) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; } else { protect = ((prot & PROT_WRITE) != 0) ? PAGE_READWRITE : PAGE_READONLY; } return protect; } static DWORD __map_mmap_prot_file(const int prot) { DWORD desiredAccess = 0; if (prot == PROT_NONE) return desiredAccess; if ((prot & PROT_READ) != 0) desiredAccess |= FILE_MAP_READ; if ((prot & PROT_WRITE) != 0) desiredAccess |= FILE_MAP_WRITE; if ((prot & PROT_EXEC) != 0) desiredAccess |= FILE_MAP_EXECUTE; return desiredAccess; } void* mmap(void *addr, size_t len, int prot, int flags, int fildes, OffsetType off) { HANDLE fm, h; void * map = MAP_FAILED; #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4293) #endif const DWORD dwFileOffsetLow = (sizeof(OffsetType) <= sizeof(DWORD)) ? (DWORD)off : (DWORD)(off & 0xFFFFFFFFL); const DWORD dwFileOffsetHigh = (sizeof(OffsetType) <= sizeof(DWORD)) ? (DWORD)0 : (DWORD)((off >> 32) & 0xFFFFFFFFL); const DWORD protect = __map_mmap_prot_page(prot); const DWORD desiredAccess = __map_mmap_prot_file(prot); const OffsetType maxSize = off + (OffsetType)len; const DWORD dwMaxSizeLow = (sizeof(OffsetType) <= sizeof(DWORD)) ? (DWORD)maxSize : (DWORD)(maxSize & 0xFFFFFFFFL); const DWORD dwMaxSizeHigh = (sizeof(OffsetType) <= sizeof(DWORD)) ? (DWORD)0 : (DWORD)((maxSize >> 32) & 0xFFFFFFFFL); #ifdef _MSC_VER #pragma warning(pop) #endif errno = 0; if (len == 0 /* Unsupported flag combinations */ || (flags & MAP_FIXED) != 0 /* Usupported protection combinations */ || prot == PROT_EXEC) { errno = EINVAL; return MAP_FAILED; } h = ((flags & MAP_ANONYMOUS) == 0) ? (HANDLE)_get_osfhandle(fildes) : INVALID_HANDLE_VALUE; if ((flags & MAP_ANONYMOUS) == 0 && h == INVALID_HANDLE_VALUE) { errno = EBADF; return MAP_FAILED; } fm = CreateFileMapping(h, NULL, protect, dwMaxSizeHigh, dwMaxSizeLow, NULL); if (fm == NULL) { errno = __map_mman_error(GetLastError(), EPERM); return MAP_FAILED; } map = MapViewOfFile(fm, desiredAccess, dwFileOffsetHigh, dwFileOffsetLow, len); CloseHandle(fm); if (map == NULL) { errno = __map_mman_error(GetLastError(), EPERM); return MAP_FAILED; } return map; } int munmap(void *addr, size_t len) { if (UnmapViewOfFile(addr)) return 0; errno = __map_mman_error(GetLastError(), EPERM); return -1; } int _mprotect(void *addr, size_t len, int prot) { DWORD newProtect = __map_mmap_prot_page(prot); DWORD oldProtect = 0; if (VirtualProtect(addr, len, newProtect, &oldProtect)) return 0; errno = __map_mman_error(GetLastError(), EPERM); return -1; } int msync(void *addr, size_t len, int flags) { if (FlushViewOfFile(addr, len)) return 0; errno = __map_mman_error(GetLastError(), EPERM); return -1; } int mlock(const void *addr, size_t len) { if (VirtualLock((LPVOID)addr, len)) return 0; errno = __map_mman_error(GetLastError(), EPERM); return -1; } int munlock(const void *addr, size_t len) { if (VirtualUnlock((LPVOID)addr, len)) return 0; errno = __map_mman_error(GetLastError(), EPERM); return -1; } #endif IsoSpecR/src/misc.cpp0000644000176200001440000000351213651127333014174 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #include "misc.h" #include #include "platform.h" #include "isoMath.h" namespace IsoSpec { void* quickselect(void ** array, int n, int start, int end) { if(start == end) return array[start]; while(true) { // Partition part int len = end - start; #if ISOSPEC_BUILDING_R int pivot = len/2 + start; #else size_t pivot = random_gen() % len + start; // Using Mersenne twister directly - we don't // need a very uniform distribution just for pivot // selection #endif void* pval = array[pivot]; double pprob = getLProb(pval); std::swap(array[pivot], array[end-1]); int loweridx = start; for(int i = start; i < end-1; i++) { if(getLProb(array[i]) < pprob) { std::swap(array[i], array[loweridx]); loweridx++; } } std::swap(array[end-1], array[loweridx]); // Selection part if(n == loweridx) return array[n]; if(n < loweridx) end = loweridx; else start = loweridx+1; }; } } // namespace IsoSpec IsoSpecR/src/marginalTrek++.h0000644000176200001440000003753413734351263015472 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once #include #include #include #include #include #include "conf.h" #include "allocator.h" #include "operators.h" #include "summator.h" #include "pod_vector.h" namespace IsoSpec { //! The marginal distribution class (a subisotopologue). /*! This class mostly provides some basic common API for subclasses, but itself is not abstract. This class represents the probability distribution generated by one element only -- a subisotopologue. For instance, it might be the distribution of C200, that might be part of, say, C200H402. It corresponds to the multinomial distribution, where each configuration can also be attributed a precise mass. The constructor method perform initial hill-climbing to find the most probable sub-isotopologue (the mode). */ class Marginal { private: bool disowned; protected: const unsigned int isotopeNo; /*!< The number of isotopes of the given element. */ const unsigned int atomCnt; /*!< The number of atoms of the given element. */ const double* const atom_lProbs; /*!< Table of log-probabilities of all the isotopeNo isotopes. */ const double* const atom_masses; /*!< Table of atomic masses of all the isotopeNo isotopes. */ const double loggamma_nominator; /*!< The constant nominator that appears in the expressions for the multinomial probabilities. */ Conf mode_conf; /*!< A subisotopologue with most probability. If not unique, one of the representatives of that class of subisotopologues. */ double mode_lprob; /*!< The log-probability of the mode subisotopologue.*/ public: //! Class constructor. /*! \param _masses A table of masses of the stable isotopes of the investigated element, e.g. for C10 it is 2: C12 and C13. \param _probs A table of natural frequencies of the stable isotopes of the investigated element, see IUPAC at https://iupac.org/isotopesmatter/ \param _isotopeNo Number of isotopes of a given element. \param _atomCnt The number of atoms of the given element, e.g. 10 for C10. \return An instance of the Marginal class. */ Marginal( const double* _masses, // masses size = logProbs size = isotopeNo const double* _probs, int _isotopeNo, int _atomCnt ); // Get rid of the C++ generated assignment constructor. Marginal& operator= (const Marginal& other) = delete; //! Copy constructor Marginal(const Marginal& other); //! Move constructor. Marginal(Marginal&& other); //! Destructor. virtual ~Marginal(); //! Get the number of isotopes of the investigated element. /*! \return The integer number of isotopes of the investigated element. */ inline int get_isotopeNo() const { return isotopeNo; } inline const double* get_lProbs() const { return atom_lProbs; } //! Get the mass of the lightest subisotopologue. /*! This is trivially obtained by considering all atomNo atoms to be the lightest isotope possible. \return The mass of the lightiest subisotopologue. */ double getLightestConfMass() const; //! Get the mass of the heaviest subisotopologue. /*! This is trivially obtained by considering all atomNo atoms to be the heaviest isotope possible. \return The mass of the heaviest subisotopologue. */ double getHeaviestConfMass() const; //! Get the mass of the monoisotopic subisotopologue. /*! The monoisotopic subisotopologue is defined as the molecule consiting only of the most likely isotope. This is frequently the lightest subisotopologue, making this frequently (but not always) equal to getLightestConfMass() */ double getMonoisotopicConfMass() const; //! The the mass of the mode subisotopologue. /*! \return The mass of one of the most probable subisotopologues. */ inline double getModeMass() { ensureModeConf(); return calc_mass(mode_conf, atom_masses, isotopeNo); } //! Get the log-probability of the mode subisotopologue. /*! \return The log-probability of a/the most probable subisotopologue. */ inline double getModeLProb() { ensureModeConf(); return mode_lprob; } //! Get the log-probability of the mode subisotopologue. Results undefined if ensureModeConf() wasn't called before. inline double fastGetModeLProb() { return mode_lprob; } //! The the probability of the mode subisotopologue. /*! \return The probability of a/the most probable subisotopologue. */ // inline double getModeProb() const { return exp(getModeLProb()); } //! Computes and returns the mode configuration, a isotopeNo-large array that the caller is responsible for delete[]-ing. Conf computeModeConf() const; //! The the log-probability of the lightest subisotopologue. /*! \return The logarithm of the smallest non-zero probability of a subisotopologue. */ inline double getSmallestLProb() const { return atomCnt * *std::min_element(atom_lProbs, atom_lProbs+isotopeNo); } //! The average mass of a single atom. /*! \return The average mass of a single atom. */ double getAtomAverageMass() const; //! The theoretical average mass of the molecule. /*! \return The theoretical average mass of the molecule. */ inline double getTheoreticalAverageMass() const { return getAtomAverageMass() * atomCnt; } //! Calculate the log-probability of a given subisotopologue. /*! \param conf A subisotopologue (a table of integers describing subsequent isotope-counts). \return The log-probability of the input subisotopologue. */ protected: ISOSPEC_FORCE_INLINE double unnormalized_logProb(Conf conf) const { double ret = 0.0; for(size_t ii = 0; ii < isotopeNo; ii++) ret += minuslogFactorial(conf[ii]) + conf[ii] * atom_lProbs[ii]; return ret; } ISOSPEC_FORCE_INLINE double logProb(Conf conf) const { return loggamma_nominator + unnormalized_logProb(conf); } public: //! Calculate the variance of the theoretical distribution describing the subisotopologue double variance() const; //! Return estimated logarithm of size of the marginal at a given ellipsoid radius double getLogSizeEstimate(double logEllipsoidRadius) const; inline void ensureModeConf() { if (mode_conf == nullptr) setupMode(); } private: void setupMode(); }; //! The marginal distribution class (a subisotopologue). class MarginalTrek : public Marginal { private: int current_count; const ConfOrderMarginal orderMarginal; std::priority_queue > pq; Allocator allocator; pod_vector _conf_lprobs; pod_vector _conf_masses; pod_vector _confs; //! Proceed to the next configuration and memoize it (as it will be surely needed). bool add_next_conf(); public: //! Move constructor: specializes the Marginal class. /*! \param tabSize The size of the table used to store configurations in the allocator. \param hashSize The size of the hash table used to store visited subisotopologues. */ MarginalTrek( Marginal&& m, int tabSize = 1000, int hashSize = 1000 ); // NOLINT(runtime/explicit) - Constructor deliberately left usable as a conversion. MarginalTrek(const MarginalTrek& other) = delete; MarginalTrek& operator=(const MarginalTrek& other) = delete; //! Check if the table of computed subisotopologues does not have to be extended. /*! This function checks if the idx-th most probable subisotopologue was memoized and if not, computes it and memoizes it. \param idx The number of the idx-th most probable subisotopologue. \return Returns false if it the provided idx exceeds the total number of subisotopologues. */ inline bool probeConfigurationIdx(int idx) { while(current_count <= idx) if(!add_next_conf()) return false; return true; } //! Get the log-probability of the mode subisotopologue. /*! \return The log-probability of a/the most probable subisotopologue. */ inline double getModeLProb() const { return mode_lprob; } inline const pod_vector& conf_lprobs() const { return _conf_lprobs; } inline const pod_vector& conf_masses() const { return _conf_masses; } inline const pod_vector& confs() const { return _confs; } virtual ~MarginalTrek(); }; //! Precalculated Marginal class /*! This class serves to calculate a set of isotopologues that is defined by the minimal probability threshold. This works faster than if you did not know the threshold. If you have no idea about the threshold, you would need to call us, to change encode the layered version of the marginal. */ class PrecalculatedMarginal : public Marginal { protected: pod_vector configurations; Conf* confs; unsigned int no_confs; double* masses; pod_vector lProbs; double* probs; Allocator allocator; public: //! The move constructor (disowns the Marginal). /*! This constructor memoizes all subisotopologues with log-probability above the provided threshold lCutOff \param Marginal An instance of the Marginal class this class is about to disown. \param lCutOff The lower limit on the log-probability of the precomputed subisotopologues. \param sort Should the subisotopologues be stored with descending probability ? \return An instance of the PrecalculatedMarginal class. */ PrecalculatedMarginal( Marginal&& m, double lCutOff, bool sort = true, int tabSize = 1000, int hashSize = 1000 ); PrecalculatedMarginal(const PrecalculatedMarginal& other) = delete; PrecalculatedMarginal& operator=(const PrecalculatedMarginal& other) = delete; //! Destructor. virtual ~PrecalculatedMarginal(); //! Is there a subisotopologue with a given number? /*! \return Returns true if idx does not exceed the number of pre-computed configurations. */ inline bool inRange(unsigned int idx) const { return idx < no_confs; } //! Get the log-probability of the idx-th subisotopologue. /*! \param idx The number of the considered subisotopologue. \return The log-probability of the idx-th subisotopologue. */ inline const double& get_lProb(int idx) const { return lProbs[idx]; } //! Get the probability of the idx-th subisotopologue. /*! \param idx The number of the considered subisotopologue. \return The probability of the idx-th subisotopologue. */ inline const double& get_prob(int idx) const { return probs[idx]; } //! Get the mass of the idx-th subisotopologue. /*! \param idx The number of the considered subisotopologue. \return The mass of the idx-th subisotopologue. */ inline const double& get_mass(int idx) const { return masses[idx]; } //! Get the table of the log-probabilities of subisotopologues. /*! \return Pointer to the first element in the table storing log-probabilities of subisotopologues. */ inline const double* get_lProbs_ptr() const { return lProbs.data(); } //! Get the table of the masses of subisotopologues. /*! \return Pointer to the first element in the table storing masses of subisotopologues. */ inline const double* get_masses_ptr() const { return masses; } //! Get the counts of isotopes that define the subisotopologue. /*! \param idx The number of the considered subisotopologue. \return The counts of isotopes that define the subisotopologue. */ inline const Conf& get_conf(int idx) const { return confs[idx]; } //! Get the number of precomputed subisotopologues. /*! \return The number of precomputed subisotopologues. */ inline unsigned int get_no_confs() const { return no_confs; } //! Get the log-probability of the mode subisotopologue. /*! \return The log-probability of a/the most probable subisotopologue. */ inline double getModeLProb() const { return mode_lprob; } }; //! LayeredMarginal class /*! An extendable version of the PrecalculatedMarginal, where you can extend the threshold at will. */ class LayeredMarginal : public Marginal { private: double current_threshold; pod_vector configurations; pod_vector fringe; pod_vector fringe_unn_lprobs; Allocator allocator; const ConfEqual equalizer; const KeyHasher keyHasher; pod_vector lProbs; pod_vector probs; pod_vector masses; double* guarded_lProbs; public: //! Move constructor: specializes the Marginal class. /*! \param tabSize The size of the table used to store configurations in the allocator. \param hashSize The size of the hash table used to store visited subisotopologues. */ LayeredMarginal(Marginal&& m, int tabSize = 1000, int hashSize = 1000); // NOLINT(runtime/explicit) - constructor deliberately left usable as a conversion LayeredMarginal(const LayeredMarginal& other) = delete; LayeredMarginal& operator=(const LayeredMarginal& other) = delete; //! Extend the set of computed subisotopologues to those above the new threshold. /*! \param new_threshold The new log-probability limiting the subisotopologues from below. \return Returns false, if there are no fringe-subisotopologues (subisotopologues that were neighbours of the previously calculated subisotopologues, with log-probability below the previous threshold). */ bool extend(double new_threshold, bool do_sort = true); //! get the log-probability of the idx-th subisotopologue, see details in @ref PrecalculatedMarginal::get_lProb. inline double get_lProb(int idx) const { return guarded_lProbs[idx]; } // access to idx == -1 is valid and gives a guardian of +inf //! get the probability of the idx-th subisotopologue, see details in @ref PrecalculatedMarginal::get_eProb. inline double get_prob(int idx) const { return probs[idx]; } //! get the mass of the idx-th subisotopologue, see details in @ref PrecalculatedMarginal::get_mass. inline double get_mass(int idx) const { return masses[idx]; } //! get the pointer to lProbs array. Accessing index -1 is legal and returns a guardian of -inf. Warning: The pointer gets invalidated on calls to extend() inline const double* get_lProbs_ptr() const { return lProbs.data()+1; } //! get the counts of isotopes that define the subisotopologue, see details in @ref PrecalculatedMarginal::get_conf. inline const Conf& get_conf(int idx) const { return configurations[idx]; } //! Get the number of precomputed subisotopologues, see details in @ref PrecalculatedMarginal::get_no_confs. inline unsigned int get_no_confs() const { return configurations.size(); } //! Get the minimal mass in current layer double get_min_mass() const; //! Get the maximal mass in current layer double get_max_mass() const; //! Get the log-probability of the mode subisotopologue. /*! \return The log-probability of a/the most probable subisotopologue. */ inline double getModeLProb() const { return mode_lprob; } }; } // namespace IsoSpec IsoSpecR/src/allocator.cpp0000644000176200001440000000260413673426762015236 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #include "allocator.h" namespace IsoSpec { template Allocator::Allocator(const int dim_, const int tabSize_): currentTab(new T[dim_ * tabSize_]), currentId(-1), dim(dim_), tabSize(tabSize_) {} template Allocator::~Allocator() { if(prevTabs.size() == 0 || currentTab != prevTabs.back()) { // It will be equal only if shiftTables throws during new[] // Make sure we don't del currentTab twice in that case delete [] currentTab; } for(unsigned int i = 0; i < prevTabs.size(); ++i) delete [] prevTabs[i]; } template void Allocator::shiftTables() { prevTabs.push_back(currentTab); currentTab = new T[dim * tabSize]; currentId = 0; } template class Allocator; } // namespace IsoSpec IsoSpecR/src/operators.cpp0000644000176200001440000000202413674147331015261 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #include "operators.h" #include "marginalTrek++.h" namespace IsoSpec { KeyHasher::KeyHasher(int _dim) : dim(_dim-1) {} ConfEqual::ConfEqual(int dim) : size( dim*sizeof(int) ) {} ConfOrderMarginal::ConfOrderMarginal(const double* _logProbs, int _dim) : logProbs(_logProbs), dim(_dim) {} ConfOrderMarginalDescending::ConfOrderMarginalDescending(const double* _logProbs, int _dim) : logProbs(_logProbs), dim(_dim) {} } // namespace IsoSpec IsoSpecR/src/operators.h0000644000176200001440000000762313674147331014740 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once #include #include "platform.h" #include "conf.h" #include "isoMath.h" #include "misc.h" namespace IsoSpec { class KeyHasher { private: int dim; public: explicit KeyHasher(int dim); inline std::size_t operator()(const int* conf) const noexcept { std::size_t seed = conf[0]; for(int i = 1; i < dim; ++i ) { constexpr_if(sizeof(std::size_t) == 8) seed = seed << 6; else // Assuming 32 bit arch. If not, well, there will be // more hash collisions but it still should run OK seed = seed << 3; seed ^= conf[i]; } return seed; }; }; class ConfEqual { private: int size; public: explicit ConfEqual(int dim); inline bool operator()(const int* conf1, const int* conf2) const { // The memcmp() function returns zero if the two strings are identical, oth- // erwise returns the difference between the first two differing bytes // (treated as unsigned char values, so that `\200' is greater than `\0', // for example). Zero-length strings are always identical. This behavior // is not required by C and portable code should only depend on the sign of // the returned value. // sacred man of memcmp. return memcmp(conf1, conf2, size) == 0; } }; class ConfOrder { // configurations comparator public: inline bool operator()(void* conf1, void* conf2) const { return *reinterpret_cast(conf1) < *reinterpret_cast(conf2); }; }; class ConfOrderMarginal { // configurations comparator const double* logProbs; int dim; public: ConfOrderMarginal(const double* logProbs, int dim); inline bool operator()(const Conf conf1, const Conf conf2) { // Return true if conf1 is less probable than conf2. return unnormalized_logProb(conf1, logProbs, dim) < unnormalized_logProb(conf2, logProbs, dim); }; }; class ConfOrderMarginalDescending { // configurations comparator const double* logProbs; int dim; public: ConfOrderMarginalDescending(const double* logProbs, int dim); inline bool operator()(const Conf conf1, const Conf conf2) { // Return true if conf1 is less probable than conf2. return unnormalized_logProb(conf1, logProbs, dim) > unnormalized_logProb(conf2, logProbs, dim); }; }; template class ReverseOrder { public: inline ReverseOrder() {} inline bool operator()(const T a, const T b) const { return a > b; } }; template class TableOrder { const T* tbl; public: inline explicit TableOrder(const T* _tbl) : tbl(_tbl) {} inline bool operator()(unsigned int i, unsigned int j) { return tbl[i] < tbl[j]; } }; } // namespace IsoSpec #include "marginalTrek++.h" class PrecalculatedMarginal; // In case marginalTrek++.h us including us, and can't be included again... namespace IsoSpec { template class OrderMarginalsBySizeDecresing { T const* const* const MT; public: explicit OrderMarginalsBySizeDecresing(T const* const * const MT_) : MT(MT_) {}; inline bool operator()(int m1, int m2) { return MT[m1]->get_no_confs() > MT[m2]->get_no_confs(); } }; } // namespace IsoSpec IsoSpecR/src/dirtyAllocator.h0000644000176200001440000000260613674147331015712 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once #include #include "pod_vector.h" namespace IsoSpec { class DirtyAllocator { private: void* currentTab; void* currentConf; void* endOfTablePtr; const int tabSize; int cellSize; pod_vector prevTabs; public: explicit DirtyAllocator(const int dim, const int tabSize = 10000); ~DirtyAllocator(); DirtyAllocator(const DirtyAllocator& other) = delete; DirtyAllocator& operator=(const DirtyAllocator& other) = delete; void shiftTables(); inline void* newConf() { if (currentConf >= endOfTablePtr) { shiftTables(); } void* ret = currentConf; currentConf = reinterpret_cast(currentConf) + cellSize; return ret; } }; } // namespace IsoSpec IsoSpecR/src/element_tables.h0000644000176200001440000000312113651127333015665 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once #include namespace IsoSpec { #ifdef __cplusplus extern "C" { #endif #define ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES 292 extern const size_t isospec_number_of_isotopic_entries; extern const int elem_table_ID[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const int elem_table_atomicNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const double elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const double elem_table_massNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const int elem_table_extraNeutrons[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const char* elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const bool elem_table_Radioactive[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; extern const double elem_table_log_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES]; #ifdef __cplusplus } #endif } // namespace IsoSpec IsoSpecR/src/platform.h0000644000176200001440000000657113734347575014560 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once #include "platform_incl.h" #if defined(__unix__) || defined(__unix) || \ (defined(__APPLE__) && defined(__MACH__)) #define ISOSPEC_TEST_WE_ARE_ON_UNIX_YAY true #define ISOSPEC_TEST_WE_ARE_ON_WINDOWS false /* CYGWIN doesn't really count as Windows for our purposes, we'll be using UNIX API anyway */ #define ISOSPEC_TEST_GOT_SYSTEM_MMAN true #define ISOSPEC_TEST_GOT_MMAN true #elif defined(__MINGW32__) || defined(_WIN32) #define ISOSPEC_TEST_WE_ARE_ON_UNIX_YAY false #define ISOSPEC_TEST_WE_ARE_ON_WINDOWS true #define ISOSPEC_TEST_GOT_SYSTEM_MMAN false #define ISOSPEC_TEST_GOT_MMAN true #else #define ISOSPEC_TEST_WE_ARE_ON_UNIX_YAY false /* Well, probably... */ #define ISOSPEC_TEST_WE_ARE_ON_WINDOWS false #define ISOSPEC_TEST_GOT_SYSTEM_MMAN false #define ISOSPEC_TEST_GOT_MMAN false #endif #if !defined(ISOSPEC_WE_ARE_ON_UNIX_YAY) #define ISOSPEC_WE_ARE_ON_UNIX_YAY ISOSPEC_TEST_WE_ARE_ON_UNIX_YAY #endif #if !defined(ISOSPEC_WE_ARE_ON_WINDOWS) #define ISOSPEC_WE_ARE_ON_WINDOWS ISOSPEC_TEST_WE_ARE_ON_WINDOWS #endif #if !defined(ISOSPEC_GOT_SYSTEM_MMAN) #define ISOSPEC_GOT_SYSTEM_MMAN ISOSPEC_TEST_GOT_SYSTEM_MMAN #endif #if !defined(ISOSPEC_GOT_MMAN) #define ISOSPEC_GOT_MMAN ISOSPEC_TEST_GOT_MMAN #endif // Note: __GNUC__ is defined by clang and gcc #ifdef __GNUC__ #define ISOSPEC_IMPOSSIBLE(condition) if(condition) __builtin_unreachable(); #define ISOSPEC_LIKELY(condition) __builtin_expect(static_cast(condition), 1) #define ISOSPEC_UNLIKELY(condition) __builtin_expect(static_cast(condition), 0) // For aggressive inlining #define ISOSPEC_FORCE_INLINE __attribute__ ((always_inline)) inline #elif defined _MSC_VER #define ISOSPEC_IMPOSSIBLE(condition) __assume(!(condition)); #define ISOSPEC_LIKELY(condition) condition #define ISOSPEC_UNLIKELY(condition) condition #define ISOSPEC_FORCE_INLINE __forceinline inline #else #define ISOSPEC_IMPOSSIBLE(condition) #define ISOSPEC_LIKELY(condition) condition #define ISOSPEC_UNLIKELY(condition) condition #define ISOSPEC_FORCE_INLINE inline #endif #ifdef ISOSPEC_DEBUG #undef ISOSPEC_IMPOSSIBLE #include #define ISOSPEC_IMPOSSIBLE(condition) assert(!(condition)); #endif /* ISOSPEC_DEBUG */ #if ISOSPEC_GOT_MMAN #if ISOSPEC_GOT_SYSTEM_MMAN #include #else #include "mman.h" #endif #else #include /* malloc, free, rand */ #endif #if defined(OPENMS_DLLAPI) /* IsoSpec is being built as a part of OpenMS: use their visibility macros */ #define ISOSPEC_EXPORT_SYMBOL OPENMS_DLLAPI #else /* it's a can of worms we don't yet want to open ourselves though... */ #define ISOSPEC_EXPORT_SYMBOL #endif #if !defined(__cpp_if_constexpr) #define constexpr_if if #define ISOSPEC_MAYBE_UNUSED #else #define constexpr_if if constexpr #define ISOSPEC_MAYBE_UNUSED [[maybe_unused]] #endif IsoSpecR/src/isoMath.cpp0000644000176200001440000000703413651127333014650 0ustar liggesusers/* * This file has been released into public domain by John D. Cook * and is used here with some slight modifications (which are hereby * also released into public domain), * * This file is part of IsoSpec. */ // NOLINT(legal/copyright) #include #include #include "isoMath.h" #include "platform.h" #include "btrd.h" namespace IsoSpec { void release_g_lfact_table() { #if ISOSPEC_GOT_MMAN munmap(g_lfact_table, ISOSPEC_G_FACT_TABLE_SIZE*sizeof(double)); #else free(g_lfact_table); #endif } double* alloc_lfact_table() { double* ret; # if ISOSPEC_GOT_MMAN ret = reinterpret_cast(mmap(nullptr, sizeof(double)*ISOSPEC_G_FACT_TABLE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); #else ret = reinterpret_cast(calloc(ISOSPEC_G_FACT_TABLE_SIZE, sizeof(double))); #endif std::atexit(release_g_lfact_table); return ret; } double* g_lfact_table = alloc_lfact_table(); double RationalApproximation(double t) { // Abramowitz and Stegun formula 26.2.23. // The absolute value of the error should be less than 4.5 e-4. double c[] = {2.515517, 0.802853, 0.010328}; double d[] = {1.432788, 0.189269, 0.001308}; return t - ((c[2]*t + c[1])*t + c[0]) / (((d[2]*t + d[1])*t + d[0])*t + 1.0); } double NormalCDFInverse(double p) { if (p < 0.5) return -RationalApproximation( sqrt(-2.0*log(p)) ); else return RationalApproximation( sqrt(-2.0*log(1-p)) ); } double NormalCDFInverse(double p, double mean, double stdev) { return mean + stdev * NormalCDFInverse(p); } double NormalCDF(double x, double mean, double stdev) { x = (x-mean)/stdev * 0.7071067811865476; // constants double a1 = 0.254829592; double a2 = -0.284496736; double a3 = 1.421413741; double a4 = -1.453152027; double a5 = 1.061405429; double p = 0.3275911; // Save the sign of x int sign = 1; if (x < 0) sign = -1; x = fabs(x); // A&S formula 7.1.26 double t = 1.0/(1.0 + p*x); double y = 1.0 - (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*exp(-x*x); return 0.5*(1.0 + sign*y); } double NormalPDF(double x, double mean, double stdev) { double two_variance = stdev * stdev * 2.0; double delta = x-mean; return exp( -delta*delta / two_variance ) / sqrt( two_variance * pi ); } const double sqrt_pi = 1.772453850905516027298167483341145182798; double LowerIncompleteGamma2(int a, double x) { double base; double exp_minus_x = exp(-x); double current_s; if(a % 2 == 0) { base = 1 - exp_minus_x; current_s = 1.0; a--; } else { base = sqrt_pi * erf(sqrt(x)); current_s = 0.5; } a = a/2; for(; a; a--) { base = base * current_s - pow(x, current_s) * exp_minus_x; current_s += 1.0; } return base; } double InverseLowerIncompleteGamma2(int a, double x) { double l = 0.0; double p = tgamma(a); double s; do { s = (l+p) / 2.0; double v = LowerIncompleteGamma2(a, s); if (x < v) p = s; else l = s; } while((p-l)*1000.0 > p); return s; } std::random_device random_dev; std::mt19937 random_gen(random_dev()); std::uniform_real_distribution stdunif(0.0, 1.0); size_t rdvariate_binom(size_t tries, double succ_prob, std::mt19937& rgen) { if (succ_prob >= 1.0) return tries; return IsoSpec::boost_binomial_distribution_variate(tries, succ_prob, rgen); } } // namespace IsoSpec IsoSpecR/src/dirtyAllocator.cpp0000644000176200001440000000321013673426762016244 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #include #include "dirtyAllocator.h" namespace IsoSpec { DirtyAllocator::DirtyAllocator( const int dim, const int tabSize_ ): tabSize(tabSize_) { cellSize = sizeof(double) + sizeof(int) * dim; // Fix memory alignment problems for SPARC if(cellSize % sizeof(double) != 0) cellSize += sizeof(double) - cellSize % sizeof(double); currentTab = malloc( cellSize * tabSize ); if(currentTab == NULL) throw std::bad_alloc(); currentConf = currentTab; endOfTablePtr = reinterpret_cast(currentTab) + cellSize*tabSize; } DirtyAllocator::~DirtyAllocator() { for(unsigned int i = 0; i < prevTabs.size(); ++i) free(prevTabs[i]); free(currentTab); } void DirtyAllocator::shiftTables() { prevTabs.push_back(currentTab); currentTab = malloc( cellSize * tabSize ); currentConf = currentTab; if(currentTab == NULL) throw std::bad_alloc(); endOfTablePtr = reinterpret_cast(currentTab) + cellSize*tabSize; } } // namespace IsoSpec IsoSpecR/src/isoSpec++.h0000644000176200001440000006623713746065013014457 0ustar liggesusers/*! Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. This file is part of IsoSpec. IsoSpec is free software: you can redistribute it and/or modify it under the terms of the Simplified ("2-clause") BSD licence. IsoSpec is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. You should have received a copy of the Simplified BSD Licence along with IsoSpec. If not, see . */ #pragma once #include #include #include #include #include #include "platform.h" #include "dirtyAllocator.h" #include "summator.h" #include "operators.h" #include "marginalTrek++.h" namespace IsoSpec { // This function is NOT guaranteed to be secure against malicious input. It should be used only for debugging. unsigned int parse_formula(const char* formula, std::vector& isotope_masses, std::vector& isotope_probabilities, int** isotopeNumbers, int** atomCounts, unsigned int* confSize, bool use_nominal_masses = false); //! The Iso class for the calculation of the isotopic distribution. /*! It contains full description of the molecule for which one would like to calculate the isotopic distribution. */ class ISOSPEC_EXPORT_SYMBOL Iso { private: //! Set up the marginal isotopic envelopes, corresponding to subisotopologues. /*! \param _isotopeMasses A table of masses of isotopes of the elements in the chemical formula, e.g. {12.0, 13.003355, 1.007825, 2.014102} for C100H202. \param _isotopeProbabilities A table of isotope frequencies of the elements in the chemical formula, e.g. {.989212, .010788, .999885, .000115} for C100H202. */ void setupMarginals(const double* _isotopeMasses, const double* _isotopeProbabilities); bool disowned; /*!< A variable showing if the Iso class was specialized by its child-class. If so, then the description of the molecules has been transfered there and Iso is a carcass class, dead as a dodo, an ex-class if you will. */ protected: int dimNumber; /*!< The number of elements in the chemical formula of the molecule. */ int* isotopeNumbers; /*!< A table with numbers of isotopes for each element. */ int* atomCounts; /*!< A table with numbers of isotopes for each element. */ unsigned int confSize; /*!< The number of bytes needed to represent the counts of isotopes present in the extended chemical formula. */ int allDim; /*!< The total number of isotopes of elements present in a chemical formula, e.g. for H20 it is 2+3=5. */ Marginal** marginals; /*!< The table of pointers to the distributions of individual subisotopologues. */ bool doMarginalsNeedSorting() const; public: Iso(); //! General constructror. /*! \param _dimNumber The number of elements in the formula, e.g. for C100H202 it would be 2, as there are only carbon and hydrogen atoms. \param _isotopeNumbers A table with numbers of isotopes for each element, e.g. for C100H202 it would be {2, 2}, because both C and H have two stable isotopes. \param _atomCounts Number of atoms of each element in the formula, e.g. for C100H202 corresponds to {100, 202}. \param _isotopeMasses A table of tables of masses of isotopes of the elements in the chemical formula, e.g. {{12.0, 13.003355}, {1.007825, 2.014102}} for C100H202. \param _isotopeProbabilities A table of tables of isotope frequencies of the elements in the chemical formula, e.g. {{.989212, .010788}, {.999885, .000115}} for C100H202. */ Iso( int _dimNumber, const int* _isotopeNumbers, const int* _atomCounts, const double* _isotopeMasses, const double* _isotopeProbabilities ); Iso( int _dimNumber, const int* _isotopeNumbers, const int* _atomCounts, const double* const * _isotopeMasses, const double* const * _isotopeProbabilities ); //! Constructor from the formula object. Iso(const char* formula, bool use_nominal_masses = false); // NOLINT(runtime/explicit) - constructor deliberately left to be used as a conversion //! Constructor from C++ std::string chemical formula. inline Iso(const std::string& formula, bool use_nominal_masses = false) : Iso(formula.c_str(), use_nominal_masses) {} // NOLINT(runtime/explicit) - constructor deliberately left to be used as a conversion //! Constructor (named) from aminoacid FASTA sequence as C string. /*! \param fasta An aminoacid FASTA sequence. May be upper/lower/mixed case, may contain selenocystein (U) or xleucine (J). Other characters, including FASTA codes of indeterminate chemical formula (X, *, -, B, ...) are silently ignored. That means "AEDA", "AE-DA", "EAXXDA", "AE DA" will all result in the same chemical formula. Subisotopologues will be in order: CHNOS, possibly with Se added at an end if present. \use_nominal_masses Whether to use nucleon number instead of the real mass of each isotope during calculations. \add_water Whether the chain should have the terminating -H and -OH groups at the N and C terminus, respectively. */ static Iso FromFASTA(const char* fasta, bool use_nominal_masses = false, bool add_water = true); //! Constructor (named) from aminoacid FASTA sequence as C++ std::string. See above for details. static inline Iso FromFASTA(const std::string& fasta, bool use_nominal_masses = false, bool add_water = true) { return FromFASTA(fasta.c_str(), use_nominal_masses, add_water); } //! The move constructor. Iso(Iso&& other); /* We're not exactly following standard copy and assign semantics with Iso objects, so delete the default assign constructor just in case, so noone tries to use it. Copy ctor declared below. */ Iso& operator=(const Iso& other) = delete; //! The copy constructor. /*! \param other The other instance of the Iso class. \param fullcopy If false, copy only the number of atoms in the formula, the size of the configuration, the total number of isotopes, and the probability of the mode isotopologue. */ Iso(const Iso& other, bool fullcopy); //! Destructor. virtual ~Iso(); //! Get the mass of the lightest peak in the isotopic distribution. double getLightestPeakMass() const; //! Get the mass of the heaviest peak in the isotopic distribution. double getHeaviestPeakMass() const; /*! Get the mass of the monoisotopic peak in the isotopic distribution. Monoisotopc molecule is defined as consisting only of the most frequent isotopes of each element. These are often (but not always) the lightest ones, making this often (but again, not always) equal to getLightestPeakMass() */ double getMonoisotopicPeakMass() const; //! Get the log-probability of the mode-configuration (if there are many modes, they share this value). double getModeLProb() const; //! Get the logprobability of the least probable subisotopologue. double getUnlikeliestPeakLProb() const; //! Get the mass of the mode-configuration (if there are many modes, it is undefined which one will be selected). double getModeMass() const; //! Get the theoretical average mass of the molecule. double getTheoreticalAverageMass() const; //! Get the theoretical variance of the distribution. double variance() const; //! Get the standard deviation of the theoretical distribution. double stddev() const { return sqrt(variance()); } //! Get the number of elements in the chemical formula of the molecule. inline int getDimNumber() const { return dimNumber; } //! Get the total number of isotopes of elements present in a chemical formula. inline int getAllDim() const { return allDim; } //! Add an element to the molecule. Note: this method can only be used BEFORE Iso is used to construct an IsoGenerator instance. void addElement(int atomCount, int noIsotopes, const double* isotopeMasses, const double* isotopeProbabilities); //! Save estimates of logarithms of target sizes of marginals using Gaussian approximation into argument array. Array priorities must have length equal to dimNumber. void saveMarginalLogSizeEstimates(double* priorities, double target_total_prob) const; }; //! The generator of isotopologues. /*! This class provides the common interface for all isotopic generators. */ class ISOSPEC_EXPORT_SYMBOL IsoGenerator : public Iso { public: const double mode_lprob; protected: double* partialLProbs; /*!< The prefix sum of the log-probabilities of the current isotopologue. */ double* partialMasses; /*!< The prefix sum of the masses of the current isotopologue. */ double* partialProbs; /*!< The prefix product of the probabilities of the current isotopologue. */ public: //! Advance to the next, not yet visited, most probable isotopologue. /*! \return Return false if it is not possible to advance. */ virtual bool advanceToNextConfiguration() = 0; //! Get the log-probability of the current isotopologue. /*! \return The log-probability of the current isotopologue. */ virtual double lprob() const { return partialLProbs[0]; } //! Get the mass of the current isotopologue. /*! \return The mass of the current isotopologue. */ virtual double mass() const { return partialMasses[0]; } //! Get the probability of the current isotopologue. /*! \return The probability of the current isotopologue. */ virtual double prob() const { return partialProbs[0]; } //! Write the signature of configuration into target memory location. It must be large enough to accomodate it. virtual void get_conf_signature(int* space) const = 0; //! Move constructor. IsoGenerator(Iso&& iso, bool alloc_partials = true); // NOLINT(runtime/explicit) - constructor deliberately left to be used as a conversion //! Destructor. virtual ~IsoGenerator(); }; //! The generator of isotopologues sorted by their probability of occurrence. /*! The subsequent isotopologues are generated with diminishing probability, starting from the mode. This algorithm take O(N*log(N)) to compute the N isotopologues because of using the Priority Queue data structure. Obtaining the N isotopologues can be achieved in O(N) if they are not required to be spit out in the descending order. */ class ISOSPEC_EXPORT_SYMBOL IsoOrderedGenerator: public IsoGenerator { private: MarginalTrek** marginalResults; /*!< Table of pointers to marginal distributions of subisotopologues. */ std::priority_queue, ConfOrder> pq; /*!< The priority queue used to generate isotopologues ordered by descending probability. */ void* topConf; /*!< Most probable configuration. */ DirtyAllocator allocator; /*!< Structure used for alocating memory for isotopologues. */ const pod_vector** logProbs; /*!< Obtained log-probabilities. */ const pod_vector** masses; /*!< Obtained masses. */ const pod_vector** marginalConfs; /*!< Obtained counts of isotopes. */ double currentLProb; /*!< The log-probability of the current isotopologue. */ double currentMass; /*!< The mass of the current isotopologue. */ double currentProb; /*!< The probability of the current isotopologue. */ int ccount; public: IsoOrderedGenerator(const IsoOrderedGenerator& other) = delete; IsoOrderedGenerator& operator=(const IsoOrderedGenerator& other) = delete; bool advanceToNextConfiguration() override final; //! Save the counts of isotopes in the space. /*! \param space An array where counts of isotopes shall be written. Must be as big as the overall number of isotopes. */ inline void get_conf_signature(int* space) const override final { int* c = getConf(topConf); if (ccount >= 0) c[ccount]--; for(int ii = 0; ii < dimNumber; ii++) { memcpy(space, marginalResults[ii]->confs()[c[ii]], isotopeNumbers[ii]*sizeof(int)); space += isotopeNumbers[ii]; } if (ccount >= 0) c[ccount]++; }; //! The move-contstructor. IsoOrderedGenerator(Iso&& iso, int _tabSize = 1000, int _hashSize = 1000); // NOLINT(runtime/explicit) - constructor deliberately left to be used as a conversion //! Destructor. virtual ~IsoOrderedGenerator(); }; //! The generator of isotopologues above a given threshold value. /*! Attention: the calculated configurations are only partially ordeded and the user should not assume they will be ordered. This algorithm computes N isotopologues in O(N). It is a considerable advantage w.r.t. the IsoOrderedGenerator. */ class ISOSPEC_EXPORT_SYMBOL IsoThresholdGenerator: public IsoGenerator { private: int* counter; /*!< An array storing the position of an isotopologue in terms of the subisotopologues ordered by decreasing probability. */ double* maxConfsLPSum; const double Lcutoff; /*!< The logarithm of the lower bound on the calculated probabilities. */ PrecalculatedMarginal** marginalResults; PrecalculatedMarginal** marginalResultsUnsorted; int* marginalOrder; const double* lProbs_ptr; const double* lProbs_ptr_start; double* partialLProbs_second; double partialLProbs_second_val, lcfmsv; bool empty; public: IsoThresholdGenerator(const IsoThresholdGenerator& other) = delete; IsoThresholdGenerator& operator=(const IsoThresholdGenerator& other) = delete; inline void get_conf_signature(int* space) const override final { counter[0] = lProbs_ptr - lProbs_ptr_start; if(marginalOrder != nullptr) { for(int ii = 0; ii < dimNumber; ii++) { int jj = marginalOrder[ii]; memcpy(space, marginalResultsUnsorted[ii]->get_conf(counter[jj]), isotopeNumbers[ii]*sizeof(int)); space += isotopeNumbers[ii]; } } else { for(int ii = 0; ii < dimNumber; ii++) { memcpy(space, marginalResultsUnsorted[ii]->get_conf(counter[ii]), isotopeNumbers[ii]*sizeof(int)); space += isotopeNumbers[ii]; } } }; //! The move-constructor. /*! \param iso An instance of the Iso class. \param _threshold The threshold value. \param _absolute If true, the _threshold is interpreted as the absolute minimal peak height for the isotopologues. If false, the _threshold is the fraction of the heighest peak's probability. \param tabSize The size of the extension of the table with configurations. \param hashSize The size of the hash-table used to store subisotopologues and check if they have been already calculated. */ IsoThresholdGenerator(Iso&& iso, double _threshold, bool _absolute = true, int _tabSize = 1000, int _hashSize = 1000, bool reorder_marginals = true); ~IsoThresholdGenerator(); // Perform highly aggressive inling as this function is often called as while(advanceToNextConfiguration()) {} // which leads to an extremely tight loop and some compilers miss this (potentially due to the length of the function). ISOSPEC_FORCE_INLINE bool advanceToNextConfiguration() override final { lProbs_ptr++; if(ISOSPEC_LIKELY(*lProbs_ptr >= lcfmsv)) { return true; } // If we reached this point, a carry is needed int idx = 0; lProbs_ptr = lProbs_ptr_start; int * cntr_ptr = counter; while(idx < dimNumber-1) { // counter[idx] = 0; *cntr_ptr = 0; idx++; cntr_ptr++; // counter[idx]++; (*cntr_ptr)++; partialLProbs[idx] = partialLProbs[idx+1] + marginalResults[idx]->get_lProb(counter[idx]); if(partialLProbs[idx] + maxConfsLPSum[idx-1] >= Lcutoff) { partialMasses[idx] = partialMasses[idx+1] + marginalResults[idx]->get_mass(counter[idx]); partialProbs[idx] = partialProbs[idx+1] * marginalResults[idx]->get_prob(counter[idx]); recalc(idx-1); return true; } } terminate_search(); return false; } ISOSPEC_FORCE_INLINE double lprob() const override final { return partialLProbs_second_val + (*(lProbs_ptr)); } ISOSPEC_FORCE_INLINE double mass() const override final { return partialMasses[1] + marginalResults[0]->get_mass(lProbs_ptr - lProbs_ptr_start); } ISOSPEC_FORCE_INLINE double prob() const override final { return partialProbs[1] * marginalResults[0]->get_prob(lProbs_ptr - lProbs_ptr_start); } //! Block the subsequent search of isotopologues. void terminate_search(); /*! Reset the generator to the beginning of the sequence. Allows it to be reused, eg. to go through the conf space once, calculate the amount of space needed to store configurations, then to allocate that memory, and go through it again, this time saving configurations (and *is* in fact faster than allocating a std::vector and depending on it to grow as needed. This is cheaper than throwing away the generator and making a new one too: marginal distributions don't need to be recalculated. */ void reset(); /*! Count the number of configurations in the distribution. This can be used to pre-allocate enough memory to store it (e.g. * std::vector's reserve() method - this is faster than depending on the vector's dynamic resizing, even though it means that * the configuration space is walked through twice. This method has to be called before the first call to advanceToNextConfiguration * and has undefined results (incl. segfaults) otherwise. */ size_t count_confs(); private: //! Recalculate the current partial log-probabilities, masses, and probabilities. ISOSPEC_FORCE_INLINE void recalc(int idx) { for(; idx > 0; idx--) { partialLProbs[idx] = partialLProbs[idx+1] + marginalResults[idx]->get_lProb(counter[idx]); partialMasses[idx] = partialMasses[idx+1] + marginalResults[idx]->get_mass(counter[idx]); partialProbs[idx] = partialProbs[idx+1] * marginalResults[idx]->get_prob(counter[idx]); } partialLProbs_second_val = *partialLProbs_second; partialLProbs[0] = *partialLProbs_second + marginalResults[0]->get_lProb(counter[0]); lcfmsv = Lcutoff - partialLProbs_second_val; } ISOSPEC_FORCE_INLINE void short_recalc(int idx) { for(; idx > 0; idx--) partialLProbs[idx] = partialLProbs[idx+1] + marginalResults[idx]->get_lProb(counter[idx]); partialLProbs_second_val = *partialLProbs_second; partialLProbs[0] = *partialLProbs_second + marginalResults[0]->get_lProb(counter[0]); lcfmsv = Lcutoff - partialLProbs_second_val; } }; class ISOSPEC_EXPORT_SYMBOL IsoLayeredGenerator : public IsoGenerator { private: int* counter; /*!< An array storing the position of an isotopologue in terms of the subisotopologues ordered by decreasing probability. */ double* maxConfsLPSum; double currentLThreshold, lastLThreshold; LayeredMarginal** marginalResults; LayeredMarginal** marginalResultsUnsorted; int* marginalOrder; const double* lProbs_ptr; const double* lProbs_ptr_start; const double** resetPositions; double* partialLProbs_second; double partialLProbs_second_val, lcfmsv, last_lcfmsv; bool marginalsNeedSorting; public: IsoLayeredGenerator(const IsoLayeredGenerator& other) = delete; IsoLayeredGenerator& operator=(const IsoLayeredGenerator& other) = delete; inline void get_conf_signature(int* space) const override final { counter[0] = lProbs_ptr - lProbs_ptr_start; if(marginalOrder != nullptr) { for(int ii = 0; ii < dimNumber; ii++) { int jj = marginalOrder[ii]; memcpy(space, marginalResultsUnsorted[ii]->get_conf(counter[jj]), isotopeNumbers[ii]*sizeof(int)); space += isotopeNumbers[ii]; } } else { for(int ii = 0; ii < dimNumber; ii++) { memcpy(space, marginalResultsUnsorted[ii]->get_conf(counter[ii]), isotopeNumbers[ii]*sizeof(int)); space += isotopeNumbers[ii]; } } }; inline double get_currentLThreshold() const { return currentLThreshold; } IsoLayeredGenerator(Iso&& iso, int _tabSize = 1000, int _hashSize = 1000, bool reorder_marginals = true, double t_prob_hint = 0.99); // NOLINT(runtime/explicit) - constructor deliberately left to be used as a conversion ~IsoLayeredGenerator(); ISOSPEC_FORCE_INLINE bool advanceToNextConfiguration() override final { do { if(advanceToNextConfigurationWithinLayer()) return true; } while(IsoLayeredGenerator::nextLayer(-2.0)); return false; } ISOSPEC_FORCE_INLINE bool advanceToNextConfigurationWithinLayer() { do{ lProbs_ptr++; if(ISOSPEC_LIKELY(*lProbs_ptr >= lcfmsv)) return true; } while(carry()); // NOLINT(whitespace/empty_loop_body) - cpplint bug, that's not an empty loop body, that's a do{...}while(...) construct return false; } ISOSPEC_FORCE_INLINE double lprob() const override final { return partialLProbs_second_val + (*(lProbs_ptr)); }; ISOSPEC_FORCE_INLINE double mass() const override final { return partialMasses[1] + marginalResults[0]->get_mass(lProbs_ptr - lProbs_ptr_start); }; ISOSPEC_FORCE_INLINE double prob() const override final { return partialProbs[1] * marginalResults[0]->get_prob(lProbs_ptr - lProbs_ptr_start); }; //! Block the subsequent search of isotopologues. void terminate_search(); //! Recalculate the current partial log-probabilities, masses, and probabilities. ISOSPEC_FORCE_INLINE void recalc(int idx) { for(; idx > 0; idx--) { partialLProbs[idx] = partialLProbs[idx+1] + marginalResults[idx]->get_lProb(counter[idx]); partialMasses[idx] = partialMasses[idx+1] + marginalResults[idx]->get_mass(counter[idx]); partialProbs[idx] = partialProbs[idx+1] * marginalResults[idx]->get_prob(counter[idx]); } partialLProbs_second_val = *partialLProbs_second; partialLProbs[0] = partialLProbs_second_val + marginalResults[0]->get_lProb(counter[0]); lcfmsv = currentLThreshold - partialLProbs_second_val; last_lcfmsv = lastLThreshold - partialLProbs_second_val; } bool nextLayer(double offset); private: bool carry(); }; class IsoStochasticGenerator : public IsoGenerator { IsoLayeredGenerator ILG; size_t to_sample_left; const double precision; const double beta_bias; double confs_prob; double chasing_prob; size_t current_count; public: IsoStochasticGenerator(Iso&& iso, size_t no_molecules, double precision = 0.9999, double beta_bias = 5.0); ISOSPEC_FORCE_INLINE size_t count() const { return current_count; } ISOSPEC_FORCE_INLINE double mass() const override final { return ILG.mass(); } ISOSPEC_FORCE_INLINE double prob() const override final { return static_cast(count()); } ISOSPEC_FORCE_INLINE double lprob() const override final { return log(prob()); } ISOSPEC_FORCE_INLINE void get_conf_signature(int* space) const override final { ILG.get_conf_signature(space); } ISOSPEC_FORCE_INLINE bool advanceToNextConfiguration() override final { /* This function will be used mainly in very small, tight loops, therefore it makes sense to * aggressively inline it, despite its seemingly large body. */ while(true) { double curr_conf_prob_left, current_prob; if(to_sample_left <= 0) return false; if(confs_prob < chasing_prob) { // Beta was last current_count = 1; to_sample_left--; if(!ILG.advanceToNextConfiguration()) return false; current_prob = ILG.prob(); confs_prob += current_prob; while(confs_prob <= chasing_prob) { if(!ILG.advanceToNextConfiguration()) return false; current_prob = ILG.prob(); confs_prob += current_prob; } if(to_sample_left <= 0) return true; curr_conf_prob_left = confs_prob - chasing_prob; } else { // Binomial was last current_count = 0; if(!ILG.advanceToNextConfiguration()) return false; current_prob = ILG.prob(); confs_prob += current_prob; curr_conf_prob_left = current_prob; } double prob_left_to_1 = precision - chasing_prob; double expected_confs = curr_conf_prob_left * to_sample_left / prob_left_to_1; if(expected_confs <= beta_bias) { // Beta mode: we keep making beta jumps until we leave the current configuration chasing_prob += rdvariate_beta_1_b(to_sample_left) * prob_left_to_1; while(chasing_prob <= confs_prob) { current_count++; to_sample_left--; if(to_sample_left == 0) return true; prob_left_to_1 = precision - chasing_prob; chasing_prob += rdvariate_beta_1_b(to_sample_left) * prob_left_to_1; } if(current_count > 0) return true; } else { // Binomial mode: a single binomial step size_t rbin = rdvariate_binom(to_sample_left, curr_conf_prob_left/prob_left_to_1); current_count += rbin; to_sample_left -= rbin; chasing_prob = confs_prob; if(current_count > 0) return true; } }; } }; } // namespace IsoSpec IsoSpecR/src/fasta.cpp0000644000176200001440000004046713734347575014367 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #include #include "element_tables.h" #include "fasta.h" namespace IsoSpec{ // We will work with C H N O S Se tuples */ const int aa_isotope_numbers[6] = {2, 2, 2, 3, 4, 6}; const double aa_elem_masses[19] = { elem_table_mass[9], elem_table_mass[10], // Carbon elem_table_mass[0], elem_table_mass[1], // Hydrogen elem_table_mass[11], elem_table_mass[12], // Nitrogen elem_table_mass[13], elem_table_mass[14], elem_table_mass[15], // Oxygen elem_table_mass[29], elem_table_mass[30], elem_table_mass[31], elem_table_mass[32], // Sulfur elem_table_mass[85], elem_table_mass[86], elem_table_mass[87], elem_table_mass[88], elem_table_mass[89], elem_table_mass[90] // Selenium }; const double aa_elem_nominal_masses[19] = { elem_table_massNo[9], elem_table_massNo[10], // Carbon elem_table_massNo[0], elem_table_massNo[1], // Hydrogen elem_table_massNo[11], elem_table_massNo[12], // Nitrogen elem_table_massNo[13], elem_table_massNo[14], elem_table_massNo[15], // Oxygen elem_table_massNo[29], elem_table_massNo[30], elem_table_massNo[31], elem_table_massNo[32], // Sulfur elem_table_massNo[85], elem_table_massNo[86], elem_table_massNo[87], elem_table_massNo[88], elem_table_massNo[89], elem_table_massNo[90] // Selenium }; const double aa_elem_probabilities[19] = { elem_table_probability[9], elem_table_probability[10], // Carbon elem_table_probability[0], elem_table_probability[1], // Hydrogen elem_table_probability[11], elem_table_probability[12], // Nitrogen elem_table_probability[13], elem_table_probability[14], elem_table_probability[15], // Oxygen elem_table_probability[29], elem_table_probability[30], elem_table_probability[31], elem_table_probability[32], // Sulfur elem_table_probability[85], elem_table_probability[86], elem_table_probability[87], elem_table_probability[88], elem_table_probability[89], elem_table_probability[90] // Selenium }; const int aa_symbol_to_elem_counts[256*6] = { /* Code: 0 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 1 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 2 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 3 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 4 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 5 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 6 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 7 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 8 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 9 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 10 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 11 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 12 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 13 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 14 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 15 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 16 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 17 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 18 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 19 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 20 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 21 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 22 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 23 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 24 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 25 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 26 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 27 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 28 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 29 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 30 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 31 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 32 ASCII char: */ 0, 0, 0, 0, 0, 0, /* Code: 33 ASCII char: ! */ 0, 0, 0, 0, 0, 0, /* Code: 34 ASCII char: " */ 0, 0, 0, 0, 0, 0, /* Code: 35 ASCII char: # */ 0, 0, 0, 0, 0, 0, /* Code: 36 ASCII char: $ */ 0, 0, 0, 0, 0, 0, /* Code: 37 ASCII char: % */ 0, 0, 0, 0, 0, 0, /* Code: 38 ASCII char: & */ 0, 0, 0, 0, 0, 0, /* Code: 39 ASCII char: ' */ 0, 0, 0, 0, 0, 0, /* Code: 40 ASCII char: ( */ 0, 0, 0, 0, 0, 0, /* Code: 41 ASCII char: ) */ 0, 0, 0, 0, 0, 0, /* Code: 42 ASCII char: * */ 0, 0, 0, 0, 0, 0, /* Code: 43 ASCII char: + */ 0, 0, 0, 0, 0, 0, /* Code: 44 ASCII char: , */ 0, 0, 0, 0, 0, 0, /* Code: 45 ASCII char: - */ 0, 0, 0, 0, 0, 0, /* Code: 46 ASCII char: . */ 0, 0, 0, 0, 0, 0, /* Code: 47 ASCII char: / */ 0, 0, 0, 0, 0, 0, /* Code: 48 ASCII char: 0 */ 0, 0, 0, 0, 0, 0, /* Code: 49 ASCII char: 1 */ 0, 0, 0, 0, 0, 0, /* Code: 50 ASCII char: 2 */ 0, 0, 0, 0, 0, 0, /* Code: 51 ASCII char: 3 */ 0, 0, 0, 0, 0, 0, /* Code: 52 ASCII char: 4 */ 0, 0, 0, 0, 0, 0, /* Code: 53 ASCII char: 5 */ 0, 0, 0, 0, 0, 0, /* Code: 54 ASCII char: 6 */ 0, 0, 0, 0, 0, 0, /* Code: 55 ASCII char: 7 */ 0, 0, 0, 0, 0, 0, /* Code: 56 ASCII char: 8 */ 0, 0, 0, 0, 0, 0, /* Code: 57 ASCII char: 9 */ 0, 0, 0, 0, 0, 0, /* Code: 58 ASCII char: : */ 0, 0, 0, 0, 0, 0, /* Code: 59 ASCII char: ; */ 0, 0, 0, 0, 0, 0, /* Code: 60 ASCII char: < */ 0, 0, 0, 0, 0, 0, /* Code: 61 ASCII char: = */ 0, 0, 0, 0, 0, 0, /* Code: 62 ASCII char: > */ 0, 0, 0, 0, 0, 0, /* Code: 63 ASCII char: ? */ 0, 0, 0, 0, 0, 0, /* Code: 64 ASCII char: @ */ 0, 0, 0, 0, 0, 0, /* Code: 65 ASCII char: A */ 3, 5, 1, 1, 0, 0, /* Code: 66 ASCII char: B */ 0, 0, 0, 0, 0, 0, /* Code: 67 ASCII char: C */ 3, 5, 1, 1, 1, 0, /* Code: 68 ASCII char: D */ 4, 5, 1, 3, 0, 0, /* Code: 69 ASCII char: E */ 5, 7, 1, 3, 0, 0, /* Code: 70 ASCII char: F */ 9, 9, 1, 1, 0, 0, /* Code: 71 ASCII char: G */ 2, 3, 1, 1, 0, 0, /* Code: 72 ASCII char: H */ 6, 7, 3, 1, 0, 0, /* Code: 73 ASCII char: I */ 6, 11, 1, 1, 0, 0, /* Code: 74 ASCII char: J */ 6, 11, 1, 1, 0, 0, /* Code: 75 ASCII char: K */ 6, 12, 2, 1, 0, 0, /* Code: 76 ASCII char: L */ 6, 11, 1, 1, 0, 0, /* Code: 77 ASCII char: M */ 5, 9, 1, 1, 1, 0, /* Code: 78 ASCII char: N */ 4, 6, 2, 2, 0, 0, /* Code: 79 ASCII char: O */ 12, 21, 3, 3, 0, 0, /* Code: 80 ASCII char: P */ 5, 7, 1, 1, 0, 0, /* Code: 81 ASCII char: Q */ 5, 8, 2, 2, 0, 0, /* Code: 82 ASCII char: R */ 6, 12, 4, 1, 0, 0, /* Code: 83 ASCII char: S */ 3, 5, 1, 2, 0, 0, /* Code: 84 ASCII char: T */ 4, 7, 1, 2, 0, 0, /* Code: 85 ASCII char: U */ 3, 5, 1, 1, 0, 1, /* Code: 86 ASCII char: V */ 5, 9, 1, 1, 0, 0, /* Code: 87 ASCII char: W */ 11, 10, 2, 1, 0, 0, /* Code: 88 ASCII char: X */ 0, 0, 0, 0, 0, 0, /* Code: 89 ASCII char: Y */ 9, 9, 1, 2, 0, 0, /* Code: 90 ASCII char: Z */ 0, 0, 0, 0, 0, 0, /* Code: 91 ASCII char: [ */ 0, 0, 0, 0, 0, 0, /* Code: 92 ASCII char: \ */ 0, 0, 0, 0, 0, 0, /* Code: 93 ASCII char: ] */ 0, 0, 0, 0, 0, 0, /* Code: 94 ASCII char: ^ */ 0, 0, 0, 0, 0, 0, /* Code: 95 ASCII char: _ */ 0, 0, 0, 0, 0, 0, /* Code: 96 ASCII char: ` */ 0, 0, 0, 0, 0, 0, /* Code: 97 ASCII char: a */ 3, 5, 1, 1, 0, 0, /* Code: 98 ASCII char: b */ 0, 0, 0, 0, 0, 0, /* Code: 99 ASCII char: c */ 3, 5, 1, 1, 1, 0, /* Code: 100 ASCII char: d */ 4, 5, 1, 3, 0, 0, /* Code: 101 ASCII char: e */ 5, 7, 1, 3, 0, 0, /* Code: 102 ASCII char: f */ 9, 9, 1, 1, 0, 0, /* Code: 103 ASCII char: g */ 2, 3, 1, 1, 0, 0, /* Code: 104 ASCII char: h */ 6, 7, 3, 1, 0, 0, /* Code: 105 ASCII char: i */ 6, 11, 1, 1, 0, 0, /* Code: 106 ASCII char: j */ 6, 11, 1, 1, 0, 0, /* Code: 107 ASCII char: k */ 6, 12, 2, 1, 0, 0, /* Code: 108 ASCII char: l */ 6, 11, 1, 1, 0, 0, /* Code: 109 ASCII char: m */ 5, 9, 1, 1, 1, 0, /* Code: 110 ASCII char: n */ 4, 6, 2, 2, 0, 0, /* Code: 111 ASCII char: o */ 12, 21, 3, 3, 0, 0, /* Code: 112 ASCII char: p */ 5, 7, 1, 1, 0, 0, /* Code: 113 ASCII char: q */ 5, 8, 2, 2, 0, 0, /* Code: 114 ASCII char: r */ 6, 12, 4, 1, 0, 0, /* Code: 115 ASCII char: s */ 3, 5, 1, 2, 0, 0, /* Code: 116 ASCII char: t */ 4, 7, 1, 2, 0, 0, /* Code: 117 ASCII char: u */ 3, 5, 1, 1, 0, 1, /* Code: 118 ASCII char: v */ 5, 9, 1, 1, 0, 0, /* Code: 119 ASCII char: w */ 11, 10, 2, 1, 0, 0, /* Code: 120 ASCII char: x */ 0, 0, 0, 0, 0, 0, /* Code: 121 ASCII char: y */ 9, 9, 1, 2, 0, 0, /* Code: 122 ASCII char: z */ 0, 0, 0, 0, 0, 0, /* Code: 123 ASCII char: { */ 0, 0, 0, 0, 0, 0, /* Code: 124 ASCII char: | */ 0, 0, 0, 0, 0, 0, /* Code: 125 ASCII char: } */ 0, 0, 0, 0, 0, 0, /* Code: 126 ASCII char: ~ */ 0, 0, 0, 0, 0, 0, /* Code: 127 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 128 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 129 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 130 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 131 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 132 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 133 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 134 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 135 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 136 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 137 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 138 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 139 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 140 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 141 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 142 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 143 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 144 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 145 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 146 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 147 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 148 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 149 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 150 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 151 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 152 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 153 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 154 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 155 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 156 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 157 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 158 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 159 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 160 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 161 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 162 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 163 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 164 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 165 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 166 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 167 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 168 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 169 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 170 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 171 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 172 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 173 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 174 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 175 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 176 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 177 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 178 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 179 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 180 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 181 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 182 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 183 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 184 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 185 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 186 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 187 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 188 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 189 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 190 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 191 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 192 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 193 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 194 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 195 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 196 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 197 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 198 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 199 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 200 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 201 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 202 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 203 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 204 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 205 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 206 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 207 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 208 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 209 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 210 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 211 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 212 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 213 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 214 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 215 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 216 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 217 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 218 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 219 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 220 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 221 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 222 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 223 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 224 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 225 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 226 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 227 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 228 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 229 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 230 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 231 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 232 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 233 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 234 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 235 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 236 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 237 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 238 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 239 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 240 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 241 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 242 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 243 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 244 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 245 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 246 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 247 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 248 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 249 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 250 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 251 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 252 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 253 unprintable */ 0, 0, 0, 0, 0, 0, /* Code: 254 unprintable */ 0, 0, 0, 0, 0, 0 }; } // namespace IsoSpec IsoSpecR/src/isoSpec++.cpp0000644000176200001440000006476113674223065015015 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #include "isoSpec++.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "platform.h" #include "conf.h" #include "dirtyAllocator.h" #include "operators.h" #include "summator.h" #include "marginalTrek++.h" #include "misc.h" #include "element_tables.h" #include "fasta.h" namespace IsoSpec { Iso::Iso() : disowned(false), dimNumber(0), isotopeNumbers(new int[0]), atomCounts(new int[0]), confSize(0), allDim(0), marginals(new Marginal*[0]) {} Iso::Iso( int _dimNumber, const int* _isotopeNumbers, const int* _atomCounts, const double* const * _isotopeMasses, const double* const * _isotopeProbabilities ) : disowned(false), dimNumber(_dimNumber), isotopeNumbers(array_copy(_isotopeNumbers, _dimNumber)), atomCounts(array_copy(_atomCounts, _dimNumber)), confSize(_dimNumber * sizeof(int)), allDim(0), marginals(nullptr) { for(int ii = 0; ii < dimNumber; ++ii) allDim += isotopeNumbers[ii]; std::unique_ptr masses(new double[allDim]); std::unique_ptr probs(new double[allDim]); size_t idx = 0; for(int ii = 0; ii < dimNumber; ++ii) for(int jj = 0; jj < isotopeNumbers[ii]; ++jj) { masses[idx] = _isotopeMasses[ii][jj]; probs[idx] = _isotopeProbabilities[ii][jj]; ++idx; } allDim = 0; // setupMarginals will recalculate it, assuming it's set to 0 try{ setupMarginals(masses.get(), probs.get()); } catch(...) { delete[] isotopeNumbers; delete[] atomCounts; // Since we're throwing in a constructor, the destructor won't run, and we don't need to NULL these. // However, this is not the fast code path and we can afford two unneeded instructions to keep // some static analysis tools happy. isotopeNumbers = nullptr; atomCounts = nullptr; throw; } } Iso::Iso( int _dimNumber, const int* _isotopeNumbers, const int* _atomCounts, const double* _isotopeMasses, const double* _isotopeProbabilities ) : disowned(false), dimNumber(_dimNumber), isotopeNumbers(array_copy(_isotopeNumbers, _dimNumber)), atomCounts(array_copy(_atomCounts, _dimNumber)), confSize(_dimNumber * sizeof(int)), allDim(0), marginals(nullptr) { try{ setupMarginals(_isotopeMasses, _isotopeProbabilities); } catch(...) { delete[] isotopeNumbers; delete[] atomCounts; // Since we're throwing in a constructor, the destructor won't run, and we don't need to NULL these. // However, this is not the fast code path and we can afford two unneeded instructions to keep // some static analysis tools happy. isotopeNumbers = nullptr; atomCounts = nullptr; throw; } } Iso::Iso(Iso&& other) : disowned(other.disowned), dimNumber(other.dimNumber), isotopeNumbers(other.isotopeNumbers), atomCounts(other.atomCounts), confSize(other.confSize), allDim(other.allDim), marginals(other.marginals) { other.disowned = true; } Iso::Iso(const Iso& other, bool fullcopy) : disowned(!fullcopy), dimNumber(other.dimNumber), isotopeNumbers(fullcopy ? array_copy(other.isotopeNumbers, dimNumber) : other.isotopeNumbers), atomCounts(fullcopy ? array_copy(other.atomCounts, dimNumber) : other.atomCounts), confSize(other.confSize), allDim(other.allDim), marginals(fullcopy ? new Marginal*[dimNumber] : other.marginals) { if(fullcopy) { for(int ii = 0; ii < dimNumber; ii++) marginals[ii] = new Marginal(*other.marginals[ii]); } } Iso Iso::FromFASTA(const char* fasta, bool use_nominal_masses, bool add_water) { int atomCounts[6]; parse_fasta(fasta, atomCounts); if(add_water) { atomCounts[1] += 2; atomCounts[3] += 1; } const int dimNr = atomCounts[5] > 0 ? 6 : 5; return Iso(dimNr, aa_isotope_numbers, atomCounts, use_nominal_masses ? aa_elem_nominal_masses : aa_elem_masses, aa_elem_probabilities); } inline void Iso::setupMarginals(const double* _isotopeMasses, const double* _isotopeProbabilities) { if (marginals == nullptr) { int ii = 0; marginals = new Marginal*[dimNumber]; try { while(ii < dimNumber) { marginals[ii] = new Marginal( &_isotopeMasses[allDim], &_isotopeProbabilities[allDim], isotopeNumbers[ii], atomCounts[ii] ); allDim += isotopeNumbers[ii]; ii++; } } catch(...) { ii--; while(ii >= 0) { delete marginals[ii]; ii--; } delete[] marginals; marginals = nullptr; throw; } } } Iso::~Iso() { if(!disowned) { if (marginals != nullptr) dealloc_table(marginals, dimNumber); delete[] isotopeNumbers; delete[] atomCounts; } } bool Iso::doMarginalsNeedSorting() const { int nontrivial_marginals = 0; for(int ii = 0; ii < dimNumber; ii++) { if(marginals[ii]->get_isotopeNo() > 1) nontrivial_marginals++; if(nontrivial_marginals > 1) return true; } return false; } double Iso::getLightestPeakMass() const { double mass = 0.0; for (int ii = 0; ii < dimNumber; ii++) mass += marginals[ii]->getLightestConfMass(); return mass; } double Iso::getHeaviestPeakMass() const { double mass = 0.0; for (int ii = 0; ii < dimNumber; ii++) mass += marginals[ii]->getHeaviestConfMass(); return mass; } double Iso::getMonoisotopicPeakMass() const { double mass = 0.0; for (int ii = 0; ii < dimNumber; ii++) mass += marginals[ii]->getMonoisotopicConfMass(); return mass; } double Iso::getUnlikeliestPeakLProb() const { double lprob = 0.0; for (int ii = 0; ii < dimNumber; ii++) lprob += marginals[ii]->getSmallestLProb(); return lprob; } double Iso::getModeMass() const { double mass = 0.0; for (int ii = 0; ii < dimNumber; ii++) mass += marginals[ii]->getModeMass(); return mass; } double Iso::getModeLProb() const { double ret = 0.0; for (int ii = 0; ii < dimNumber; ii++) ret += marginals[ii]->getModeLProb(); return ret; } double Iso::getTheoreticalAverageMass() const { double mass = 0.0; for (int ii = 0; ii < dimNumber; ii++) mass += marginals[ii]->getTheoreticalAverageMass(); return mass; } double Iso::variance() const { double ret = 0.0; for(int ii = 0; ii < dimNumber; ii++) ret += marginals[ii]->variance(); return ret; } Iso::Iso(const char* formula, bool use_nominal_masses) : disowned(false), allDim(0), marginals(nullptr) { std::vector isotope_masses; std::vector isotope_probabilities; dimNumber = parse_formula(formula, isotope_masses, isotope_probabilities, &isotopeNumbers, &atomCounts, &confSize, use_nominal_masses); setupMarginals(isotope_masses.data(), isotope_probabilities.data()); } void Iso::addElement(int atomCount, int noIsotopes, const double* isotopeMasses, const double* isotopeProbabilities) { Marginal* m = new Marginal(isotopeMasses, isotopeProbabilities, noIsotopes, atomCount); realloc_append(&isotopeNumbers, noIsotopes, dimNumber); realloc_append(&atomCounts, atomCount, dimNumber); realloc_append(&marginals, m, dimNumber); dimNumber++; confSize += sizeof(int); allDim += noIsotopes; } void Iso::saveMarginalLogSizeEstimates(double* priorities, double target_total_prob) const { /* * We shall now use Gaussian approximations of the marginal multinomial distributions to estimate * how many configurations we shall need to visit from each marginal. This should be approximately * proportional to the volume of the optimal P-ellipsoid of the marginal, which, in turn is defined * by the quantile function of the chi-square distribution plus some modifications. * * We're dropping the constant factor and the (monotonic) exp() transform - these will be used as keys * for sorting, so only the ordering is important. */ double K = allDim - dimNumber; double log_R2 = log(InverseChiSquareCDF2(K, target_total_prob)); for(int ii = 0; ii < dimNumber; ii++) priorities[ii] = marginals[ii]->getLogSizeEstimate(log_R2); } unsigned int parse_formula(const char* formula, std::vector& isotope_masses, std::vector& isotope_probabilities, int** isotopeNumbers, int** atomCounts, unsigned int* confSize, bool use_nominal_masses) { // This function is NOT guaranteed to be secure against malicious input. It should be used only for debugging. size_t slen = strlen(formula); // Yes, it would be more elegant to use std::string here, but it's the only promiment place where it would be used in IsoSpec, and avoiding it here // means we can run the whole thing through Clang's memory sanitizer without the need for instrumented libc++/libstdc++. That's worth messing with char pointers a // little bit. std::vector > elements; std::vector numbers; if(slen == 0) throw std::invalid_argument("Invalid formula: can't be empty"); if(!isdigit(formula[slen-1])) throw std::invalid_argument("Invalid formula: every element must be followed by a number - write H2O1 and not H2O for water"); for(size_t ii = 0; ii < slen; ii++) if(!isdigit(formula[ii]) && !isalpha(formula[ii])) throw std::invalid_argument("Invalid formula: contains invalid (non-digit, non-alpha) character"); size_t position = 0; while(position < slen) { size_t elem_end = position; while(isalpha(formula[elem_end])) elem_end++; size_t digit_end = elem_end; while(isdigit(formula[digit_end])) digit_end++; elements.emplace_back(&formula[position], elem_end-position); numbers.push_back(std::stoi(&formula[elem_end])); position = digit_end; } std::vector element_indexes; for (unsigned int i = 0; i < elements.size(); i++) { int idx = -1; for(int j = 0; j < ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES; j++) { if ((strlen(elem_table_symbol[j]) == elements[i].second) && (strncmp(elements[i].first, elem_table_symbol[j], elements[i].second) == 0)) { idx = j; break; } } if(idx < 0) throw std::invalid_argument("Invalid formula"); element_indexes.push_back(idx); } std::vector _isotope_numbers; const double* masses = use_nominal_masses ? elem_table_massNo : elem_table_mass; for(std::vector::iterator it = element_indexes.begin(); it != element_indexes.end(); ++it) { int num = 0; int at_idx = *it; int elem_ID = elem_table_ID[at_idx]; while(at_idx < ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES && elem_table_ID[at_idx] == elem_ID) { isotope_masses.push_back(masses[at_idx]); isotope_probabilities.push_back(elem_table_probability[at_idx]); at_idx++; num++; } _isotope_numbers.push_back(num); } const unsigned int dimNumber = elements.size(); *isotopeNumbers = array_copy(_isotope_numbers.data(), dimNumber); *atomCounts = array_copy(numbers.data(), dimNumber); *confSize = dimNumber * sizeof(int); return dimNumber; } /* * ---------------------------------------------------------------------------------------------------------- */ IsoGenerator::IsoGenerator(Iso&& iso, bool alloc_partials) : Iso(std::move(iso)), mode_lprob(getModeLProb()), partialLProbs(alloc_partials ? new double[dimNumber+1] : nullptr), partialMasses(alloc_partials ? new double[dimNumber+1] : nullptr), partialProbs(alloc_partials ? new double[dimNumber+1] : nullptr) { for(int ii = 0; ii < dimNumber; ++ii) marginals[ii]->ensureModeConf(); if(alloc_partials) { partialLProbs[dimNumber] = 0.0; partialMasses[dimNumber] = 0.0; partialProbs[dimNumber] = 1.0; } } IsoGenerator::~IsoGenerator() { if(partialLProbs != nullptr) delete[] partialLProbs; if(partialMasses != nullptr) delete[] partialMasses; if(partialProbs != nullptr) delete[] partialProbs; } /* * ---------------------------------------------------------------------------------------------------------- */ static const double minsqrt = -1.3407796239501852e+154; // == constexpr(-sqrt(std::numeric_limits::max())); IsoThresholdGenerator::IsoThresholdGenerator(Iso&& iso, double _threshold, bool _absolute, int tabSize, int hashSize, bool reorder_marginals) : IsoGenerator(std::move(iso)), Lcutoff(_threshold <= 0.0 ? minsqrt : (_absolute ? log(_threshold) : log(_threshold) + mode_lprob)) { counter = new int[dimNumber]; maxConfsLPSum = new double[dimNumber-1]; marginalResultsUnsorted = new PrecalculatedMarginal*[dimNumber]; empty = false; const bool marginalsNeedSorting = doMarginalsNeedSorting(); for(int ii = 0; ii < dimNumber; ii++) { counter[ii] = 0; marginalResultsUnsorted[ii] = new PrecalculatedMarginal(std::move(*(marginals[ii])), Lcutoff - mode_lprob + marginals[ii]->fastGetModeLProb(), marginalsNeedSorting, tabSize, hashSize); if(!marginalResultsUnsorted[ii]->inRange(0)) empty = true; } if(reorder_marginals && dimNumber > 1) { OrderMarginalsBySizeDecresing comparator(marginalResultsUnsorted); int* tmpMarginalOrder = new int[dimNumber]; for(int ii = 0; ii < dimNumber; ii++) tmpMarginalOrder[ii] = ii; std::sort(tmpMarginalOrder, tmpMarginalOrder + dimNumber, comparator); marginalResults = new PrecalculatedMarginal*[dimNumber]; for(int ii = 0; ii < dimNumber; ii++) marginalResults[ii] = marginalResultsUnsorted[tmpMarginalOrder[ii]]; marginalOrder = new int[dimNumber]; for(int ii = 0; ii < dimNumber; ii++) marginalOrder[tmpMarginalOrder[ii]] = ii; delete[] tmpMarginalOrder; } else { marginalResults = marginalResultsUnsorted; marginalOrder = nullptr; } lProbs_ptr_start = marginalResults[0]->get_lProbs_ptr(); if(dimNumber > 1) maxConfsLPSum[0] = marginalResults[0]->fastGetModeLProb(); for(int ii = 1; ii < dimNumber-1; ii++) maxConfsLPSum[ii] = maxConfsLPSum[ii-1] + marginalResults[ii]->fastGetModeLProb(); lProbs_ptr = lProbs_ptr_start; partialLProbs_second = partialLProbs; partialLProbs_second++; if(!empty) { recalc(dimNumber-1); counter[0]--; lProbs_ptr--; } else { terminate_search(); lcfmsv = std::numeric_limits::infinity(); } } void IsoThresholdGenerator::terminate_search() { for(int ii = 0; ii < dimNumber; ii++) { counter[ii] = marginalResults[ii]->get_no_confs()-1; partialLProbs[ii] = -std::numeric_limits::infinity(); } partialLProbs[dimNumber] = -std::numeric_limits::infinity(); lProbs_ptr = lProbs_ptr_start + marginalResults[0]->get_no_confs()-1; } size_t IsoThresholdGenerator::count_confs() { if(empty) return 0; if(dimNumber == 1) return marginalResults[0]->get_no_confs(); const double* lProbs_ptr_l = marginalResults[0]->get_lProbs_ptr() + marginalResults[0]->get_no_confs(); std::unique_ptr lProbs_restarts(new const double*[dimNumber]); for(int ii = 0; ii < dimNumber; ii++) lProbs_restarts[ii] = lProbs_ptr_l; size_t count = 0; while(*lProbs_ptr_l < lcfmsv) lProbs_ptr_l--; while(true) { count += lProbs_ptr_l - lProbs_ptr_start + 1; int idx = 0; int * cntr_ptr = counter; while(idx < dimNumber - 1) { *cntr_ptr = 0; idx++; cntr_ptr++; (*cntr_ptr)++; partialLProbs[idx] = partialLProbs[idx+1] + marginalResults[idx]->get_lProb(counter[idx]); if(partialLProbs[idx] + maxConfsLPSum[idx-1] >= Lcutoff) { short_recalc(idx-1); lProbs_ptr_l = lProbs_restarts[idx]; while(*lProbs_ptr_l < lcfmsv) lProbs_ptr_l--; for(idx--; idx > 0; idx--) lProbs_restarts[idx] = lProbs_ptr_l; break; } } if(idx == dimNumber - 1) { reset(); return count; } } } void IsoThresholdGenerator::reset() { if(empty) { terminate_search(); return; } partialLProbs[dimNumber] = 0.0; memset(counter, 0, sizeof(int)*dimNumber); recalc(dimNumber-1); counter[0]--; lProbs_ptr = lProbs_ptr_start - 1; } IsoThresholdGenerator::~IsoThresholdGenerator() { delete[] counter; delete[] maxConfsLPSum; if (marginalResultsUnsorted != marginalResults) delete[] marginalResultsUnsorted; dealloc_table(marginalResults, dimNumber); if(marginalOrder != nullptr) delete[] marginalOrder; } /* * ------------------------------------------------------------------------------------------------------------------------ */ IsoLayeredGenerator::IsoLayeredGenerator(Iso&& iso, int tabSize, int hashSize, bool reorder_marginals, double t_prob_hint) : IsoGenerator(std::move(iso)) { counter = new int[dimNumber]; maxConfsLPSum = new double[dimNumber-1]; currentLThreshold = nextafter(mode_lprob, -std::numeric_limits::infinity()); lastLThreshold = (std::numeric_limits::min)(); marginalResultsUnsorted = new LayeredMarginal*[dimNumber]; resetPositions = new const double*[dimNumber]; marginalsNeedSorting = doMarginalsNeedSorting(); memset(counter, 0, sizeof(int)*dimNumber); for(int ii = 0; ii < dimNumber; ii++) marginalResultsUnsorted[ii] = new LayeredMarginal(std::move(*(marginals[ii])), tabSize, hashSize); if(reorder_marginals && dimNumber > 1) { double* marginal_priorities = new double[dimNumber]; saveMarginalLogSizeEstimates(marginal_priorities, t_prob_hint); int* tmpMarginalOrder = new int[dimNumber]; for(int ii = 0; ii < dimNumber; ii++) tmpMarginalOrder[ii] = ii; TableOrder TO(marginal_priorities); std::sort(tmpMarginalOrder, tmpMarginalOrder + dimNumber, TO); marginalResults = new LayeredMarginal*[dimNumber]; for(int ii = 0; ii < dimNumber; ii++) marginalResults[ii] = marginalResultsUnsorted[tmpMarginalOrder[ii]]; marginalOrder = new int[dimNumber]; for(int ii = 0; ii < dimNumber; ii++) marginalOrder[tmpMarginalOrder[ii]] = ii; delete[] tmpMarginalOrder; delete[] marginal_priorities; } else { marginalResults = marginalResultsUnsorted; marginalOrder = nullptr; } lProbs_ptr_start = marginalResults[0]->get_lProbs_ptr(); if(dimNumber > 1) maxConfsLPSum[0] = marginalResults[0]->fastGetModeLProb(); for(int ii = 1; ii < dimNumber-1; ii++) maxConfsLPSum[ii] = maxConfsLPSum[ii-1] + marginalResults[ii]->fastGetModeLProb(); lProbs_ptr = lProbs_ptr_start; partialLProbs_second = partialLProbs; partialLProbs_second++; counter[0]--; lProbs_ptr--; lastLThreshold = 10.0; IsoLayeredGenerator::nextLayer(-0.00001); } bool IsoLayeredGenerator::nextLayer(double offset) { size_t first_mrg_size = marginalResults[0]->get_no_confs(); if(lastLThreshold < getUnlikeliestPeakLProb()) return false; lastLThreshold = currentLThreshold; currentLThreshold += offset; for(int ii = 0; ii < dimNumber; ii++) { marginalResults[ii]->extend(currentLThreshold - mode_lprob + marginalResults[ii]->fastGetModeLProb(), marginalsNeedSorting); counter[ii] = 0; } lProbs_ptr_start = marginalResults[0]->get_lProbs_ptr(); // vector relocation might have happened lProbs_ptr = lProbs_ptr_start + first_mrg_size - 1; for(int ii = 0; ii < dimNumber; ii++) resetPositions[ii] = lProbs_ptr; recalc(dimNumber-1); return true; } bool IsoLayeredGenerator::carry() { // If we reached this point, a carry is needed int idx = 0; int * cntr_ptr = counter; while(idx < dimNumber-1) { *cntr_ptr = 0; idx++; cntr_ptr++; (*cntr_ptr)++; partialLProbs[idx] = partialLProbs[idx+1] + marginalResults[idx]->get_lProb(counter[idx]); if(partialLProbs[idx] + maxConfsLPSum[idx-1] >= currentLThreshold) { partialMasses[idx] = partialMasses[idx+1] + marginalResults[idx]->get_mass(counter[idx]); partialProbs[idx] = partialProbs[idx+1] * marginalResults[idx]->get_prob(counter[idx]); recalc(idx-1); lProbs_ptr = resetPositions[idx]; while(*lProbs_ptr <= last_lcfmsv) lProbs_ptr--; for(int ii = 0; ii < idx; ii++) resetPositions[ii] = lProbs_ptr; return true; } } return false; } void IsoLayeredGenerator::terminate_search() { for(int ii = 0; ii < dimNumber; ii++) { counter[ii] = marginalResults[ii]->get_no_confs()-1; partialLProbs[ii] = -std::numeric_limits::infinity(); } partialLProbs[dimNumber] = -std::numeric_limits::infinity(); lProbs_ptr = lProbs_ptr_start + marginalResults[0]->get_no_confs()-1; } IsoLayeredGenerator::~IsoLayeredGenerator() { delete[] counter; delete[] maxConfsLPSum; delete[] resetPositions; if (marginalResultsUnsorted != marginalResults) delete[] marginalResultsUnsorted; dealloc_table(marginalResults, dimNumber); if(marginalOrder != nullptr) delete[] marginalOrder; } /* * ------------------------------------------------------------------------------------------------------------------------ */ IsoOrderedGenerator::IsoOrderedGenerator(Iso&& iso, int _tabSize, int _hashSize) : IsoGenerator(std::move(iso), false), allocator(dimNumber, _tabSize) { partialLProbs = ¤tLProb; partialMasses = ¤tMass; partialProbs = ¤tProb; marginalResults = new MarginalTrek*[dimNumber]; for(int i = 0; i < dimNumber; i++) marginalResults[i] = new MarginalTrek(std::move(*(marginals[i])), _tabSize, _hashSize); logProbs = new const pod_vector*[dimNumber]; masses = new const pod_vector*[dimNumber]; marginalConfs = new const pod_vector*[dimNumber]; for(int i = 0; i < dimNumber; i++) { masses[i] = &marginalResults[i]->conf_masses(); logProbs[i] = &marginalResults[i]->conf_lprobs(); marginalConfs[i] = &marginalResults[i]->confs(); } topConf = allocator.newConf(); memset( reinterpret_cast(topConf) + sizeof(double), 0, sizeof(int)*dimNumber ); *(reinterpret_cast(topConf)) = combinedSum( getConf(topConf), logProbs, dimNumber ); pq.push(topConf); } IsoOrderedGenerator::~IsoOrderedGenerator() { dealloc_table(marginalResults, dimNumber); delete[] logProbs; delete[] masses; delete[] marginalConfs; partialLProbs = nullptr; partialMasses = nullptr; partialProbs = nullptr; } bool IsoOrderedGenerator::advanceToNextConfiguration() { if(pq.size() < 1) return false; topConf = pq.top(); pq.pop(); int* topConfIsoCounts = getConf(topConf); currentLProb = *(reinterpret_cast(topConf)); currentMass = combinedSum( topConfIsoCounts, masses, dimNumber ); currentProb = exp(currentLProb); ccount = -1; for(int j = 0; j < dimNumber; ++j) { if(marginalResults[j]->probeConfigurationIdx(topConfIsoCounts[j] + 1)) { if(ccount == -1) { topConfIsoCounts[j]++; *(reinterpret_cast(topConf)) = combinedSum(topConfIsoCounts, logProbs, dimNumber); pq.push(topConf); topConfIsoCounts[j]--; ccount = j; } else { void* acceptedCandidate = allocator.newConf(); int* acceptedCandidateIsoCounts = getConf(acceptedCandidate); memcpy(acceptedCandidateIsoCounts, topConfIsoCounts, confSize); acceptedCandidateIsoCounts[j]++; *(reinterpret_cast(acceptedCandidate)) = combinedSum(acceptedCandidateIsoCounts, logProbs, dimNumber); pq.push(acceptedCandidate); } } if(topConfIsoCounts[j] > 0) break; } if(ccount >=0) topConfIsoCounts[ccount]++; return true; } /* * --------------------------------------------------------------------------------------------------- */ IsoStochasticGenerator::IsoStochasticGenerator(Iso&& iso, size_t no_molecules, double _precision, double _beta_bias) : IsoGenerator(std::move(iso)), ILG(std::move(*this)), to_sample_left(no_molecules), precision(_precision), beta_bias(_beta_bias), confs_prob(0.0), chasing_prob(0.0) {} /* * --------------------------------------------------------------------------------------------------- */ } // namespace IsoSpec IsoSpecR/src/pod_vector.h0000644000176200001440000002330513734351263015057 0ustar liggesusers/*! Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. This file is part of IsoSpec. IsoSpec is free software: you can redistribute it and/or modify it under the terms of the Simplified ("2-clause") BSD licence. IsoSpec is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. You should have received a copy of the Simplified BSD Licence along with IsoSpec. If not, see . */ #pragma once #include #include #include #include #include #include "platform.h" template class unsafe_pod_vector; template class pod_vector { #if !ISOSPEC_BUILDING_R static_assert(std::is_trivially_copyable::value, "Cannot use a pod_vector with a non-Plain Old Data type."); #endif T* backend_past_end; T* first_free; T* store; public: explicit pod_vector(size_t initial_size = 16) { store = reinterpret_cast(malloc(sizeof(T) * initial_size)); if(store == NULL) throw std::bad_alloc(); first_free = store; backend_past_end = store + initial_size; } pod_vector(const pod_vector& other) = delete; pod_vector& operator=(const pod_vector& other) = delete; pod_vector(pod_vector&& other) { backend_past_end = other.backend_past_end; first_free = other.first_free; store = other.store; other.backend_past_end = other.first_free = other.store = NULL; } ~pod_vector() { free(store); } explicit pod_vector(unsafe_pod_vector&& other) { backend_past_end = other.backend_past_end; first_free = other.first_free; store = other.store; } void fast_reserve(size_t n) { ISOSPEC_IMPOSSIBLE(n < static_cast(backend_past_end - store)); T* new_store = reinterpret_cast(realloc(store, n * sizeof(T))); if(new_store == NULL) throw std::bad_alloc(); first_free = new_store + (first_free - store); backend_past_end = new_store + n; store = new_store; } void reserve(size_t n) { if (n > static_cast(backend_past_end - store)) fast_reserve(n); } ISOSPEC_FORCE_INLINE void nocheck_push_back(const T& val) noexcept { ISOSPEC_IMPOSSIBLE(first_free >= backend_past_end); *first_free = val; first_free++; } ISOSPEC_FORCE_INLINE void push_back(const T& val) { if(first_free >= backend_past_end) fast_reserve((std::max)(4, (backend_past_end-store)) * 2); *first_free = val; first_free++; } ISOSPEC_FORCE_INLINE T& operator[](size_t n) noexcept { ISOSPEC_IMPOSSIBLE(store + n >= first_free); return store[n]; } ISOSPEC_FORCE_INLINE const T& operator[](size_t n) const noexcept { ISOSPEC_IMPOSSIBLE(store + n >= first_free); return store[n]; } ISOSPEC_FORCE_INLINE size_t size() const noexcept { return first_free - store; } ISOSPEC_FORCE_INLINE size_t capacity() const noexcept { return backend_past_end - store; } ISOSPEC_FORCE_INLINE T* data() noexcept { return store; } ISOSPEC_FORCE_INLINE const T* data() const noexcept { return store; } ISOSPEC_FORCE_INLINE bool empty() const noexcept { return first_free == store; } ISOSPEC_FORCE_INLINE const T& back() const noexcept { ISOSPEC_IMPOSSIBLE(first_free > backend_past_end); return *(first_free-1); } ISOSPEC_FORCE_INLINE void pop_back() noexcept { // Unlike std::vector we do not ever shrink backend storage unless explicitly requested. ISOSPEC_IMPOSSIBLE(first_free == store); first_free--; } void swap(pod_vector& other) noexcept { std::swap(backend_past_end, other.backend_past_end); std::swap(first_free, other.first_free); std::swap(store, other.store); } typedef T* iterator; typedef const T* const_iterator; typedef T value_type; typedef size_t size_type; typedef T& reference; typedef const T& const_reference; iterator begin() noexcept { return store; }; const_iterator begin() const noexcept { return store; } const_iterator cbegin() const noexcept { return store; } iterator end() noexcept { return first_free; } const_iterator end() const noexcept { return first_free; } const_iterator cend() const noexcept { return first_free; } ISOSPEC_FORCE_INLINE const T& front() const noexcept { ISOSPEC_IMPOSSIBLE(store == first_free); return *store; } void clear() { free(store); first_free = store = backend_past_end = NULL; } friend class unsafe_pod_vector; }; template class unsafe_pod_vector { #if !ISOSPEC_BUILDING_R static_assert(std::is_trivially_copyable::value, "Cannot use a pod_vector with a non-Plain Old Data type."); static_assert(std::is_trivially_copyable >::value, "Cannot use a pod_vector with a non-Plain Old Data type."); #endif T* backend_past_end; T* first_free; T* store; public: unsafe_pod_vector() = default; void init() { memset(this, 0, sizeof(*this)); } void init(size_t initial_size) { store = reinterpret_cast(malloc(sizeof(T) * initial_size)); if(store == NULL) throw std::bad_alloc(); first_free = store; backend_past_end = store + initial_size; } unsafe_pod_vector(const pod_vector& other) = delete; // NOLINT(runtime/explicit) - seriously? Deleted constructors have to be marked explicit? unsafe_pod_vector& operator=(const pod_vector& other) = delete; unsafe_pod_vector(unsafe_pod_vector&& other) { memcpy(this, *other, sizeof(*this)); } ~unsafe_pod_vector() = default; void free() { free(store); } void fast_reserve(size_t n) { ISOSPEC_IMPOSSIBLE(n < static_cast(backend_past_end - store)); T* new_store = reinterpret_cast(realloc(store, n * sizeof(T))); if(new_store == NULL) throw std::bad_alloc(); first_free = new_store + (first_free - store); backend_past_end = new_store + n; store = new_store; } void reserve(size_t n) { if (n > backend_past_end - store) fast_reserve(n); } void resize(size_t new_size) { ISOSPEC_IMPOSSIBLE(new_size < first_free - store); size_t cap = capacity(); if(cap < new_size) { do { cap = cap * 2; } while(cap < new_size); fast_reserve(cap); } first_free = store + new_size; } void resize_and_wipe(size_t new_size) { size_t old_size = size(); ISOSPEC_IMPOSSIBLE(new_size < old_size); resize(new_size); memset(store+old_size, 0, (new_size-old_size) * sizeof(T)); } ISOSPEC_FORCE_INLINE void nocheck_push_back(const T& val) noexcept { ISOSPEC_IMPOSSIBLE(first_free >= backend_past_end); *first_free = val; first_free++; } ISOSPEC_FORCE_INLINE void push_back(const T& val) { if(first_free >= backend_past_end) fast_reserve((std::max)(4, (backend_past_end-store)) * 2); *first_free = val; first_free++; } ISOSPEC_FORCE_INLINE T& operator[](size_t n) noexcept { ISOSPEC_IMPOSSIBLE(store + n >= first_free); return store[n]; } ISOSPEC_FORCE_INLINE const T& operator[](size_t n) const noexcept { ISOSPEC_IMPOSSIBLE(store + n >= first_free); return store[n]; } ISOSPEC_FORCE_INLINE size_t size() const noexcept { return first_free - store; } ISOSPEC_FORCE_INLINE size_t capacity() const noexcept { return backend_past_end - store; } ISOSPEC_FORCE_INLINE T* data() noexcept { return store; } ISOSPEC_FORCE_INLINE const T* data() const noexcept { return store; } ISOSPEC_FORCE_INLINE bool empty() const noexcept { return first_free == store; } ISOSPEC_FORCE_INLINE const T& back() const noexcept { ISOSPEC_IMPOSSIBLE(first_free > backend_past_end); return *(first_free-1); } ISOSPEC_FORCE_INLINE void pop_back() noexcept { // Unlike std::vector we do not ever shrink backend storage unless explicitly requested. ISOSPEC_IMPOSSIBLE(first_free == store); first_free--; } void swap(pod_vector& other) noexcept { std::swap(backend_past_end, other.backend_past_end); std::swap(first_free, other.first_free); std::swap(store, other.store); } typedef T* iterator; typedef const T* const_iterator; typedef T value_type; typedef size_t size_type; typedef T& reference; typedef const T& const_reference; iterator begin() noexcept { return store; }; const_iterator begin() const noexcept { return store; } const_iterator cbegin() const noexcept { return store; } iterator end() noexcept { return first_free; } const_iterator end() const noexcept { return first_free; } const_iterator cend() const noexcept { return first_free; } ISOSPEC_FORCE_INLINE const T& front() const noexcept { ISOSPEC_IMPOSSIBLE(store == first_free); return *store; } void clear() { free(store); first_free = store = backend_past_end = NULL; } friend class pod_vector; }; IsoSpecR/src/misc.h0000644000176200001440000001135413673426762013660 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once #include #include #include #include #include "isoMath.h" #include "pod_vector.h" namespace IsoSpec { inline double combinedSum( const int* conf, const std::vector** valuesContainer, int dimNumber ){ double res = 0.0; for(int i = 0; i < dimNumber; i++) res += (*(valuesContainer[i]))[conf[i]]; return res; } inline double combinedSum( const int* conf, const pod_vector** valuesContainer, int dimNumber ){ double res = 0.0; for(int i = 0; i < dimNumber; i++) res += (*(valuesContainer[i]))[conf[i]]; return res; } inline int* getConf(void* conf) { return reinterpret_cast( reinterpret_cast(conf) + sizeof(double) ); } inline double getLProb(void* conf) { double ret = *reinterpret_cast(conf); return ret; } inline double unnormalized_logProb(const int* conf, const double* logProbs, int dim) { double res = 0.0; for(int i = 0; i < dim; i++) res += minuslogFactorial(conf[i]) + conf[i] * logProbs[i]; return res; } inline double calc_mass(const int* conf, const double* masses, int dim) { double res = 0.0; for(int i = 0; i < dim; i++) { res += conf[i] * masses[i]; } return res; } template void printArray(const T* array, int size, const char* prefix = "") { if (strlen(prefix) > 0) std::cout << prefix << " "; for (int i = 0; i < size; i++) std::cout << array[i] << " "; std::cout << std::endl; } template void printVector(const std::vector& vec) { printArray(vec.data(), vec.size()); } template void printOffsets(const T** array, int size, const T* offset, const char* prefix = "") { if (strlen(prefix) > 0) std::cout << prefix << " "; for (int i = 0; i < size; i++) std::cout << array[i] - offset << " "; std::cout << std::endl; } template void printNestedArray(const T** array, const int* shape, int size) { for (int i = 0; i < size; i++) printArray(array[i], shape[i]); std::cout << std::endl; } //! Quickly select the n'th positional statistic, including the weights. void* quickselect(const void** array, int n, int start, int end); template inline static T* array_copy(const T* A, int size) { T* ret = new T[size]; memcpy(ret, A, size*sizeof(T)); return ret; } template static T* array_copy_nptr(const T* A, int size) { if(A == nullptr) return nullptr; return array_copy(A, size); } template void dealloc_table(T* tbl, int dim) { for(int i = 0; i < dim; i++) { delete tbl[i]; } delete[] tbl; } template void realloc_append(T** array, T what, size_t old_array_size) { T* newT = new T[old_array_size+1]; memcpy(newT, *array, old_array_size*sizeof(T)); newT[old_array_size] = what; delete[] *array; *array = newT; } template size_t* get_order(T* order_array, size_t N) { size_t* arr = new size_t[N]; for(size_t ii = 0; ii < N; ii++) arr[ii] = ii; std::sort(arr, arr + N, [&](int i, int j) { return order_array[i] < order_array[j]; }); return arr; } template size_t* get_inverse_order(T* order_array, size_t N) { size_t* arr = new size_t[N]; for(size_t ii = 0; ii < N; ii++) arr[ii] = ii; std::sort(arr, arr + N, [&](int i, int j) { return order_array[i] > order_array[j]; }); return arr; } template void impose_order(size_t* O, size_t N, TA* A, TB* B) { for(size_t ii = 0; ii < N; ii++) { if(ii != O[ii]) { size_t curr_ii = ii; TA ta = A[ii]; TB tb = B[ii]; size_t next_ii = O[ii]; while(next_ii != ii) { A[curr_ii] = A[next_ii]; B[curr_ii] = B[next_ii]; O[curr_ii] = curr_ii; curr_ii = next_ii; next_ii = O[next_ii]; } A[curr_ii] = ta; B[curr_ii] = tb; O[curr_ii] = curr_ii; } } } } // namespace IsoSpec IsoSpecR/src/Rinterface.cpp0000644000176200001440000001606613660731436015340 0ustar liggesusers/* * Copyright (C) 2015-2018 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #include #include "cwrapper.h" #include "misc.h" #include "isoSpec++.h" #include "fixedEnvelopes.h" #include #include using namespace Rcpp; using namespace IsoSpec; FixedEnvelope mkIsoG(Iso& iso, int algo, double stopCondition, bool trim, bool get_confs) { switch(algo) { case ISOSPEC_ALGO_LAYERED_ESTIMATE: // Not used anymore, just fall through to the next case case ISOSPEC_ALGO_LAYERED: return FixedEnvelope::FromTotalProb(std::move(iso), stopCondition, trim, get_confs); case ISOSPEC_ALGO_ORDERED: return FixedEnvelope::FromTotalProb(std::move(iso), stopCondition, true, get_confs); // Using the ordered algo in R is *completely* pointless today // The only point of ordered algo is when one is using the generator // interface, which we are not exposing in R // We'll just do layered, trim and sort it afterwards - it's equivalent // and much faster case ISOSPEC_ALGO_THRESHOLD_ABSOLUTE: case ISOSPEC_ALGO_THRESHOLD_RELATIVE: throw std::logic_error(""); } throw std::logic_error("Invalid algorithm selected"); } // [[Rcpp::export]] NumericMatrix Rinterface( const IntegerVector& molecule, const DataFrame& isotopes, double stopCondition, int algo = 0, int tabSize = 1000, int hashSize = 1000, double step = .3, bool showCounts = false, bool trim = true ){ unsigned int dimNumber = molecule.size(); std::vector stdIsotopeNumbers; std::vector stdIsotopeMasses; std::vector stdIsotopeProbabilities; const CharacterVector& element = isotopes["element"]; const CharacterVector& isotope = isotopes["isotope"]; const NumericVector& mass = isotopes["mass"]; const NumericVector& abundance = isotopes["abundance"]; const CharacterVector& molecule_names = molecule.names(); CharacterVector stdIsotopeTags = CharacterVector::create("mass", "prob"); for (unsigned int i=0; i IM; std::vector IP; size_t tot = 0; for(size_t ii = 0; ii >( molecule).data(), IM.data(), IP.data()); unsigned int columnsNo = stdIsotopeTags.size(); // standard unsigned int isotopesNo = iso.getAllDim(); if(algo == ISOSPEC_ALGO_THRESHOLD_ABSOLUTE || algo == ISOSPEC_ALGO_THRESHOLD_RELATIVE) { IsoThresholdGenerator ITG(std::move(iso), stopCondition, (algo == ISOSPEC_ALGO_THRESHOLD_ABSOLUTE)); size_t no_confs = ITG.count_confs(); size_t ii = 0; NumericMatrix res(no_confs, columnsNo); if(showCounts) { int* conf_sig = new int[isotopesNo]; while(ITG.advanceToNextConfiguration()) { res(ii,0) = ITG.mass(); res(ii,1) = ITG.prob(); ITG.get_conf_signature(conf_sig); for(size_t jj = 0; jj < isotopesNo; jj++) res(ii, 2+jj) = conf_sig[jj]; ii++; } delete[] conf_sig; } else while(ITG.advanceToNextConfiguration()) { res(ii,0) = ITG.mass(); res(ii,1) = ITG.prob(); ii++; } colnames(res) = stdIsotopeTags; //This is RCPP sugar. It sucks. return res; } // The remaining (layered) algos FixedEnvelope TAB = mkIsoG(iso, algo, stopCondition, trim, showCounts); const double* probs = TAB.probs(); const double* masses = TAB.masses(); const int* confs = TAB.confs(); std::vector ordering; size_t confs_no = TAB.confs_no(); const bool needs_sorting = (ISOSPEC_ALGO_ORDERED == algo); const unsigned int isotopesNoplus2 = isotopesNo + 2; NumericMatrix res(confs_no, columnsNo); if(needs_sorting) { // We need to sort the confs for backward compatibility ordering.reserve(confs_no); for(size_t i = 0; i < confs_no; i++) ordering.push_back(i); std::sort(ordering.begin(), ordering.end(), [&probs](size_t idx1, size_t idx2) -> bool { return probs[idx1] > probs[idx2]; }); unsigned int idx; for(size_t i = 0; i < confs_no; i++) { idx = ordering[i]; res(i,0) = masses[idx]; res(i,1) = probs[idx]; } if(showCounts) { unsigned int confs_idx; for(size_t i = 0; i < confs_no; i++) { confs_idx = ordering[i]; for(size_t j = 2; j < isotopesNoplus2; j++) { res(i,j) = confs[confs_idx]; confs_idx++; } } } } else { for(size_t i = 0; i < confs_no; i++) { res(i,0) = masses[i]; res(i,1) = probs[i]; } if(showCounts) { size_t confs_idx = 0; for(size_t i = 0; i < confs_no; i++) { for(size_t j = 2; j < isotopesNoplus2; j++) { res(i,j) = confs[confs_idx]; confs_idx++; } } } } colnames(res) = stdIsotopeTags; //This is RCPP sugar. It sucks. return(res); } IsoSpecR/src/summator.h0000644000176200001440000000440313651127333014555 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once #include #include #include namespace IsoSpec { class SSummator { // Shewchuk algorithm std::vector partials; int maxpart; public: inline SSummator() { maxpart = 0; } inline SSummator(const SSummator& other) : partials(other.partials), maxpart(other.maxpart) {} inline void add(double x) { unsigned int i = 0; for(int pidx = 0; pidx < maxpart; pidx++) { double y = partials[pidx]; if(std::abs(x) < std::abs(y)) std::swap(x, y); double hi = x+y; double lo = y-(hi-x); if(lo != 0.0) { partials[i] = lo; i += 1; } x = hi; } while(partials.size() <= i) partials.push_back(0.0); partials[i] = x; maxpart = i+1; } inline double get() { double ret = 0.0; for(int i = 0; i < maxpart; i++) ret += partials[i]; return ret; } }; class Summator{ // Kahan algorithm double sum; double c; public: inline Summator() { sum = 0.0; c = 0.0;} inline void add(double what) { double y = what - c; double t = sum + y; c = (t - sum) - y; sum = t; } inline double get() { return sum; } }; class TSummator { // Trivial algorithm, for testing only double sum; public: inline TSummator() { sum = 0.0; } inline void add(double what) { sum += what; } inline double get() { return sum; } }; } // namespace IsoSpec IsoSpecR/src/fixedEnvelopes.cpp0000644000176200001440000005253013741011725016222 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #include "fixedEnvelopes.h" #include #include "isoMath.h" namespace IsoSpec { FixedEnvelope::FixedEnvelope(const FixedEnvelope& other) : _masses(array_copy(other._masses, other._confs_no)), _probs(array_copy(other._probs, other._confs_no)), _confs(array_copy(other._confs, other._confs_no*other.allDim)), _confs_no(other._confs_no), allDim(other.allDim), sorted_by_mass(other.sorted_by_mass), sorted_by_prob(other.sorted_by_prob), total_prob(other.total_prob) {} FixedEnvelope::FixedEnvelope(FixedEnvelope&& other) : _masses(other._masses), _probs(other._probs), _confs(other._confs), _confs_no(other._confs_no), allDim(other.allDim), sorted_by_mass(other.sorted_by_mass), sorted_by_prob(other.sorted_by_prob), total_prob(other.total_prob) { other._masses = nullptr; other._probs = nullptr; other._confs = nullptr; other._confs_no = 0; other.total_prob = 0.0; } FixedEnvelope::FixedEnvelope(double* in_masses, double* in_probs, size_t in_confs_no, bool masses_sorted, bool probs_sorted, double _total_prob) : _masses(in_masses), _probs(in_probs), _confs(nullptr), _confs_no(in_confs_no), allDim(0), sorted_by_mass(masses_sorted), sorted_by_prob(probs_sorted), total_prob(_total_prob) {} FixedEnvelope FixedEnvelope::operator+(const FixedEnvelope& other) const { double* nprobs = reinterpret_cast(malloc(sizeof(double) * (_confs_no+other._confs_no))); if(nprobs == nullptr) throw std::bad_alloc(); double* nmasses = reinterpret_cast(malloc(sizeof(double) * (_confs_no+other._confs_no))); if(nmasses == nullptr) { free(nprobs); throw std::bad_alloc(); } memcpy(nprobs, _probs, sizeof(double) * _confs_no); memcpy(nmasses, _masses, sizeof(double) * _confs_no); memcpy(nprobs+_confs_no, other._probs, sizeof(double) * other._confs_no); memcpy(nmasses+_confs_no, other._masses, sizeof(double) * other._confs_no); return FixedEnvelope(nmasses, nprobs, _confs_no + other._confs_no); } FixedEnvelope FixedEnvelope::operator*(const FixedEnvelope& other) const { double* nprobs = reinterpret_cast(malloc(sizeof(double) * _confs_no * other._confs_no)); if(nprobs == nullptr) throw std::bad_alloc(); // deepcode ignore CMemoryLeak: It's not a memleak: the memory is passed to FixedEnvelope which // deepcode ignore CMemoryLeak: takes ownership of it, and will properly free() it in destructor. double* nmasses = reinterpret_cast(malloc(sizeof(double) * _confs_no * other._confs_no)); if(nmasses == nullptr) { free(nprobs); throw std::bad_alloc(); } size_t tgt_idx = 0; for(size_t ii = 0; ii < _confs_no; ii++) for(size_t jj = 0; jj < other._confs_no; jj++) { nprobs[tgt_idx] = _probs[ii] * other._probs[jj]; nmasses[tgt_idx] = _masses[ii] + other._masses[jj]; tgt_idx++; } return FixedEnvelope(nmasses, nprobs, tgt_idx); } void FixedEnvelope::sort_by_mass() { if(sorted_by_mass) return; sort_by(_masses); sorted_by_mass = true; sorted_by_prob = false; } void FixedEnvelope::sort_by_prob() { if(sorted_by_prob) return; sort_by(_probs); sorted_by_prob = true; sorted_by_mass = false; } template void reorder_array(T* arr, size_t* order, size_t size, bool can_destroy = false) { if(!can_destroy) { size_t* order_c = new size_t[size]; memcpy(order_c, order, sizeof(size_t)*size); order = order_c; } for(size_t ii = 0; ii < size; ii++) while(order[ii] != ii) { std::swap(arr[ii], arr[order[ii]]); std::swap(order[order[ii]], order[ii]); } if(!can_destroy) delete[] order; } void FixedEnvelope::sort_by(double* order) { size_t* indices = new size_t[_confs_no]; for(size_t ii = 0; ii < _confs_no; ii++) indices[ii] = ii; std::sort(indices, indices + _confs_no, TableOrder(order)); size_t* inverse = new size_t[_confs_no]; for(size_t ii = 0; ii < _confs_no; ii++) inverse[indices[ii]] = ii; delete[] indices; reorder_array(_masses, inverse, _confs_no); reorder_array(_probs, inverse, _confs_no); if(_confs != nullptr) { int* swapspace = new int[allDim]; for(size_t ii = 0; ii < _confs_no; ii++) while(order[ii] != ii) { memcpy(swapspace, &_confs[ii*allDim], allDimSizeofInt); memcpy(&_confs[ii*allDim], &_confs[inverse[ii]*allDim], allDimSizeofInt); memcpy(&_confs[inverse[ii]*allDim], swapspace, allDimSizeofInt); } delete[] swapspace; } delete[] inverse; } double FixedEnvelope::get_total_prob() { if(std::isnan(total_prob)) { total_prob = 0.0; for(size_t ii = 0; ii < _confs_no; ii++) total_prob += _probs[ii]; } return total_prob; } void FixedEnvelope::scale(double factor) { for(size_t ii = 0; ii < _confs_no; ii++) _probs[ii] *= factor; total_prob *= factor; } void FixedEnvelope::normalize() { double tp = get_total_prob(); if(tp != 1.0) { scale(1.0/tp); total_prob = 1.0; } } FixedEnvelope FixedEnvelope::LinearCombination(const std::vector& spectra, const std::vector& intensities) { return LinearCombination(spectra.data(), intensities.data(), spectra.size()); } FixedEnvelope FixedEnvelope::LinearCombination(const FixedEnvelope* const * spectra, const double* intensities, size_t size) { size_t ret_size = 0; for(size_t ii = 0; ii < size; ii++) ret_size += spectra[ii]->_confs_no; double* newprobs = reinterpret_cast(malloc(sizeof(double)*ret_size)); if(newprobs == nullptr) throw std::bad_alloc(); double* newmasses = reinterpret_cast(malloc(sizeof(double)*ret_size)); if(newmasses == nullptr) { free(newprobs); throw std::bad_alloc(); } size_t cntr = 0; for(size_t ii = 0; ii < size; ii++) { double mul = intensities[ii]; for(size_t jj = 0; jj < spectra[ii]->_confs_no; jj++) newprobs[jj+cntr] = spectra[ii]->_probs[jj] * mul; memcpy(newmasses + cntr, spectra[ii]->_masses, sizeof(double) * spectra[ii]->_confs_no); cntr += spectra[ii]->_confs_no; } return FixedEnvelope(newmasses, newprobs, cntr); } double FixedEnvelope::WassersteinDistance(FixedEnvelope& other) { double ret = 0.0; if((get_total_prob()*0.999 > other.get_total_prob()) || (other.get_total_prob() > get_total_prob()*1.001)) throw std::logic_error("Spectra must be normalized before computing Wasserstein Distance"); if(_confs_no == 0 || other._confs_no == 0) return 0.0; sort_by_mass(); other.sort_by_mass(); size_t idx_this = 0; size_t idx_other = 0; double acc_prob = 0.0; double last_point = 0.0; while(idx_this < _confs_no && idx_other < other._confs_no) { if(_masses[idx_this] < other._masses[idx_other]) { ret += (_masses[idx_this] - last_point) * std::abs(acc_prob); acc_prob += _probs[idx_this]; last_point = _masses[idx_this]; idx_this++; } else { ret += (other._masses[idx_other] - last_point) * std::abs(acc_prob); acc_prob -= other._probs[idx_other]; last_point = other._masses[idx_other]; idx_other++; } } acc_prob = std::abs(acc_prob); while(idx_this < _confs_no) { ret += (_masses[idx_this] - last_point) * acc_prob; acc_prob -= _probs[idx_this]; last_point = _masses[idx_this]; idx_this++; } while(idx_other < other._confs_no) { ret += (other._masses[idx_other] - last_point) * acc_prob; acc_prob -= other._probs[idx_other]; last_point = other._masses[idx_other]; idx_other++; } return ret; } double FixedEnvelope::OrientedWassersteinDistance(FixedEnvelope& other) { double ret = 0.0; if((get_total_prob()*0.999 > other.get_total_prob()) || (other.get_total_prob() > get_total_prob()*1.001)) throw std::logic_error("Spectra must be normalized before computing Wasserstein Distance"); if(_confs_no == 0 || other._confs_no == 0) return 0.0; sort_by_mass(); other.sort_by_mass(); size_t idx_this = 0; size_t idx_other = 0; double acc_prob = 0.0; double last_point = 0.0; while(idx_this < _confs_no && idx_other < other._confs_no) { if(_masses[idx_this] < other._masses[idx_other]) { ret += (_masses[idx_this] - last_point) * acc_prob; acc_prob += _probs[idx_this]; last_point = _masses[idx_this]; idx_this++; } else { ret += (other._masses[idx_other] - last_point) * acc_prob; acc_prob -= other._probs[idx_other]; last_point = other._masses[idx_other]; idx_other++; } } while(idx_this < _confs_no) { ret += (_masses[idx_this] - last_point) * acc_prob; acc_prob -= _probs[idx_this]; last_point = _masses[idx_this]; idx_this++; } while(idx_other < other._confs_no) { ret += (other._masses[idx_other] - last_point) * acc_prob; acc_prob -= other._probs[idx_other]; last_point = other._masses[idx_other]; idx_other++; } return ret; } FixedEnvelope FixedEnvelope::bin(double bin_width, double middle) { sort_by_mass(); FixedEnvelope ret; if(_confs_no == 0) return ret; ret.reallocate_memory(ISOSPEC_INIT_TABLE_SIZE); size_t ii = 0; double half_width = 0.5*bin_width; double hwmm = half_width-middle; while(ii < _confs_no) { double current_bin_middle = floor((_masses[ii]+hwmm)/bin_width)*bin_width + middle; double current_bin_end = current_bin_middle + half_width; double bin_prob = 0.0; while(ii < _confs_no && _masses[ii] <= current_bin_end) { bin_prob += _probs[ii]; ii++; } ret.store_conf(current_bin_middle, bin_prob); } return ret; } template void FixedEnvelope::reallocate_memory(size_t new_size) { current_size = new_size; // FIXME: Handle overflow gracefully here. It definitely could happen for people still stuck on 32 bits... _masses = reinterpret_cast(realloc(_masses, new_size * sizeof(double))); if(_masses == nullptr) throw std::bad_alloc(); tmasses = _masses + _confs_no; _probs = reinterpret_cast(realloc(_probs, new_size * sizeof(double))); if(_probs == nullptr) throw std::bad_alloc(); tprobs = _probs + _confs_no; constexpr_if(tgetConfs) { _confs = reinterpret_cast(realloc(_confs, new_size * allDimSizeofInt)); if(_confs == nullptr) throw std::bad_alloc(); tconfs = _confs + (allDim * _confs_no); } } void FixedEnvelope::slow_reallocate_memory(size_t new_size) { current_size = new_size; // FIXME: Handle overflow gracefully here. It definitely could happen for people still stuck on 32 bits... _masses = reinterpret_cast(realloc(_masses, new_size * sizeof(double))); if(_masses == nullptr) throw std::bad_alloc(); tmasses = _masses + _confs_no; _probs = reinterpret_cast(realloc(_probs, new_size * sizeof(double))); if(_probs == nullptr) throw std::bad_alloc(); tprobs = _probs + _confs_no; if(_confs != nullptr) { _confs = reinterpret_cast(realloc(_confs, new_size * allDimSizeofInt)); if(_confs == nullptr) throw std::bad_alloc(); tconfs = _confs + (allDim * _confs_no); } } template void FixedEnvelope::threshold_init(Iso&& iso, double threshold, bool absolute) { IsoThresholdGenerator generator(std::move(iso), threshold, absolute); size_t tab_size = generator.count_confs(); this->allDim = generator.getAllDim(); this->allDimSizeofInt = this->allDim * sizeof(int); this->reallocate_memory(tab_size); double* ttmasses = this->_masses; double* ttprobs = this->_probs; ISOSPEC_MAYBE_UNUSED int* ttconfs; constexpr_if(tgetConfs) ttconfs = _confs; while(generator.advanceToNextConfiguration()) { *ttmasses = generator.mass(); ttmasses++; *ttprobs = generator.prob(); ttprobs++; constexpr_if(tgetConfs) { generator.get_conf_signature(ttconfs); ttconfs += allDim; } } this->_confs_no = tab_size; } template void FixedEnvelope::threshold_init(Iso&& iso, double threshold, bool absolute); template void FixedEnvelope::threshold_init(Iso&& iso, double threshold, bool absolute); template void FixedEnvelope::total_prob_init(Iso&& iso, double target_total_prob, bool optimize) { if(target_total_prob <= 0.0) return; if(target_total_prob >= 1.0) { threshold_init(std::move(iso), 0.0, true); return; } IsoLayeredGenerator generator(std::move(iso), 1000, 1000, true, std::min(target_total_prob, 0.9999)); this->allDim = generator.getAllDim(); this->allDimSizeofInt = this->allDim*sizeof(int); this->reallocate_memory(ISOSPEC_INIT_TABLE_SIZE); size_t last_switch = 0; double prob_at_last_switch = 0.0; double prob_so_far = 0.0; double layer_delta; const double sum_above = log1p(-target_total_prob) - 2.3025850929940455; // log(0.1); do { // Store confs until we accumulate more prob than needed - and, if optimizing, // store also the rest of the last layer while(generator.advanceToNextConfigurationWithinLayer()) { this->template addConfILG(generator); prob_so_far += *(tprobs-1); // The just-stored probability if(prob_so_far >= target_total_prob) { if (optimize) { while(generator.advanceToNextConfigurationWithinLayer()) this->template addConfILG(generator); break; } else return; } } if(prob_so_far >= target_total_prob) break; last_switch = this->_confs_no; prob_at_last_switch = prob_so_far; layer_delta = sum_above - log1p(-prob_so_far); layer_delta = (std::max)((std::min)(layer_delta, -0.1), -5.0); } while(generator.nextLayer(layer_delta)); if(!optimize || prob_so_far <= target_total_prob) return; // Right. We have extra configurations and we have been asked to produce an optimal p-set, so // now we shall trim unneeded configurations, using an algorithm dubbed "quicktrim" // - similar to the quickselect algorithm, except that we use the cumulative sum of elements // left of pivot to decide whether to go left or right, instead of the positional index. // We'll be sorting by the prob array, permuting the other ones in parallel. int* conf_swapspace = nullptr; constexpr_if(tgetConfs) conf_swapspace = reinterpret_cast(malloc(this->allDimSizeofInt)); size_t start = last_switch; size_t end = this->_confs_no; double sum_to_start = prob_at_last_switch; while(start < end) { // Partition part size_t len = end - start; #if ISOSPEC_BUILDING_R size_t pivot = len/2 + start; #else size_t pivot = random_gen() % len + start; // Using Mersenne twister directly - we don't // need a very uniform distribution just for pivot // selection #endif double pprob = this->_probs[pivot]; swap(pivot, end-1, conf_swapspace); double new_csum = sum_to_start; size_t loweridx = start; for(size_t ii = start; ii < end-1; ii++) if(this->_probs[ii] > pprob) { swap(ii, loweridx, conf_swapspace); new_csum += this->_probs[loweridx]; loweridx++; } swap(end-1, loweridx, conf_swapspace); // Selection part if(new_csum < target_total_prob) { start = loweridx + 1; sum_to_start = new_csum + this->_probs[loweridx]; } else end = loweridx; } constexpr_if(tgetConfs) free(conf_swapspace); if(end <= current_size/2) // Overhead in memory of 2x or more, shrink to fit this->template reallocate_memory(end); this->_confs_no = end; } template void FixedEnvelope::total_prob_init(Iso&& iso, double target_total_prob, bool optimize); template void FixedEnvelope::total_prob_init(Iso&& iso, double target_total_prob, bool optimize); template void FixedEnvelope::stochastic_init(Iso&& iso, size_t _no_molecules, double _precision, double _beta_bias) { IsoStochasticGenerator generator(std::move(iso), _no_molecules, _precision, _beta_bias); this->allDim = generator.getAllDim(); this->allDimSizeofInt = this->allDim * sizeof(int); this->reallocate_memory(ISOSPEC_INIT_TABLE_SIZE); while(generator.advanceToNextConfiguration()) addConfILG(generator); } template void FixedEnvelope::stochastic_init(Iso&& iso, size_t _no_molecules, double _precision, double _beta_bias); template void FixedEnvelope::stochastic_init(Iso&& iso, size_t _no_molecules, double _precision, double _beta_bias); double FixedEnvelope::empiric_average_mass() { double ret = 0.0; for(size_t ii = 0; ii < _confs_no; ii++) { ret += _masses[ii] * _probs[ii]; } return ret / get_total_prob(); } double FixedEnvelope::empiric_variance() { double ret = 0.0; double avg = empiric_average_mass(); for(size_t ii = 0; ii < _confs_no; ii++) { double msq = _masses[ii] - avg; ret += msq * msq * _probs[ii]; } return ret / get_total_prob(); } FixedEnvelope FixedEnvelope::Binned(Iso&& iso, double target_total_prob, double bin_width, double bin_middle) { FixedEnvelope ret; double min_mass = iso.getLightestPeakMass(); double range_len = iso.getHeaviestPeakMass() - min_mass; size_t no_bins = static_cast(range_len / bin_width) + 2; double half_width = 0.5*bin_width; double hwmm = half_width-bin_middle; size_t idx_min = floor((min_mass + hwmm) / bin_width); size_t idx_max = idx_min + no_bins; double* acc; # if ISOSPEC_GOT_MMAN acc = reinterpret_cast(mmap(nullptr, sizeof(double)*no_bins, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); #else // This will probably crash for large molecules and high resolutions... acc = reinterpret_cast(calloc(no_bins, sizeof(double))); #endif if(acc == NULL) throw std::bad_alloc(); acc -= idx_min; IsoLayeredGenerator ITG(std::move(iso)); bool non_empty; while((non_empty = ITG.advanceToNextConfiguration()) && ITG.prob() == 0.0) {} if(non_empty) { double accum_prob = ITG.prob(); size_t nonzero_idx = floor((ITG.mass() + hwmm)/bin_width); acc[nonzero_idx] = accum_prob; while(ITG.advanceToNextConfiguration() && accum_prob < target_total_prob) { double prob = ITG.prob(); accum_prob += prob; size_t bin_idx = floor((ITG.mass() + hwmm)/bin_width); acc[bin_idx] += prob; } // Making the assumption that there won't be gaps of more than 10 Da in the spectrum. This is true for all // molecules made of natural elements. size_t distance_10da = static_cast(10.0/bin_width) + 1; size_t empty_steps = 0; ret.reallocate_memory(ISOSPEC_INIT_TABLE_SIZE); for(size_t ii = nonzero_idx; ii >= idx_min && empty_steps < distance_10da; ii--) { if(acc[ii] > 0.0) { empty_steps = 0; ret.store_conf(static_cast(ii)*bin_width + bin_middle, acc[ii]); } else empty_steps++; } empty_steps = 0; for(size_t ii = nonzero_idx+1; ii < idx_max && empty_steps < distance_10da; ii++) { if(acc[ii] > 0.0) { empty_steps = 0; ret.store_conf(static_cast(ii)*bin_width + bin_middle, acc[ii]); } else empty_steps++; } } acc += idx_min; # if ISOSPEC_GOT_MMAN munmap(acc, sizeof(double)*no_bins); #else free(acc); #endif return ret; } } // namespace IsoSpec IsoSpecR/src/fixedEnvelopes.h0000644000176200001440000001725213741011725015671 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once #include #include #include #include #include "isoSpec++.h" #ifdef DEBUG #define ISOSPEC_INIT_TABLE_SIZE 16 #else #define ISOSPEC_INIT_TABLE_SIZE 1024 #endif namespace IsoSpec { class ISOSPEC_EXPORT_SYMBOL FixedEnvelope { protected: double* _masses; double* _probs; int* _confs; size_t _confs_no; int allDim; bool sorted_by_mass; bool sorted_by_prob; double total_prob; size_t current_size; double* tmasses; double* tprobs; int* tconfs; int allDimSizeofInt; public: ISOSPEC_FORCE_INLINE FixedEnvelope() : _masses(nullptr), _probs(nullptr), _confs(nullptr), _confs_no(0), allDim(0), sorted_by_mass(false), sorted_by_prob(false), total_prob(0.0), current_size(0), allDimSizeofInt(0) // Deliberately not initializing tmasses, tprobs, tconfs {}; FixedEnvelope(const FixedEnvelope& other); FixedEnvelope(FixedEnvelope&& other); FixedEnvelope(double* masses, double* probs, size_t confs_no, bool masses_sorted = false, bool probs_sorted = false, double _total_prob = NAN); virtual ~FixedEnvelope() { free(_masses); free(_probs); free(_confs); }; FixedEnvelope operator+(const FixedEnvelope& other) const; FixedEnvelope operator*(const FixedEnvelope& other) const; inline size_t confs_no() const { return _confs_no; } inline int getAllDim() const { return allDim; } inline const double* masses() const { return _masses; } inline const double* probs() const { return _probs; } inline const int* confs() const { return _confs; } inline double* release_masses() { double* ret = _masses; _masses = nullptr; return ret; } inline double* release_probs() { double* ret = _probs; _probs = nullptr; return ret; } inline int* release_confs() { int* ret = _confs; _confs = nullptr; return ret; } inline double mass(size_t i) const { return _masses[i]; } inline double prob(size_t i) const { return _probs[i]; } inline const int* conf(size_t i) const { return _confs + i*allDim; } void sort_by_mass(); void sort_by_prob(); double get_total_prob(); void scale(double factor); void normalize(); double empiric_average_mass(); double empiric_variance(); double empiric_stddev() { return sqrt(empiric_variance()); } double WassersteinDistance(FixedEnvelope& other); double OrientedWassersteinDistance(FixedEnvelope& other); static FixedEnvelope LinearCombination(const std::vector& spectra, const std::vector& intensities); static FixedEnvelope LinearCombination(const FixedEnvelope* const * spectra, const double* intensities, size_t size); FixedEnvelope bin(double bin_width = 1.0, double middle = 0.0); private: void sort_by(double* order); protected: template ISOSPEC_FORCE_INLINE void store_conf(const T& generator) { *tmasses = generator.mass(); tmasses++; *tprobs = generator.prob(); tprobs++; constexpr_if(tgetConfs) { generator.get_conf_signature(tconfs); tconfs += allDim; } } ISOSPEC_FORCE_INLINE void store_conf(double _mass, double _prob) { if(_confs_no == current_size) reallocate_memory(current_size*2); *tprobs = _prob; *tmasses = _mass; tprobs++; tmasses++; _confs_no++; } template ISOSPEC_FORCE_INLINE void swap(size_t idx1, size_t idx2, ISOSPEC_MAYBE_UNUSED int* conf_swapspace) { std::swap(this->_probs[idx1], this->_probs[idx2]); std::swap(this->_masses[idx1], this->_masses[idx2]); constexpr_if(tgetConfs) { int* c1 = this->_confs + (idx1*this->allDim); int* c2 = this->_confs + (idx2*this->allDim); memcpy(conf_swapspace, c1, this->allDimSizeofInt); memcpy(c1, c2, this->allDimSizeofInt); memcpy(c2, conf_swapspace, this->allDimSizeofInt); } } template void reallocate_memory(size_t new_size); void slow_reallocate_memory(size_t new_size); public: template void threshold_init(Iso&& iso, double threshold, bool absolute); template void addConfILG(const GenType& generator) { if(this->_confs_no == this->current_size) this->template reallocate_memory(this->current_size*2); this->template store_conf(generator); this->_confs_no++; } template void total_prob_init(Iso&& iso, double target_prob, bool trim); static FixedEnvelope FromThreshold(Iso&& iso, double threshold, bool absolute, bool tgetConfs = false) { FixedEnvelope ret; if(tgetConfs) ret.threshold_init(std::move(iso), threshold, absolute); else ret.threshold_init(std::move(iso), threshold, absolute); return ret; } inline static FixedEnvelope FromThreshold(const Iso& iso, double _threshold, bool _absolute, bool tgetConfs = false) { return FromThreshold(Iso(iso, false), _threshold, _absolute, tgetConfs); } static FixedEnvelope FromTotalProb(Iso&& iso, double target_total_prob, bool optimize, bool tgetConfs = false) { FixedEnvelope ret; if(tgetConfs) ret.total_prob_init(std::move(iso), target_total_prob, optimize); else ret.total_prob_init(std::move(iso), target_total_prob, optimize); return ret; } inline static FixedEnvelope FromTotalProb(const Iso& iso, double _target_total_prob, bool _optimize, bool tgetConfs = false) { return FromTotalProb(Iso(iso, false), _target_total_prob, _optimize, tgetConfs); } template void stochastic_init(Iso&& iso, size_t _no_molecules, double _precision, double _beta_bias); inline static FixedEnvelope FromStochastic(Iso&& iso, size_t _no_molecules, double _precision = 0.9999, double _beta_bias = 5.0, bool tgetConfs = false) { FixedEnvelope ret; if(tgetConfs) ret.stochastic_init(std::move(iso), _no_molecules, _precision, _beta_bias); else ret.stochastic_init(std::move(iso), _no_molecules, _precision, _beta_bias); return ret; } static FixedEnvelope FromStochastic(const Iso& iso, size_t _no_molecules, double _precision = 0.9999, double _beta_bias = 5.0, bool tgetConfs = false) { return FromStochastic(Iso(iso, false), _no_molecules, _precision, _beta_bias, tgetConfs); } static FixedEnvelope Binned(Iso&& iso, double target_total_prob, double bin_width, double bin_middle = 0.0); static FixedEnvelope Binned(const Iso& iso, double target_total_prob, double bin_width, double bin_middle = 0.0) { return Binned(Iso(iso, false), target_total_prob, bin_width, bin_middle); } }; } // namespace IsoSpec IsoSpecR/src/unity-build.cpp0000644000176200001440000000275213671361335015517 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #include "platform.h" #if !ISOSPEC_BUILDING_R #if !ISOSPEC_GOT_SYSTEM_MMAN && ISOSPEC_GOT_MMAN #include "mman.cpp" // NOLINT(build/include) #endif // A poor-man's replacement for LTO. We're small enough that we can do that. And // ignore cpplint's complaints about it. #include "allocator.cpp" // NOLINT(build/include) #include "dirtyAllocator.cpp" // NOLINT(build/include) #include "isoSpec++.cpp" // NOLINT(build/include) #include "isoMath.cpp" // NOLINT(build/include) #include "marginalTrek++.cpp" // NOLINT(build/include) #include "operators.cpp" // NOLINT(build/include) #include "element_tables.cpp" // NOLINT(build/include) #include "fasta.cpp" // NOLINT(build/include) #include "cwrapper.cpp" // NOLINT(build/include) #include "fixedEnvelopes.cpp" // NOLINT(build/include) #include "misc.cpp" // NOLINT(build/include) #endif IsoSpecR/src/cwrapper.cpp0000644000176200001440000002757613737365127015116 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #include #include #include #include #include "cwrapper.h" #include "misc.h" #include "marginalTrek++.h" #include "isoSpec++.h" #include "fixedEnvelopes.h" #include "fasta.h" using namespace IsoSpec; // NOLINT(build/namespaces) - all of this really should be in a namespace IsoSpec, but C doesn't have them... extern "C" { void * setupIso(int dimNumber, const int* isotopeNumbers, const int* atomCounts, const double* isotopeMasses, const double* isotopeProbabilities) { Iso* iso = new Iso(dimNumber, isotopeNumbers, atomCounts, isotopeMasses, isotopeProbabilities); return reinterpret_cast(iso); } void * isoFromFasta(const char* fasta, bool use_nominal_masses, bool add_water) { Iso* iso = new Iso(Iso::FromFASTA(fasta, use_nominal_masses, add_water)); return reinterpret_cast(iso); } void deleteIso(void* iso) { delete reinterpret_cast(iso); } double getLightestPeakMassIso(void* iso) { return reinterpret_cast(iso)->getLightestPeakMass(); } double getHeaviestPeakMassIso(void* iso) { return reinterpret_cast(iso)->getHeaviestPeakMass(); } double getMonoisotopicPeakMassIso(void* iso) { return reinterpret_cast(iso)->getMonoisotopicPeakMass(); } double getModeLProbIso(void* iso) { return reinterpret_cast(iso)->getModeLProb(); } double getModeMassIso(void* iso) { return reinterpret_cast(iso)->getModeMass(); } double getTheoreticalAverageMassIso(void* iso) { return reinterpret_cast(iso)->getTheoreticalAverageMass(); } double getIsoVariance(void* iso) { return reinterpret_cast(iso)->variance(); } double getIsoStddev(void* iso) { return reinterpret_cast(iso)->stddev(); } double* getMarginalLogSizeEstimates(void* iso, double target_total_prob) { Iso* i = reinterpret_cast(iso); double* ret = reinterpret_cast(malloc(sizeof(double)*i->getDimNumber())); if(ret != nullptr) i->saveMarginalLogSizeEstimates(ret, target_total_prob); return ret; } #define ISOSPEC_C_FN_CODE(generatorType, dataType, method)\ dataType method##generatorType(void* generator){ return reinterpret_cast(generator)->method(); } #define ISOSPEC_C_FN_CODE_GET_CONF_SIGNATURE(generatorType)\ void get_conf_signature##generatorType(void* generator, int* space)\ { reinterpret_cast(generator)->get_conf_signature(space); } #define ISOSPEC_C_FN_DELETE(generatorType) void delete##generatorType(void* generator){ delete reinterpret_cast(generator); } #define ISOSPEC_C_FN_CODES(generatorType)\ ISOSPEC_C_FN_CODE(generatorType, double, mass) \ ISOSPEC_C_FN_CODE(generatorType, double, lprob) \ ISOSPEC_C_FN_CODE(generatorType, double, prob) \ ISOSPEC_C_FN_CODE_GET_CONF_SIGNATURE(generatorType) \ ISOSPEC_C_FN_CODE(generatorType, bool, advanceToNextConfiguration) \ ISOSPEC_C_FN_DELETE(generatorType) // ______________________________________________________THRESHOLD GENERATOR void* setupIsoThresholdGenerator(void* iso, double threshold, bool _absolute, int _tabSize, int _hashSize, bool reorder_marginals) { IsoThresholdGenerator* iso_tmp = new IsoThresholdGenerator( std::move(*reinterpret_cast(iso)), threshold, _absolute, _tabSize, _hashSize, reorder_marginals); return reinterpret_cast(iso_tmp); } ISOSPEC_C_FN_CODES(IsoThresholdGenerator) // ______________________________________________________LAYERED GENERATOR void* setupIsoLayeredGenerator(void* iso, int _tabSize, int _hashSize, bool reorder_marginals, double t_prob_hint ) { IsoLayeredGenerator* iso_tmp = new IsoLayeredGenerator( std::move(*reinterpret_cast(iso)), _tabSize, _hashSize, reorder_marginals, t_prob_hint ); return reinterpret_cast(iso_tmp); } ISOSPEC_C_FN_CODES(IsoLayeredGenerator) // ______________________________________________________ORDERED GENERATOR void* setupIsoOrderedGenerator(void* iso, int _tabSize, int _hashSize) { IsoOrderedGenerator* iso_tmp = new IsoOrderedGenerator( std::move(*reinterpret_cast(iso)), _tabSize, _hashSize); return reinterpret_cast(iso_tmp); } ISOSPEC_C_FN_CODES(IsoOrderedGenerator) // ______________________________________________________STOCHASTIC GENERATOR void* setupIsoStochasticGenerator(void* iso, size_t no_molecules, double precision, double beta_bias) { IsoStochasticGenerator* iso_tmp = new IsoStochasticGenerator( std::move(*reinterpret_cast(iso)), no_molecules, precision, beta_bias); return reinterpret_cast(iso_tmp); } ISOSPEC_C_FN_CODES(IsoStochasticGenerator) // ______________________________________________________ FixedEnvelopes void* setupThresholdFixedEnvelope(void* iso, double threshold, bool absolute, bool get_confs) { FixedEnvelope* ret = new FixedEnvelope( // Use copy elision to allocate on heap with named constructor FixedEnvelope::FromThreshold(Iso(*reinterpret_cast(iso), true), threshold, absolute, get_confs)); return reinterpret_cast(ret); } void* setupTotalProbFixedEnvelope(void* iso, double target_coverage, bool optimize, bool get_confs) { FixedEnvelope* ret = new FixedEnvelope( // Use copy elision to allocate on heap with named constructor FixedEnvelope::FromTotalProb(Iso(*reinterpret_cast(iso), true), target_coverage, optimize, get_confs)); return reinterpret_cast(ret); } void* setupStochasticFixedEnvelope(void* iso, size_t no_molecules, double precision, double beta_bias, bool get_confs) { FixedEnvelope* ret = new FixedEnvelope( // Use copy elision to allocate on heap with named constructor FixedEnvelope::FromStochastic(Iso(*reinterpret_cast(iso), true), no_molecules, precision, beta_bias, get_confs)); return reinterpret_cast(ret); } void* setupBinnedFixedEnvelope(void* iso, double target_total_prob, double bin_width, double bin_middle) { FixedEnvelope* ret = new FixedEnvelope( // Use copy elision to allocate on heap with named constructor FixedEnvelope::Binned(Iso(*reinterpret_cast(iso), true), target_total_prob, bin_width, bin_middle)); return reinterpret_cast(ret); } void* setupFixedEnvelope(double* masses, double* probs, size_t size, bool mass_sorted, bool prob_sorted, double total_prob) { FixedEnvelope* ret = new FixedEnvelope(masses, probs, size, mass_sorted, prob_sorted, total_prob); return reinterpret_cast(ret); } void deleteFixedEnvelope(void* t, bool release_everything) { FixedEnvelope* tt = reinterpret_cast(t); if(release_everything) { tt->release_masses(); tt->release_probs(); tt->release_confs(); } delete tt; } const double* massesFixedEnvelope(void* tabulator) { return reinterpret_cast(tabulator)->release_masses(); } const double* probsFixedEnvelope(void* tabulator) { return reinterpret_cast(tabulator)->release_probs(); } const int* confsFixedEnvelope(void* tabulator) { return reinterpret_cast(tabulator)->release_confs(); } size_t confs_noFixedEnvelope(void* tabulator) { return reinterpret_cast(tabulator)->confs_no(); } double empiricAverageMass(void* tabulator) { return reinterpret_cast(tabulator)->empiric_average_mass(); } double empiricVariance(void* tabulator) { return reinterpret_cast(tabulator)->empiric_variance(); } double empiricStddev(void* tabulator) { return reinterpret_cast(tabulator)->empiric_stddev(); } double wassersteinDistance(void* tabulator1, void* tabulator2) { try { return reinterpret_cast(tabulator1)->WassersteinDistance(*reinterpret_cast(tabulator2)); } catch(std::logic_error&) { return NAN; } } double orientedWassersteinDistance(void* tabulator1, void* tabulator2) { try { return reinterpret_cast(tabulator1)->OrientedWassersteinDistance(*reinterpret_cast(tabulator2)); } catch(std::logic_error&) { return NAN; } } void* addEnvelopes(void* tabulator1, void* tabulator2) { // Hopefully the compiler will do the copy elision... return reinterpret_cast(new FixedEnvelope((*reinterpret_cast(tabulator1))+(*reinterpret_cast(tabulator2)))); } void* convolveEnvelopes(void* tabulator1, void* tabulator2) { // Hopefully the compiler will do the copy elision... return reinterpret_cast(new FixedEnvelope((*reinterpret_cast(tabulator1))*(*reinterpret_cast(tabulator2)))); } double getTotalProbOfEnvelope(void* envelope) { return reinterpret_cast(envelope)->get_total_prob(); } void scaleEnvelope(void* envelope, double factor) { reinterpret_cast(envelope)->scale(factor); } void normalizeEnvelope(void* envelope) { reinterpret_cast(envelope)->normalize(); } void* binnedEnvelope(void* envelope, double width, double middle) { // Again, counting on copy elision... return reinterpret_cast(new FixedEnvelope(reinterpret_cast(envelope)->bin(width, middle))); } void* linearCombination(void* const * const envelopes, const double* intensities, size_t count) { // Same... return reinterpret_cast(new FixedEnvelope(FixedEnvelope::LinearCombination(reinterpret_cast(envelopes), intensities, count))); } void sortEnvelopeByMass(void* envelope) { reinterpret_cast(envelope)->sort_by_mass(); } void sortEnvelopeByProb(void* envelope) { reinterpret_cast(envelope)->sort_by_prob(); } void freeReleasedArray(void* array) { free(array); } void parse_fasta_c(const char* fasta, int atomCounts[6]) { // Same thing, only this time with C linkage parse_fasta(fasta, atomCounts); } } // extern "C" ends here IsoSpecR/src/marginalTrek++.cpp0000644000176200001440000004450513734351263016021 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "platform.h" #include "marginalTrek++.h" #include "conf.h" #include "allocator.h" #include "operators.h" #include "summator.h" #include "element_tables.h" #include "misc.h" namespace IsoSpec { //! Find one of the most probable subisotopologues. /*! The algorithm uses the hill-climbing algorithm. It starts from a subisotopologue close to the mean of the underlying multinomial distribution. There might be more than one modes, in case of which this function will return only one of them, close to the mean. \param atomCnt */ void writeInitialConfiguration(const int atomCnt, const int isotopeNo, const double* lprobs, int* res) { /*! Here we perform hill climbing to the mode of the marginal distribution (the subisotopologue distribution). We start from the point close to the mean of the underlying multinomial distribution. */ // This approximates the mode (heuristics: the mean is close to the mode). for(int i = 0; i < isotopeNo; ++i) res[i] = static_cast( atomCnt * exp(lprobs[i]) ) + 1; // The number of assigned atoms above. int s = 0; for(int i = 0; i < isotopeNo; ++i) s += res[i]; int diff = atomCnt - s; // Too little: enlarging fist index. if( diff > 0 ){ res[0] += diff; } // Too much: trying to redistribute the difference: hopefully the first element is the largest. if( diff < 0 ){ diff = abs(diff); int i = 0; while( diff > 0){ int coordDiff = res[i] - diff; if( coordDiff >= 0 ){ res[i] -= diff; diff = 0; } else { res[i] = 0; i++; diff = abs(coordDiff); } } } // What we computed so far will be very close to the mode: hillclimb the rest of the way bool modified = true; double LP = unnormalized_logProb(res, lprobs, isotopeNo); double NLP; while(modified) { modified = false; for(int ii = 0; ii < isotopeNo; ii++) for(int jj = 0; jj < isotopeNo; jj++) if(ii != jj && res[ii] > 0) { res[ii]--; res[jj]++; NLP = unnormalized_logProb(res, lprobs, isotopeNo); if(NLP > LP || (NLP == LP && ii > jj)) { modified = true; LP = NLP; } else { res[ii]++; res[jj]--; } } } } double* getMLogProbs(const double* probs, int isoNo) { /*! Here we order the processor to round the numbers up rather than down. Rounding down could result in the algorithm falling in an infinite loop because of the numerical instability of summing. */ for(int ii = 0; ii < isoNo; ii++) if(probs[ii] <= 0.0 || probs[ii] > 1.0) throw std::invalid_argument("All isotope probabilities p must fulfill: 0.0 < p <= 1.0"); double* ret = new double[isoNo]; // here we change the table of probabilities and log it. for(int i = 0; i < isoNo; i++) { ret[i] = log(probs[i]); for(int j = 0; j < ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES; j++) if(elem_table_probability[j] == probs[i]) { ret[i] = elem_table_log_probability[j]; break; } } return ret; } double get_loggamma_nominator(int x) { // calculate log gamma of the nominator calculated in the binomial exression. double ret = lgamma(x+1); return ret; } int verify_atom_cnt(int atomCnt) { #if !ISOSPEC_BUILDING_OPENMS if(ISOSPEC_G_FACT_TABLE_SIZE-1 <= atomCnt) throw std::length_error("Subisotopologue too large, size limit (that is, the maximum number of atoms of a single element in a molecule) is: " + std::to_string(ISOSPEC_G_FACT_TABLE_SIZE-1)); #endif return atomCnt; } Marginal::Marginal( const double* _masses, const double* _probs, int _isotopeNo, int _atomCnt ) : disowned(false), isotopeNo(_isotopeNo), atomCnt(verify_atom_cnt(_atomCnt)), atom_lProbs(getMLogProbs(_probs, isotopeNo)), atom_masses(array_copy(_masses, _isotopeNo)), loggamma_nominator(get_loggamma_nominator(_atomCnt)), mode_conf(nullptr) // Deliberately not initializing mode_lprob {} Marginal::Marginal(const Marginal& other) : disowned(false), isotopeNo(other.isotopeNo), atomCnt(other.atomCnt), atom_lProbs(array_copy(other.atom_lProbs, isotopeNo)), atom_masses(array_copy(other.atom_masses, isotopeNo)), loggamma_nominator(other.loggamma_nominator) { if(other.mode_conf == nullptr) { mode_conf = nullptr; // Deliberately not initializing mode_lprob. In this state other.mode_lprob is uninitialized too. } else { mode_conf = array_copy(other.mode_conf, isotopeNo); mode_lprob = other.mode_lprob; } } // the move-constructor: used in the specialization of the marginal. Marginal::Marginal(Marginal&& other) : disowned(other.disowned), isotopeNo(other.isotopeNo), atomCnt(other.atomCnt), atom_lProbs(other.atom_lProbs), atom_masses(other.atom_masses), loggamma_nominator(other.loggamma_nominator) { other.disowned = true; if(other.mode_conf == nullptr) { mode_conf = nullptr; // Deliberately not initializing mode_lprob. In this state other.mode_lprob is uninitialized too. } else { mode_conf = other.mode_conf; mode_lprob = other.mode_lprob; } } Marginal::~Marginal() { if(!disowned) { delete[] atom_masses; delete[] atom_lProbs; delete[] mode_conf; } } Conf Marginal::computeModeConf() const { Conf res = new int[isotopeNo]; writeInitialConfiguration(atomCnt, isotopeNo, atom_lProbs, res); return res; } void Marginal::setupMode() { ISOSPEC_IMPOSSIBLE(mode_conf != nullptr); mode_conf = computeModeConf(); mode_lprob = logProb(mode_conf); } double Marginal::getLightestConfMass() const { double ret_mass = std::numeric_limits::infinity(); for(unsigned int ii = 0; ii < isotopeNo; ii++) if( ret_mass > atom_masses[ii] ) ret_mass = atom_masses[ii]; return ret_mass*atomCnt; } double Marginal::getHeaviestConfMass() const { double ret_mass = 0.0; for(unsigned int ii = 0; ii < isotopeNo; ii++) if( ret_mass < atom_masses[ii] ) ret_mass = atom_masses[ii]; return ret_mass*atomCnt; } double Marginal::getMonoisotopicConfMass() const { double found_prob = -std::numeric_limits::infinity(); double found_mass = 0.0; // to avoid uninitialized var warning for(unsigned int ii = 0; ii < isotopeNo; ii++) if( found_prob < atom_lProbs[ii] ) { found_prob = atom_lProbs[ii]; found_mass = atom_masses[ii]; } return found_mass*atomCnt; } double Marginal::getAtomAverageMass() const { double ret = 0.0; for(unsigned int ii = 0; ii < isotopeNo; ii++) ret += exp(atom_lProbs[ii]) * atom_masses[ii]; return ret; } double Marginal::variance() const { double ret = 0.0; double mean = getAtomAverageMass(); for(size_t ii = 0; ii < isotopeNo; ii++) { double msq = atom_masses[ii] - mean; ret += exp(atom_lProbs[ii]) * msq * msq; } return ret * atomCnt; } double Marginal::getLogSizeEstimate(double logEllipsoidRadius) const { if(isotopeNo <= 1) return -std::numeric_limits::infinity(); const double i = static_cast(isotopeNo); const double k = i - 1.0; const double n = static_cast(atomCnt); double sum_lprobs = 0.0; for(int jj = 0; jj < i; jj++) sum_lprobs += atom_lProbs[jj]; double log_V_simplex = k * log(n) - lgamma(i); double log_N_simplex = lgamma(n+i) - lgamma(n+1.0) - lgamma(i); double log_V_ellipsoid = (k * (log(n) + logpi + logEllipsoidRadius) + sum_lprobs) * 0.5 - lgamma((i+1)*0.5); return log_N_simplex + log_V_ellipsoid - log_V_simplex; } // this is roughly an equivalent of IsoSpec-Threshold-Generator MarginalTrek::MarginalTrek( Marginal&& m, int tabSize, int ) : Marginal(std::move(m)), current_count(0), orderMarginal(atom_lProbs, isotopeNo), pq(), allocator(isotopeNo, tabSize) { int* initialConf = allocator.makeCopy(mode_conf); pq.push({unnormalized_logProb(mode_conf), initialConf}); current_count = 0; add_next_conf(); } bool MarginalTrek::add_next_conf() { /*! Add next configuration. If visited all, return false. */ if(pq.size() < 1) return false; double logprob = pq.top().first + loggamma_nominator; Conf topConf = pq.top().second; pq.pop(); ++current_count; _confs.push_back(topConf); _conf_masses.push_back(calc_mass(topConf, atom_masses, isotopeNo)); _conf_lprobs.push_back(logprob); for( unsigned int j = 0; j < isotopeNo; ++j ) { if( topConf[j] > mode_conf[j]) continue; if( topConf[j] > 0 ) { for( unsigned int i = 0; i < isotopeNo; ++i ) { if( topConf[i] < mode_conf[i] ) continue; // Growing index different than decreasing one AND // Remain on simplex condition. if( i != j ){ Conf acceptedCandidate = allocator.makeCopy(topConf); ++acceptedCandidate[i]; --acceptedCandidate[j]; double new_prob = unnormalized_logProb(acceptedCandidate); pq.push({new_prob, acceptedCandidate}); } if( topConf[i] > mode_conf[i] ) break; } } if( topConf[j] < mode_conf[j] ) break; } return true; } MarginalTrek::~MarginalTrek() { } PrecalculatedMarginal::PrecalculatedMarginal(Marginal&& m, double lCutOff, bool sort, int tabSize, int ) : Marginal(std::move(m)), allocator(isotopeNo, tabSize) { Conf currentConf = allocator.makeCopy(mode_conf); if(logProb(currentConf) >= lCutOff) { configurations.push_back(currentConf); lProbs.push_back(mode_lprob); } unsigned int idx = 0; std::unique_ptr prob_partials(new double[isotopeNo]); std::unique_ptr prob_part_acc(new double[isotopeNo+1]); prob_part_acc[0] = loggamma_nominator; while(idx < configurations.size()) { currentConf = configurations[idx]; idx++; for(size_t ii = 0; ii < isotopeNo; ii++) prob_partials[ii] = minuslogFactorial(currentConf[ii]) + currentConf[ii] * atom_lProbs[ii]; for(unsigned int ii = 0; ii < isotopeNo; ii++ ) { if(currentConf[ii] > mode_conf[ii]) continue; if(currentConf[ii] != 0) { double prev_partial_ii = prob_partials[ii]; currentConf[ii]--; prob_partials[ii] = minuslogFactorial(currentConf[ii]) + currentConf[ii] * atom_lProbs[ii]; for(unsigned int jj = 0; jj < isotopeNo; jj++ ) { prob_part_acc[jj+1] = prob_part_acc[jj] + prob_partials[jj]; if(currentConf[jj] < mode_conf[jj]) continue; if( ii != jj ) { double logp = prob_part_acc[jj] + minuslogFactorial(1+currentConf[jj]) + (1+currentConf[jj]) * atom_lProbs[jj]; for(size_t kk = jj+1; kk < isotopeNo; kk++) logp += prob_partials[kk]; if (logp >= lCutOff) { auto tmp = allocator.makeCopy(currentConf); tmp[jj]++; configurations.push_back(tmp); lProbs.push_back(logp); } } else prob_part_acc[jj+1] = prob_part_acc[jj] + prob_partials[jj]; if (currentConf[jj] > mode_conf[jj]) break; } currentConf[ii]++; prob_partials[ii] = prev_partial_ii; } if(currentConf[ii] < mode_conf[ii]) break; } } no_confs = configurations.size(); confs = configurations.data(); if(sort && no_confs > 0) { std::unique_ptr order_arr(get_inverse_order(lProbs.data(), no_confs)); impose_order(order_arr.get(), no_confs, lProbs.data(), confs); } probs = new double[no_confs]; masses = new double[no_confs]; for(unsigned int ii = 0; ii < no_confs; ii++) { probs[ii] = exp(lProbs[ii]); masses[ii] = calc_mass(confs[ii], atom_masses, isotopeNo); } lProbs.push_back(-std::numeric_limits::infinity()); } PrecalculatedMarginal::~PrecalculatedMarginal() { if(masses != nullptr) delete[] masses; if(probs != nullptr) delete[] probs; } LayeredMarginal::LayeredMarginal(Marginal&& m, int tabSize, int) : Marginal(std::move(m)), current_threshold(1.0), allocator(isotopeNo, tabSize), equalizer(isotopeNo), keyHasher(isotopeNo) { fringe.push_back(mode_conf); lProbs.push_back(std::numeric_limits::infinity()); fringe_unn_lprobs.push_back(unnormalized_logProb(mode_conf)); lProbs.push_back(-std::numeric_limits::infinity()); guarded_lProbs = lProbs.data()+1; } bool LayeredMarginal::extend(double new_threshold, bool do_sort) { new_threshold -= loggamma_nominator; if(fringe.empty()) return false; lProbs.pop_back(); // Remove the +inf guardian pod_vector new_fringe; pod_vector new_fringe_unn_lprobs; while(!fringe.empty()) { Conf currentConf = fringe.back(); fringe.pop_back(); double opc = fringe_unn_lprobs.back(); fringe_unn_lprobs.pop_back(); if(opc < new_threshold) { new_fringe.push_back(currentConf); new_fringe_unn_lprobs.push_back(opc); } else { configurations.push_back(currentConf); lProbs.push_back(opc+loggamma_nominator); for(unsigned int ii = 0; ii < isotopeNo; ii++ ) { if(currentConf[ii] > mode_conf[ii]) continue; if(currentConf[ii] > 0) { currentConf[ii]--; for(unsigned int jj = 0; jj < isotopeNo; jj++ ) { if(currentConf[jj] < mode_conf[jj]) continue; if( ii != jj ) { Conf nc = allocator.makeCopy(currentConf); nc[jj]++; double lpc = unnormalized_logProb(nc); if(lpc >= new_threshold) { fringe.push_back(nc); fringe_unn_lprobs.push_back(lpc); } else { new_fringe.push_back(nc); new_fringe_unn_lprobs.push_back(lpc); } } if(currentConf[jj] > mode_conf[jj]) break; } currentConf[ii]++; } if(currentConf[ii] < mode_conf[ii]) break; } } } current_threshold = new_threshold; fringe.swap(new_fringe); fringe_unn_lprobs.swap(new_fringe_unn_lprobs); if(do_sort) { size_t to_sort_size = configurations.size() - probs.size(); if(to_sort_size > 0) { std::unique_ptr order_arr(get_inverse_order(lProbs.data()+1+probs.size(), to_sort_size)); double* P = lProbs.data()+1+probs.size(); Conf* C = configurations.data()+probs.size(); size_t* O = order_arr.get(); impose_order(O, to_sort_size, P, C); } } if(probs.capacity() * 2 < configurations.size() + 2) { // Reserve space for new values probs.reserve(configurations.size()); masses.reserve(configurations.size()); } // Otherwise we're growing slowly enough that standard reallocations on push_back work better - we waste some extra memory // but don't reallocate on every call // printVector(lProbs); for(unsigned int ii = probs.size(); ii < configurations.size(); ii++) { probs.push_back(exp(lProbs[ii+1])); masses.push_back(calc_mass(configurations[ii], atom_masses, isotopeNo)); } lProbs.push_back(-std::numeric_limits::infinity()); // Restore guardian guarded_lProbs = lProbs.data()+1; // Vector might have reallocated its backing storage return true; } double LayeredMarginal::get_min_mass() const { double ret = std::numeric_limits::infinity(); for(pod_vector::const_iterator it = masses.cbegin(); it != masses.cend(); ++it) if(*it < ret) ret = *it; return ret; } double LayeredMarginal::get_max_mass() const { double ret = -std::numeric_limits::infinity(); for(pod_vector::const_iterator it = masses.cbegin(); it != masses.cend(); ++it) if(*it > ret) ret = *it; return ret; } } // namespace IsoSpec IsoSpecR/src/fasta.h0000644000176200001440000000237413660733247014020 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once namespace IsoSpec{ // We will work with C H N O S Se tuples */ extern const int aa_isotope_numbers[6]; extern const double aa_elem_masses[19]; extern const double aa_elem_nominal_masses[19]; extern const double aa_elem_probabilities[19]; extern const int aa_symbol_to_elem_counts[256*6]; inline void parse_fasta(const char* fasta, int atomCounts[6]) { memset(atomCounts, 0, sizeof(decltype(atomCounts[0]))*6); for(size_t idx = 0; fasta[idx] != '\0'; ++idx) { const int* counts = &aa_symbol_to_elem_counts[fasta[idx]*6]; for(int ii = 0; ii < 6; ++ii) atomCounts[ii] += counts[ii]; } } } // namespace IsoSpec IsoSpecR/src/allocator.h0000644000176200001440000000307113673426762014702 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once #include #include "conf.h" #include "pod_vector.h" namespace IsoSpec { template inline void copyConf( const T* source, T* destination, int dim ){ memcpy(destination, source, dim*sizeof(T)); } template class Allocator { private: T* currentTab; int currentId; const int dim, tabSize; pod_vector prevTabs; public: explicit Allocator(const int dim, const int tabSize = 10000); ~Allocator(); Allocator(const Allocator& other) = delete; Allocator& operator=(const Allocator& other) = delete; void shiftTables(); inline T* newConf() { currentId++; if (currentId >= tabSize) shiftTables(); return &(currentTab[ currentId * dim ]); } inline T* makeCopy(const T* conf) { T* currentPlace = newConf(); copyConf( conf, currentPlace, dim ); return currentPlace; } }; } // namespace IsoSpec IsoSpecR/src/RcppExports.cpp0000644000176200001440000000272313507710304015531 0ustar liggesusers// Generated by using Rcpp::compileAttributes() -> do not edit by hand // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 #include using namespace Rcpp; // Rinterface NumericMatrix Rinterface(const IntegerVector& molecule, const DataFrame& isotopes, double stopCondition, int algo, int tabSize, int hashSize, double step, bool showCounts, bool trim); RcppExport SEXP _IsoSpecR_Rinterface(SEXP moleculeSEXP, SEXP isotopesSEXP, SEXP stopConditionSEXP, SEXP algoSEXP, SEXP tabSizeSEXP, SEXP hashSizeSEXP, SEXP stepSEXP, SEXP showCountsSEXP, SEXP trimSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< const IntegerVector& >::type molecule(moleculeSEXP); Rcpp::traits::input_parameter< const DataFrame& >::type isotopes(isotopesSEXP); Rcpp::traits::input_parameter< double >::type stopCondition(stopConditionSEXP); Rcpp::traits::input_parameter< int >::type algo(algoSEXP); Rcpp::traits::input_parameter< int >::type tabSize(tabSizeSEXP); Rcpp::traits::input_parameter< int >::type hashSize(hashSizeSEXP); Rcpp::traits::input_parameter< double >::type step(stepSEXP); Rcpp::traits::input_parameter< bool >::type showCounts(showCountsSEXP); Rcpp::traits::input_parameter< bool >::type trim(trimSEXP); rcpp_result_gen = Rcpp::wrap(Rinterface(molecule, isotopes, stopCondition, algo, tabSize, hashSize, step, showCounts, trim)); return rcpp_result_gen; END_RCPP } IsoSpecR/src/btrd.h0000644000176200001440000001354013663263626013654 0ustar liggesusers/* This file was taken from Boost, as permitted by Boost licence, * with slight modifications. The reason is: we don't want to introduce * dependency on the whole Boost just for this one thing. * * Source: boost random/binomial_distribution.hpp header file, at version 1.71 * * Copyright Steven Watanabe 2010 * Distributed under the Boost Software License, Version 1.0. (See * accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) * * See http://www.boost.org for most recent version including documentation. * */ #pragma once #include "isoMath.h" #include #include #include #include namespace IsoSpec { typedef double RealType; typedef int64_t IntType; static const RealType btrd_binomial_table[10] = { 0.08106146679532726, 0.04134069595540929, 0.02767792568499834, 0.02079067210376509, 0.01664469118982119, 0.01387612882307075, 0.01189670994589177, 0.01041126526197209, 0.009255462182712733, 0.008330563433362871 }; /** * The binomial distribution is an integer valued distribution with * two parameters, @c t and @c p. The values of the distribution * are within the range [0,t]. * * The distribution function is * \f$\displaystyle P(k) = {t \choose k}p^k(1-p)^{t-k}\f$. * * The algorithm used is the BTRD algorithm described in * * @blockquote * "The generation of binomial random variates", Wolfgang Hormann, * Journal of Statistical Computation and Simulation, Volume 46, * Issue 1 & 2 April 1993 , pages 101 - 110 * @endblockquote */ // computes the correction factor for the Stirling approximation // for log(k!) static RealType fc(IntType k) { if(k < 10) { return btrd_binomial_table[k]; } else { RealType ikp1 = RealType(1) / (k + 1); return (RealType(1)/12 - (RealType(1)/360 - (RealType(1)/1260)*(ikp1*ikp1))*(ikp1*ikp1))*ikp1; } } IntType btrd(IntType _t, RealType p, IntType m, std::mt19937& urng = random_gen) { using std::floor; using std::abs; using std::log; RealType btrd_r = p/(1-p); RealType btrd_nr = (_t+1)*btrd_r; RealType btrd_npq = _t*p*(1-p); RealType sqrt_npq = sqrt(btrd_npq); RealType btrd_b = 1.15 + 2.53 * sqrt_npq; RealType btrd_a = -0.0873 + 0.0248*btrd_b + 0.01*p; RealType btrd_c = _t*p + 0.5; RealType btrd_alpha = (2.83 + 5.1/btrd_b) * sqrt_npq; RealType btrd_v_r = 0.92 - 4.2/btrd_b; RealType btrd_u_rv_r = 0.86*btrd_v_r; while(true) { RealType u; RealType v = stdunif(urng); if(v <= btrd_u_rv_r) { u = v/btrd_v_r - 0.43; return static_cast(floor( (2*btrd_a/(0.5 - abs(u)) + btrd_b)*u + btrd_c)); } if(v >= btrd_v_r) { u = stdunif(urng) - 0.5; } else { u = v/btrd_v_r - 0.93; u = ((u < 0)? -0.5 : 0.5) - u; v = stdunif(urng) * btrd_v_r; } RealType us = 0.5 - abs(u); IntType k = static_cast(floor((2*btrd_a/us + btrd_b)*u + btrd_c)); if(k < 0 || k > _t) continue; v = v*btrd_alpha/(btrd_a/(us*us) + btrd_b); RealType km = abs(k - m); if(km <= 15) { RealType f = 1; if(m < k) { IntType i = m; do { ++i; f = f*(btrd_nr/i - btrd_r); } while(i != k); } else if(m > k) { IntType i = k; do { ++i; v = v*(btrd_nr/i - btrd_r); } while(i != m); } if(v <= f) return k; else continue; } else { // final acceptance/rejection v = log(v); RealType rho = (km/btrd_npq)*(((km/3. + 0.625)*km + 1./6)/btrd_npq + 0.5); RealType t = -km*km/(2*btrd_npq); if(v < t - rho) return k; if(v > t + rho) continue; IntType nm = _t - m + 1; RealType h = (m + 0.5)*log((m + 1)/(btrd_r*nm)) + fc(m) + fc(_t - m); IntType nk = _t - k + 1; if(v <= h + (_t+1)*log(static_cast(nm)/nk) + (k + 0.5)*log(nk*btrd_r/(k+1)) - fc(k) - fc(_t - k)) { return k; } else { continue; } } } } IntType invert(IntType t, RealType p, std::mt19937& urng = random_gen) { RealType q = 1 - p; RealType s = p / q; RealType a = (t + 1) * s; RealType r = pow((1 - p), static_cast(t)); RealType u = stdunif(urng); IntType x = 0; while(u > r) { u = u - r; ++x; RealType r1 = ((a/x) - s) * r; // If r gets too small then the round-off error // becomes a problem. At this point, p(i) is // decreasing exponentially, so if we just call // it 0, it's close enough. Note that the // minimum value of q_n is about 1e-7, so we // may need to be a little careful to make sure that // we don't terminate the first time through the loop // for float. (Hence the test that r is decreasing) if(r1 < std::numeric_limits::epsilon() && r1 < r) { break; } r = r1; } return x; } IntType boost_binomial_distribution_variate(IntType t_arg, RealType p_arg, std::mt19937& urng = random_gen) { bool other_side = p_arg > 0.5; RealType fake_p = other_side ? 1.0 - p_arg : p_arg; IntType m = static_cast((t_arg+1)*fake_p); IntType result; if(m < 11) result = invert(t_arg, fake_p, urng); else result = btrd(t_arg, fake_p, m, urng); if(other_side) return t_arg - result; else return result; } } // namespace IsoSpec IsoSpecR/src/cwrapper.h0000644000176200001440000001267313737365546014560 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once #define ISOSPEC_ALGO_LAYERED 0 #define ISOSPEC_ALGO_ORDERED 1 #define ISOSPEC_ALGO_THRESHOLD_ABSOLUTE 2 #define ISOSPEC_ALGO_THRESHOLD_RELATIVE 3 #define ISOSPEC_ALGO_LAYERED_ESTIMATE 4 #ifdef __cplusplus extern "C" { #else #include #endif void * setupIso(int dimNumber, const int* isotopeNumbers, const int* atomCounts, const double* isotopeMasses, const double* isotopeProbabilities); void * isoFromFasta(const char* fasta, bool use_nominal_masses, bool add_water); double getLightestPeakMassIso(void* iso); double getHeaviestPeakMassIso(void* iso); double getMonoisotopicPeakMassIso(void* iso); double getModeLProbIso(void* iso); double getModeMassIso(void* iso); double getTheoreticalAverageMassIso(void* iso); double getIsoVariance(void* iso); double getIsoStddev(void* iso); double* getMarginalLogSizeEstimates(void* iso, double target_total_prob); void deleteIso(void* iso); #define ISOSPEC_C_FN_HEADER(generatorType, dataType, method)\ dataType method##generatorType(void* generator); #define ISOSPEC_C_FN_HEADER_GET_CONF_SIGNATURE(generatorType)\ void method##generatorType(void* generator); #define ISOSPEC_C_FN_HEADERS(generatorType)\ ISOSPEC_C_FN_HEADER(generatorType, double, mass) \ ISOSPEC_C_FN_HEADER(generatorType, double, lprob) \ ISOSPEC_C_FN_HEADER(generatorType, double, prob) \ ISOSPEC_C_FN_HEADER_GET_CONF_SIGNATURE(generatorType) \ ISOSPEC_C_FN_HEADER(generatorType, bool, advanceToNextConfiguration) \ ISOSPEC_C_FN_HEADER(generatorType, void, delete) // ______________________________________________________THRESHOLD GENERATOR void* setupIsoThresholdGenerator(void* iso, double threshold, bool _absolute, int _tabSize, int _hashSize, bool reorder_marginals); ISOSPEC_C_FN_HEADERS(IsoThresholdGenerator) // ______________________________________________________LAYERED GENERATOR void* setupIsoLayeredGenerator(void* iso, int _tabSize, int _hashSize, bool reorder_marginals, double t_prob_hint); ISOSPEC_C_FN_HEADERS(IsoLayeredGenerator) // ______________________________________________________ORDERED GENERATOR void* setupIsoOrderedGenerator(void* iso, int _tabSize, int _hashSize); ISOSPEC_C_FN_HEADERS(IsoOrderedGenerator) void* setupIsoStochasticGenerator(void* iso, size_t no_molecules, double precision, double beta_bias); ISOSPEC_C_FN_HEADERS(IsoStochasticGenerator) void* setupThresholdFixedEnvelope(void* iso, double threshold, bool absolute, bool get_confs); void* setupTotalProbFixedEnvelope(void* iso, double taget_coverage, bool optimize, bool get_confs); void* setupStochasticFixedEnvelope(void* iso, size_t no_molecules, double precision, double beta_bias, bool get_confs); void* setupBinnedFixedEnvelope(void* iso, double target_total_prob, double bin_width, double bin_middle); void freeReleasedArray(void* array); void* setupFixedEnvelope(double* masses, double* probs, size_t size, bool mass_sorted, bool prob_sorted, double total_prob); void deleteFixedEnvelope(void* tabulator, bool releaseEverything); const double* massesFixedEnvelope(void* tabulator); const double* probsFixedEnvelope(void* tabulator); const int* confsFixedEnvelope(void* tabulator); size_t confs_noFixedEnvelope(void* tabulator); double empiricAverageMass(void* tabulator); double empiricVariance(void* tabulator); double empiricStddev(void* tabulator); double wassersteinDistance(void* tabulator1, void* tabulator2); double orientedWassersteinDistance(void* tabulator1, void* tabulator2); void* addEnvelopes(void* tabulator1, void* tabulator2); void* convolveEnvelopes(void* tabulator1, void* tabulator2); double getTotalProbOfEnvelope(void* envelope); void scaleEnvelope(void* envelope, double factor); void normalizeEnvelope(void* envelope); void* binnedEnvelope(void* envelope, double width, double middle); void* linearCombination(void* const * const envelopes, const double* intensities, size_t count); void sortEnvelopeByMass(void* envelope); void sortEnvelopeByProb(void* envelope); void parse_fasta_c(const char* fasta, int atomCounts[6]); #ifdef __cplusplus } #endif IsoSpecR/src/IsoSpecR_init.c0000644000176200001440000000074213507710304015411 0ustar liggesusers#include #include #include // for NULL #include /* .Call calls */ extern SEXP _IsoSpecR_Rinterface(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); static const R_CallMethodDef CallEntries[] = { {"_IsoSpecR_Rinterface", (DL_FUNC) &_IsoSpecR_Rinterface, 9}, {NULL, NULL, 0} }; void R_init_IsoSpecR(DllInfo *dll) { R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); } IsoSpecR/src/conf.h0000644000176200001440000000171213674217136013641 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #pragma once namespace IsoSpec { typedef int* Conf; struct ProbAndConfPtr { // For some reason std::pair isn't trivially copyable... double first; Conf second; ProbAndConfPtr(double p, Conf c) : first(p), second(c) {} bool operator<(const ProbAndConfPtr& other) const { return first < other.first; } }; } // namespace IsoSpec IsoSpecR/src/element_tables.cpp0000644000176200001440000015335413651127333016236 0ustar liggesusers/* * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek. * * This file is part of IsoSpec. * * IsoSpec is free software: you can redistribute it and/or modify * it under the terms of the Simplified ("2-clause") BSD licence. * * IsoSpec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the Simplified BSD Licence * along with IsoSpec. If not, see . */ #include "element_tables.h" namespace IsoSpec { #ifdef __cplusplus extern "C" { #endif const size_t isospec_number_of_isotopic_entries = ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES; const int elem_table_ID [ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES] = { 1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6, 7, 7, 8, 8, 8, 9, 10, 10, 10, 11, 12, 12, 12, 13, 14, 14, 14, 15, 16, 16, 16, 16, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 20, 20, 20, 21, 22, 22, 22, 22, 22, 23, 23, 24, 24, 24, 24, 25, 26, 26, 26, 26, 27, 28, 28, 28, 28, 28, 29, 29, 30, 30, 30, 30, 30, 31, 31, 32, 32, 32, 32, 32, 33, 34, 34, 34, 34, 34, 34, 35, 35, 36, 36, 36, 36, 36, 36, 37, 37, 38, 38, 38, 38, 39, 40, 40, 40, 40, 40, 41, 42, 42, 42, 42, 42, 42, 42, 44, 44, 44, 44, 44, 44, 44, 45, 46, 46, 46, 46, 46, 46, 47, 47, 48, 48, 48, 48, 48, 48, 48, 48, 49, 49, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 51, 52, 52, 52, 52, 52, 52, 52, 52, 53, 54, 54, 54, 54, 54, 54, 54, 54, 54, 55, 56, 56, 56, 56, 56, 56, 56, 57, 57, 58, 58, 58, 58, 59, 60, 60, 60, 60, 60, 60, 60, 62, 62, 62, 62, 62, 62, 62, 63, 63, 64, 64, 64, 64, 64, 64, 64, 65, 66, 66, 66, 66, 66, 66, 66, 67, 68, 68, 68, 68, 68, 68, 69, 70, 70, 70, 70, 70, 70, 70, 71, 71, 72, 72, 72, 72, 72, 72, 73, 73, 74, 74, 74, 74, 74, 75, 75, 76, 76, 76, 76, 76, 76, 76, 77, 77, 78, 78, 78, 78, 78, 78, 79, 80, 80, 80, 80, 80, 80, 80, 81, 81, 82, 82, 82, 82, 83, 92, 92, 92, 90, 91, 1000, // Electron 1001, // Missing electron 1002, // Protonation 1002, // Protonation (Deuterium) }; const int elem_table_atomicNo [ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES] = { 1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6, 7, 7, 8, 8, 8, 9, 10, 10, 10, 11, 12, 12, 12, 13, 14, 14, 14, 15, 16, 16, 16, 16, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 20, 20, 20, 21, 22, 22, 22, 22, 22, 23, 23, 24, 24, 24, 24, 25, 26, 26, 26, 26, 27, 28, 28, 28, 28, 28, 29, 29, 30, 30, 30, 30, 30, 31, 31, 32, 32, 32, 32, 32, 33, 34, 34, 34, 34, 34, 34, 35, 35, 36, 36, 36, 36, 36, 36, 37, 37, 38, 38, 38, 38, 39, 40, 40, 40, 40, 40, 41, 42, 42, 42, 42, 42, 42, 42, 44, 44, 44, 44, 44, 44, 44, 45, 46, 46, 46, 46, 46, 46, 47, 47, 48, 48, 48, 48, 48, 48, 48, 48, 49, 49, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 51, 52, 52, 52, 52, 52, 52, 52, 52, 53, 54, 54, 54, 54, 54, 54, 54, 54, 54, 55, 56, 56, 56, 56, 56, 56, 56, 57, 57, 58, 58, 58, 58, 59, 60, 60, 60, 60, 60, 60, 60, 62, 62, 62, 62, 62, 62, 62, 63, 63, 64, 64, 64, 64, 64, 64, 64, 65, 66, 66, 66, 66, 66, 66, 66, 67, 68, 68, 68, 68, 68, 68, 69, 70, 70, 70, 70, 70, 70, 70, 71, 71, 72, 72, 72, 72, 72, 72, 73, 73, 74, 74, 74, 74, 74, 75, 75, 76, 76, 76, 76, 76, 76, 76, 77, 77, 78, 78, 78, 78, 78, 78, 79, 80, 80, 80, 80, 80, 80, 80, 81, 81, 82, 82, 82, 82, 83, 92, 92, 92, 90, 91, 0, 0, 1, 1, }; const double elem_table_mass [ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES] = { 1.00782503227, 2.01410177819, 3.016029322, 4.00260325414, 6.0151228871, 7.016003443, 9.01218316, 10.0129373, 11.0093053, 12, 13.0033548352, 14.0030740042, 15.0001088994, 15.9949146202, 16.9991317576, 17.9991596137, 18.9984031637, 19.992440182, 20.99384673, 21.99138512, 22.989769282, 23.985041701, 24.98583703, 25.98259302, 26.98153858, 27.9769265353, 28.9764946653, 29.973770012, 30.9737619986, 31.9720711741, 32.9714589101, 33.96786703, 35.9670812, 34.96885273, 36.96590264, 35.96754512, 37.9627322, 39.962383122, 38.963706493, 39.96399824, 40.961825263, 39.96259092, 41.9586181, 42.9587662, 43.9554822, 45.953692, 47.95252289, 44.9559086, 45.9526283, 46.9517593, 47.9479423, 48.9478663, 49.9447873, 49.9471567, 50.9439577, 49.9460427, 51.9405064, 52.9406484, 53.9388794, 54.9380443, 53.9396093, 55.9349363, 56.9353933, 57.9332743, 58.9331944, 57.9353423, 59.9307863, 60.9310563, 61.9283454, 63.9279674, 62.9295984, 64.9277906, 63.9291426, 65.9260347, 66.9271287, 67.9248457, 69.925322, 68.9255749, 70.9247037, 69.9242497, 71.92207586, 72.92345904, 73.921177761, 75.92140272, 74.9215957, 73.92247591, 75.91921372, 76.91991426, 77.9173092, 79.9165229, 81.9167001, 78.9183381, 80.9162901, 77.9203656, 79.9163786, 81.9134837, 82.9141272, 83.911497733, 85.910610633, 84.911789743, 86.909180536, 83.9134199, 85.9092619, 86.9088789, 87.9056139, 88.905842, 89.904702, 90.905642, 91.905032, 93.906312, 95.908272, 92.906372, 91.9068086, 93.9050853, 94.9058393, 95.9046763, 96.9060183, 97.9054053, 99.9074728, 95.9075903, 97.905296, 98.9059348, 99.9042148, 100.9055779, 101.9043449, 103.905432, 102.905502, 101.905602, 103.9040311, 104.9050809, 105.9034809, 107.9038929, 109.9051726, 106.905092, 108.9047551, 105.9064609, 107.9041839, 109.9030074, 110.9041834, 111.9027634, 112.9044083, 113.9033653, 115.9047632, 112.9040627, 114.903878789, 111.9048244, 113.9027837, 114.90334471, 115.9017431, 116.9029543, 117.9016073, 118.9033116, 119.9022027, 121.903442, 123.9052778, 120.903812, 122.904212, 119.904062, 121.903041, 122.904271, 123.902821, 124.904431, 125.903311, 127.9044617, 129.906222759, 126.904473, 123.905892, 125.904303, 127.9035318, 128.904780864, 129.90350941, 130.9050842, 131.904155094, 133.9053957, 135.907214488, 132.905451967, 129.906322, 131.9050618, 133.9045082, 134.9056882, 135.9045762, 136.9058272, 137.9052472, 137.907123, 138.906362, 135.9071293, 137.905998, 139.905442, 141.909252, 140.907662, 141.907732, 142.909822, 143.910092, 144.912582, 145.913122, 147.916902, 149.920902, 143.912012, 146.914902, 147.914832, 148.917192, 149.917282, 151.919742, 153.922222, 150.919862, 152.921242, 151.919802, 153.920872, 154.922632, 155.922132, 156.923972, 157.924112, 159.927062, 158.925352, 155.924282, 157.924422, 159.925202, 160.926942, 161.926812, 162.928742, 163.929182, 164.930332, 161.928792, 163.929212, 165.930302, 166.932052, 167.932382, 169.935472, 168.934222, 167.933892, 169.934772, 170.936332, 171.936392, 172.938222, 173.938872, 175.942582, 174.940782, 175.942692, 173.940052, 175.941412, 176.943232, 177.943712, 178.945822, 179.946562, 179.947462, 180.948002, 179.946712, 181.9482047, 182.9502237, 183.9509317, 185.954362, 184.9529559, 186.955751, 183.9524891, 185.953841, 186.955751, 187.955841, 188.958142, 189.958442, 191.961482, 190.960592, 192.962922, 189.959934, 191.961042, 193.9626817, 194.9647927, 195.9649527, 197.967892, 196.9665696, 195.965832, 197.9667693, 198.9682813, 199.9683273, 200.9703036, 201.9706436, 203.9734943, 202.9723451, 204.9744281, 203.9730449, 205.9744669, 206.9758979, 207.9766539, 208.980401, 234.040952, 235.043932, 238.050792, 232.038062, 231.035882, 0.000548579909065, // Electron -0.000548579909065, // Missing electron 1.007276466879, // Protonation 2.013553212745, // Protonation (deuterium) }; const double elem_table_massNo [ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES] = { 1.0, 2.0, 3.0, 4.0, 6.0, 7.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 36.0, 35.0, 37.0, 36.0, 38.0, 40.0, 39.0, 40.0, 41.0, 40.0, 42.0, 43.0, 44.0, 46.0, 48.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 50.0, 51.0, 50.0, 52.0, 53.0, 54.0, 55.0, 54.0, 56.0, 57.0, 58.0, 59.0, 58.0, 60.0, 61.0, 62.0, 64.0, 63.0, 65.0, 64.0, 66.0, 67.0, 68.0, 70.0, 69.0, 71.0, 70.0, 72.0, 73.0, 74.0, 76.0, 75.0, 74.0, 76.0, 77.0, 78.0, 80.0, 82.0, 79.0, 81.0, 78.0, 80.0, 82.0, 83.0, 84.0, 86.0, 85.0, 87.0, 84.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 94.0, 96.0, 93.0, 92.0, 94.0, 95.0, 96.0, 97.0, 98.0, 100.0, 96.0, 98.0, 99.0, 100.0, 101.0, 102.0, 104.0, 103.0, 102.0, 104.0, 105.0, 106.0, 108.0, 110.0, 107.0, 109.0, 106.0, 108.0, 110.0, 111.0, 112.0, 113.0, 114.0, 116.0, 113.0, 115.0, 112.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0, 120.0, 122.0, 124.0, 121.0, 123.0, 120.0, 122.0, 123.0, 124.0, 125.0, 126.0, 128.0, 130.0, 127.0, 124.0, 126.0, 128.0, 129.0, 130.0, 131.0, 132.0, 134.0, 136.0, 133.0, 130.0, 132.0, 134.0, 135.0, 136.0, 137.0, 138.0, 138.0, 139.0, 136.0, 138.0, 140.0, 142.0, 141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 148.0, 150.0, 144.0, 147.0, 148.0, 149.0, 150.0, 152.0, 154.0, 151.0, 153.0, 152.0, 154.0, 155.0, 156.0, 157.0, 158.0, 160.0, 159.0, 156.0, 158.0, 160.0, 161.0, 162.0, 163.0, 164.0, 165.0, 162.0, 164.0, 166.0, 167.0, 168.0, 170.0, 169.0, 168.0, 170.0, 171.0, 172.0, 173.0, 174.0, 176.0, 175.0, 176.0, 174.0, 176.0, 177.0, 178.0, 179.0, 180.0, 180.0, 181.0, 180.0, 182.0, 183.0, 184.0, 186.0, 185.0, 187.0, 184.0, 186.0, 187.0, 188.0, 189.0, 190.0, 192.0, 191.0, 193.0, 190.0, 192.0, 194.0, 195.0, 196.0, 198.0, 197.0, 196.0, 198.0, 199.0, 200.0, 201.0, 202.0, 204.0, 203.0, 205.0, 204.0, 206.0, 207.0, 208.0, 209.0, 234.0, 235.0, 238.0, 232.0, 231.0, 0.0, 0.0, 1.0, 2.0, }; const int elem_table_extraNeutrons [ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES] = { 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 4, 0, 2, 0, 2, 4, 0, 1, 2, 0, 2, 3, 4, 6, 8, 0, 0, 1, 2, 3, 4, 0, 1, 0, 2, 3, 4, 0, 0, 2, 3, 4, 0, 0, 2, 3, 4, 6, 0, 2, 0, 2, 3, 4, 6, 0, 2, 0, 2, 3, 4, 6, 0, 0, 2, 3, 4, 6, 8, 0, 2, 0, 2, 4, 5, 6, 8, 0, 2, 0, 2, 3, 4, 0, 0, 1, 2, 4, 6, 0, 0, 2, 3, 4, 5, 6, 8, 0, 2, 3, 4, 5, 6, 8, 0, 0, 2, 3, 4, 6, 8, 0, 2, 0, 2, 4, 5, 6, 7, 8, 10, 0, 2, 0, 2, 3, 4, 5, 6, 7, 8, 10, 12, 0, 2, 0, 2, 3, 4, 5, 6, 8, 10, 0, 0, 2, 4, 5, 6, 7, 8, 10, 12, 0, 0, 2, 4, 5, 6, 7, 8, 0, 1, 0, 2, 4, 6, 0, 0, 1, 2, 3, 4, 6, 8, 0, 3, 4, 5, 6, 8, 10, 0, 2, 0, 2, 3, 4, 5, 6, 8, 0, 0, 2, 4, 5, 6, 7, 8, 0, 0, 2, 4, 5, 6, 8, 0, 0, 2, 3, 4, 5, 6, 8, 0, 1, 0, 2, 3, 4, 5, 6, 0, 1, 0, 2, 3, 4, 6, 0, 2, 0, 2, 3, 4, 5, 6, 8, 0, 2, 0, 2, 4, 5, 6, 8, 0, 0, 2, 3, 4, 5, 6, 8, 0, 2, 0, 2, 3, 4, 0, 1, 2, 5, 0, 0, 0, 0, 0, 1, }; const char* elem_table_element [ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES] = { "hydrogen", "hydrogen", "helium", "helium", "lithium", "lithium", "beryllium", "boron", "boron", "carbon", "carbon", "nitrogen", "nitrogen", "oxygen", "oxygen", "oxygen", "fluorine", "neon", "neon", "neon", "sodium", "magnesium", "magnesium", "magnesium", "aluminium", "silicon", "silicon", "silicon", "phosphorus", "sulfur", "sulfur", "sulfur", "sulfur", "chlorine", "chlorine", "argon", "argon", "argon", "potassium", "potassium", "potassium", "calcium", "calcium", "calcium", "calcium", "calcium", "calcium", "scandium", "titanium", "titanium", "titanium", "titanium", "titanium", "vanadium", "vanadium", "chromium", "chromium", "chromium", "chromium", "manganese", "iron", "iron", "iron", "iron", "cobalt", "nickel", "nickel", "nickel", "nickel", "nickel", "copper", "copper", "zinc", "zinc", "zinc", "zinc", "zinc", "gallium", "gallium", "germanium", "germanium", "germanium", "germanium", "germanium", "arsenic", "selenium", "selenium", "selenium", "selenium", "selenium", "selenium", "bromine", "bromine", "krypton", "krypton", "krypton", "krypton", "krypton", "krypton", "rubidium", "rubidium", "strontium", "strontium", "strontium", "strontium", "yttrium", "zirconium", "zirconium", "zirconium", "zirconium", "zirconium", "niobium", "molybdenum", "molybdenum", "molybdenum", "molybdenum", "molybdenum", "molybdenum", "molybdenum", "ruthenium", "ruthenium", "ruthenium", "ruthenium", "ruthenium", "ruthenium", "ruthenium", "rhodium", "palladium", "palladium", "palladium", "palladium", "palladium", "palladium", "silver", "silver", "cadmium", "cadmium", "cadmium", "cadmium", "cadmium", "cadmium", "cadmium", "cadmium", "indium", "indium", "tin", "tin", "tin", "tin", "tin", "tin", "tin", "tin", "tin", "tin", "antimony", "antimony", "tellurium", "tellurium", "tellurium", "tellurium", "tellurium", "tellurium", "tellurium", "tellurium", "iodine", "xenon", "xenon", "xenon", "xenon", "xenon", "xenon", "xenon", "xenon", "xenon", "caesium", "barium", "barium", "barium", "barium", "barium", "barium", "barium", "lanthanum", "lanthanum", "cerium", "cerium", "cerium", "cerium", "praseodymium", "neodymium", "neodymium", "neodymium", "neodymium", "neodymium", "neodymium", "neodymium", "samarium", "samarium", "samarium", "samarium", "samarium", "samarium", "samarium", "europium", "europium", "gadolinium", "gadolinium", "gadolinium", "gadolinium", "gadolinium", "gadolinium", "gadolinium", "terbium", "dysprosium", "dysprosium", "dysprosium", "dysprosium", "dysprosium", "dysprosium", "dysprosium", "holmium", "erbium", "erbium", "erbium", "erbium", "erbium", "erbium", "thulium", "ytterbium", "ytterbium", "ytterbium", "ytterbium", "ytterbium", "ytterbium", "ytterbium", "lutetium", "lutetium", "hafnium", "hafnium", "hafnium", "hafnium", "hafnium", "hafnium", "tantalum", "tantalum", "tungsten", "tungsten", "tungsten", "tungsten", "tungsten", "rhenium", "rhenium", "osmium", "osmium", "osmium", "osmium", "osmium", "osmium", "osmium", "iridium", "iridium", "platinum", "platinum", "platinum", "platinum", "platinum", "platinum", "gold", "mercury", "mercury", "mercury", "mercury", "mercury", "mercury", "mercury", "thallium", "thallium", "lead", "lead", "lead", "lead", "bismuth", "uranium", "uranium", "uranium", "thorium", "protactinium", "electron", "missing electron", "protonation", "protonation", // with deuteron }; const char* elem_table_symbol [ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES] = { "H", "H", "He", "He", "Li", "Li", "Be", "B", "B", "C", "C", "N", "N", "O", "O", "O", "F", "Ne", "Ne", "Ne", "Na", "Mg", "Mg", "Mg", "Al", "Si", "Si", "Si", "P", "S", "S", "S", "S", "Cl", "Cl", "Ar", "Ar", "Ar", "K", "K", "K", "Ca", "Ca", "Ca", "Ca", "Ca", "Ca", "Sc", "Ti", "Ti", "Ti", "Ti", "Ti", "V", "V", "Cr", "Cr", "Cr", "Cr", "Mn", "Fe", "Fe", "Fe", "Fe", "Co", "Ni", "Ni", "Ni", "Ni", "Ni", "Cu", "Cu", "Zn", "Zn", "Zn", "Zn", "Zn", "Ga", "Ga", "Ge", "Ge", "Ge", "Ge", "Ge", "As", "Se", "Se", "Se", "Se", "Se", "Se", "Br", "Br", "Kr", "Kr", "Kr", "Kr", "Kr", "Kr", "Rb", "Rb", "Sr", "Sr", "Sr", "Sr", "Y", "Zr", "Zr", "Zr", "Zr", "Zr", "Nb", "Mo", "Mo", "Mo", "Mo", "Mo", "Mo", "Mo", "Ru", "Ru", "Ru", "Ru", "Ru", "Ru", "Ru", "Rh", "Pd", "Pd", "Pd", "Pd", "Pd", "Pd", "Ag", "Ag", "Cd", "Cd", "Cd", "Cd", "Cd", "Cd", "Cd", "Cd", "In", "In", "Sn", "Sn", "Sn", "Sn", "Sn", "Sn", "Sn", "Sn", "Sn", "Sn", "Sb", "Sb", "Te", "Te", "Te", "Te", "Te", "Te", "Te", "Te", "I", "Xe", "Xe", "Xe", "Xe", "Xe", "Xe", "Xe", "Xe", "Xe", "Cs", "Ba", "Ba", "Ba", "Ba", "Ba", "Ba", "Ba", "La", "La", "Ce", "Ce", "Ce", "Ce", "Pr", "Nd", "Nd", "Nd", "Nd", "Nd", "Nd", "Nd", "Sm", "Sm", "Sm", "Sm", "Sm", "Sm", "Sm", "Eu", "Eu", "Gd", "Gd", "Gd", "Gd", "Gd", "Gd", "Gd", "Tb", "Dy", "Dy", "Dy", "Dy", "Dy", "Dy", "Dy", "Ho", "Er", "Er", "Er", "Er", "Er", "Er", "Tm", "Yb", "Yb", "Yb", "Yb", "Yb", "Yb", "Yb", "Lu", "Lu", "Hf", "Hf", "Hf", "Hf", "Hf", "Hf", "Ta", "Ta", "W", "W", "W", "W", "W", "Re", "Re", "Os", "Os", "Os", "Os", "Os", "Os", "Os", "Ir", "Ir", "Pt", "Pt", "Pt", "Pt", "Pt", "Pt", "Au", "Hg", "Hg", "Hg", "Hg", "Hg", "Hg", "Hg", "Tl", "Tl", "Pb", "Pb", "Pb", "Pb", "Bi", "U", "U", "U", "Th", "Pa", "E", "Me", "Pn", "Pn", }; const bool elem_table_Radioactive [ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES] = { false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, true, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, true, false, false, true, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, true, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false, false, false, true, false, true, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, true, false, false, false, true, false, true, true, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, true, false, true, false, false, false, false, false, true, true, true, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, true, true, true, true, false, false, false, false, }; const double elem_table_probability [ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES] = { 0.999884290164307909520857720053754746913909912109375000000000, 0.000115709835692033314582735648023970043141162022948265075684, 0.000001342999991941999914655050951672876635711872950196266174, 0.999998657000008006612290500925155356526374816894531250000000, 0.075933925285977116326208147256693337112665176391601562500000, 0.924066074714022800407065005856566131114959716796875000000000, 1.000000000000000000000000000000000000000000000000000000000000, 0.199480830670926506664741850727295968681573867797851562500000, 0.800519169329073410068531302385963499546051025390625000000000, 0.989211941850466902614869013632414862513542175292968750000000, 0.010788058149533083507343178553128382191061973571777343750000, 0.996358014567941707717579902237048372626304626464843750000000, 0.003641985432058271465738386041266494430601596832275390625000, 0.997567609729561044495937949250219389796257019042968750000000, 0.000380998476006095935803702490218825005285907536745071411133, 0.002051391794432822109073288885383590240962803363800048828125, 1.000000000000000000000000000000000000000000000000000000000000, 0.904766666333356561757739200402284041047096252441406250000000, 0.002709810313278070148523823945652111433446407318115234375000, 0.092523523353365264010328417043638182803988456726074218750000, 1.000000000000000000000000000000000000000000000000000000000000, 0.789876809855211581279377242026384919881820678710937500000000, 0.100001999840012789633192369365133345127105712890625000000000, 0.110121190304775615209642580794024979695677757263183593750000, 1.000000000000000000000000000000000000000000000000000000000000, 0.922220833349999713490774411184247583150863647460937500000000, 0.046858437698747611166449900110819726251065731048583984375000, 0.030920728951252581667707985957349592354148626327514648437500, 1.000000000000000000000000000000000000000000000000000000000000, 0.949850011999040066967836537514813244342803955078125000000000, 0.007519398448124149821059081233443066594190895557403564453125, 0.042520598352131823427502155254842364229261875152587890625000, 0.000109991200703943683199964587160479823069181293249130249023, 0.757594848103037898923162174469325691461563110351562500000000, 0.242405151896962045565686594272847287356853485107421875000000, 0.003336205796380696270847510120916012965608388185501098632812, 0.000629799206452999775149304007015871320618316531181335449219, 0.996033994997166272078459314798237755894660949707031250000000, 0.932580526071084436878777523816097527742385864257812500000000, 0.000117099885242112454345267402722186034225160256028175354004, 0.067302374043673424131029037198459263890981674194335937500000, 0.969400838426726974006442105746828019618988037109375000000000, 0.006472228417153705684605746739634923869743943214416503906250, 0.001350985058105257227353823701321289263432845473289489746094, 0.020860869278785776348428271376178599894046783447265625000000, 0.000042999524425259849917842214228613784143817611038684844971, 0.001872079294802999303859447621789513505063951015472412109375, 1.000000000000000000000000000000000000000000000000000000000000, 0.082520097588289403889305617667559999972581863403320312500000, 0.074411070671519405350657905273692449554800987243652343750000, 0.737141543014838140912559083517407998442649841308593750000000, 0.054113506379234489751528514034362160600721836090087890625000, 0.051813782346118462951434224805780104361474514007568359375000, 0.002503979968160254584302881752932989911641925573348999023438, 0.997496020031839680797247638111002743244171142578125000000000, 0.043450743830478963380947732275672024115920066833496093750000, 0.837881075122238416774678171350387856364250183105468750000000, 0.095010483865806516501351097758742980659008026123046875000000, 0.023657697181476075587447382986283628270030021667480468750000, 1.000000000000000000000000000000000000000000000000000000000000, 0.058452792721208068904559240763774141669273376464843750000000, 0.917532497856775930422656983864726498723030090332031250000000, 0.021190743592002535267138085828264593146741390228271484375000, 0.002823965830013456732028309659199294401332736015319824218750, 1.000000000000000000000000000000000000000000000000000000000000, 0.680769095231327558970235713786678388714790344238281250000000, 0.262230419610671172669924544607056304812431335449218750000000, 0.011399083035777891892426083586542517878115177154541015625000, 0.036346250253448952882706635136855766177177429199218750000000, 0.009255151868774300419340228529563319170847535133361816406250, 0.691494255172344751692037334578344598412513732910156250000000, 0.308505744827655137285660202906001359224319458007812500000000, 0.491645713885820234700929631799226626753807067871093750000000, 0.277325508740183801492662496457342058420181274414062500000000, 0.040405292597461665848879164286699960939586162567138671875000, 0.184515103497573135227227680843498092144727706909179687500000, 0.006108381278961075126765489784474993939511477947235107421875, 0.601079797840404217446064194518839940428733825683593750000000, 0.398920202159595671531633342965506017208099365234375000000000, 0.205705812301332946478993335404084064066410064697265625000000, 0.274503726116209989527305879164487123489379882812500000000000, 0.077504017086240106770844704442424699664115905761718750000000, 0.364982406812098314485837136089685373008251190185546875000000, 0.077304037684118531714716482383664697408676147460937500000000, 1.000000000000000000000000000000000000000000000000000000000000, 0.008938426836876709608015190156038443092256784439086914062500, 0.093712506598838590798905556766840163618326187133789062500000, 0.076302570747548426055573145276866853237152099609375000000000, 0.237686167234566703143627819372341036796569824218750000000000, 0.496053694549759227605534306348999962210655212402343750000000, 0.087306634032410290746639702774700708687305450439453125000000, 0.506898896176611657438115798868238925933837890625000000000000, 0.493101103823388231539581738616107031702995300292968750000000, 0.003552948126957346328819165037771199422422796487808227539062, 0.022860666234272977725971998097520554438233375549316406250000, 0.115931407401451927463575941601447993889451026916503906250000, 0.115000220996773441783922464765055337920784950256347656250000, 0.569863179997571966950431487930472940206527709960937500000000, 0.172791577242972227423933873069472610950469970703125000000000, 0.721691132354705722207199869444593787193298339843750000000000, 0.278308867645294166770497668039752170443534851074218750000000, 0.005609775608975640752429381308274969342164695262908935546875, 0.098606055757769678349333730693615507334470748901367187500000, 0.070007199712011511372189431767765199765563011169433593750000, 0.825776968921243081922511919401586055755615234375000000000000, 1.000000000000000000000000000000000000000000000000000000000000, 0.514422711621750239352479638910153880715370178222656250000000, 0.112234410554393593262290096390643157064914703369140625000000, 0.171550886397901253266340404479706194251775741577148437500000, 0.173788376250214926521664438041625544428825378417968750000000, 0.028003615175739928616627238966430013533681631088256835937500, 1.000000000000000000000000000000000000000000000000000000000000, 0.145308494342837241086741073559096548706293106079101562500000, 0.091496458524138415957516201615362660959362983703613281250000, 0.158387558641321063435114524509117472916841506958007812500000, 0.166690329831184980147185115129104815423488616943359375000000, 0.095999792030779435014764544575882609933614730834960937500000, 0.243900902666405350327494261364336125552654266357421875000000, 0.098216463963333416886669624545902479439973831176757812500000, 0.055402974808013198682044020415560225956141948699951171875000, 0.018726273471579152340993346115283202379941940307617187500000, 0.127588609866636532030881312493875157088041305541992187500000, 0.126054915071900669465421174209041055291891098022460937500000, 0.170586053375378299268305681835045106709003448486328125000000, 0.315451225206183960558803391904802992939949035644531250000000, 0.186189948200308125203505937861336860805749893188476562500000, 1.000000000000000000000000000000000000000000000000000000000000, 0.010207550187954890497099569302008603699505329132080078125000, 0.111463248820283120088525663504697149619460105895996093750000, 0.223336399264176588275176982278935611248016357421875000000000, 0.273264416540030363744762098576757125556468963623046875000000, 0.264546508837878890929573572066146880388259887695312500000000, 0.117181876349676070137029171291942475363612174987792968750000, 0.518389668985958174118877650471404194831848144531250000000000, 0.481610331014041714858819887012941762804985046386718750000000, 0.012567197514954164816458614950533956289291381835937500000000, 0.008928009053980960965657409644791187020018696784973144531250, 0.124890149496662231087817929164884844794869422912597656250000, 0.127983459688489453753845737082883715629577636718750000000000, 0.241267197414976458658131264201074372977018356323242187500000, 0.122184752800125570604272695618419675156474113464355468750000, 0.287277937020044504823346187549759633839130401611328125000000, 0.074901297010766587636254598692175932228565216064453125000000, 0.042954845418549769675564675708301365375518798828125000000000, 0.957045154581450119302132861776044592261314392089843750000000, 0.009707379007667929146641050408561568474397063255310058593750, 0.006608215781738930282018795736576066701672971248626708984375, 0.003409079548521898664348306340343697229400277137756347656250, 0.145370749897527656857576516813423950225114822387695312500000, 0.076859248003039171148742525474517606198787689208984375000000, 0.242144620952342848330118840749491937458515167236328125000000, 0.085916802463334898676272644024720648303627967834472656250000, 0.325722055045137792728127124064485542476177215576171875000000, 0.046317494276545329023875297025369945913553237915039062500000, 0.057944355024143474885978122301821713335812091827392578125000, 0.572091349038115315472907695948379114270210266113281250000000, 0.427908650961884573504789841535966843366622924804687500000000, 0.000909764371027903685079651907585684966761618852615356445312, 0.025505394102927340937991829150632838718593120574951171875000, 0.008927687728878220055350745099076448241248726844787597656250, 0.047401722953754971134898710261040832847356796264648437500000, 0.070696689557404629455916733604681212455034255981445312500000, 0.188376210561464557668998054396070074290037155151367187500000, 0.317407791382032011817670991149498149752616882324218750000000, 0.340774739342510235573513455165084451436996459960937500000000, 1.000000000000000000000000000000000000000000000000000000000000, 0.000952296533640617525774685336870106766582466661930084228516, 0.000890196759683794711613680217254795934422872960567474365234, 0.019102830465697103606848017420816177036613225936889648437500, 0.264005869018636762923790683998959138989448547363281250000000, 0.040709981815666186621971434078659513033926486968994140625000, 0.212323527142361190289676642350968904793262481689453125000000, 0.269085350529324029977829013660084456205368041992187500000000, 0.104356830141138279266499466757522895932197570800781250000000, 0.088573117593851946605099101361702196300029754638671875000000, 1.000000000000000000000000000000000000000000000000000000000000, 0.001060985146207953045902061539607075246749445796012878417969, 0.001010985846198153050023993415607037604786455631256103515625, 0.024171461599537605313692267827718751505017280578613281250000, 0.065920277116120362670415033790050074458122253417968750000000, 0.078541300421794094099858796198532218113541603088378906250000, 0.112320827508414877726750091824214905500411987304687500000000, 0.716974162361726841119491382414707913994789123535156250000000, 0.000888171872103250392010975744483403104823082685470581054688, 0.999111828127896672846475212281802669167518615722656250000000, 0.001851973331584025024912354417949700291501358151435852050781, 0.002511963827720880421123794690174690913408994674682617187500, 0.884492463308528265031327464384958148002624511718750000000000, 0.111143599532166723053983048430382041260600090026855468750000, 1.000000000000000000000000000000000000000000000000000000000000, 0.271519166958828106483991859931848011910915374755859375000000, 0.121740433020292235233306143982190405949950218200683593750000, 0.237977663997580829446931716120161581784486770629882812500000, 0.082929723850915446070608538775559281930327415466308593750000, 0.171890140355501652713599014532519504427909851074218750000000, 0.057561075412857647115583148433870519511401653289794921875000, 0.056381796404024006608146635244338540360331535339355468750000, 0.030772522277086666181444840617587033193558454513549804687500, 0.149881578776357327065227309503825381398200988769531250000000, 0.112382691006085513873991033051424892619252204895019531250000, 0.138246406123312015612469849656918086111545562744140625000000, 0.073792068527347848272412988990254234522581100463867187500000, 0.267451009404714612482933944193064235150814056396484375000000, 0.227473723885095902019770619517657905817031860351562500000000, 0.478103065570820051632949798658955842256546020507812500000000, 0.521896934429179837344747738825390115380287170410156250000000, 0.002009636255837693018938550082452820788603276014328002929688, 0.021826049485043207132317633067941642366349697113037109375000, 0.147985214676143617129611129712429828941822052001953125000000, 0.204672954195290635048820604424690827727317810058593750000000, 0.156491675006823760529783839956508018076419830322265625000000, 0.248435033258980114689862261911912355571985244750976562500000, 0.218579437121880937322515592313720844686031341552734375000000, 1.000000000000000000000000000000000000000000000000000000000000, 0.000562985756460361477619691594753703611786477267742156982422, 0.000952975889709990254573812595850768047966994345188140869141, 0.023291210732368467645203580218549177516251802444458007812500, 0.188889421097646226233024435714469291269779205322265625000000, 0.254747154896981076177553404704667627811431884765625000000000, 0.248957901365095435330943018925609067082405090332031250000000, 0.282598350261738351374418698469526134431362152099609375000000, 1.000000000000000000000000000000000000000000000000000000000000, 0.001395973476503946332158423437874716910300776362419128417969, 0.016012695758780580435054474719436257146298885345458984375000, 0.335027234482544788995994622382568195462226867675781250000000, 0.228686654953555862368475004586798604577779769897460937500000, 0.269776674243189351631855288360384292900562286376953125000000, 0.149100767085425356395234075534972362220287322998046875000000, 1.000000000000000000000000000000000000000000000000000000000000, 0.001232929969577727796758992440118163358420133590698242187500, 0.029822206098693591902470956256365752778947353363037109375000, 0.140905996539396560773838018576498143374919891357421875000000, 0.216800685721051017429417129278590437024831771850585937500000, 0.161027253651992552363481081556528806686401367187500000000000, 0.320249909805123023076589561242144554853439331054687500000000, 0.129961018214165419104588750087714288383722305297851562500000, 0.974008767577204226384424146090168505907058715820312500000000, 0.025991232422795697287742910930319339968264102935791015625000, 0.001609652315099938373749166586890169128309935331344604492188, 0.052668623577307296934613134453684324398636817932128906250000, 0.185969830516608397585898160286888014525175094604492187500000, 0.272821070648739838482299546740250661969184875488281250000000, 0.136190582834107815068946933934057597070932388305664062500000, 0.350740240108136591690168870627530850470066070556640625000000, 0.000120131992311552486551486096377772128107608295977115631104, 0.999879868007688354936135510797612369060516357421875000000000, 0.001209872963338849303702171589236513682408258318901062011719, 0.264988176241494621798722164385253563523292541503906250000000, 0.143124971877952811283307710255030542612075805664062500000000, 0.306387829277925793913794905165559612214565277099609375000000, 0.284289149639287863635672692907974123954772949218750000000000, 0.374005039798408045470523575204424560070037841796875000000000, 0.625994960201591843507173962279921397566795349121093750000000, 0.000209947723016968765524098428087995671376120299100875854492, 0.015926034417430057904541129687459033448249101638793945312500, 0.019615115836156795520173190539026109036058187484741210937500, 0.132457018202467580181291850749403238296508789062500000000000, 0.161519781574387955025429164379602298140525817871093750000000, 0.262554623898649197588639481182326562702655792236328125000000, 0.407717478347891348899878494194126687943935394287109375000000, 0.373050779688124722888176165724871680140495300292968750000000, 0.626949220311875166089521371759474277496337890625000000000000, 0.000121987349911814132899338936066868654961581341922283172607, 0.007821588901230941415221309398475568741559982299804687500000, 0.328605923565726210089366077227168716490268707275390625000000, 0.337788971283677852408544595164130441844463348388671875000000, 0.252107856415289710572125159160350449383258819580078125000000, 0.073553672484163390432598816914833150804042816162109375000000, 1.000000000000000000000000000000000000000000000000000000000000, 0.001509815802472098391837085351596670079743489623069763183594, 0.099707835644051417967048678292485419660806655883789062500000, 0.168701418426951910145561441822792403399944305419921875000000, 0.230990819120067331082779560347262304276227951049804687500000, 0.131793921141620695713925215386552736163139343261718750000000, 0.298589572072207154462830658303573727607727050781250000000000, 0.068706617792629293139938795320631470531225204467773437500000, 0.295204095918081610427918803907232359051704406738281250000000, 0.704795904081918278549778733577113598585128784179687500000000, 0.014094362255097959285565778486670751590281724929809570312500, 0.241003598560575765796798464180028531700372695922851562500000, 0.221011595361855245345239495691203046590089797973632812500000, 0.523890443822470963652904174523428082466125488281250000000000, 1.000000000000000000000000000000000000000000000000000000000000, 0.000054599923560107009460132254652364736102754250168800354004, 0.007204689913434121108226637630878030904568731784820556640625, 0.992740710163005690702675565262325108051300048828125000000000, 1.000000000000000000000000000000000000000000000000000000000000, 1.000000000000000000000000000000000000000000000000000000000000, 1.0, 1.0, 0.999884290164307909520857720053754746913909912109375000000000, 0.000115709835692033314582735648023970043141162022948265075684, }; const double elem_table_log_probability [ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES] = { -0.000115716530591520062594239337538937206772970966994762420654, -9.064424917075021070900220365729182958602905273437500000000000, -13.520604646423175054792409355286508798599243164062500000000000, -0.000001343000893767296712052561162564767727189973811618983746, -2.577891720978651601825504258158616721630096435546875000000000, -0.078971700466369670889932308455172460526227951049804687500000, 0.000000000000000000000000000000000000000000000000000000000000, -1.612037134131381055368592569720931351184844970703125000000000, -0.222494800137427506392384657374350354075431823730468750000000, -0.010846671177187771836769591971005866071209311485290527343750, -4.529315483514038120915756735485047101974487304687500000000000, -0.003648633607616148452623683340334537206217646598815917968750, -5.615226297668721500144783931318670511245727539062500000000000, -0.002435353337518350851781390176142849668394774198532104492188, -7.872715182829573166145564755424857139587402343750000000000000, -6.189236792082963845018639403861016035079956054687500000000000, 0.000000000000000000000000000000000000000000000000000000000000, -0.100078195781331494296217954342864686623215675354003906250000, -5.910876641640641970809610938886180520057678222656250000000000, -2.380292360271312634978357891668565571308135986328125000000000, 0.000000000000000000000000000000000000000000000000000000000000, -0.235878282572628383828572395941591821610927581787109375000000, -2.302565094793883382351395994191989302635192871093750000000000, -2.206173789605455404227996041299775242805480957031250000000000, 0.000000000000000000000000000000000000000000000000000000000000, -0.080970568540825488268453113960276823490858078002929687500000, -3.060624186220378017964094397029839456081390380859375000000000, -3.476328480144544208485513081541284918785095214843750000000000, 0.000000000000000000000000000000000000000000000000000000000000, -0.051451188958515865767839869704403099603950977325439453125000, -4.890269137820559386398144852137193083763122558593750000000000, -3.157766653355948971437783256988041102886199951171875000000000, -9.115110188972028737453001667745411396026611328125000000000000, -0.277606537419771426389303314863354898989200592041015625000000, -1.417144771312495832304989562544506043195724487304687500000000, -5.702921106825801444983881083317101001739501953125000000000000, -7.370109509296556282720302988309413194656372070312500000000000, -0.003973890456746663815690290277871099533513188362121582031250, -0.069799776156532433724066777358530089259147644042968750000000, -9.052483267360123875278077321127057075500488281250000000000000, -2.698559767416127019856730839819647371768951416015625000000000, -0.031077090678799931117159971449837030377238988876342773437500, -5.040234806716209270405215647770091891288757324218750000000000, -6.606921279942914004834619845496490597724914550781250000000000, -3.869880158236262079896050636307336390018463134765625000000000, -10.054321502209552008366699737962335348129272460937500000000000, -6.280705543488890540970714937429875135421752929687500000000000, 0.000000000000000000000000000000000000000000000000000000000000, -2.494713408178120150893164463923312723636627197265625000000000, -2.598150548864236686341655513388104736804962158203125000000000, -0.304975352295239643396485007542651146650314331054687500000000, -2.916671468480125817279713373864069581031799316406250000000000, -2.960099096648749483762230738648213446140289306640625000000000, -5.989873825712285437816717603709548711776733398437500000000000, -0.002507120169096173530054461053850900498218834400177001953125, -3.136127308188753737283605005359277129173278808593750000000000, -0.176879103699552453488053060937090776860713958740234375000000, -2.353768036988251211028000398073345422744750976562500000000000, -3.744066754776672834026385316974483430385589599609375000000000, 0.000000000000000000000000000000000000000000000000000000000000, -2.839535812544084603104010966490022838115692138671875000000000, -0.086067279673300162157190129619266372174024581909179687500000, -3.854190815670504033363386042765341699123382568359375000000000, -5.869613059277937416879922238877043128013610839843750000000000, 0.000000000000000000000000000000000000000000000000000000000000, -0.384532097536943340276849312431295402348041534423828125000000, -1.338531697560186861650777245813515037298202514648437500000000, -4.474222362274872466514352709054946899414062500000000000000000, -3.314664237037550087450199498562142252922058105468750000000000, -4.682574923715371539856278104707598686218261718750000000000000, -0.368900435688631012087768112905905582010746002197265625000000, -1.176014814002444008878001113771460950374603271484375000000000, -0.709996915609857004447746930964058265089988708496093750000000, -1.282563340904273152531800405995454639196395874023437500000000, -3.208794497707758708315850526560097932815551757812500000000000, -1.690023957076583371872402494773268699645996093750000000000000, -5.098093470692335316130083811003714799880981445312500000000000, -0.509027578151938331352255318051902577280998229980468750000000, -0.918993876681337473755206701753195375204086303710937500000000, -1.581308226517597503857359697576612234115600585937500000000000, -1.292790443930836863373201595095451921224594116210937500000000, -2.557425510595298323579527277615852653980255126953125000000000, -1.007906127076126923114429700945038348436355590820312500000000, -2.560009090805706488680471011321060359477996826171875000000000, 0.000000000000000000000000000000000000000000000000000000000000, -4.717395674310531639150667615467682480812072753906250000000000, -2.367523623737181281967423274181783199310302734375000000000000, -2.573048648630889889687978211441077291965484619140625000000000, -1.436804100526558380934716296906117349863052368164062500000000, -0.701071102975730831019518518587574362754821777343750000000000, -2.438328827816317101451204507611691951751708984375000000000000, -0.679443711102156733261381305055692791938781738281250000000000, -0.707041047215952844773312335746595636010169982910156250000000, -5.639977561836668762396129750413820147514343261718750000000000, -3.778337476933724126126890041632577776908874511718750000000000, -2.154756578276459499932116159470751881599426269531250000000000, -2.162821228909660220551813836209475994110107421875000000000000, -0.562358982058553724669991424889303743839263916015625000000000, -1.755669166607024767046141278115101158618927001953125000000000, -0.326158026142060741836559145667706616222858428955078125000000, -1.279023747338471794776637580071110278367996215820312500000000, -5.183244558647554178776317712618038058280944824218750000000000, -2.316622601837921013867571673472411930561065673828125000000000, -2.659157189193052328590738397906534373760223388671875000000000, -0.191430555333882340685036638205929193645715713500976562500000, 0.000000000000000000000000000000000000000000000000000000000000, -0.664709955358130821778672725486103445291519165039062500000000, -2.187165643480033949686003325041383504867553710937500000000000, -1.762875342696557545707491954090073704719543457031250000000000, -1.749916948420700224531287858553696423768997192382812500000000, -3.575421663722070153568211026140488684177398681640625000000000, 0.000000000000000000000000000000000000000000000000000000000000, -1.928896249393138528915869756019674241542816162109375000000000, -2.391455012103930855005273770075291395187377929687500000000000, -1.842710346617601580021528206998482346534729003906250000000000, -1.791617500319007794118419951701071113348007202148437500000000, -2.343409253862695162951013116980902850627899169921875000000000, -1.410993272797839370724659602274186909198760986328125000000000, -2.320581420206905942649200369487516582012176513671875000000000, -2.893121989774980473697496563545428216457366943359375000000000, -3.977827742728266446903262476553209125995635986328125000000000, -2.058944176423800787034679160569794476032257080078125000000000, -2.071037633074694905843671222100965678691864013671875000000000, -1.768515397703714908672623096208553761243820190429687500000000, -1.153751204177984268639534093381371349096298217773437500000000, -1.680987899482990099997437027923297137022018432617187500000000, 0.000000000000000000000000000000000000000000000000000000000000, -4.584627618010170380102863418869674205780029296875000000000000, -2.194060349407264354226754221599549055099487304687500000000000, -1.499076127310911887846600620832759886980056762695312500000000, -1.297315393792867643796284937707241624593734741210937500000000, -1.329738206325086657955125701846554875373840332031250000000000, -2.144028052451655508292560625704936683177947998046875000000000, -0.657028062796280343249577526876237243413925170898437500000000, -0.730619933776488150733996462804498150944709777832031250000000, -4.376665231519177190477876138174906373023986816406250000000000, -4.718561859232925925766721775289624929428100585937500000000000, -2.080320732081178736194715384044684469699859619140625000000000, -2.055854244595972435405428768717683851718902587890625000000000, -1.421850256682005708697147383645642548799514770507812500000000, -2.102221012532442756537420791573822498321533203125000000000000, -1.247305110167633124262920318869873881340026855468750000000000, -2.591584072043251474326552852289751172065734863281250000000000, -3.147605821582104113076638896018266677856445312500000000000000, -0.043904705171597842305875047941299271769821643829345703125000, -4.634868960235463575259018398355692625045776367187500000000000, -5.019441588675102039474040793720632791519165039062500000000000, -5.681312951243271847090454684803262352943420410156250000000000, -1.928467904013302591792466955666895955801010131835937500000000, -2.565779477876660052970692049711942672729492187500000000000000, -1.418220124080461719273671405971981585025787353515625000000000, -2.454375864191848055639866288402117788791656494140625000000000, -1.121710853164690879779641363711562007665634155273437500000000, -3.072235543110140021383358543971553444862365722656250000000000, -2.848272125086215300626690805074758827686309814453125000000000, -0.558456599237618478426270485215354710817337036132812500000000, -0.848845538512307262735134827380534261465072631835937500000000, -7.002324924918669424300787795800715684890747070312500000000000, -3.668865315739671117967191094066947698593139648437500000000000, -4.718597850559019590832576795946806669235229492187500000000000, -3.049096701706386802754877862753346562385559082031250000000000, -2.649356530974964485380951373372226953506469726562500000000000, -1.669314195717893856141245123581029474735260009765625000000000, -1.147567923673684653351756423944607377052307128906250000000000, -1.076533608421685217493291020218748599290847778320312500000000, 0.000000000000000000000000000000000000000000000000000000000000, -6.956634086757649271248737932182848453521728515625000000000000, -7.024068041375896243039278488140553236007690429687500000000000, -3.957918762987576943856993239023722708225250244140625000000000, -1.331783944951729026229259034153074026107788085937500000000000, -3.201281963147128539759478371706791222095489501953125000000000, -1.549644096147559713116947932576294988393783569335937500000000, -1.312726661492457758129148714942857623100280761718750000000000, -2.259939193445343441624117986066266894340515136718750000000000, -2.423926880572130126978436237550340592861175537109375000000000, 0.000000000000000000000000000000000000000000000000000000000000, -6.848557419252292000066972832428291440010070800781250000000000, -6.896829338845804180380127945682033896446228027343750000000000, -3.722582614455130833874818563344888389110565185546875000000000, -2.719309189565115580933252203976735472679138183593750000000000, -2.544130672523534641982223547529429197311401367187500000000000, -2.186395971313551900294669394497759640216827392578125000000000, -0.332715474789523235621402363904053345322608947753906250000000, -7.026345284034602123313106858404353260993957519531250000000000, -0.000888566530440708531556059934786162557429634034633636474609, -6.291503542654471203832144965417683124542236328125000000000000, -5.986690430272505913933400734094902873039245605468750000000000, -0.122741286268200244791160002932883799076080322265625000000000, -2.196932224286036738902794240857474505901336669921875000000000, 0.000000000000000000000000000000000000000000000000000000000000, -1.303722545566528001614869936020113527774810791015625000000000, -2.105864098995690714133388610207475721836090087890625000000000, -1.435578458464392248572494281688705086708068847656250000000000, -2.489761730430327446583760320208966732025146484375000000000000, -1.760899725099839052688821539049968123435974121093750000000000, -2.854908713800850428299327177228406071662902832031250000000000, -2.875608931369854293080834395368583500385284423828125000000000, -3.481133121051686263314195457496680319309234619140625000000000, -1.897909771509530774125096286297775804996490478515625000000000, -2.185845347988713882614320027641952037811279296875000000000000, -1.978717634408995396100294783536810427904129028320312500000000, -2.606504025680458358493751802598126232624053955078125000000000, -1.318818871830977013104302386636845767498016357421875000000000, -1.480720546667873893653677441761828958988189697265625000000000, -0.737928951383980402667361886415164917707443237304687500000000, -0.650285154216317162756411107693566009402275085449218750000000, -6.209801540532629005042508651968091726303100585937500000000000, -3.824651092041761124562526674708351492881774902343750000000000, -1.910642911045310476936265331460162997245788574218750000000000, -1.586341919151083468264573639316949993371963500976562500000000, -1.854752465261401805918239915627054870128631591796875000000000, -1.392573903203236485026650370855350047349929809570312500000000, -1.520605773895307155640921337180770933628082275390625000000000, 0.000000000000000000000000000000000000000000000000000000000000, -7.482256229504544720043668348807841539382934570312500000000000, -6.955920953990032629121742502320557832717895507812500000000000, -3.759679211363279094371137034613639116287231445312500000000000, -1.666593508702244319508167791354935616254806518554687500000000, -1.367483775157640080166743246081750839948654174804687500000000, -1.390471467634422086945278351777233183383941650390625000000000, -1.263728646463758931162146836868487298488616943359375000000000, 0.000000000000000000000000000000000000000000000000000000000000, -6.574163274461459316455602674977853894233703613281250000000000, -4.134373386461300370342542009893804788589477539062500000000000, -1.093543453498669215662175702163949608802795410156250000000000, -1.475402531411262208038692733680363744497299194335937500000000, -1.310160794679168905219057705835439264774322509765625000000000, -1.903132912453214142800561603507958352565765380859375000000000, 0.000000000000000000000000000000000000000000000000000000000000, -6.698361853186871606169461301760748028755187988281250000000000, -3.512501991875810691823289744206704199314117431640625000000000, -1.959662302151332857746979243529494851827621459960937500000000, -1.528776846501670894085123109107371419668197631835937500000000, -1.826181650981897996999236966075841337442398071289062500000000, -1.138653619844293141127877788676414638757705688476562500000000, -2.040520733384556528733355662552639842033386230468750000000000, -0.026334973760810023724054929061821894720196723937988281250000, -3.649996012338110329409346377360634505748748779296875000000000, -6.431737076661124596910212858347222208976745605468750000000000, -2.943735378782415867959798561059869825839996337890625000000000, -1.682170819948636486529380817955825477838516235351562500000000, -1.298939117547105670524842935265041887760162353515625000000000, -1.993700029844323484695678416755981743335723876953125000000000, -1.047709386165366352017258577689062803983688354492187500000000, -9.026919483738925720217594061978161334991455078125000000000000, -0.000120139208737295727770326425609681564310449175536632537842, -6.717239913861373423742406885139644145965576171875000000000000, -1.328070071947949681856471215724013745784759521484375000000000, -1.944037101159571623298916165367700159549713134765625000000000, -1.182903563582415440436079734354279935359954833984375000000000, -1.257763426233626136152565777592826634645462036132812500000000, -0.983486006261584555510069094452774152159690856933593750000000, -0.468412958710625382252601411892101168632507324218750000000000, -8.468651996251450597696930344682186841964721679687500000000000, -4.139800124064825226355424092616885900497436523437500000000000, -3.931454793849565199082007893593981862068176269531250000000000, -2.021497077106750417385683249449357390403747558593750000000000, -1.823127657291570224984411652258131653070449829101562500000000, -1.337296127555923419549799291417002677917480468750000000000000, -0.897180799465370992784585268964292481541633605957031250000000, -0.986040730030275258677363581227837130427360534667968750000000, -0.466889729967265909582607719130464829504489898681640625000000, -9.011593207854545539703394751995801925659179687500000000000000, -4.850867560763419739089385984698310494422912597656250000000000, -1.112896046865470500719652591214980930089950561523437500000000, -1.085333923798379451852724741911515593528747558593750000000000, -1.377898281389317913792069703049492090940475463867187500000000, -2.609739901377524873282709449995309114456176757812500000000000, 0.000000000000000000000000000000000000000000000000000000000000, -6.495767620713909451524159521795809268951416015625000000000000, -2.305511012885385291326656442834064364433288574218750000000000, -1.779624881481258524829058842442464083433151245117187500000000, -1.465377313319132568381064629647880792617797851562500000000000, -2.026515779816368212351562760886736214160919189453125000000000, -1.208685317218918919834891312348190695047378540039062500000000, -2.677909755533927516069070406956598162651062011718750000000000, -1.220088311290825400234894004825036972761154174804687500000000, -0.349847015838577246604756965098204091191291809082031250000000, -4.261980401619341662922124669421464204788208007812500000000000, -1.422943413816338820154783206817228347063064575195312500000000, -1.509540111140063478600836788245942443609237670898437500000000, -0.646472693195343506289418655796907842159271240234375000000000, 0.000000000000000000000000000000000000000000000000000000000000, -9.815478075212435982166425674222409725189208984375000000000000, -4.933023088148108747930109529988840222358703613281250000000000, -0.007285766694735069763655399555091207730583846569061279296875, 0.000000000000000000000000000000000000000000000000000000000000, 0.000000000000000000000000000000000000000000000000000000000000, 0.0, 0.0, -0.000115716530591520062594239337538937206772970966994762420654, -9.064424917075021070900220365729182958602905273437500000000000, }; #ifdef __cplusplus } #endif } // namespace IsoSpec IsoSpecR/R/0000755000176200001440000000000013747057301012151 5ustar liggesusersIsoSpecR/R/add_custom_isotopes.R0000644000176200001440000000162713507710304016342 0ustar liggesusers#' An example of how to add your own elements. #' #' This can be used, for instance, with isotopically labelled molecules. #' #' @export custom_isotopes_example = function(){ print(isotopicData) print('Here are some isotopes') i = isotopicData$IsoSpecShortZero print(i) # convention: D for deuterium, M for N15 foney_elements = data.frame( element = c('D', 'M'), isotope = c('D', 'M'), mass = c(i[i$isotope == 'H2', 'mass'], i[i$isotope == 'N15', 'mass']), abundance = c(1, 1), ratioC = c(NA, NA) # this is not important actually, I just keep it for consistency ) isotopes = rbind(i, foney_elements) print(isotopes) # Your atom counts: # C C13 H N N15 O # 37 6 71 9 4 13 # translate into: your_molecule = c(C=37, H=71, N=9, O=13, D=6, M=4) res = IsoSpecify(your_molecule, .999, isotopes, showCounts=T) print(res) return(invisible()) }IsoSpecR/R/sysdata.rda0000644000176200001440000003071013507710304014303 0ustar liggesusersBZh91AY&SY4U/ (}_P( RzS!zh7mv|}tZTC筻[e tWEwU^(Ibݵd톻4]vhI4 AL=25Dɀ4 '$ Oښd1=?AiSi& hfѓ&L&bi҈&`11LCF&LLhFL5Si0#&D)22hi @ҟOѠ4hT44Q&M!1OJyF5O52z=&y?I HmǝIjy@mG&lMGB; kJzHoXPuS 5 [hΡďWYONHm399Q_rVEGCkSۣ O9)25Q 0p,@6|!U,!!4hj JfB$i, ͷٗ3 dž$=Srd%<4u(N3z)/LaX\ץCަ e.ZX!E`w.'vb"v,ZE9IY`qȤ0bɐ5s \WWWL`ԋ𤈭JԪ*RdMMN `ڑ9$B&a\DI3Ui(CU*P&CNB±1bѴFF H eUUe dK˅X+\+*]B]^N {UUUb P*`k0g*V5RCUSTi"fY,uujUR"4ZX*v$]NXR.^dLȹfbP!++\/Ed Y`/B.jzTHjݎo478AF @ L4WT" E|1Pto,PS1p"8 5>;cP" "/à;XLD uDEPEC"i({,uEt_j5,Ry k0Z*^*‡WyA-44]vx縰! Ɂv< KI|W!96lyA.\=o;{lz'^PsQEKn%SeQz)AQ*!]W=M H}_ypB-1߶a\;)+S`t2HV(2~_:a<"`R̆JfPvA!eMCv3o9vh_.M;y9/6۾I&\ ݋m*U /\:6V؈&+^&YAcyc'D*,̔t"K7Td2E8f yG-j&kUec Fe vH#4Z\H6fzR0Oàv #/7 coc;CJsb8&H| t|шㅘ|q@V<95Xuvg:x!Hw\::)4Ck&>^=e`J!FT;56PG鸊%2S@$m̚%0:SnyJoBm^t(v{oq/.ZVDA;|ұև\Udp~ ]밮oOm&)Ơ ktt8}I-30AS74:R<¶mͳF \`;PwE^V>&N8,N]̫+:aj2F4ck >zk+|N6|oAp txX'ӉNeh[%al.;cu>?I/LQ4:ȋ4_{Dh08e|Dh"}(.7JkQ')72(h{ 0"v8>ma>5Ekܱjʀ2C1}ovk:ҬsXS|,Iid\pb?#b.+<+qJZ%qv!/^'}ߡd?..K$I-2?`.&Rfp13yU&}刚p(R@30Yin:sgN_2)ބ8r[oxz G?S7-&pT9 8*p2Y1 ͅTQ^;\8wals pD{@m1*aJųLQ޹b7v9lຣEovq>_rDͻR+9L ;J^~p=[;Q )~5"n>VjIagYFKI34 @-%5HF4G"kχTSDz*XWV,8RF ]EԔ0U44b` D!$c0cm?Caqx''}GIek(D:їqdO=A Q8Eur <3*,.c鴹ֲ^_A|`ņ BHBHB,`>+4T̖e! F+1a3Y NiHF `<]k:66&bǢ d&Dkduf4Nbcbk&;EqcADQht F٢Bbņ4󨁎jtE^Gߙ!}.=/;=W>uUES*QTzJˋ˨TX$(+2H)&&*,Ws͘ڱ4b]y3M}ۏL7 ʻ-IJɑieE"U[AxWYS,xxh4ccOBQjaF1`$$'nH H{+K0L|P#x,eW L#" *kwt˒N#2aT7 A51`z4 Ǭ*r(`1_FLTc+8}|2|s)\K! P$(!ZmUYM.&)#fФ!(ь> 4i$x{7sN~^qF T|9N*d)Fa8ҜaJ4Ў _?ud{LYdTy?\70n.o %jw) Hcf ighaodE0"" "7L&c6xꪗ9vy5\qNէ,XG/~NbߧbGw;7[8X^=#-xC"FC(d X$"ޛ_&r'ge[R,,pVQ]:ߗx٧ '&aP4[67l=lҭ|}{mosOklw3n7kkҪܷ 5m2o]Yqi+݅7IڧaSUψ{ovP7q׵)[YԌm̽,EϖKP^łlTv]IQ<\~Ό!ƍüWb7ŚHbw {ﹳ+iz H18|ӌ=S4.R'ͅ\O)^_~-: '`nJ*`cS8cȬ+ѷ }SĪN@( A)L"MleyGrx xGkc<I>nvǒYnR.݊ tXɟDi@VI8[=3|"ݕ#Gm_z{.Pux\|,?=.oB"2PRJ;">VOOr|SQB8: $J Zډ'n ϲ@7'N^Mv't &élo1Rz]uLn0"ZխW*f,B&,C4:Ҝ܊0OFitn s $ȑ]uO:Sʺ>Ò qWԚ~8O1]w|qc!DRsb/PІ5V+,"qz$ؙ%k` Uuޏz@`B0!f|D3ҁU/I9{'Ȅ7SxL&'=Fx91!Kptߵ\׸\xi zȁ:"|?aFT372e, A-q `"Vj"#ķ/fHY~7SC1 싲YjR|Igi1I ٺ@~~~af$PN?ȿ91)Arm.DPg<֞xiu)-I2{![wŲll4K j%sl0H&fb'd'!wf/ Aso׮-; .o5810T&|m-slnlG\gcj^Pyca٢(<雎>6<$H6*t0_A7;20|ޤD9.Ah<ա[!g y`@rn_bs T9^KTD(E[((i~Hwher8dD#m{8=|"hR%1ENGɌ||EU 8.%nZ_`#eCCV4=4p|@>D?DtiQ\6KA`IԚEM66D#:5~>Q#"#}x.-e̒-4|*4P"1+To-W|[T ^>J  />Wo.VUExzE :PNW#:Pk4!-%9c P!DD? d=&8լG77륶wkXldatzZ͆A ]<skϋ6cxuܫI au4~;-6䆔R&a,l0Rx~n|%{3h;OIvOz"-@*b $rYM,clM+V~^է|E~eӕ^9FT @?)%0#}>>(s=p?ȚOwPë1dyy)y,ah8K/IzJ9’Jځ8qg"0T{w2nO)H6ĭR=c !mV6ÊKٍ]\A ,b1z+#kG Q3-gw8΄9j4zK%A6΂@;S]+d f#)^M_>+=#4ޙ'%Y*1ΟbYM;u \t|"E/ *wKPa5u*q[]ͤVdhxIEP`tdр'/JT}![Aв$m7:ma낮?sXtD뽪oO$J#yi*Н筊"~$}O;č.6珱-1ʎa5iM! eGHp3 0j< z>\([E<0pBڪ7ghgV+)tҼUA(7;H@pR0y.a=M-cϒ8j_C4__Ÿ4P>S[p.{|茉XDKo(S@. ũ=wk\?Lfmr(QDL/@}3({xWR\~~:VI IjBIg"LJ?Bʦ:-7HDQ%԰E^g [BIԦҸjNNk>ipv$ d5 I 7fUܞ4lo~("Neu2YKl%XչP Bb69)A (5cr6t[E#Kڌ edW5$3` `sq<l:Q%l5Y*R:~F At$=f[3îPcVf{>?OozN]DYA@X S̪%2o* 'fES<Ǐ`̰ clr!p3D#0٘d40T9y,K˲!҄\6zȀ$ mbG۸B) i:=N1X\1t1tEgZ_"awifi s9YX`' ɞ{}vk=r9./?w3Uoil1"mZޟ}\S“5 #۝lSYCO2AX,ZR&؋kW{rǜanU=j!+. VNP_Ėҋx(g&јZ6k+e|hTA2&Vp[ysX;RN8'I 9꯰n#h2dkc>7^EТbFgϧ K8+I+ sv=dX Iue/AGȾb䠼`rGRl, PUDsq4G`$@W(FB-ۂ Q1v T|w 9W#B;xV>t^v'!kJnh aUYEv_eCIe5h\AmڟΎǂ@8oiL*M1ӛs @\FRټq.e- 肇x^^~WLB!0Ҝx7DA㜘lW=M?~wƌ8L4᧱2 V&-f`oneP$\6hkR*6bjtJ..p0|/g|C\wnl.q!ɕ$?&!8#|٫%NA0)"ߌ4$^ZEQ6xJ>;1PIh.GhR6:0hniuP Sf?PRk$j|.$c4݆3h`~4gïs( Yك39Nu: ]qYng^s۰ivJ}dLVI6&73+-HKE0_x#^r&i{{‰z]B@/TIsoSpecR/R/RcppExports.R0000644000176200001440000000064413507710304014562 0ustar liggesusers# Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 Rinterface <- function(molecule, isotopes, stopCondition, algo = 0L, tabSize = 1000L, hashSize = 1000L, step = .3, showCounts = FALSE, trim = TRUE) { .Call('_IsoSpecR_Rinterface', PACKAGE = 'IsoSpecR', molecule, isotopes, stopCondition, algo, tabSize, hashSize, step, showCounts, trim) } IsoSpecR/R/data_description.R0000644000176200001440000000271513747057301015615 0ustar liggesusers# # Copyright (C) 2015-2018 Mateusz Łącki and Michał Startek. # # This file is part of IsoSpec. # # IsoSpec is free software: you can redistribute it and/or modify # it under the terms of the Simplified ("2-clause") BSD licence. # # IsoSpec is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # You should have received a copy of the Simplified BSD Licence # along with IsoSpec. If not, see . # #' Data on isotope masses, abundances and other. #' #' A list of data frames or table data frames (dplyr like), containing different information on isotopes. #' @format A list of 6 tbl_df's or data frames, each constaining: #' \describe{ #' \item{element}{The symbol of an element from Mendeleev's periodic table.} #' \item{isotope}{String composed of the nucleon number and the symbol of element.} #' \item{mass}{Isotope's Mass in Daltons.} #' \item{abundance}{The abundance of the isotopes. In case of enviPat data abundances do not sum to one. In case of all other, they do.} #' \item{ratioC}{As in enviPat reference manual: "Maximum number of atoms of an element for one C-atom in a molecule, based on 99.99 \% of case molecules".} #' } #' @source R Package enviPat and Commission on Isotopic Abundances and Atomic Weights, CIAAW, \url{https://www.ciaaw.org/index.htm}. "isotopicData" IsoSpecR/R/IsoSpecR.R0000644000176200001440000000734313507710304013763 0ustar liggesusers# # Copyright (C) 2015-2018 Mateusz Łącki and Michał Startek. # # This file is part of IsoSpec. # # IsoSpec is free software: you can redistribute it and/or modify # it under the terms of the Simplified ("2-clause") BSD licence. # # IsoSpec is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # You should have received a copy of the Simplified BSD Licence # along with IsoSpec. If not, see . # #' @useDynLib IsoSpecR #' @importFrom Rcpp sourceCpp NULL .onUnload <- function (libpath) { library.dynam.unload("IsoSpecR", libpath) } #' Calculate the isotopic fine structure peaks. #' #' \code{IsoSpecify} is a wrapper around \code{Rinterface} that calls the C++ implementation of the IsoSpec algorithm. Given a molecular formula, it will calculate the smallest set of infinitely resolved peaks (isotopologues) that jointly is \code{p} probable, where \code{p} is provided by the user. #' #' @param molecule A named integer vector, e.g. \code{c(C=2,H=6,O=1)}, containing the chemical formula of the substance of interest. #' @param stopCondition A numeric value between 0 and 1. #' @param showCounts Logical. If \code{TRUE}, then we output matrix contains additionally counts of isotopes for each isotopologue. #' @param trim Logical. If \code{FALSE}, then we output matrix contains additionally isotopologues that otherwise would get trimmed in order to find the smalles possible p-set. Therefore, switching to \code{FALSE} results in a slightly larger set then the optimal p-set. #' @param algo An integer: 0 - use standard IsoSpec algoritm, #' where \code{stopCondition} specifies the probability of the optimal p-set, #' 1 - use a version of algorithm that uses priority queue. Slower than 0, but does not require sorting. #' 2 - use a threshold version of the algorithm, where \code{stopCondition} specifies the height of the pruned peaks. #' 3 - for the threshold version of IsoSpec with \code{stopCondition} being #' the percentage of the highest peak below which isotopologues get pruned. #' @param isotopes A named list of isotopic information required for IsoSpec. The names must be valid element symbols, see \code{isotopicData} for examples. Each enlisted object should be a \code{data.frame} containing columns \code{element} (specifying the symbol of the element), \code{mass} (specifying the mass of the isotope), \code{abundance} (specyfying the assumed frequency of finding that isotope). #' @param step The percent of the the percentile of isotopologues in the current isolayer, specyfying the cutoff for the next isolayer. It has been optimised and better not change the default value. #' @return A numeric matrix containing the masses, the logarithms of probability, and, optionally, counts of isotopologues. Attention: this matrix does not have to be sorted. Sorting it would also compromise the linear complexity of our algorithm. #' @export #' @examples #' library(IsoSpecR) #' res <- IsoSpecify( molecule = c(C=10,H=22,O=1), stopCondition = .9999 ) #' print(res) IsoSpecify <- function( molecule, stopCondition, isotopes= NULL, showCounts = FALSE, trim = TRUE, algo = 0, step = .25 ){ if(is.null(isotopes)){ isotopes <- isotopicData$IsoSpec } Rinterface( molecule = molecule[ molecule > 0 ], isotopes = isotopes, stopCondition = stopCondition, algo = as.integer(algo), tabSize = 1000, hashSize = 1000, step = step, showCounts = showCounts, trim = trim ) } IsoSpecR/MD50000644000176200001440000000503613747131146012264 0ustar liggesusers1ca9de76ce46ebb76d3b1f74a41cbc8e *DESCRIPTION 513ddabb2bff1be2f6812eaf0311bb11 *LICENCE c078b6e0fa518b325a07ce389bf3d352 *NAMESPACE d1a2d80c3997ba23db82f818bad08078 *R/IsoSpecR.R b37449bb16675df7b5a8bb164c0b881d *R/RcppExports.R 3fdda023a712844026886024d75210d4 *R/add_custom_isotopes.R 4edc65180ace4b56308a3dc7e8f5a671 *R/data_description.R f242a82ad404e57004e64bae74eedc59 *R/sysdata.rda f242a82ad404e57004e64bae74eedc59 *data/isotopicData.rda bd877d0b453ac8111f2191b74c4e9557 *inst/CITATION 27036285c7f80f58624888d8a82e3e9a *man/IsoSpecify.Rd 1376dd1264ff8f3b950b907034922b14 *man/custom_isotopes_example.Rd 6954e263d0e6521590a404604c1574b9 *man/isotopicData.Rd 66534317e16fe020015c9be1adcc4a06 *src/IsoSpecR_init.c 258378b814464af1414cc8f3e982b4b9 *src/RcppExports.cpp d178866feb63f9a2f47f1c6eb356428b *src/Rinterface.cpp f254c31d905862536708b7ae4899076e *src/allocator.cpp 2f5c436cf0886b4d885c64588be4165c *src/allocator.h 1fc304ce4267cd9af4b1d40112742980 *src/btrd.h 3ba9c5d453d043855fb7611893cb8f55 *src/conf.h 39b61624ad3c75cd3a5b779379deba0a *src/cwrapper.cpp fc8059050e98908f262e165ccda17042 *src/cwrapper.h 718aaedc3a77d00cc96f7c252033c61e *src/dirtyAllocator.cpp 067227b319b5e79331739045f377c5ed *src/dirtyAllocator.h 85776b02baafa830c2a99b55ab2a9ca5 *src/element_tables.cpp f9bdfba4cd4b286953807738d71de034 *src/element_tables.h 993448e9b469bd93efbba29738f31c94 *src/fasta.cpp 7f261711c42d2308b5464caf6d9d959c *src/fasta.h a110111265df93222392bfbc2252c806 *src/fixedEnvelopes.cpp 75cc072dc0fa73bc1296e43b5986137d *src/fixedEnvelopes.h 98c716503b6691c42ec22ce7b43c5b1f *src/isoMath.cpp 3d84cda0ba933a0638abcd380d372f5e *src/isoMath.h 7c8a567536bd2ecc51ab033078fcbd82 *src/isoSpec++.cpp 8a4ca59414ac92b438c06cb7c4f63184 *src/isoSpec++.h efecc6ef4eee77a2fcddf49d1ae769c9 *src/marginalTrek++.cpp 6fc2403016b4360e3d5a948cff52866c *src/marginalTrek++.h 3c2c90f00ce9fbdf655ad50533e6f019 *src/misc.cpp d45e6b308887c76cc0f56b4eacc087f2 *src/misc.h c00af37d1f76dc55a3672f22fd247d6b *src/mman.cpp 5e48c83cbc068e1dc14429cab1bdb06a *src/mman.h 5cd7fd97963a85bac2f10177a64c5a34 *src/operators.cpp fe09f7bd2aae1c231ff32a97aa6eefcd *src/operators.h 46f053c4489fd4da7ad8aed2607bcae2 *src/platform.h 659f46822879eb8becbdff53fd86fbad *src/platform_incl.h c0fa052107dd3ae2b549ab32af0b9e85 *src/pod_vector.h ada0d1a8ca87a4cbb41de499f06c7cc4 *src/summator.h f193346e97d17a9354e5bbbb2d6750d8 *src/unity-build.cpp 7acb598337f369472f0645e5ee30f71e *tests/testthat.R 27480ca86468d6a3d86af1722a12e2b6 *tests/testthat/envipat.Rd 8384122280a2eec17bba261f635c060c *tests/testthat/test-numerical-stability.R IsoSpecR/inst/0000755000176200001440000000000013746613715012733 5ustar liggesusersIsoSpecR/inst/CITATION0000644000176200001440000000311213746613715014065 0ustar liggesuserscitHeader("To cite IsoSpecR in publications use:") citEntry(entry = "Article", title = "{IsoSpec2}: Ultrafast Fine Structure Calculator", author = personList(as.person("Mateusz K. Łącki"), as.person("Dirk Valkenborg"), as.person("Michał Startek")), year = "2020", journal = "Analytical Chemistry", volume = "92", number = "14", pages = "9472-9475", doi = "10.1021/acs.analchem.0c00959", url = "https://doi.org/10.1021/acs.analchem.0c00959", textVersion = paste("Mateusz K. Łącki, Dirk Valkenborg, Michał Startek (2020).", "IsoSpec2: Ultrafast Fine Structure Calculator", "Analytical Chemistry, Published online ahead of print", "URL https://doi.org/10.1021/acs.analchem.0c00959") ) citEntry(entry = "Article", title = "{IsoSpec}: Hyperfast Fine Structure Calculator", author = personList(as.person("Mateusz K. Łącki"), as.person("Michał Startek"), as.person("Dirk Valkenborg"), as.person("Anna Gambin")), journal = "Analytical Chemistry", year = "2017", volume = "89", number = "6", pages = "3272-3277", url = "https://doi.org/10.1021/acs.analchem.6b01459", textVersion = paste("Mateusz K. Łącki, Michał Startek, Dirk Valkenborg, Anna Gambin (2017).", "IsoSpec: Hyperfast Fine Structure Calculator.", "Analytical Chemistry 89(6), 3272-3277.", "URL https://doi.org/10.1021/acs.analchem.6b01459") )