NMF/0000755000176200001440000000000013711174456010703 5ustar liggesusersNMF/NAMESPACE0000644000176200001440000001151213620502674012116 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(.DollarNames,NMF) S3method(nmfFormals,NMFStrategy) S3method(nmfFormals,NMFStrategyFunction) S3method(nmfFormals,NMFStrategyIterative) S3method(nmfFormals,character) S3method(plot,NMF.consensus) S3method(plot,NMF.rank) S3method(print,fcnnls) S3method(print,foreach_backend) S3method(profplot,default) S3method(register,doMPI_backend) S3method(register,doParallel_backend) S3method(register,foreach_backend) S3method(scale,NMF) S3method(silhouette,NMF) S3method(silhouette,NMFfitX) S3method(summary,NMF.rank) S3method(t,NMF) S3method(t,NMFstd) export(".basis<-") export(".coef<-") export("algorithm<-") export("basis<-") export("basisnames<-") export("coef<-") export("fit<-") export("name<-") export("niter<-") export("objective<-") export("residuals<-") export("seeding<-") export(.atrack) export(.basis) export(.coef) export(.fcnnls) export(.getRNG) export(ForeachBackend) export(NMFSeed) export(NMFStop) export(NMFStrategy) export(aheatmap) export(algorithm) export(atrack) export(basis) export(basiscor) export(basismap) export(basisnames) export(bterms) export(canFit) export(coef) export(coefficients) export(coefmap) export(compare) export(connectivity) export(consensus) export(consensushc) export(consensusmap) export(cophcor) export(cterms) export(deviance) export(dispersion) export(entropy) export(evar) export(existsNMFMethod) export(existsNMFSeed) export(extractFeatures) export(fcnnls) export(featureScore) export(fit) export(fitted) export(gVariable) export(getDoBackend) export(getDoParHosts) export(getDoParNHosts) export(getNMFMethod) export(getNMFSeed) export(getRNG1) export(hasBasis) export(hasCoef) export(hasTrack) export(hostfile) export(ibasis) export(ibterms) export(icoef) export(icterms) export(is.empty.nmf) export(is.mixed) export(is.nmf) export(is.partial.nmf) export(isNMFfit) export(iterms) export(loadings) export(logs) export(match_atrack) export(metaHeatmap) export(minfit) export(misc) export(modelname) export(name) export(nbasis) export(nbterms) export(ncterms) export(neq.constraints.inplace) export(niter) export(nmf) export(nmf.equal) export(nmf.getOption) export(nmf.options) export(nmf.printOptions) export(nmf.resetOptions) export(nmf.stop.connectivity) export(nmf.stop.iteration) export(nmf.stop.stationary) export(nmf.stop.threshold) export(nmfAlgorithm) export(nmfApply) export(nmfArgs) export(nmfCheck) export(nmfDistance) export(nmfEstimateRank) export(nmfFormals) export(nmfModel) export(nmfModels) export(nmfObject) export(nmfRegisterAlgorithm) export(nmfReport) export(nmfSeed) export(nmfWrapper) export(nmf_update.KL.h) export(nmf_update.KL.h_R) export(nmf_update.KL.w) export(nmf_update.KL.w_R) export(nmf_update.brunet) export(nmf_update.brunet_R) export(nmf_update.euclidean.h) export(nmf_update.euclidean.h_R) export(nmf_update.euclidean.w) export(nmf_update.euclidean.w_R) export(nmf_update.euclidean_offset.h) export(nmf_update.euclidean_offset.w) export(nmf_update.lee) export(nmf_update.lee_R) export(nmf_update.ns) export(nmf_update.ns_R) export(nmf_update.offset) export(nmf_update.offset_R) export(nneg) export(nrun) export(nterms) export(objective) export(offset) export(pmax.inplace) export(posneg) export(predict) export(profcor) export(profplot) export(purity) export(randomize) export(register) export(removeNMFMethod) export(removeNMFSeed) export(residuals) export(rmatrix) export(rnmf) export(rss) export(run) export(runtime) export(runtime.all) export(scoef) export(seed) export(seeding) export(seqtime) export(setDoBackend) export(setNMFMethod) export(setNMFSeed) export(smoothing) export(sparseness) export(staticVar) export(str_args) export(summary) export(syntheticNMF) export(trackError) export(ts_eval) export(ts_tempfile) export(which.best) exportClasses(NMF) exportClasses(NMFList) exportClasses(NMFOffset) exportClasses(NMFfit) exportClasses(NMFfitX) exportClasses(NMFfitX1) exportClasses(NMFfitXn) exportClasses(NMFns) exportClasses(NMFstd) exportMethods("$") exportMethods("$<-") exportMethods("[") exportMethods("basisnames<-") exportMethods("dimnames<-") exportMethods("objective<-") exportMethods(.DollarNames) exportMethods(dim) exportMethods(dimnames) exportMethods(fitted) exportMethods(objective) exportMethods(plot) exportMethods(rposneg) exportMethods(show) exportPattern("^featureNames") exportPattern("^metagenes") exportPattern("^metaprofiles") exportPattern("^nmeta") exportPattern("^sampleNames") import(RColorBrewer) import(cluster) import(digest) import(doParallel) import(foreach) import(ggplot2) import(grDevices) import(graphics) import(grid) import(gridBase) import(methods) import(pkgmaker) import(registry) import(reshape2) import(rngtools) import(stats) import(stringr) importFrom(colorspace,sequential_hcl) importFrom(utils, capture.output, .DollarNames, file_test, flush.console, getS3method, packageDescription, setTxtProgressBar, str) useDynLib(NMF, .registration = TRUE) NMF/demo/0000755000176200001440000000000013620502674011623 5ustar liggesusersNMF/demo/aheatmap.R0000644000176200001440000000365713620502674013541 0ustar liggesusers# Generate random data n <- 50; p <- 20 x <- abs(rmatrix(n, p, rnorm, mean=4, sd=1)) x[1:10, seq(1, 10, 2)] <- x[1:10, seq(1, 10, 2)] + 3 x[11:20, seq(2, 10, 2)] <- x[11:20, seq(2, 10, 2)] + 2 rownames(x) <- paste("ROW", 1:n) colnames(x) <- paste("COL", 1:p) ## Scaling aheatmap(x, scale = "row") aheatmap(x, scale = "col") # partially matched to 'column' aheatmap(x, scale = "r1") # each row sum up to 1 aheatmap(x, scale = "c1") # each colum sum up to 1 ## Heatmap colors aheatmap(x, color = colorRampPalette(c("navy", "white", "firebrick3"))(50)) # color specification as an integer: use R basic colors aheatmap(x, color = 1L) # color specification as a negative integer: use reverse basic palette aheatmap(x, color = -1L) # color specification as a numeric: use HCL color aheatmap(x, color = 1) # do not cluster the rows aheatmap(x, Rowv = NA) # no heatmap legend aheatmap(x, legend = FALSE) # cell and font size aheatmap(x, cellwidth = 10, cellheight = 5) # directly write into a file aheatmap(x, cellwidth = 15, cellheight = 12, fontsize = 8, filename = "aheatmap.pdf") unlink('aheatmap.pdf') # Generate column annotations annotation = data.frame(Var1 = factor(1:p %% 2 == 0, labels = c("Class1", "Class2")), Var2 = 1:10) aheatmap(x, annCol = annotation) aheatmap(x, annCol = annotation, annLegend = FALSE) # Specify colors Var1 = c("navy", "darkgreen") names(Var1) = c("Class1", "Class2") Var2 = c("lightgreen", "navy") ann_colors = list(Var1 = Var1, Var2 = Var2) aheatmap(x, annCol = annotation, annColors = ann_colors) # Specifying clustering from distance matrix drows = dist(x, method = "minkowski") dcols = dist(t(x), method = "minkowski") aheatmap(x, Rowv = drows, Colv = dcols) # Display text in each cells t <- outer(as.character(outer(letters, letters, paste0)), letters, paste0)[1:n, 1:p] aheatmap(x, txt = t) # NA values are shown as empty cells t.na <- t t.na[sample(length(t.na), 500)] <- NA # half of the cells aheatmap(x, txt = t.na) NMF/demo/heatmaps.R0000644000176200001440000000170413620502674013552 0ustar liggesusers#' # random data with underlying NMF model v <- syntheticNMF(20, 3, 10) # estimate a model x <- nmf(v, 3) # highligh row only (using custom colors) basismap(x, tracks=':basis', annColor=list(basis=1:3)) ## character annotation vector: ok if it does not contain 'basis' # annotate first and second row + automatic special track basismap(x, annRow=c('alpha', 'beta')) # no special track here basismap(x, annRow=c('alpha', 'beta', ':basis'), tracks=NA) # with special track `basis` basismap(x, annRow=list(c('alpha', 'beta'), ':basis'), tracks=NA) # highligh columns only (using custom colors) basismap(x, tracks='basis:') # changing the name of the basis annotation track basismap(x, annRow=list(new_name=':basis')) # coefficient matrix coefmap(x, annCol=c('alpha', 'beta')) # annotate first and second sample coefmap(x, annCol=list('basis', Greek=c('alpha', 'beta'))) # annotate first and second sample + basis annotation coefmap(x, annCol=c(new_name='basis')) NMF/demo/nmf.R0000644000176200001440000001201613620502674012526 0ustar liggesusers# generate a synthetic dataset with known classes: 50 features, 23 samples (10+5+8) n <- 20; counts <- c(5, 3, 2); p <- sum(counts) x <- syntheticNMF(n, counts) dim(x) # build the true cluster membership groups <- unlist(mapply(rep, seq(counts), counts)) # run on a data.frame res <- nmf(data.frame(x), 3) # missing method: use algorithm suitable for seed res <- nmf(x, 2, seed=rnmf(2, x)) algorithm(res) res <- nmf(x, 2, seed=rnmf(2, x, model='NMFns')) algorithm(res) # compare some NMF algorithms (tracking the approximation error) res <- nmf(x, 2, list('brunet', 'lee', 'nsNMF'), .options='t') res summary(res, class=groups) # plot the track of the residual errors plot(res) # specify algorithm by its name res <- nmf(x, 3, 'nsNMF', seed=123) # nonsmooth NMF # names are partially matched so this also works identical(res, nmf(x, 3, 'ns', seed=123)) res <- nmf(x, 3, 'offset') # NMF with offset # run a custom algorithm defined as a standard function myfun <- function(x, start, alpha){ # update starting point # ... basis(start) <- 3 * basis(start) # return updated point start } res <- nmf(x, 2, myfun, alpha=3) algorithm(res) # error: alpha missing try( nmf(x, 2, myfun) ) # possibly the algorithm fits a non-standard NMF model, e.g. NMFns model res <- nmf(x, 2, myfun, alpha=3, model='NMFns') modelname(res) # assume a known NMF model compatible with the matrix `x` y <- rnmf(3, x) # fits an NMF model (with default method) on some data using y as a starting point res <- nmf(x, y) # the fit can be reproduced using the same starting point nmf.equal(nmf(x, y), res) # missing method: use default algorithm res <- nmf(x, 3) # Fit a 3-rank model providing an initial value for the basis matrix nmf(x, rmatrix(nrow(x), 3), 'snmf/r') # Fit a 3-rank model providing an initial value for the mixture coefficient matrix nmf(x, rmatrix(3, ncol(x)), 'snmf/l') # default fit res <- nmf(x, 2) summary(res, class=groups) # run default algorithm multiple times (only keep the best fit) res <- nmf(x, 3, nrun=10) res summary(res, class=groups) # run default algorithm multiple times keeping all the fits res <- nmf(x, 3, nrun=10, .options='k') res summary(res, class=groups) ## Note: one could have equivalently done # res <- nmf(V, 3, nrun=10, .options=list(keep.all=TRUE)) # use a method that fit different model res <- nmf(x, 2, 'nsNMF') fit(res) # pass parameter theta to the model via `...` res <- nmf(x, 2, 'nsNMF', theta=0.2) fit(res) ## handling arguments in `...` and model parameters myfun <- function(x, start, theta=100){ cat("theta in myfun=", theta, "\n\n"); start } # no conflict: default theta fit( nmf(x, 2, myfun) ) # no conlfict: theta is passed to the algorithm fit( nmf(x, 2, myfun, theta=1) ) # conflict: theta is used as model parameter fit( nmf(x, 2, myfun, model='NMFns', theta=0.1) ) # conflict solved: can pass different theta to model and algorithm fit( nmf(x, 2, myfun, model=list('NMFns', theta=0.1), theta=5) ) ## USING SEEDING METHODS # run default algorithm with the Non-negative Double SVD seeding method ('nndsvd') res <- nmf(x, 3, seed='nndsvd') ## Note: partial match also works identical(res, nmf(x, 3, seed='nn')) # run nsNMF algorithm, fixing the seed of the random number generator res <- nmf(x, 3, 'nsNMF', seed=123456) nmf.equal(nmf(x, 3, 'nsNMF', seed=123456), res) # run default algorithm specifying the starting point following the NMF standard model start.std <- nmfModel(W=matrix(0.5, n, 3), H=matrix(0.2, 3, p)) nmf(x, start.std) # to run nsNMF algorithm with an explicit starting point, this one # needs to follow the 'NMFns' model: start.ns <- nmfModel(model='NMFns', W=matrix(0.5, n, 3), H=matrix(0.2, 3, p)) nmf(x, start.ns) # Note: the method name does not need to be specified as it is infered from the # when there is only one algorithm defined for the model. # if the model is not appropriate (as defined by the algorihtm) an error is thrown # [cf. the standard model doesn't include a smoothing parameter used in nsNMF] try( nmf(x, start.std, method='nsNMF') ) ## Callback functions # Pass a callback function to only save summary measure of each run res <- nmf(x, 3, nrun=3, .callback=summary) # the callback results are simplified into a matrix res$.callback res <- nmf(x, 3, nrun=3, .callback=summary, .opt='-S') # the callback results are simplified into a matrix res$.callback # Pass a custom callback function cb <- function(obj, i){ if( i %% 2 ) sparseness(obj) >= 0.5 } res <- nmf(x, 3, nrun=3, .callback=cb) res$.callback # Passs a callback function which throws an error cb <- function(){ i<-0; function(object){ i <<- i+1; if( i == 1 ) stop('SOME BIG ERROR'); summary(object) }} res <- nmf(x, 3, nrun=3, .callback=cb()) ## PARALLEL COMPUTATIONS # try using 3 cores, but use sequential if not possible res <- nmf(x, 3, nrun=3, .options='p3') # force using 3 cores, error if not possible res <- nmf(x, 3, nrun=3, .options='P3') # use externally defined cluster library(parallel) cl <- makeCluster(6) res <- nmf(x, 3, nrun=3, .pbackend=cl) # use externally registered backend registerDoParallel(cl) res <- nmf(x, 3, nrun=3, .pbackend=NULL) NMF/demo/00Index0000644000176200001440000000013713620502674012756 0ustar liggesusersnmf Using the main function nmf() heatmaps Heatmaps of NMF objects aheatmap Annotated heatmaps NMF/.Rinstignore0000644000176200001440000000002713620502674013202 0ustar liggesusersvignettes/cleveref.sty NMF/README.md0000644000176200001440000000330013620502674012152 0ustar liggesusers## Background Nonnegative Matrix Factorization (NMF) is an unsupervised learning technique that has been applied successfully in several fields, including signal processing, face recognition and text mining. Recent applications of NMF in bioinformatics have demonstrated its ability to extract meaningful information from high-dimensional data such as gene expression microarrays. Developments in NMF theory and applications have resulted in a variety of algorithms and methods. However, most NMF implementations have been on commercial platforms, while those that are freely available typically require programming skills. This limits their use by the wider research community. ## Results Our objective is to provide the bioinformatics community with an open-source, easy-to-use and unified interface to standard NMF algorithms, as well as with a simple framework to help implement and test new NMF methods. For that purpose, we have developed a package for the R/BioConductor platform. The package ports public code to R, and is structured to enable users to easily modify and/or add algorithms. It includes a number of published NMF algorithms and initialization methods and facilitates the combination of these to produce new NMF strategies. Commonly used benchmark data and visualization methods are provided to help in the comparison and interpretation of the results. ## Conclusions The NMF package helps realize the potential of Nonnegative Matrix Factorization, especially in bioinformatics, providing easy access to methods that have already yielded new insights in many applications. ----- __Travis check:__ [![Build Status](https://travis-ci.org/renozao/NMF.png?branch=devel)](https://travis-ci.org/renozao/NMF) NMF/data/0000755000176200001440000000000013620502674011610 5ustar liggesusersNMF/data/esGolub.rda0000644000176200001440000107763013620502674013716 0ustar liggesusers7zXZi"6!Xs])TW"nRʟXg%>e@HrŽ԰5s.x *qjnqLlk$$(?{lR穯A|j%%ϸ5~KZ8 EZw i\KD2%HxK`2^f{ WDsDO=n6|~øWzyUX+. o9Kk9/j8ˆҏW]Z-E[Q>ǥJBN4%>C3٭ZhBYxu|S@c^[47 $(6L9Y k">m#i#`Fdt'`NRoļX6m4?㑣෮ߴ]>yY:̫XzS y!(ᚸjZ|=v$%517z\di6V5>09}Z$}O!pZ`L@xefOˡc t(Gf8eОDr/4[Dm&j ݰ,1aFMw]nvja9!hW?=٘#$#P()m*sNs$p,t>3IoZˊBH2b2-?]c-a-W_*-y a Yev=.N.d}v*+ ~6jD`ZT{2OJ;iln'Pwh!{՚^Jsv_IU[ A mAIEԂСa&ӷ2 ӋY|4PUIC c>" (Fg "tYSjូ>v/sYa67H UWK1ȑu,vUG7IjdO%L/WB+fF4q6f˗=%_YVN?gG2Ԛ\tXZ0W::pZHfo/Յjhn,3ԁ:UK |2ء 1ct  ׀V*[X4+hrZK& V%geMP:)-tg#+״b=)'vc DQ8WۉäIJQOpfV_?담1([Ho4/;*ZSVm9f*u MD~@*΄Ԛ.=j21۲^zSi\ EUQS MjrdR`#~ƜDn' bGvRk*"A;Bra4DF\nȾpj#&r,$ZndF7ȃg^qU.d )54ثX@ s}6?~;uIݧw?B;@RUɿ輓ՄI%QSֻj%-IyJaW')Q hT2A ΰ\9P]Qw}L]WG,73~V< a]ſ3Q(]VƳVAWؤ0¾GeQ;YKꚵ)OU)(v6my/خiv0 X7Y1ꥩvvG*8o{*ݡ}u=Q(0,=x ҡ3ÊUR6MLˆ}\;YX-3gG PH_FwXdgW|ֶD;;.UWBu(Zf>/@r5^;LxFz'tS+UwvrD%ΠTgbX)=Pky&p5Ц?",h(.&ksT"/.90D5 rr.<,eQ*5H 8%>SpSTNKIyӞ+uDB ЪIW3r*jQ=z; l6 #-L ՟]9Upq}`m]̝>r{X{\uʭ[4& JXsFFB@ګډ1!|pi.:yC7D|֕#yQ@2R @}^v)ld1[iv=PyY*ׄ |Dx=|ك_yo!)H*:Iݮ*̓Bi {ID)6y:2řO E|@E%P#o O>XV\ hMvUWJQQvܓLDŽ4l)Q_z7r0˕ Uݦ= ?xo4,:zdFbğW݋ёow \H;紆,[tqݩkM{i[ &WU FNk.U}HgnK|Вh~c+e+mD]&G6sA|>_s`-c[1?9@{ xa.QC,W:C2"\O9%` uYś9u-=~[ Տ6 WRnygzh+hkƦT2?:ػ<(=Œ=QTl֜3-zx$n蛁HI}ε=&ɏQ\w؛VG;הL̈߃}D9Wl -#BmaZkNEu߉tVưAˮHH=cg.\P~ا {ډnF_|ܐRJ+^> 6/<ޘ'ɒ(d 8ڮ["][=AV4o'h/X4'lY)3ܶ@-Jҿe)4dwB)tI@dm(D\Pɮ"ξ@ɜ214c݈.Ľ)un+-/szy4 ZkiBK/\NĚ <f|P<2bhc6JV/X+D踑\7pNUy OkM #i| CԷN ˌqթ1S qJ-`0"}*?fqr'_ʪp(-l!ysl fO-,)m6}b{!M(U qiLUz@AtTP>a*T"H1Hkmɭ}NE=21*Vhk#/)q8a NA@ "a_j]=y5:Ga#O :6r%6:G U(Ey՚ёu2:m@izryk΋ʼ3 è{Q$+Jgt}'5~q4j 2M L@W>^%-w2On Kȴtk;k,#;0d_wXF\A>9I³[T4ҊWm\.)%k fÂzH.]*riWO#хa)ұ ֿIAƃx9=|_rVApu]UYDʈo0\ds:&22Ć)JCI4tG:: %x%_fGzwe:6$[ 'H ɗ]9/B&o lCt1a-v?Fs]~ iV[[q]gϩcQ +BY匜W pVṡ6\I-4Ta&gNFstSjتs?킣qnyc\]JDNH˞[qMr ݗ8XR`btJ32ěp4X53 라a%n^4}`x~ OePGrmˏ 笩4B74 }J0lV{}|DŽ##VKwD߽ɳ*{C<甬qZLiW.zaQ?0\ J68 N!ཛJ^2 T`K(]& :l ZN~I\sd CcޅsIpA.Q>znHlE]jjy[/,s]cIl]xQDv8pb8 $J=ԫXV<cvkEl]V" uiaߣҙZ_1Z%m'l\},f#"Q_ w/ b}bK81/ ;BTr{e'N| Y29)|ֆ)s!.>y=An3c Gh>Ts6s`f\lT%0[ xIoH%20f#ǔu}3P2Diop<6 SU}q$P7g. n[P'UwbXL=j 3¤lY*;TܐQ׾,DBzJʘ_u%/3”]+U_ٲ UAtˆ[I~cEþJF ╇'#[P)"Y֦U#3ɑUW%|:#\JUˊ<H@"4QG1K'Ϋ1-zGyƗTI/Wnʘ7Abh'8͞ p@߇,Q귏PsYYCʘZ07gEG:Ġ^酸4XJ7/sZ a7I#ep!%?Az+3=GE`)gל Fô|mUb\p@Ti}!4F5ToSȈOPɾᐨpfA0POVt%;Wۇ[Fk*iqRuQRG~NyK07k>t~odC)`b >3/ $F8B=i:@*pMxn3Yv"~qTㄏ T 90X廖1t;,* rAwBt"_CMp\K6"J)n,? ={QC`l|^}s.p jŧ,ś Toci:H *U]ѡQDp Ư/ ?L^ i7f'Xȸ $q#ʧ$ ö́i% 70/IVBܰDp,ݘM%$iqnObk|5ׂ6,5Uջj>:ty}D1LFb]%݀ ݶ1sԸcsb'JK @;gqϻT7T%Fԯ&I3)MA2rڡ^&wR0 mQ,pZKQe)IKr};[A[agv| UOcrDatqFڬaL:3ҡ!74x2O=i6Vmb(/ދ>R_^ʙRbv촖zE"{Y]A>$ 5sVoÒ"w#0,>ظhsNCP-f,GLܽ xi0\ -IrLHQ 1uݭ81Uor 0(w8k%PQ ev|vOE L{;]%Y7Tr!'KdmFSF%Xq(iذUoNG]2v-uZ`@;=\Ɲd(cx7,bAFusFL͜}1$ '7xDaTءv׶A;̱ؕ\kymϟg}NBsiE|L>tEtkJGKC mU4]8xZNriYv퉊~qs;{W!Ϲũ͠ є+ y*JpO^{k[DWP ZV:l[X#QKJfx+SfG04FFzӴZM]e?}iDXWekYG}d!tr4%lP*$Z΢԰eGAo&!Wa b@JX +uWo,\t|y|Iy=oA5\H}KӇkMDqwo9bM!_GK* 0)^ǯ-F{FQ#\19MR6 GRW'9>7D:ov4dKЈyslgza#z}rKsƋrYג?1%X<̴do[h{KJ! )zVRUc4DW},w>ηsU-/3ȄSrF6["lb0W2i$\jʃ8?~}kU?,âV H=?[2ImCE`bV@'>B: 4#b~#@L9&S^.PTKj;k0Aܙ$@5I nO)"3 GAys*̔vޯwZ8p~P4ݜLLN9`)ǣ?ڲ7fޘguöHQG e?ERL%Z2;nb:@~GQT݀@~9 xy™<? !Ĝt1["Wn.o424Ls:jsC?z^J{&vV{%6ӓd[@' "4&iI[x:c_4 Ud # |\υ` uM*84W!iv9dF#ӂtŴw@Gnܷp2 /=qF*=Ϭj7?n7b'^cyYAu_AԦcC#G_a*2N%E};d0+Ɠd3~-Ji@Dbf$9e7*.Έv z\?(C./Mh l@Ze`BҪKSOPgin0mD:Zϭ2͎ ;_V?ԏX"r1t';`g[hQ  zpY']A5qmuz%8ʍ.j&#&u#/[ uub_'HΏ2c(*P`xn9*?1?VV:ޙ5+43Usap1R@u`Et:O.!]TVT'UPaz?|.!d Z= X}WR_l!,aR@vNn ɷ5^Y3]1Kw]b; Dgܯ5._4g u@'O2_`~=|J_ÌVd`KPJ9jLB^X+y4fCHϿfBXe O\Z feA0 3 *Y#XJ/*mQ}=͆@7%#EI`qN| hҧAh/Cg$fG9ȼIT u\ZLّ?rFkSp}P!>+[ U+|W%ȡoCaTGշqCͭ=GwB)xk>#[=.FR_r,ޚ$(\U><ޠ(aPl_Xh){:R&?(2=#a4+`=a?r!W[rC ezyA[E"iktj)l(sBO>ݢϚעd$bHx;\\K@Lg{uHm6UT~Qd#p-kXoWSQ&W!dM9<+ sqdDڌڱ8UujPyخ<&=@+ujQKTλçK2v9C\5+TZAj :Z_{{]l ,QPo4} &%݅bԿy ? k}\v}~f~7@)dm)'0|{XuˋA>冔g`d [Զ?Z_wT$}B~&n@ݎѥ10 |:" ۃ{9E9sľWזWgKǕ_Ydxb^AŜz麚7ar 艵y `!b-IJ8.sr)XFpspz}raQm ޻f+CCx1eϡ̚Q(cQT?UUʰq J,o@¹=Sb*F؟N󜥟s9O9.b" 4U-I|!N ~E ]~I?y'  >9c!76}p`BYcaoVXa8Mya<~/p B Zƛ_2_؇|rW 9җDȉkWzA͕7 D+I0i(OrWg0_$I~#e· / jq;#v0f [0qK\T@0Δ{I %Y#YK`%V8 d GEC$ѩ9GI0: +k͛U;M?u3|!C÷q['.w>ih/6!kɡc]dX'O~} 0^W/wpĬ>:tn! SHr)THOn/5I[A|[jc,c:d?栳#%.`7Gjs=5~3Ѱ9G-AjWjmh-"׊}4!p*ēfmwnV65UBָ>RzD3U#0|*+h^{Ƥ!*Ȕ6Yj06Bðvd+ !A:Yo @H'¢-u <6h6 HzvMVrL*l=h 2=E_!Ar&谒twxW;qAAR:jjT)Qc%!K=,|hMrOp oKNF ׆HFJۉ\_KMd?fN(%t lХ/,iHO-{!}Y5m#{3Fj VV}K)puոߌB׮UckK!ťQԶ(4M"S wq?FĿdSߊ@E*"Y)U%%5: 2?65H?UM&w >Wݵd柂x ʙ]֚òqWַ??aյ>_=UPn,x4V{-# 1Tfj9"GJ<8Gs<^>;3Up./>"l%?f?lakcȹ%,X RHw(|L46+ÚH,ii˳LCp)fT`Bix|N3**fG67CZ1hb.!JjXE.EwcdSx+eL_. pc7% '.> g,aCBZW̠6O\p$ nulf9FR݌[4ND#^v&@BT\ZFp~^L%!p>b6W53| y(j>@G4f4 T{+qo_C5S6?P4ƾj/]u~'x5C) @b֒]T5kiUpC^A 6 }}Ma3f VUbN_=fZP::xz$NF_=ǂ-T |׋{h:-(=Pe4KT{Dj2Q=Dj)Xa ^V-$|\:nӯ=A$[`\IN3&?3$=dPD<'$7-}l:M菃]$qH!\$1L R#zm`L.|wtZS([GQ̺2 z3[NIgf|x(Cedo 5bOn#i>1p+nu& "iJa!`k_pЉZ@Iay3 "ZoaM=,<'KKPLKi=e=pT#8W7 gAKi=h'ɏm[f2}'vTvwT;v 5}q[? 06PH@<`U?tc{g6ʾXeSԵn_zYOU_>۬ 9V#S"u@múB]tؘ=M yǶP6 SFO]=CfhĀ 1~-B_mD!K笥( J4O0Xj<;Y8)RFs,Jm(vOT+Ƀ%FyvaT omk*5-dD'xP^GjW EWt?#N1>Z߷9_Qt4@߾.z@L{E&|kGsaVBѸa=Ѥh Q$ HyA}3oj h#vӀo:?oF6fë\x*^DlLx&aUz' _2#J*$LveT8Xt9ZuV0oX li~Z(P. B"L]pE /X֜JJ0UA(UΊbvEN-"G ^p9̍+@^2HZU r}:+DZpTm s?:C =\k g ;oxP_O;vF7Z0LS xKOtbOH%>ȃ/*4 gƩH8KounPzH0voCA^*7<!bN"Nklkj UOXYlU9  `jkqJX5tid{zN'J|s0DZa䞋2+i';Ĺ`keo+:y`(QΚ-<V=d(ف#zL]=eMB`O8sYsՒ)ny1H 8Ę3A⧅|d*NU1`r=g f/cT,]~~9HzRJvQ*^ps̘|B=Tc(ƫ w'迳]ߞ,W8'%)08Ts!F<A뮥kv1o`~?ۊDѭ ^>kG9)Q(wd]02;E"ָ̊;#7=u>rfr ޴`z _Wk>B{ʲ<È⳶]dm/6yUS^*8P+c s bn ˋ1g(ޫh Vx!&eo, F? % !#j?>p|d׵6-KCm7,odKvFzAK1@* \4j9~MiFDדTzcMg!^!Ӽmxq5:LaqT (t:=nN_*>fjPLH38C5b7#wMB0K~ 0i?j8I5$= rۙ xfٕ:}+QO@|J#@z.S;c1Y"e&Gީ?$6ğ(KH\dr|K[!2PXG`RZWᐨ;ڎlŷ#$PB,>a KX6ĬMBbsS@J13hwN9<<^2i˵2xF<)YOʋi$reYHS ihO2bErRROVuhecڪ]q up&wա~}#; !$qпpBTqɵW7@2rCzDu]n(o G 4!;(d,4]>/x6}FfւofpTGV.'ZVB͡( 'p8/n-:Վԛpٸ"X`;[{|}fgԸt~;xѢf\9i)ƥȅ\ŷfOHd㟾\OOFC`iW# o C<摰6Ck3Y[풚'j;LOV}2 hxFl >=9U\&A\j*NMnc9Y D*8~ɑ{|Yޓ{W]nN; ;ȂIv w¼hUuC"AyW(0(SA%R5P4|wê^N=$F*|^|(Zʄ%UUk1jHX8#1glL=*"ҝix-E[і|BN.rO[ܾpJԹ[z [_"Cq*j%n+JYNni`8ڨNB!̨g_YyP^e:HDBP❷|?3])ubs\L]ۃ.sGj*2a3aqԅHyInǻ UZn!5"Di."#! !B s/.8tұд5 ]\CnZ #qȆ5#y}A+u[;EQɓ#/Aٙ!IDI)N`ZQ$j*Z| G_.]VkB7,Tmf%EC<^duL{Cmۧ"$b,_S3v(/N)yՂ ëC%"Mg0XMNrBϛ'|Y" ; x],6ZFd;_h"]FJa#aH=|D2زd\#)EwіA{kVy-9`p'=H4r(% Fj5> #&7; 86`zLoބH[ Go+y ̧_ zVD'qr_s xbe|1M0]ZG\!ФB3~~:sQ#%SڂxsoW&^/T>ÌC9"MRI~`_p!|ZW#,?eD **>qtSΫaSMl@'Rrk&_.a5ő]l2׭ ` (\=(H,m?L \o<i{yO:Њ ǥO.-7Y<-)6:Nĉiw ;MbtKOa/"UdyY|$'k*t@f"ORcy<ٻdH2iUT:er L'JY6t-" 8C;*,m`8h"I1 Uʹws؍pLd)E潣/pۺqح-dğ~#Ĝw9ͨLctr~|3.3˰/[x {ΑquB7b~tٻN  gdlFVɘ=E bZnuLtf˲\&d(Cܒm|;hH۸n&<LT !N+A]FѠ_nW298b}hWdȩURo m 6>s*-G8َ&Sv_3.!)%I͸2Е  ,v3a } LX*fxec GXMA@Nw<m 'pweILiwCV/ai5 L/pX-)owmSl _K ^j8ΊYJ%:) M3 Fύ0BRǯQ}vM0Ljmfء!V =UOi[w1'B?-*h^KLkq>wT4ԱwS@`-pҺlR6s#OuM++*/0V{v6m xM>ddP҇Wp.- cmtTeҜk8O2՚?蘰zRT|:='Uj^T(JwnXDY .`u9dZG'rDS\J9t^sT ]Al7s y|9y9JvAAMO *C̅|2uN=4  |uU6%7ڛp2rT 0J쭥 ;T&='遛F路UeLaA+%U܉,?+w!džw&x\}2eDp$RbHS\j|\p6 *g) /\v ! [15~_4wWTVd A,H  ћ%-1 ;y=x8գFOwYmX& ˰6]2 #U}Gt˜]dY[s |׷@0/v41&S̲잎MlSSD^.=9Du,a΁%uoig֧;9 owMzɤzy83AF]$G yl6; b !Aq7`)?*9Yos#*'v!GA![oR}O g87o$ G7|֫_v>5w_Dr.xIIN^o=: En㮖g$??jFzC5̌94fʸطwx[9:R3FT.dX5sW&K;Yv;[=L=-"4iT gq>$jy4#(`TM/F,sk-أq$P(Fl)ػb~8S:X t._?U%/U&f#¼>pFmJ*"+:fEWG|â{OB$ ?zX+ P\i}Ð0^ laSvxG8DVee>Ů8seL]y֪Z}}dn\qޓꕭ]ν !kpiM s)cX-u2BwCӎ|+_7U}V!u hUZx2߸U 68[[a^cPiiM xЩI K|ɠ/v՘[p3'b$hB ӏ:e9+$t<M-ϖX1/ӋuLZs,Da2vSux;M&v4F5H' QJzT '`}S>wD4!eGi_Z˨,%kH$֔hl@R%,(Qщ_ZEoRoa54žX;R5z Ԫ4XUvPP3:"س$T(oP1XD̗;\5E*RN7dBy bwRS6wtw6_WC{1ֺnTϽՎ2rjjUh91zAFȷ7Jj2|}L؊}iSDÈp u$S42Aryebj.ItƬ I03u|`!@iz0! AV/$< Fo|QͰ{CcU.KxxO(6WV9Z rۢ4紽 Cl۟W? d[ʒet˘&*]H /PP<Ռ2IY F$PY/(iwJۘ 3m7No]5 ^Jٕ `EgdM%bteT :͆I^AKn岢lW=Z礧o7 n,vKqŬ @??_#.t/ aoXLꖎ!ޏ qH ylget{[bZvfƮ|ؘkX>{eg.I~RBhBcƥ9PSmzP jFɨ;ae#5 kmqZvhZj6-tuyDrrő;%ddM$QYn8D9#g:lb0x6E7ъn/)ե7Z^Z,!ӂ2GC "*? 8G' TW}LpN#F'_l{"]%@8r<_}@Gҗq Qцmڵ73`͠MM)XCRJO@$sh.YA]2Ds^3ژ,Bw:sѠ :v\ޏ [z6N.Zn8 l/>؈Ȩؘa{YOzO EM8l<|T [;Mb? GV@S`L=5ioO-M X]Rĭ/͐ht&Vh1Mˤ^ r'>aQ$"x!y'L؎cmBb@ Q]m{ 1t/͸J͉޳S/C{!lAad,ʃ]с! W~dwDߗdSWB{N&D$^;rhoA#$N}QH2ݯ^ɰ?7C4m޲ԪHPŠ sn~s`IXR'y efSpv uS%w3H}a,$\CDt^X"y6LYrE8xdb=@"q+0#-7TQ&X?H@Hg_\I%3jRWA^VJ\nm[ep]+ m'B4p$L U终e#&m("yakM-(?>$h|v:YQ>bhE|A=2Rx:b^$,g*O sD}6-A!>"VQ ^;ꌱ jK=n3{"Ĕs3ԾZV]Zc[وa΋קy F%8^) jH֦iF9/[ѦCf`di =孁)̻.+C*O֌v-p3a`T$YD?EXE3TLY2ue Sv towlź0)GHJ/t[ٞ&A\xx{Ry𯄝}!ig]d:UC/l{55$jͻ2jfV]x# duD @ީAQ7;8rh57qUQvp޶Pt)K4`Ó96ĵW3׸.S D)hNR$ QMF=se Յ  vQYf4WrĤĜ}.`ÝcuQ_{rĦ VH=TQ6/E7H6:{!FzkX]ۙ7R-J5#n![/=--,ݟ/^t&hv\P>8!3Kp^; à,!.t,`TE\ު ^O_%{6šn0';z[+BZN6AƎYƹ705=/`7H[OZbt9)R5{dL0r1=4%n'ILؘ;L"bٗj܀Ta"tVEq+ƚT HBp\UD2o6;iRPcdaea! 61R[ܘpV]볫Z=w,wDDg[gcdYl$3T *nUj^Pe_gCE|`akϿɴz({LO_jILulVG '6:~z4΍_bo!_ T`(v^NK%+ 8[zaژT`@X{50ʇ2JUz$426FyCp[sG%4MlYRthXOt%3mu4^6\ClwNgZ[@i٘aZ@ aphH?z-b5i) z'mZP]mhդ;= /:l g.nK"fvjZGOOñ.i|:3=s*E"Z7 Qk?\.P~wZ0Q߉ kݿ"9$\:-akc:Nd?Pk-{W}3 r+?MQ_:oŽ*^ާSӌb" 5)6HO`FwI ҡ2wcgU[ Ռ5S?UA%&-ɏMܝNxS.t+m59NrǸbPL(l󊙶 K">|Rl0Y:iRO߫ O) Mp/$HgaVGq/m$5UUJ(e|L7ߡ˜%gWic4fm<6B_]$7+}pπ5o˴5qJPJW8oN[' @qIs ̛Mz :~k KVxQ L,skqӞFHRa/sL)hN$N]AQڊe|$[v&6H@(`~D漿 DX5݂q`R2+ ,7rG^Y^(7z<.9tg]ls'5+pk 9@hՓsse5%gQձj3 QIp:UmT ٭ѷ߂a$~b41ZA>{f fKː3^Oo㻆;9h4TuA9D7~!#rwQC{ReՊ!oG.VqdYx2.(g<\8$rJ闏6(sO @7+o- oc G.[.yrq4^F# :X{\Xs O5SlO/[ èF]Hݵڙ.J/ pj`Q= L{zFP;۾ 3Kq @IUJj~V'A),9o }Y=$c}x w×s AX" hRRr1vmG@f(z Jqli74f]KT$8D׽C Zuʒ;׉OBq۶/E~N*(7kMs-t{B[7 hRoJ ;J.S^-3|USZLbbʋ2ٸ|M@$c؉wzm8GUo+ȷ/4C(0~~a"C$ϳferHJ&'6=xF4lF^>|=u.(\:V$mՠ<&͹ yfX!yGӮTA|aN*Y_ u>١mka=7RPAE!آ`y"K>:-zPiG!0V)+A-|.d&[qG8L$tt6Re9f35^UXAbǪ0ÁXs4xn[4Q tCZu}d uD&`Ȱ(& Vާ JQ@5E4\޾y{YQZ^ uS@]ۢD PXP^'AHF|ӚW.Zgj7}fACFEo jsR]1+S6Vɻws.k /q/3m>?wŞG2Vu@gQMJ!Vyr| &9YxcߜM 8ԯ;̘8`ӅޖL%̦c+B@m4sK B.‘hb[Tv&FkTG:crI椹 :y qsWnqLnA XD>hf1\_)2,$ȴʅddTajLm.qp.RߺG0Viц'mfQdϜ7{욙/覑y5΂~@jF͔wd'ivS7% ͑*ҌxT).̍ qc/mu *?etKkH0{bbd6_) ZieI!99,I$XZ``곷dbSW}aoT4fy aW]Uyiؽ*xpw'UC=Z1cK&0jG5WlK2PG]gF.RKd`'aSRKQ^Y`L*Х #7> aUCem;:3NYvG͔_8;3ҬnRW.3¤ntˌn< ҂ZO{j Sf<×!r!8D٤V%ia6A>ʰd #j| xMqccu:UEs'j>TS)e(~}O./!7Dr]^M(=vZ_}>(%= Ƒ ݿQᅤag]=|3C"J3 R yebԑ.[Vb|gG¸ӟwѶ>%ڜ<zXU7 h$6}A\ cPtn/c4u @f--t5y5[֝3r6u˙Ssxo[e^)]}rk0G/ -Zm)>cUiK2ULikѲEaGCEgw*k~cp-!Y nWyKOCD}\,lQ [ {Mq eQT6C1'Ay3AtV\:<luxZ-vd=pȭigxO[Վ'E8< ;.H; z躕hQ_M'QB>gP]sm75G`BʲW (f<".4Knr^}4ZʄS|cTȄoE]<`\NU='$>tՀH]UwKb~VQ -V"e qډo`+dmwR'm33*^Q=oHm'"6$7Rɘ<7P͇'ezUB+%tٶ-_D] yj_#nimS>bcz(S_WJ$ʌJ5e M"g\+ErLLP.˪+X{~ɮa@b3=*6wIOu90EUL<ڡ8GΦv7(ޗN) j.脶tP7*HO Cu9aҒĐ mJ‰V rD(xSheʎ,Hλ&Ea:^w ab y1>ӻ {$߄>.k-!y">tOKLB=}9#)E2dwLYfG~3i1y52h͈)vǩloC3i\@WoffP-6vo¤HyQ`)>'Nq"f*NYE@8ݕӮ"z՗B++^4"d$,TVLcR1<]-l j sgpZ/X)ZT60 Ϙ _ $D_]y{;ִJ'a MB;r]&u|xXvYko1Q( Qe,lU%,`c # ս0ĮCܷ"0=B:StbċzVGN -|+{:%>0\+B *VFbG[N J۫ ;$jc VA:P҈+#V%fD~_!H~h[{m4wCw} (綪fj7Eg@/ DG4 AP $Hg'/]pN~n*?%nv]65n'#gBu :^nnUjMkIdTp @F_.K< #D'Pﳓ% DŽh he'z>-d 11qOc\ц{P#h0C~c_Z@jfٍ=/91 HT[WJN1wu^;xw.,snvK7%aM} Toݠ=_i@: ~}B6'#uTa}Ԡ(s-ϭ>Y? lrQ Bדejp!nן~$* Sa4cGX5Z)YWtJW`i#e2;̠C嶣`0 1|PJ(mFVŢc`|tgTxDQHkdoȹGaN.ltQ'Ԋݼw6󵓶-$p.iO钞"fDK`-PU,32{(?tCEt,aQӢ}zKqП멮̈́TnTܔ ${&Aw2YG]tOH2"wF#݇'%%vw/Țd3+D nʩma]AݣDoiGsZֱgbF"YVvdW_#&$m˰P+ܞ()?V暰.C@B,* |Z*)!KJQy mƠ^٘T]4GY;jk!+'H%:+4~VDcjNAjZj_}>B_{ #T/{T$1 :`+eTQUߴ?;ka+vZxH|;Jˆkiee6k}GOݠ=ps3)Yw,t6 #v!IwP4dn+Uɔ8J8=~(RZ)wi*sɨډs^FsqY9C5*ūSʕqhA-U7ϡ+Ib3) k&rwRwӰ H8#;!gXBW밻0R+/ yqæ4jW 8t&(jQ]ei҅LO[0I P |Zsޣ>"&F/Lu%#g]kECHECh[#uO JIA#@|:u<,wJ"P{£Q4昖i(*d; kXXf.R.FrtMUedodg1Ӫo6 Βʒ)RӃN2rͻ^0ѕmki7;SJd?6j^= |v+oF=H}ܿ~}t̻S!$Ɲ_3<o&{^$\2?A00lh݆aÂ繲l,pbR,P.xh!v*LVt_XRvN^AielMQ[ax v1=kjL߉PߋYtڭ~@u_.E_MC'>fzHj?/δL -Rɏ ȉECydXS[ܴ+#H w{d,p'|/3nG2㮜3Vܭ!$zBE}ԡ_"W337t\iԒ0VY9Gx/dA}ccᷔFld震A+XB.+B7gbQJT0|=<[< B|Cѭ{18v&5z?i8ȭ?A YdYXp_=sw)MFT 9no^2zXX¢* H$y$ zJ{Ga&Hy*䖄,h8]@N`*es !ABWKo\;eȇ;CYa. k&1lA]FV9w"e6ؕ@[WxoFO'nRK! Aiez!mFǰRuH 8.d>O\"Hf~D@X-k %QC⽦JF8DwS-|+]5-T1>W.i ]s/;6#DmYf)?AzOAF(w|N~k:4 ` 4Xh"a{益G"#5QMU孀Ě{$u=l_&SXѱ*\c-cCoA'MrCiG4 1(Mzo&J;i`zmMQ%h!KDG5ia7zgU7Oaٳ[Z˭CqS@w^A~}Pò>;*TVkz\$2O ~aPa ^4ʰZŒO;`RDP⩍;؈H"}zS4gq4j+a8 (#Maz ,=kLn1Ưvw8[l}ZQ_XF#QG uuI%bgd%hq?O QRGm=ڜ~*>QL@R=Sb0y@is7Kz;QduBX6cJ4pJh^ms*3drRz:cVE|FyWy@t{9A -fY,[J##R9N]*8U.F[5F}Uu>zjb ^N( o̴vYk_Rw)6,n"E׻ $(? &p+i?yՆӣ t:-!%sVH-FjԿ}[ 8[_nΚT3k$btŊQ [_gu۟Z9^iLG֔uFQl_j"rOw;<6H`TYbf/.wxK@z1ao]s(>tfh(W$k. (Cz2A6\H2?BRY_\&[Q?Qqm)ІXYW(*O32'OJڈ2c'm% j1e?m]7o<\6 )ky&XT:8~nEb:T| v oDE6s$y~M/^DBr랻#?C]3UPҟ6h3s]a}wT[~0ꥍ$RF2kM'q^`d-7ʶ~lj$ëp^Q|0c=vMR&LX!=}Iwz(Tz`X $ qe420vopґ PIZQ&lamU>%:uvkm0'*Z/+l֝5D\Z#*)zڐg@/3Fe0mȂHhN<5/VYw *R #oSCl4p_ L;ʄ ߊ DL !8e3V.4^Ր{FNqXUIBLAƇ9`*Kyj;,fߜ>WµBٞ.ytb"kE^Wz 3G/q•MDlXrj1 mYފsɶKΦdp;JxJ?U0>1 EU &f.vYF|cȮOǃ3UEFbA]&2eu?$f.r%}Zxn~qj{پ^0{(ɒfk9vx`vBwSRD19Y0M{Jix(7'{oD%ypӒ(ƋQe`T\͏ 8 2г u{1K<T~'&u[y;">esp>G,a-ORJzb@!ej4olL<5ݰ_!IۂHe'7xDZm__ROH/U0^'ݐlA: -R^)4bR:xfx*3B`ēѼs5 &X{[ \NnߘC-qok2 `Ct1B;|1^k>Ěqc7~"S/NS- P;R*Y#U,tSDj:T+%co%35=I%žg54y D`= G᲎-~yNV OJrҩ^0 3޹9˴ٙt~ M孙| bvVK n(6pKοVT+I B8jñjňSz_oxc>>2МjWo6'i19W$UnL")bZ^"E19vf1wTx|gU-ɛ8ě~m] ȰA`q7<_|Dv6T 䤝}v1X/3e IApc}d&̋dQES+2hD~ -QExrlP@$VZ82KkRYDI'{m4Q)?:د0nG]w գ9~S"،[^>;nhk-?)vNs~\2uZ`R;iQ*t"[4w6Guʞj+QEР`l:tQ G3|;1B=+@oLT'։[j!&TߎI\mܩj _M:FJKȭT*=Ti== 7S}+4q7 J3lmdtwNwN1DFMz@2AlyƧ G68?vb3[c!z:b|(Ok>uCZ-—M8_/^$[gu$A$nlAGa>kgfY`$ȲsQ)CJ7Amhb}4Iq7tNw i\jhe\sFZERPߏL'%PЛwZy5ƎJo2_~`c3naR TYE:-#ZSh!qlcխ=7(έ\_nGYhZ0S&l6%gx,u'oA $Q: ֝I{4'nK OPvvɸF|]WS2.q90g o|Mvnr(x`{'p7s/F{Z5bu2[\4muw, MWS L_1NJ'3'.IK4OQ`,EfD 噚fnRmx"5<3}Bc ھv7j&!3%}'y2mCZ[MR3x c+‹uT~#?;&[u ͲfkxAV/#3D-?MX::߈~,ˣ 1*Uh8"t4uݪ9wG&UT^?J=UQzrH\~iZ'#$׬;W@~[ocs܃vBeб27 oɈ]Ϲ,#0{LjS( 5`BJ X#;!XWXw+UQ CA &A&JXjUL7> 8~vom7XKa[iJ@E#P *)S3Yx(<ګawzpfgcFHz{c6}â<9Fz^J[$ۺ".9gD_M-GO&j6D4Q礲C乳Bfq"]TpM{F *俽<%__nߣ5}IY9WL<}zOB)_;Pϣ 免 f=)Ր$W_ ulm%QKɺ?_p_u{R6sso<Dng7SjW"U H/u[##tY0'?6%bbO{ÖWPWNs\:ڟB*沦 > +hO\qAkZ#@' jcH֎c+Κ×] |H=&kȋ n@QȦOFyRNݹďmЬ^RC1vLNk7ݷBSEXxnџP[>)-^dzv~̙aMf2 E ^U@ؑb]C5K̪+3 kNe{'7!hBoYYAz֭l'#J#R6ʀz)7} P/# m*pKm[T Tf  gll48]tg}V'&nD&)6IbhB%tPvb&;?5ߥos,>2${Hy6BleH{{oenދ~xqqh-Poq y5Z/K?.c4`(XLXv?sTT[K;|Ӕ?CMD @ bNx%+<@Q# Z 5|ScTq #h~xuYɻnKǞAeG+kèo_W)ekk:czԴkmܺx19CJ荠eЮ#xx%Wcy|F[ 57X$m[b`U@CwlS q)9k ^(+}ޣ@ R(BuP[7|y_b3 {o+L}L?3ca [}R?*uX &LEmSl7XlJplb=+; I]i>EN'n{y8F^n[o"vIJnE߾@u I5"^3?Rܶ+]A}*7n}YޠxjJ+ 5?>I c}43Jun3f^l%+ǫlGcZI/^_\n=+fN,j!_Ȳj:v҈R>0OkZ0I)$dp=9?i D5TA( X+?0SCV5϶,gφ(3jn%~)MMHȭ/ X'>\hG@^ P4d^pOŞ"͖*6Bfcpv#¸z02m /Ae{!@m=0v|vpq-}o>aMh?9׃EꢡQB;s#ZC2LJfNj`GI M+QaL]sѻ07M%DvP͟xNJV2 aU[+PpHX-jO B]cũ%ds`x[?uh:!5U[k c!u:b.R\:ZFU__]L E2Ʃ0ОTXG-f@.I_{Rspċ &WjkI'|xc9{`P@*gTF+r/vLzkƬ$ZYԾvcu5 +,٫dcVEiJfS/nOxW@n\Y7WЙӟLk]| K?"͒ 6UL楅։^.$@).A7dox3H azy;x7Kꍯb _% /6f_䞰 %p!S[=x''@d_L> Tn$rEߕN_S+ZBsX09:'KG.o;9kqQLo~IP1W]qU&4G7Hsw[RU|ҢȮ0q8Zo/!7F8b wؙW]WW224{_/ΉFYl& 9@~sЦ PSԘ_[Dg YsLEUCI=ۯ.E^&W!PM`ŃZ:iͱ5Gog6֏tl= Oȧ "߁W]JD$ `8͂~5w{i; \_l`'E"f'^s6\䌁\qW}*AbhŸv  Tjtfu,h#}؇kd]͝_-LVmO S@+#r֊GOG3sW{Orn? N (Lɓ1$n}*ܩ|@ͨ0q`c*X%FckN"߂be$P,]H"X,wfIYZrު={-G#9"VES Z_q?ۊşȞv5_z(YiD L)a1v*UiIâ]dW4 G;="FrFSђyG3gq S0N(~Rs*1o`ӵs1g4F7ebAٵ^S~ ~hr՗u+{4Išu06е '6= ԌA>=cJZJLM &: ?-Nvp侉)"RCT!Z>͞ kW|/λ/nVs~޽ɣvϛY.<\r6&ĮA\YVhOJw ىzfWBz vDN:K&ؕ![e[K.HVo^'*&9nع>AQ*7![n Iكw$QK,=?;/i"'B"%"ɐ6L[zp~THnV;ΩC]//)0x3P/z|@gs쓌M-;("m~9ߤ&UYuNff9ܦS+q+qv:4Z[G5eP1mП l ޶9 |7ӼӪ Xq* K6Cmm{g=!6z 8zgAzK:he$L7;)i,8fz&>MH/HXxxSb }/Du0nߗ߂o18ý(msi!@]gawipK'^s Ar hEꁊu%,0yp3-ミ7*oiE^y?D2zȾ6'3CtwZȗ+4Hd0M~NHތ Y4A7ט48 7V bGgk:dzw^2)+pe+a$f#:G&[OŦ<ɩ2$E#09oZ ssd= 1[ J|Î(.J>9 %#-Yxw +Z :B.4wΨhVߧrNxr4 5g|ĬxG~c!#bO3":wY M94@cj*6'ɦQåeU|MfZ{ztuOڅ%M? QI{ql7A[xE A&\a% 6?NR1 }nEFƜ"/ 2~y Pڞk~*[Iԍj#.t]ȂղGA.,i2wy "lْ:#SQ(w'@7s  uo6 y$B0w`#qi3i p!Ӓ%_g䒝5/E1L:0C98s>Rg1ю4mE⏩# nuUot{XۚW(_S=ro4x;`uRU:ӆi(nXTOTʘ;Z 34&g{i/B Uo ~LIq&*./L| ې5s*(1IIe\xk%K\!<';0h~aZCI M@G}ZU!u523#zFxy O{8.5$(yhvR:"]y&}R_6ɹ|}c6DÔ3^V]ޱFG6cv.w^x87U#gQ҈ CY%g yY MǧVOA74k&Ԓ+^t7~R&ʺyq1= >Oajt]BiV1sӱo}C6|J[.u {%iIX?rQTƒθom>:,L= n-c_ИuWV T'9U-߁*1wϻGV(*I ExT0?CZ%"6ڷ8&r󕃦klpxy{Zޞ$=I.Nbgk-Qkar)Ac&v阮jD!.bSOX((UՆEEfT4i;V6DW˝"5UH2tX͈YgZ߀abbQc*k2pֳНI?<#y!41@lIKE!lx54+E þZ d?Dk]WcAD0y̕E\E(TWTB([3ȘE,} ?Nxvmng%3([rI<|y ={xS"$lE\GZ8?VPd_:<1gG+ɮ%,0~sߒ}GnlMk1T Y#6Khjrmc!IVnTWS)~.`FxeΓT#R, {$ ǴnX=v0x ͬDJe/=2&h"?1i.%X3 KAUHFZ2/)? QNU%D4B3ftp+de1 ^2+R6n0A,Vb#+pK$v pdn!kL!Dm$-RkvP99)cxuiaʂ۶8 `wPY$;RS-z-{ҍso3R0jbmfD29* . 2@!:l(!uvrz'HKWXfaH&sP\44ݭ@H~k7Bj2\2vxr- %g7z|`ȡ0W pn|xϨ3fT&{Ҭ݆+#_v1P%'AAhq*&"D|qrvC [ud,ᾕ]5x㮹_]p3!*MEml.t#}4N |`uw7Q(. ܸikNRU b~zϲV3XeI}MФH;VYۯ*QcZm>ձL9ZLf.eH L"&\Մw5`I$tW]#/08}Awʠ}8(;i٧>2* 'j^XX_t>AO]u<8*rB~bmyyA-29.['frT#(Żaft$#FV9JKm;ٖ/a?e p(r(D"G~0 H'U IMOye%ɘ;hr.YTӀrRDXh<7k[y ߂l؎<@eFA]3Ksr 5$d)I.t, hVuaGvpeL-Fc)2aш[YwAhLj7ѧm6nOJ,cY#-5R~N&J_YB`ذ5&Fbvn꾈H l``N";)93m5 <09Y7/cfGD6An YKI|h|[/iNE˸$|Xk;=]&>:O&J>3oN2:X&_'IZ2o-L]+BgWD0\MjEsk +MO:/;nDmBbK7i^BRv3!;y>l!#9cXs1 .Niz@ b[ed\+ۤaXrJTJP+}G;x"'>+Di42":y"+~u7XW꼖+ fޠO91w2y+-c0_ 81sVHT? )&P5Vݜb(gzu^5?z)\~V^y AC9AVO?H }5v2GqRNe7!>O*TɷPxa2o9iO@ h\="n?3Y:Ҋ#,5\(x QgE&uB&Is;& Qy1P! Ca{Z2T4ﺛ(3;:LjȀO!'CªgAሴޡx,@➃8fwlpgV TцhFFj.=XiSpJ^j*!Tgpg$l  NZJyylڃ|-RNrE U~f6?>]JV9ԉ7d,7݂̪\BzeJv}aJ11ukrm\+ytT<z©TOγdyܽmEEiKE:)od!- ă%a^?d %okZf}, rk6XBWx7+fޚj0ʯM,tWLw凨.َJv^!Ӎkmz6LcYk+LЍtgSȱb!g)}vḱ6̺;˓gsaBG2];uJ'Og.v<8} WTT& u'a^ aQyW3PN#+TI?BOG/o:=4yz"jZ.޼΃_9ݙA|57ۊIy(h*3:7179U=ue0E7WY2'y{vtn)1"$U+! pBeͧIPc8 GB&QMd䘿ci./¦[ ֯`/Lǩv;u.ɇݍ~hlUgș1Ta/W* ІqGXzQ˦%ɀHXNv/xx R՛NQTvtldF$]a-SEnuXZՅ=w@nr@8"hst@Ǟ?> ª׳:)Y,~/+: vG$P!O'rF~Ң{з?g`&͹0 |p8辳;"?Γ\<ԏ. e RfBNL90sQ6rtzS"÷^HF*vZAIhӑ>CYw{5Y7&m+j8Ͽx۝9qL9>vgIؙZ_jtb;G9\v 1z6{kf{OG̐JHrS(I^Q2bnm^pb3#Oɡ䦶xaZo*ħuPnprt08wa1_Zu!|I !! 봧&2XVtC_溺I%RYF_e]٦t Ę'֞KcKuuoc+TxgTKfV5ɏ)n| U./{Q]Sy{ ^]NsyثKjazge-8PL0frE ˹Z ~DqsFל}W=Ow? }s2]v8Ap=TH [&돯C,E9qg~Mo:UBVY" !A9jV:~6GR<lt$G{0vѭ.X/F6<  ]aPc\oNBBextIq|8Eaa3!iniH'ߦ~{Cἶ>GH7tÕ nVYOrv'-~Iyo*CssU^¯Xh=4sy|KCҹ4P#U.L PLɧVد㒝$FA-t#~@|C^VnҾg̨\NdE[.fq[vnEmh8 -hi7J27K03M){^$rk$le})e!^h&HʪW.W,AX̧uAZ1Y#(D$,L?o'Ҝs rt&ѫ*hDb.F{ڛmF_j]<`|?C׶m*ׄ"&B즸Tޞjm6nQ_NO;VE\i{q *s~yp6a?ikkyC؁fp͘Looc)j4J@U R#.ώ Jاj¡<`P}YNl {^+V6:J&MȢIR߰|)E okza\KP{r %G/68cW#U&\6Uv5 (Qt]_h,|{Q@FtQb4"Z+|1?&<.sbXig3ha=hrr?-[s穹\$5gE>i+ΚNɐXk6 .=/Uej0L( ;v{`?[b:ǟ`m6k =oQ\Yt7:<ݰߜ%%iW6qRq֟`:i Lr㉜~!]+auL4Pٝp5Q%9[ɁB-bVePQMgq\؇(bYz{KZ,$@ea*j"֎G̘) R[ #hO&v^4nam{j$KPEu4`w>*OVECu/Bib-m5'7L"ݺeAc}2 07]o0: tf;_uٳ&替4ҋs ɑ ]Tg9Mƣ持Qʏ*ꜟ]F) IVx'N Riyrn)X{tɳqQnXIwQ\710ZkJ$Qw,= >1îK 5zLiT`ܖ|-1!ԅ@(zYj}!?6#Dmz#g:36ڷ%AfDVjtH7C| C9ɘ bH%Osܦs]"v_2IZZ^/G?}$xe1?wˀYCNHO_V.çaYh_2*:BW&Z&; ,{l,V̞!0聾}Ϋ(?P[͗UM5@Wk+2] tIu`H5 ܥ JcaH bIled 4/8#C,Pcy S_m]nuHTWCx/Ihk+f.(Y#@`40 M ci kݟnB}s%| #݈0˿P#.1g+ kܓj`61&D؋+6;{4nCk) !WU{^GjtǾ[J =ole׺(W,mI,k q8\A.f""R,YHRMIV7`޴9@CFyF/Zp>cweFZslmVdvV!󋋭ߖfW;`% jYLa;0T)^-uF|ۖ5E_zVO t$/ޡDDl\ -RL aR} QEy[>d2{ '{ nMZ+ E<'~"+뉘]:1BHۦ򐪢AöWeꋸ6ʎU$-`ձ 3zLuP@ɣ KW/: ~\@֌0DۥEs(f"o^/1K4,N=O]ϮЂjH}8+S|,,۱;݈xR*S;‹J.<ޚtɵ6cNmf2iul{DY[:/ >Ls5\%5ėN]p+0 -٫pʐ9oѕY@]4vb`4%4EٗY!.`fGSO€{@woKm1vMu !=VgMW6c_V Qπ$=z3Rc0UeEb1R )0#`o+3WauVr 5c+TrvEπp>;ϷC'0HyzeK|s.,͒˕`/WlQefkeSX7Ɛ|03E1V{,""&=a}wެKe~-"wP)>_9DQrhhHkulvSj4rxV;0*z/͂߳#I9 .O"? g6 Yg} -X/xLu$>~c4OS*/ϩ8vEtg'PpL]!<7%O=ӢS= ir> ϮCm@qJwg )bDu;c2O,l$-3l㝖;9lU#R7э\V]^xN{[A(lG'gyo!qdl% a7r ҶZZ N$oXb3zMy_3kZD*ԼS#e!c9f8vd߬i,`HB`X. VO cg"yY(p;)Sœy}g7:\"RL^Z-lYcE!ME ,N )V(.I lurQlP)ܬAO 744#4A"7Pw{gJ=gf>Is!/ƭPĦ5MS+[/dtjv;FՐ {#Ș& X"Ntp7*(3n=UokxcqM(辘 0m6ڡ@> }f+R,;'2Ro7Yܴ%j+&ʜ2Yl=GqCܠoK}D=7ךSK0Ɨ3.o̽ؗ~%_ y?nI7;mCj/U8,~Ij>W-=h!ۊ%C6~3Vy/ts#L59C|=f}m-op#uXZ`<@)<>_Ykw[ 'aASQ^P%d7q|Ҏā!t4F~~n;q$}B;L(~w=#F^#̙Q~i2N˫D҆ERSnzMknr;PHl :e"H([Gpl\ۛ?=sd( l=YO+\(`m%l՚Bb-)VOj8՗Vy@~Wsrrݞ>>`R&n~̗z{#F9-tet;~z?E3!au%b&ੈv3}J-\񘽔R[x[Kj,&!j"JuC(PXtkFi.w <#94'vek'dY'̍l&# 7!B$Z?hɲ 8=f$ B=6BXkg^pw|]R̸ RQ;LbɇRݴO/v֞q JVUa GS4)eK ~ѼS,O '\4Kp?nnK7BpΪ+-_mGvd@mOc-nd&}'ؓ *^s\/b,9s(Ɨ 6%mNz%::[arN{8F t_ SkWv1A5xv}6^ jΊVFmրWK:5R|$'xVHw7M̏[&sax 9=L}PS_0e}}4OphSkO$N\= c&k2nKd\ n{97 ƱOMK'܈봴hӥo:3? ]Ťw2[=]Khjj xl#\D猓Fu~Ͽ&Ta#q8]_1EXNEFr>ZU3=!QUsȔG6Q|Si޻05  ΫSflqy] ?Eq+ќ :B,4d^<zMn${! ۬w7]F 6>m=׊-|Г_fSm~9POqИjxFJ+2pc┚yd'uoс:Ў55xޗf<tHZ ?V%l JST8Ry1924+%yī[7噁u1f+FariIi{M ?VYlB `or =GY _5kF!4e l`*E5V$~ߖQ9P5; (H Ω M%$'r)?ë(݃2clo ·s1}ݑQYO7{\+brw()^j|&4|,\"0l"nuybv`o&* :b|ngO[>:1"> ,b\68q~+ު?_\6`/{'OdžFЕ$Sfٻ4Kf޾%0 !מz&ҷiks.@&a* -ӈKͦ8fNݐKsIA>NW(AfB1+<;Y5(e9)LI,3mx׋_[3CQU") giiěT0& q^^BmרDzȽ xPF ^2G{<lVEީ' ~$ ejЩW;6]O7sNFН%xHPRVV;ŸQ].bϕ(F*ڐ,\θd䰑Jl%ʭ5ɉ 6vdD25""eZ bT_yn^<%cdT(o)bl60.<3I;}DHy"ME{O8v<8Q53S!)yXKV?V >"eј/P9xw4YkkAS GC{͕oxG'h /\n(*:,& JBMf>@"ml{KWM`50 nW>eKu$-U*h.*ؕݓ5 )_o` =IվW௢nbP4㮒wgڸ!" Re|)<`o׻$xqTHc +߄[ s~C\ଋNa&x.KY`s/|%IWo٪CS""z5VyD36<Ѹt=<E[4S m&U'LbFaM!$6gŧuDc0Y5(hdV&CrOcqëkZ zks+,i~PplL0R/>$pc8KpJLi]Ӯ3X"kY2{%n(?j_TD7+gs(ٝ Zrjf-_#=G+3u[T2#a~6(!E쵟$%+<_g̠ۿʑ[o.jY?8j(^P :bb׆[KfC@AމV#b$RO3ƍ1Y+ZGѴx.a4+9DGI\"o=7'PKu{^7V;΃PxZ74aqزqG oXN4`)i#ÚEpofM_% qᢉn܍L5))[dc'=/ #j*ҷPi1 MG{~khɛh @wmkgT _};iOvux5O]4~5kp *`^ `G0uQ¼u .2##*`cIVUL' hd~3qݜ1UC5 :XmG8-޻]qoFϭB~XxK VIy}"ՑVy%&3;,ș mmIK FxLt)=\i((\% g`#Oddc={&.KdRD,Mpdˈq)d)%ic[f^V_ssLo,{݆KK.,3QYY_OBnxE DD'dϜ5/S\T n{Ti7Эn=9&NY}}pVyqlIO ,VH|xNy{F]흶'/qrkvl@^=;lP;O@[VZIx-x G5μ;f:j8Y}dd/ 2}Xd 4 6cTtV;nS6xhh=ºgY}SY3;law Ee-wu&]C"ܪ8LgL[GKVU~HZ|}?,Z@6+EZE.m!չ=sukl]B{Ԕq SE>;7BAP (eQۄvƱEqc͔1m^pI8 7V|4owUvJ:vv.0fW /h4Ge< Ej#[n߽ f|HXRNY[-!d*^sI4Y\P5rI\(0(ps |вו;wZ,lnxGo NXww{ҥn$ʢmCS+L^oXY56/nXI??ZE|m =^T*9 pE*X)̉@zOh>HdoIqq 攌5Nf |;IR(h].lW%Q#;m m+r EgvP8L)NX_.}w7U.(2x+ǖj 7mԬeG=wɆב<9j_~ټ&Ɋ4~#U/l'Ļ; ,v^^4lȾ^v D#d`]d޿xz%#[~cBip~gFِVNʭk7A=ڴ%aIr\[WV! B0k&$>9'MSODib$x \6:yris%6{*[kMeLk~C*<ۅ6 W^M鞳aWxVn5 oc/q7I~=+rS~ Ѥ; Q!$zYn;Kѿtp fdfKH cP#:M_dXo )`grd{_블_FVќ'Pc&")*j}l#5E'Spe&ix⽎OUm_2]ʣlf!䨤}v`n@Jzr?-&^+}k.?s],{"֣2/JxC3#[b̧{;hz\QQwz^#0hadI.ad.MS'~ƱuhPkVGok!Y~+.[c"*eK66n?U%6Х'qLd /4Fu OvⒹs0Wgf$xVbvY&_*R b 'ﳆ8R:" Y{JA^5L/-Ď Mp"RU?Nj/W;կ=~dVT QL¸c$ #D#4V9HhK!b׫3jUpW_U~x ZFK0Rfun EH/` *LO#4kl YT,a"b pPw1 4^,#_CH;6;'CPq㱰q>̀3`m _X !'BTTج }䛴| Ӵ,x;(\jKrWo(3f[5P0y~`ʼSZ@> 3d <5VK=N=O%-e]fY}  kړ"lUrlt{F`Un$ a*Rs`0ɯQ!>2"D2+6$rw pi Y)ko=@ u[k2 ifC";j췃FZ:*ombp`fJd7A Ã3Sߣ˞0%;f Xc{inP!M8 }Ҏq X=Zy}v'V WB[]OW* R1zbEo.rC.⁐y:WFNCOJjpUd~Н>lahL_w}翚,xMPr9}ΧO? Nh16c5n""/#ߋCp`y ɸ XѲMlޚmoSˬMd(ro a$WZEWT.G4nliiolADNz/;TV>pL-ƣθL?եt}YVqmB #ԡGlv?Wzqż" ͋>a-܊숐>Z\RBD>yY%[ 8"r/K:]X[/DFյ &[ДRbՃf+UEkD{8Yub$1,8 A"ByiR}j{D?-tp4nה];#r6 !n`~ 2X2T :>"(Ȓ)j!BcA|2P@mNs4qn1\vZhmI*HJM \q|=ݭlXV^↙?]'-tgo='LfHP(ȟ |C4YP7S*[ )Z+l4~gIz0تth11,Cч?k<zzO .Rq0KFӦt-@U?{˒Ȓq{)uM2.yPT'^J:/ssm16-V+Qҩ%2TI9k׈:-,T3S:FKsݢҜ@b,o8cRI֬<\qPQLS_RM"EU?&fO~EMCs.BZ6A/s&Rp ݃xk*"G,giW>gQ1B{V.(4LUğcm-Xgςjѡ|e cT!6`sOYmAHΊ>17i8>én?~Pq.?pOWS/2sf6) UAR{K|Jnx S)(sFhU$@9_Vx4?`FvAΔX~eHרo=W'J=xό+xUf~\n3$\PЭ2o`+1o%d =)&ʹ=3iBR ~+b}43"'N֞cGO6aY\#+q/'[pcJ%Σ7}#"Rny8e8+l1P3֜۱PKItPrknc31]+×$kRM&9 -5M:gVBLk`gkoi>U` 񙠩Rnq;V-JDq;ͰJO׆Py'o&@Buw^F@QZuUXH* n(VvZ1!2*L_lR=esyrǭ8tel%/^I)Ev+4zCWb+65rV2x[lnk5lCRB6I gI,׼ˠbm{`d跓pi@vvw;֎kTeCsιAn{Z,c-5O hS6<*4\%>qnvyrؾQ&ꉏZR2 ДZ' WgL֣LeOzG9,/.CԚ6*Z?gX4?vrc$^.ʳ'&DLpE&jj^LsY];d*T. U7e&myr £5.w\⊍|a t}` *rmmoz1J٣P[y m9[yaj*g9"eN/ C|_ |ZnZD4{rif+S3ϽN=7:|>_62ڧ5E~pXU}nج X\eX\OWTv|e@pNJ} '5جb}N:]]c"XC5vv q:B?#$?ό6BRŦbcw+q貌jè1B%`GNDbRX|n[} phpֲ< #|@|@WuGU"}Pl8'zlqfgHyK9NAԿq16sn U~!BUQvB-L{PX]}k"nGi5mlDॢԋ:Js: D{+uz7ymʔ!+m>;(^ ;SDV'e̿f1mE0ȓ)$衎cKWlihN5!&:HRsyI]PflJVE}Ӹ5ST/?ѯ1G2@<esz|o|DmqǢ #yj14cɝi:jXiJ9تn8u!2r~ZѾZ{ +%25Ǖo*o8*CdS@-Mo+&ttJ 38f.qST77ZHm4bV> )U[.ㄸ@ljXD_}i{i8n>W?R5gU\Lx. x |v!YцM 9^)%6mSEyєd >:\1^_^zN쇫PaU{HrOQIXG%ϵ}!EGuO:nl_}L]wi-Yi(tG馻INL"{DA)ЦS,aFO92 tIʜ5Yj@ע inPmXaVV-6%&IP.xz3{oL؃f0><8d ]`zKhH"X]=CaU q|XT DZm|z``XU)s%S'`BUxϳtGeue>yX M z Z58!L-vĭş+ǟa8Xuщ;r>w[{19Wukƍ~\wUkw8ѯ>[CA0݌Q P($ƩUxoD@>'Ptԛz-!!,*TRG;ui*=LWh*OA>@D'nAu6?xm0A>µ9*}RHNq;}DŽG%vťa@|Coo[*1"ӖQC1?b_^В9iICu0Λ\%[ 7 UI4 gŃf -cu܆!wB6uD,  {ɽ<Us~!- VH& f0p.P714B.ǓHq8@U7p8v8vX,Z&Bm駋Q`Aj<%.|n?>7jšn&.{تm›SχCa\>AmuskɌ/iȌOi6|0 <Ć*Gw-lmp X;r<OUPweA[PRFee 5w=/6;Bgr/pٹ%FK u'uYV1 QݓZUe +b#ZpWf)э~B4Wt΢~˗VXUaJ cx4x~@Mo=YWKswI8Ewֹ3e\b'h+i}zWuyG4Wx4G8G~\"etfJՂo,$fGk޴W3Y۱mw #%;ru. xC%yL_]fh\#Sf\VR ~ΠדFdAM\lbpt{OHcJ?pD+PMSܑ j%0==o'XL:lQvWNuKDJ"%Yr 5ICƸ1Ehabf;1T6X?Sex3稾USSjKN<+4r:ݹduJ+?dVE:YVAsh7TG;f  P|WD m!yLKעl387`g8b`\.Hrt }kf[1²xz= 5`DCFfO0=^Bu,3 c̀ǙwuyGtn1{hS&=@fHoe_b[K1H%gZ2+ؚXh%ZgTiW'[ !U#P7+ H E:č}wa .ݨ8á+J' IDZb|"2^.jq܆#Rv&x| s1cۅ^~;!ե\)]lI ܒuR/E z0 0f]R^-QO~]Q)u;cQ%sdiD.d셺q/5 Om&.Ij㦩,_ԛicI,׾)iC g!ƵY4Iyz廘!iWr \QGX )!f~0F *yr }y!x]}7wo4[  r{u=(2椲cF.72ecm=D-rNaسyʕQ H[/ Bh?6SUQ4t^uAul+ZAeS R Q07<;|-Uę*$bUGcV1@0J)p!F[:dOpV!Dr,e/p.w+tUۭ03GHl\luAv$`G`| 84-Z*άaeE ߊxU8:4G// L7_#G>[F;w Zzx ykr( y9ЁoʕB2r [*Zo߱;CtM)*gjv/5Eߚ0&+(+Ȑ9e(wf}W}P#KXa(6KկvJKȻqaHHKU2UJ3pGJ$lb.y>UkvO $90xlk&0^(ۥ5h&O[QMiDU AL/eTmqUdxnqTu!ѵ2'8׌U]*.23 ?=Tog;p4=hr͙pYvHT4Zb#a7y؈Ȍ1ҷr0jXbS[z h2 h-?2~F1'Y ER3e(FVk%LA|e Mh+Yp $`kq()RMP0jN8$)du[bւVmOJFzR@4wdmz# 0Jv}Hܲhƚ|cnNvxNd/𜉱K<)'#q‰3v6p!ٜ|]k 'p(%譿2)Y& (Ffm<ܫG (Hw`TJ}Ր׌~4Į%{ca9Փ}! j;`% !cѻfա K͟Wdsi~BSN*9ğH}_WO; oI>cu{!UF$)Xv›xKuصJq7Aޫ>{;𡅻IPҿ1AO bdd w>u>G~$ytZ}bf5<;z2[vi07c&b[8 F?/e9"cb7^fSŵM񨯱lNid>sk@XM!OW 0b3{?%0?O-qC˽{u:JvcJ! @h0q"ӜmºFN̚{-:6.㵃հUH Q|)MnvGLϛZIJE{ yt%vwt*Dp].NJn!Tv;MHwN}h2Z`QۃH\X1VO_|ԁ_ǒ1*)-9ɧzNQv|zm:OY5dJDpNU1IJHUB\&Ea=0e!H2"./7s/lVsvvN܇ @ &mlFR?LefMhAu1sp5$D i+VCޤeKd6!VE|+%yavHyMh\b!;~gkH/!vAF5pCt7O7 ?_J-b}'HE HVϠț7ZJr<*8 9lQJI.Cd Sa7Ɖ, ]?>oz&"%L+c]6BJc+@xqN yqLܒ֢a#B:u'8,*oIߍ30{= T\)@1BFl+{Zùo5.oceH:!| 89P8ū\V⓿Klwk_z|[\7k[ЧS=| wWU_tWA )ik^$sw, 5sGT0}#M+ffLJp˽V!},Rjrka-U~)ATfUQ(%9?o8Ű-Q0I{tѪZ0 nQ9[T}ۈd0ZIjeI]'[ MeۿK `rTP}9,9qwط~E>(r2[8woaz LocbjV;>r"].nY&b_OKpM/Cks{kef-("SťAh $֬!;g}q\y2;(w^UuY 5茎|_ gRi't"I)ˈC3 y^g §0s%h3P Sȿb 4; )ež.AOjzr3`RyְEK.\Kba^ 3g5hq,v F+HUDeџx\t*aHE (nm.rD%3h5(<~%RH#g>`oV.\\`̫?u.XN#!iKiq?M47P4sJ3-B8\c7AJ2Ą½WI_vQMꃃI7j+k 5j_g2vwU ٍ?Hi'Ӧ5rL̋Ἰy(!mA2.B_+9Lo\U iOa S'^k TQ$/TVP~èOe^^Ƭh9||4W|]c0_| 69v *t=i`.ˑ D_NoN5\.H!'kCD>2h5x8BP5a{ [A5J{ l)\8dvj%X+F@Ҽh<oIx@~i9RQ4 6Y8`HKwÝ/T>\߃_2A!V5#Vo}vp[|9;vV龁iY!ks1g2`%,-)6&ž$Z\,b–!yˍc'LZfrRѾ3A-<1ȄnNm H6>O<FK8 Yb j9X.[51qBZ7νM1YliY|٪^ 4a.vÙĶGkOF eAzๆm4+D-mNtu!>2OYw+yK5 ]J@ [h%8B^yگcnubwU9xӤ N[~H]EKqx][_0}P:@讬eH87MfhZc94ܱ)fjnUx&\&t V;g:UonoԬO洤MX(AoAR Ѩ(Rڥ4bcHL7]Z@⚙wOB|xGτʉ>l0D^]`zk|euc:+_I]OmGt*̪Km8tlzM 33VYQ)[FmA,g[+_I՛%gm sRwfw&1reN_>a-G[Oz y/T5\"ug72B|BVມ:^](4α mYrdVhc˚vHp֚/=3'k\3?4Zk`,f8͐-xODeGO_#2Nπ|hd'M("oE/<63 YA5_O)Oh%mOZ p)pc9DA.??WɦM#(bW`%Q+'V!%hC6 ʰ0:.o-W>Ø.f$&+reM1j$dm{!9B(Z\ FJ<}|7R.U3TKٓbqj"kdoSX(+a"W% 3Q/VH|\}QtJ=X9P :!mҐH<;e7DSCrqey6m{Vb~^L*iM:DJخ":eɔ*P Ko9DgI]Z3+J$'HDby%,2s5rQnHM:.TF3 Y;˖iSb_r&+Ԗ8ܷMB%Iv #cH2u[sя#hp!hTHjke~5aBbuwyE@!?+U/Mh ]JϟcVdoy`㡈j<xB롗{@؀]3!_()=<5;J8Xľ2 5 I` JXwی QU-[U@`eV>z>xvb^TQFg~n{^fhdby'bHn~ds>ԤJoJ=I:֧h2W2([fx*_} }iۜ&ain"ޔD!/H߿,r`X e65Kw pk)MM|* [=^A"]iY >TΡ@,3k(E7<tGO$N|*gHzr!+cowPƙ+Fn8|XД‰JBeyqipc߃y: <;귻A ރ9T}ldF!; b߶@.d5,H9᳐)aT&5@jx]1ҷX Uv1IK-mFTa`%rݵ<@Am0.E``E 7(y Lӌ4T} v:!S ?oEeo|l/ې_)fqFN9ZhJ8 e$Rr:whL!V|,bɨqI$wRXu2RQ;C4(*<.mݻxvE2,m}9  `DUS_K$ c1Y/ >U8GK$-sMѯ5B(]OL.}n'(}N>> \ӆT2?oKP$ |=E6U\u?EݠC\r?L!,h ~޸ru@]\j3g&Pk) Сvvm.! c-!+v{ Jv1;' *r#{//q]@ _QFm޻ tbIpQ'nY1VRY0"UAٛ 5>&s?#O~u*9TNna<,穭Vߤv=_0kȍq]FSi?1 Wk^CR XaB#D 5':v/1smdL,¯?D~FWb*5t໸xp3Y)OSHѮp0ǚ{+ۑ~R.9NԨQ~@T|\tb}f#puݜz4`di J>E:1&TLէf.YKYF͏&Ce,iS8dT# SX4_*e݈h;rQ'b!z.GVL>`|;̈Gi!F֝1/ip)Rk6Տ5үm*z*",jĤR6 4 H=/\ۗD㈝]-ϐ (]'Pw-pUU!U5n-@[:m`j*.>)%zT$h `5eCÅBeNK@j|k3EN !+%E& jQ#*t(][acFL k*`$ L !J_nn~Q螧zNQ=\ۖ_޳%"l5"͸:N9GfڇBvK(`3k"jtZRHd ʙhg/{,|Oy{83 UYr`c&s \Jŵ\N%#b/Zrا !+RͯABDXE@j|.׾P' 1oXd.2bRZ<%mCy|UZ5h;tƤ2_ )`,O{nZSrZ{MU Yo7JRZ@E]@ĸ % Sop1mёn2{G ÑNtvX b q!ޛk}_PW:R&4cTw>EWo2Bjr*CԛNE ka2B>T`ow(S1)BHy0Q]Qzڵxnhлح<;޻FXFuƬ Nu{Ck9lpOOjslh" ś>__-W:|Z2Ay=DԄ=D87jɬ6u)5B%ט)5*70 eGadt7\F[5\{@}Go4!̓0y> YUoR4ow` ɧT!_:7)8 pJ0$Řk6k5kITxupA3׻O` _n2鯿zQ$7G!Gl3Cqq|RAq4w|50kC.Y+}%=e軜3"_ {ⱥ(^:Ȁn AeXimn3Tl{2peu:!d{ƶJƳx$Zfk}"-䰮eJǓXKDAo\7TmQה+*#>pH2f%4 `3%7&ͼ-Z,6p #.\1s)VkRoWd(-vu`#DIT̕cҶ]'aqx)m7344 :Fô+ uC"$~XmUje/n~Dt'cz qtFmqƠsDi\ >2 3B"@i_xF/(7!{+!PՎҡTIr=3+)u ?K'i-N"כDΌR*xQo^^*ŘYaKQ+Z^ثD́W=Tsho0h1{]EJGc”\p ޓ!z\{jEY /16cwնj]*Znl9l/+7ǖ;"z+@!3tk RY7 /(DtX%Gu#fQ1P:ٌqEt!ux-|Zfd~)iUi Y~CۨyId0%K'M?Z].4f!fj'A1v F"m*X=3;6pull`UK}R돼D4xEEh:wUD Tsf+EXWdwJiãy!+ q>M(x t2ѰݘG5#DAi/ǽp;m@Ds :)H@2u8Ig;?Lwٙ-Y\\.~v"6iN?}54bWj R[S[ml,hP2T H Ӗ^۵N<|>#JWXht.6KXa'J"v {ӣQ\p)熡HPťis:_?8$$M\*P{Ip+p0]Bnn(N~``o#GzARsL-clUJb;`6!zF7Xpfd1DdnxzsƼbi w gPR U')$Xq'2<2! Rn;z۲~`fԧ;ؙO!ZSEBTzSVZ-V9b徝d#߹^ͅn$^Q4 ,;VCLfC^@Pl$ש@F&2' e hFCsNzG1l#b[+Pg7X.(ƌ^B`km@en ͘N–i G0%10c <#e?ԡ9&2[GT#MLOP_1L7SR>\ف[ CwU`-Ȅ\8)MJ~a0a=ы4WA7{O,YLzyfK-}{ Ct! "eATW7E:\usuvb0C1 ]þd# &m(IlJg=i$NB-ļPb:smD }>'2ܹeiIQaI sksV#Brs؊(buݿ?ZhD?5iQX&ֵzDU*6yEktϛ%Ac␋u8A 18!:ud>!rQ)*DeSBwISM{4/u7J~Y;qǎиuŝzL v=lJc,NX[dќ6kl)9^^7d@y%G %lcn9z7(y~ѥV};x˛XQDYֶ/ Lb{c:eYӺ0uq"7*1T@>|z¾7n Z'E=9J'ya\K-n#;/'V*PK"ËVx@qՑʌ ˯#+{7Hۧ+cqۖ7gX3\yIkWeuG8p-(Eh=7`L@y4DR^',DCX^6^C0CpmX_֕*r>;>L`Tq4$bliHZ\ek: ׏s%=QC^M*6ŞsۜS$Y:6&bM,jF*QJĠYU=R_4#2Wqgw~EQq)LA~\͘S,S h۱wOd:r"Dܐ^528*_.Aٕr4\n7h!54 ^ lSk]=@j#;p[J; Fr yھLY&F!DQ mSEyh,?łaYo2ϬqQaNص)am0!߽D3Z>[|݅IOxNg{b_ - r7n&tY1 F{n0;B>sC& rnm|Inq\r?Ú+/>!;CW; (ldY0\Vy}QȃGik{> ߽_˞nYn^g02@) ip'2I4ܹѡc_XJbU@Y`~+혝rl_eu;(DŽm(_8 ԇ΋u)3Jِ3u,L ê*C76cP,8d'1"F"Yl89?(2d 1.zUO8_7l8os;F9X!LRNT9V٢?Zk{-d <1q"q`Fwa҇qpO (;vL_k8P@GaS=;W91eqjue@s\wo){sM-uy?@z2,}ݥa3nFР}Gzpr:%Q*L)w@6o)х) /pKaꌌL~|2ecwͻL$m`q(כ'!]Q7Aճ9/-OH2]>8vj?r2=.E&Ϟ+5 JH~Om:^5v VPseVjDl`-wZlҖhS(X^ "nCUɨatLHzHēTaf_k |Cǻ]E7Ah}qbCQo iy'LN&Rg '48E-%FׯPs`jZŕjG7{ HDeIaX|t9fLM9'2yc1g”@iFWp~%m&yђaH'a#Mk!JĦ rB{RbRՋ60komB=Ďɽ]CɀQݥvAY'D܁Y.LMmvmzojCe1 A |'FS2H"~z 7mt | *omZ:R18ujРTMv^a|(>ELl:*C5trFr< i,q>^ñ4 GOq ɔè}iSj&x ho׸1  t>@vTé_xdǏkсN*ԣvq43eEZV']\=Ԧ ˘e= rQ!ߗǑ}Vi2+mwFaaTi>./ú~COvc\[]TE]R;gKSi>؉c@5w@K`n*!E;Ѷ l@ڈuhYMr_tE*st7#5a|o"h@-5VU^`1-,8Ԇcv-a#,m1FX\VyʍR[`fZ\M󊺎? +AƄ˭D  eqarE&r/-/һO#8#z߬#ŝmHMnXh2ՈrUm"4FB4,AǨ%@J.&~.%XA= ]2JDYR B<#6=#͎-:%ȧ~`NQkH<80;NO:EJO;" 4~†* `!ٌh sc: x)΂U|×ԠK\# *.׿W!ӌӇp%QOyNXyMr t0F#N%yRlB`s(ͶhEf>\,n(t^N%Rd7s㊏wJ"P>Ow@t d6zVUy&M2I~4+F}ʪ(O, &yrF29 &{&#,\VQwpP<_A%xϚ>0Ycq :a<!G҈,Of;v92=Iq^-$ܪzAڄ4,#2<~Q[ȪO^]@dn ^|t 4߇Lh0=yVp:w7߷3s1|pN ·0n\|)(}p8<lH??u;|gzjV~~6oz@aL}.ؑ(SCxVýQ^8 !l^00$3z;~ZLl6!%ϡG\z@e#,ZrLǢ, Cg_ZXN1&"qvBNA`MND.dsF|3,|RC-c'u˘RU5Y w$$t`WnH`|lVʓ͵MGKݝ"GDM"(L0i~#CKa&l]@UYdwҴ8w10$U+ d mϷ~lu)1Q>ޤmʴ%eˉX<[1Ga`ZعjjZ :: BꛡVgwwy4O`\H1l!gGr\z7xVߛ |ƽۺUW-kn]Y ίN=`" M7-L[8H'0ha97_ճKѰ98W;3#JԒ2YԲW_u&NECcaTB5Y)i6E'/CK`3Td/P}etC4";r|3p{2v XI(5%#'*v@;XOWK ]5Mv/jWx y|I񮇠Y7-pP&,țYd==xцꚭ RcD0 gr5HHI QUTUMIn?\` _¯C" &3ǃYf$"*^HkWP>& w\ o$ov,=@VVw#^Sϟ92~.0e{ln7;CoHA&O7$<}e J ZY_6Jw%asSzW~UA?cV oT/E" څ&׆v>kV!yRsgx龺cIjoF]ܖ/]nCaxHsV` _7+k e2Ù~:.`|%m G^l`o} +4i3Ӌ*f"b^-]-< T݊Gdar6\}F# Uzhr[į BVt]YZv83R"m7qIw8nS4Yu >%-dFY\x#IMXUO$(i1zֲ$3J?75S;04F& qMQ%STɊ\ vCo^~ ^A{p$Eŧeڡ16nkZ5uZtsnO &qckO" %A[ -D##,Ӯ/L=@w/)l+ yC@@g3V€Mq VkU2VqD9D@/~2.}MH E mX=A;]`%IبéJ8 poSΦ}E*!h:Pڶ2?lװcS 6Ig,Mx9Xbem6~oLJx͍~[')bﴉBJ$ (tj{߳U<|1FdŖrGv*6G.7$OO{hX"$Z3G<'L暮-Ar v_/dXKΡPAHx1 Fvgۧ~:j"ǹ q ]s/';VS2A(M2TI}Md`q1 k:]4jn^?d{Rv3º"QAd!yA_?>.FP)3œh&a |w^ӔUf52>"1"I"uˆmBt~PQoI4us6\k&XGNcmh/ygpk(򉲐ŋt"V\}6x ٶNޕ,g?{ I,ϡZ؃,D靴w2s.D&xHZ1>rmٰ#9q(HSr/ÉC~B7OE@qiI7I'&OI+˞-HBҠhbwˊ 8} 4wtP7iZIv\6ۑܬ]<ٳ~TwFs_TE,̒1~FeEk'J \"H @aẙ**cD b >$2Rϗ˩c,&PdawdJ*l)߬+.{U>z).7. DH e0:7/%+r]R9E% EJ\wleۗB>wcǟ0XzB"'ԣ1n)H^k&^&nmSߞ̣ QpJb)JB[< .sFtk& \ǒ_e;lK~\Vqz-V8Naf#Oyc+AFV@뽱z{y6^XFA8 c_~3'!ɇdm\ƥitg:O0uEh9 g8jxl| ïҞV/7G2&> iԟ[;=i\ZT t#-dЮ*IHIO\MSl<)&W7PW ;-qgk\jX鎍}IZ,R nݴeseIF5gw斓f" ǎ4zL _eNt-bRRU/ #^ngk1Aw[똸Jٽ#Q:6\Ҿ @+?Fad t;R:MѢX-Bp NT@:=qR'''~5>io9rCdLܛך,xD@"ë( hAMS|gVT95*,7ܪI*V T5>0 H0:r:ei~^*37C~JȆ<=Qe6NڍNAа~$ԥ>iRvZқ&T]ٮUlim 4ȤZgչX=~Ð'|Q ~[3:({o+*mg6$G*ٓ.T3(Hf=Ӣ5Glƒ8.3#kuҞM8CQJM '2>ލn o#E"rzCB(_ ᐙO2Px]U{BL \]) tΥ6A/kN'oWwYZ~SF>'y6NV:x'r]|0\c !Ib~Qν\op2iܱiBM|̚R=I6-v ҺN5ՎU4qFqN| /~yOFw/4'>VwË/Nr۶sN0L@,ɷЁ%Ȳ*G`}r's_0&3)M6oj6\3zxwȠh6cxI ; Ԛ |+MgtP^ǿ>"ysuvO9IMF7!((^e$^/aG5gGYǁ䖗z>2*:rDrVt$ 2ǦGv.Md y̻?F!kȃ ݈d֙vPa3)N!*-$$kbMp6eب0DK蛌>c`pAUCܖR.MVi(hCdx)>GCȜb{C$Y+,$aڷj WmM 2uŕ3_4XT2f11UfޟN;L'^oVWQA)B; @^:iwQq1kf@oZV`K28reF7'^ +/QTkVsUNu33kYmvVxc60ܳA5:_>R"6q5m^;"ŗ3CIZKn2$hWW{,OH(fL:@殩_(h晧J(C0zj 9S/{ @싁}lͪp [pPjS6[! /]IU`dJh리lM}vQ.(;w[d66B4MؑVRh},яd~dS=eHuJFZ ]toŷ{H2]J̉$}!XGVl43=Ll)Z`줴:L`p>V gėjS&y|`c"ͶT]&[ D_9S%@Qu`iEn;'F[\TajZ5OSJ]E8E8̈́ݭpǹ7S`oX;tX6KxZ3K\D s;a9XKSBr^+ N'3 "5Zk h|vC3ܝ"uO0Fip7|C@^"_:+lTn5CijCbdڄʄqC枀)UGp+.B$ABt}e|9D$D,e+fȯ^rS$>הȄU`ulֽnIHj,O,-bMe(uC?ņ7E}PgNCEcTݎZu|jE2 ݝGbŊKbؖ".'l̞olͫiȜ4k1d#U÷/8-CzFη]nXR~(p?D[4z1.qmulGcMjf9JWA2Aֆ$4nK;}/O HCp"f sY[(EX>p|+3^SmRī#\ b"nyVfzq݄`#i[q}ܙ u= )(wm[+SǸ4ؚ&iX… |k~QTp=~!~(xnm 7MO)S(ԭTG`)eݩbG6J&8gu"QHHC}85g;,S _[ \䝉\.cF.`ePN{7 UNKG^۬"| <^5WRc40*6z*v);荈Q|L@LCeXuxD)Tȫ#{uCVyo-XzdRgX. Xo)$t3}{U Ŭ**ǵ]b!e9-̯˵}z?W1i"|nc0K}V9`?]TMGآ4v&$pDB;(f&(ρovy}-ܬrNKRwD/CB3]dt^~܀ԬZ7?q]0o&-WWIzީS10h4-2q.OυLv#[j7z];'EFatEX--^bOg, Фg8#-|=(VB*z~{>FR`RYb-C {:2 DF8=ē\LX.̉'MZ48qo *6bt <z";t_, (ȒRX]4g->kMKb+|%>ױ6׹u:*`kŻ?\Dnб [t5~7*j/,kַ%O+?~vUgʁe=/`M,ќ|ԎO t06¬ x˅` ?Nŏ6IÈ82_Z>ZSHbݸE#PQO2?]=pz\R)Y|=-ݷatjZ)hD{UA~v$4a-VMYGwSiigۧ*&n_.@etC`{W)1@0.8"ZZ2?c9j&>UPQ uqWψ43('rW ) #SkK'Bh`R,j{ϷT A|k|Q 5(2l 9lw׏&|] W!y,C= ܮ"eHN,K$PƑšH1 m Yeg\_rHE"q|=rX3YE[d/3 R*mH!D\/*Fp܏$ RitP-dj;"z"i:]4 L*Ҷsb VoZA@HDUh_M0xV ]@q-6FWaL|{H79/l'y+P +ƑZ>`_I8  fq*|y!Oͥ7xuSa6p12y صnhoԈO2lW -)~NM|9ptr=,Bx!T50t#!aCKH]K*܀/@)G0b*ą(#sCkDCcw`Pى:f{ўUǰuL,/h<'pVE kf])% zϊ%̌+YgX:O` }x:Dnels{%8C'FM0?l~?} ul>Cjcc*=Y%}7eN4X3F;&nU7?.@tbV- L11\%SBO $c>#$6J p \F6df˔*?ƃC^[-اn¹5_nj[c3/:yI[V;\5Tnz&ž䷜2/W7ri"fK์9, DM{|3DLeYtcO9kN٨f"e ^gB'Lw6*3 sH: %Ǐ:ܝ`]MDP=+7F?9ǧ %̝)|%d2dH9̼Fʆ4zj?M)_EU":D쵯:\kU7FB0r0-x!6,ESp7vLsi-P,v-L%9j48 Q߿]wg$mkޅRW͒nZAO?! fk6_O,1.~#bHX_Rr(J)jv!)=Q1 E`.%Qpm!pzp hE|܄9rW+lՂgVѮ"ͷ%83V/Iu:;ox P5nm%F')Vd܄܍)7FƝ . }rHj6W ŽX.NoE? s^)BԳb`ć:<pΎvy祄vh} syd nI0bg { ON* wmyr5C#(j`'T;/^W|Vke<Ƽ]W -W5͝Yne8`M |{^8v(> ]$1|/ I)gqٞ} ""c3642|ƭ\찜 zڍZAexTy#F/ƲUF(- g<%tcTB*k!)GRQl3L0s 8NXώtN@ȳ$x:nH%}PF-H\?JħFr4f:K|ƃ+ˤl)oWy=t n޷e 4xYJHIh%Jd\oW7AI׺˰哟*GN;s fԕP-)IDրt퍁~G8l 4U跭͚wuZ1r˯AӬS#O1I-]61jc#Lhw<+;l $m1$G@aȼ⻭1P"aHm3\I}F.*g42pgϑ b։9.Y)H1>㤆`#~a!jrEFS}c;U '픰ك:9D;O)\_n9jJ.;>MzuFRM`l'\:^ ZQ;L `}i6eduK^#Q6DMҰsm}-ymA*ȊF8s=oEsn98J'Y6KI`AsKu:֙Մv++yF:lĪz7nߣA pw?[VhuKe3P>+}rv?" Gp~Pu-J*^A&evG|w [:@γr;O.Dp.z~~`h%̟4zQҥ`:𔗊+>`dF\IX;+ʷxϤ1t$-|ʓRGs&VX\Nt !4Xد]d2o؝`TXNB;DEKE?@X^M5:BXBիඉ/!bF3z04& H7>)Ma~ +ȕZ/̪'* }Bz!JbyBa?4MyD=ZhCC\%F~A1&{j6η,KNSܛ&èos6P;rP@n1SZe/1??&9\x^ߝ3lK>q0iw8|AsQ!$L哫 F ,KEZA5]Xnׅg '{ܓHb?L2Bp]N¬I8짡UUw) ? wը퀛Џ\@aT-f~N2)hNskΞܡ NAgBMrrVEB}=4tۊ .#c`->2npDM6q:vk8"{ F V>s98.Ԭ>+ZOiޓg9ޢ~F$Ѻe"e~,ʎ󛒽(sB1-w =aϋEa#}JH6gھbf8y6x)3ބ OYOP>XHx%w!/?n|]j7bMK 3JE/w?X~O1'PwgVJɆpe J~0~=s^k :Cɺ xOX}cpffJtN!M ȭ bؐ>SYc_ ]6?nYpmN6HM}t8ol~ sO^ "p֪"J^i!gߖ_NO"jm/ jȲD-2 H$`>zߛEM2Otbz-m`ߧ*2t~'Bm\F9kjFJ4CP F)z0cu?)P>`Q~NancIy=*Tm=F"#8q Xsj*ߪ`[/// e|`Iz.0>5dNkөk&we5>HNp3+)Gf$] xųUtBc=׆]`k&!scÉ 4NFdg4{=ARq+8["_ ZPNRg} †j$yYij $ aq4.$+|\_Cr5}>qA񖨮ףxbow1+h6e䥡D{i(`jA1WFxWGSR@-uBQ[VD!q %w]rf'xrK^aX(r &R~§jWҼO }+ y|YؑRNDli)\ze$\oq('ZEiEhhi0ƥb5q*y%Z(ho="!c`%+5.ʕ%jE2N^IsZ{ Q~hcpdxC=\DD>M|C"LuKPDq;ָU< jcW} Ukq_Atd=c:!Vd`xQzRԯO1R3լ GWx_4;32җ>#D#}8 zI3x@YU}am C u(Y85֊rEqz7\ ! '%^[mH55^7с-#NdN`SOQ^2l2HO+բcMwlNL1MՖM3O4u*X3IEk9mytuwb}[zt=s͑947EFʡxEv:}Uw6x!Gɠ%c5/|޼nZh,9@˙P@PCtA:m0ޚszKQ}[Sߑwf: H`aN8,7 omŧd aP"2i.,ԒGtrǜq{tWFLV"͂AY "|) WZ;0No0;jhmWl(apvua^P3E婳ˠP{PC>! RᢽcU~>q53cxwbK >q5zvֈ:lu!! cW5qٽAiZ"a@ghv9`k@S9/ acz}Lji˫ Xw{1%%9Y4? 8vgd9qq@ƒX X&s9G;G=E7s~AnUМ쓄lBpk.g:(Q5! {xX~">v{ZT&ˆ"BxBR?ԒG2D3=]K UH|R)?^NQuQ""6cq/Z"\&J0z6dG0r!.$@9\*:{Cdm]o 5?L`Pй1aB$v[5M3N ۜװ9rL[P ^+ݿ>z&=>_3=7~{n&ץZ!³,;NHn1bp -.G.w<L1V*t\ik3ldxO?.EJY:{'Ab=>],:bwraa3:̈^E@ģt Ȃ%y#_/e7Pnktz| d7!za`r*V'*UNy(IU6:%,AfTԒ@B/M7[ Knڇ|eQتj-ѨDAJ5@S6M 5|]N^ Ts*xIz;+rEY(}$ 9G<*vh%Hcoa PɿR- ܝɧ`s.[Y:p˖XG)Q$ mk _5U]ˏ9"}!:r+,l/]t ـ7f!cFbKqg?JIwrjuYk#~55@fjE,7,e"'D3`"ռ9UxY5K(1"wTB#w- v}^ ú).1f $/;f6霴əjԴ40 H (joۂ a$X*#FU-<CSۑmJЩwL` Łv,EEG@kגvC@TVQon|p #E7<MdoKUjmT鉦`aaH]N#"P$Nb&L~MTu᱄ǫg@Nq)9 >礢aAQ^%4|HΚjE!D;1:aa^IwBC$AF,ç|?/E^ RdY@9|rPLGu'nhh zVE?W +k=Xoc>7g/X1C[ęrUceE~eb$MP v/zws)c`:NxKǮeL0I !VZ/8;W&'~:voݡ_-+zІ9K)NZ%]OkԛJUR'p;Аm:Kmi5:1Tǝc~Dn}%㶠*x,#_.7l_7`}RȞbkVN8VW8ةl;:asw~ :n[7,L x"h٧)ެ̪eTL:{,3 7Q vzi#q-ǻXf+j/934~HV&DAKdgH݂҉w?q0>E*,60(얮4>Db$g oNNX:'D"H)"s-"ZM٦64ɬ>'q'ͪeJ2KkK_sK]Xʗ:&Oah`j32&w'jϵ~ KCxcSٛw}}7/?"Sy7Lg ``%Z) 149)<~b{wA+~}Sl O"Y\$!h_ѡ_u(ʳٿS7!absYia\zN ae#LGg+/pHۋec1r<ɱc"{gWCTm3t|F$ ,q [+Ch%`>܊R;Ҥ%޾s ΅Gu$jt$2pS/Axt[BSR;3M~BQ(߾"mfC72Sߑ(LVR-M/q%-ܶX۫p}!ߘE0!.% u娬Hyi e@5D0Qڎ{A>@h #(M'1%VSOq 1jP0ej6{>7&Nw3]q)#AFʝ@UM1} (mn%h nk*-u$3,ڵ9oSU_!qm-R=RZV@P<qN0zN!'hbO"ch@8 G1KDZ?ԍPU/'PAsbܧ 0ZS8bl;|uJ׃ڌYʝk]}`|:%}*ʙȀ:|?ct|rw̴!"p})W4j5z&ԂXA۝^ *.ǡV"s׆SBHQQ?@Z  yƊOk<)Ń<2]u!qA-d޲[.Lm"qJ|}B={fTݞT-ķ< 8ȽoMgViTby{v_ YؗjuQz gXg=hFio 3sMk Zz;9HO5 _͝f"DC g6Xg5OIך/vNH.Așc>LAi Zۥ")dd[[c+k h Q{t.׊7P(٧r)YX`coAD'voD dW}D&Pb4G)U` DN{_ \F)̺Fi- 8]GG(a5Z&¨ZO3=q^}OZVg3舯Hq'Z!xn-`޵e/fym؄~R2>QGcpc'Ŝ4D}tIoJύF՚? 9%5ўl,1z`%,_~Yp\/p]o;i=@,SzL-O>s$?aՇdnț8SOth29aL*ޗ}xfw7b?z3SbrH0CUYJBaԂKS(39Ƞދ}YKmR\.a-VRbu|GAŷ.b/ͬ]i[9ĎbA4z{\yޜ* Ͼ:]̃|9 >H*"_ :q0lF3?5灠5.=R}9$m{ B xwRsssrjH1.6آVl[/5%qfMAAVy1QݙC[mQa@5י;Y{ē8Xvh(Հ_280i7@^^{YTE(zA#N`u@#s(|Gh$<^=#G> hqƑ4AKo:\,`BxdJ =cۅV+2'xqˋ6hgn߈hD-z|1992%8,;w=KީT. n{D^}ޥ<1 omϽ4H :ܸa*ƨl:S2>N4X I(auW4U+n(r>g+5MDb\uPaO*؆lF#_˺SN#Q9yOF/,2}u䍕rKT]kU d7Mmڕ\H&qu$K.!K?hMAlrsKL h"AfB<獪Mb풕 cg2xGk/volrɧ>raur.5˸,v4bXϾzU~O.B^*szawqbq4tDHK#tOWU3"|ZND 86Ld uO_%LޤnDHs#ݫF$Bo ;#XG.Op R:w-/25>}h{Le=اj,P=gϐy?Ne\\BCEC^[Mul 虙k+dDbÚ;F$}:- 1s9Ժ'~` [DoJ]Gg?51+8 8pKth+Otbu2s0& @GN۶#R39(-pGp !=OChC eOG)Hl=N*Cjx&ZNke} 2-# ,+:c~|;rPdJ ~+H4Z ` .tn6 B,NǘbUgT@w'ZGg:  ^"bh`$XBӇA|ec&^ X 蓇oSI낿]+0;עPϭ>4)Ηoa־AH ^OW (7o/f&I#Z[D:OFwEc,ulV,_YiF" *y}Yl;6M99o .)뺺nErvIpXdFT !Nkټ}+0p8MFnҌQD啵LHݒ80M6H!#y2ā&1oʹ8C5YT }.=a`(?gDDdyN#H"kϛB7: M8HgG9x3MPf}ěͮŞkݩ 2i&8X )nstgƇbhY] )\g)j}gu6?:`q\_*Yi{,b8Cϫ~n.j ]pUhV xWǽ&o)29}~Zޏ ah#lFs;&KӠa8h7&G Pםw$23v ]{vZ?(ا-ՒŤcK0BzruzhC[lNA@m ?nU<&EZW[1BXJT%Pܰ~<9 f;o/^_^_0e8PtPWKvG%MkS:QXzR$'Պ%gٜ+0GTzCnLM a=Pk9;BЃ>&?6`1)n=?-|HH43\NFzr>ڗgfj8&ր^3]U寠:r s2ni3Ϧ)]+Ɩ ;z;րsѸiqw0Z(@b,W%\HJ- a퐜 s.P]bn $;d9v8l5#}~7dS&K̵-~ 0$C;_*x[ y$@1_G]cwLf >%WJRZi%Ƕ&㕇hkl|ET!+HG]aoo ɛd h& cfЈj;4A"TUV%B|dIJB!5>SZ{mKR^պW5kfɻeЊرSkxUXpTЇ2'NnQ+6(CŭPzoٓQ )4hS6Cq]笇 {`}k{Q$p,!.<53T-{,C5{ie[fJ."h/iA)zG(zjk\ed/^'DF HHi:nɣƻ@~JR9p#~ sc WBDoCmzerXF.a\x Wup?GUNnsx`={Dl0W HCmBƣ@Ʈ;≠E'qz OWL5xv,if2$$jUpH Xbt<+ӈ0㸐|%뛯6"bcjxDBuѫb9Q\,D(pz͆FezSM;z,{I@F9lGCjy_s>6`N_ho>(`hgHfh)r[fQmKMh2ίmElQRFK d9N̍}_*8ro?$‘΀FdXM50>AZdt](~ 0dk?Չ>Y,>RjWGYFEf;_}GqVOv!HK1d1eG9(֗;[Wr>G3Qo/,㗭P 2Ci =NRt_'s0DT1<.y3h?خMD}Fnn/ qfK3v.Z= `uvEJqr 16'4 &6kv'C G'u1zDIEH}$ڕ*8- vԴiׂ8)lD1r] < C3"2DM/^>4doyJI#B3Fn$K0 <3zNyx )RNA) ]MCU2n[~x -A$`Q+|)S/ YQuetT| @\h ϧ}mA c@Cyfڮ(Y즏cf_&0cag[Ia&ly+E(H /; Fv&X>/J#wiq&PtHƣ?s*ݘ{3dQ "9}r9-R6nW$vZHEFUqp&K^)L[1+oU6y_dU-l"@t!yE:Bwϡ`cmMcW3$+)E0SZ ^Y̎Ӗéq,!.J, Ɂm/e%6#-&OU4DyWT޻# 0cJ#YMɄJ7}uFnV2R$-t>J5gV})}Cl Ig7Ԫ.>+t*ʇm"L\S+5zJtT6Ru$ss/HDezWؼ۸`~O>Y9 I:f2;6.׬O<Ɖϔ \݋bٙ_Xx?#3U2Cif}*G#[Zɀ+€`ZH]?@=͚WrX5* nrIڬ<nӍ.0 i1$#CsbOʡ(nq+.Rzx;eI Bd*҅=CXDHuCv|#4Fwt\,Bu0& ~KDN|!V ͸p0 "#H4hwx*J,wh\̬ѯھo< ~jܑiLsbDa89TbVr!)Ͷs+`Pƥ+=ؒMV2%{XKJ qݪ[ʈRfc le~V3tfEgi'F28`LÙҴcYj6=1~_K1W;7} G.UmNULi6z vq[;zJsL2sL"`K{EX1hv1uj s[jRn+Xv"myR \Q_vHp=|E ҘA/Qŭx[ XC.'dT~u ܱЌJ wnRؼkpH"O}p{*ր/i:|Lh}zB*,Pҋ3ON Dmq2D>r68XtޛVW׻RH>An!17wI횒[fBLT"(B<6Tl\`mꝐf;dzozمT>}-'g$]׿()Qw׀EUBrJ{#x^AAs ٩s=n"Lj>ZG8CfWWC -[j'ޢjDvM fM?Bq*q-thqFJ%gpS2#SA*}ۄ8XA6* ¹>Oɜ([xeLUX7e'S ۪A_̈́0JJ# ꣑ޘ|~/zƤ$Zyn"`);j%h,G4r ߒ7KNdͻ^#7qmgwtl<݆^?V ѵbX~x՛F1XӺʶ}n?̭b>^<& Ag#;nV-$&G&䑎Zm}t*\IjМ뜥. a:[hи5y`cqn1T C:d[}ڞF4w@ԌUFquw0?sDR^dW1L߷OxաIV7.8#0jǦ0t8藻pƣ+E\l&zKaMHTg@~Y,9[/j⢕UȈZh,~u#gب?",$(Bf 顊П8l-Jj2VIg;?@9Ձ؎ɱ{~% gyg4[PpwIyi|/d`sTJ#5QŶzMCPw)' DT)H?SJ um\N%OQV$WVkhjg=]%\V{-)ǁҁp}/哊2x;wC):T1%cDLwϮ(=D 5pt<>jLWώw2@h*Śm8QQO[cnvA*ŘdݶݙJ g u}q ^]h$XF;uظAes 鉟l S,!@^ 厌ZMOF o]bIN:IXRl_h)U R&GjR_oc8m+RA VvTį _(*^ Hm;2r/5QfydVfGtlͨ`Xu79UzX٠=VF;5LK2[݀*{wo%{M2CMi>GiIy$ qTð=WRHi4I8;Tz[Sf,m(zzjb#w ~X+DCoH8f\5 m0q@GCN_Mm4jYօ aPPʳ OMz B^^kXS-#-ٰ3ׁe,Ќ=X>h-Yrj F!ЋpUXݵ" ,[#NpLnkXGՋE9|V:+5҇]z;;0<E}tX`G Vbk) `9MdO,%Q?!3<=A*ʪai*#*_3 8P/Cz8ЂJa΋/xԷy+"t :Sk ,!`rTUlڠ@P!!rA \lzT Sq~pc#I].dŰh.f5)k<؂ʑ:hRy+B-HCbD2JW4l$߁)#[G- @ ,w b&/k7)]B?ԶV]S0Ffk*27n&JFT*u-FVHg"ڮxT`8q16eimdٖ#GH tX\]bΕ[U YEfravV9˛sZzds_[pҽPuST u]f{tDRCeU(p&Ε}[&" 6E|EyjK%ztbncY} ݃k\Dc|1b[H5I0XAz.&(2̶-oQ_k`4^pٚ%#u{5Ђ՜m*h>T*fr+.i;EOmB#4dZ*C-*t9 AYX^lW/ g〺' #/4^FTb]f#Jc{焀 @Mc@i]2FŹ:rВ(B#x𢋊,?+p*F$:?XOnpt!sXBxP 7f90~ vEckNd;fM>"|i}9׌]g&\L%~t. I:+X+8b0[fW[-Zˡ+ bMz<@bX)euTOv&e6Ӻ,6W<( ChJ9[ӑ(8wۭR;_\j ayn3.sHbF!*߀UdycH'wX:U[ڧ}?aظsRMy@[&9gO o$O![Kôm6Dp9?GE)JPHȠȦiU R28(2s_s VC?z!A*"J\ vY@ӊq1oQAe#* rrX:fԴ =Hcb*#h>*N qԉwwjIkCp> A ~Z.b?dP}MV1&83 8yw_w-]HݍN_@nZVҩ*?팺r'^42D R!̲dfV:v+Y1_AfSpVN@iDP[vo[Fʹ~zi$ D" |3o1=V#Z5 k)~ϊyduV=١_恍b#hq˱G|P߹lÎ;ӗrr ht}$; c j:5Etb5ُuP{FkdM]0g1[U >h= ЮJ!:,&RZ=⤅$oqe%Lj=Cء]P"13x2TufV$` X-*UWwDf='M6ɏMdo?G@)@Ap/Wy|F׆di*:b-- L+z?F hc[&u%hK}$V}GHZ5rW]F>і;x˙f$\J2$If|VJlבsq˶_|Q: p Ƥy_׬%aI ,zMRh2,0%qe-& LO%I-bꄌ.!}Eg5L3q7Km $hJHBVF˼:d.r|\ T <=Fc+TjyyF"3=+?c>]P|K{Q,/x~ο~y5]p5`txbAS=M){c ߫#d`~-~U 5"_8 VDyV|L̿{IK)U.q7gtDzjAPqCZ=)XT!Ӟ~7oS:n#k&zȴ aJh!^~E^Q?2)d̸c!r\Y:a%& Kq;/A0eS/# Pj,_ b}&fdZyS5Bm9",&7eAѦP|j='|KsP=T4sMV#S3} _xi(xZQO<Vv8Qf;F CK>#o®~+$HOR|g]6GkJTiY$& QHZ<?ؗް0_ߠ1dYyGM4u(%/ԅkN' fY0x"K _04pZ>-!-MBNiV!l0R%0fjf 'p~)11,L{k 9 E*ےjk*0P!$ ;cQQ|6]EM>~A sQVլ9W(_ڒ`z7*y~v;qOeNU!k Z ht"ôJNMi# .@3FUc`PV!ݦ@&_/Q<lkf{y\F cANus ^ZDۻ9NV^anuc58JtBܗO5o/o!Uϔ'JWgC,%kܜ%J@ݏHB-\|&)Ŋ\nڸP2O@oA@-a4ifޔAgz~2ʕY[JTδa(R: 2c{I%0LWw1%ktlOEf.'EWѮB8mbۄpصB;Opy> lrv%!m %A1Ig=X_Gg08jA\?kSipEa)n΍Ӱ%۷RyQ)be7B&R\X 璸T渻OVg( -q1 Qp0S6GfLI)o! 7+1.q_UvƢY[nJ_5YK ({ޢylqkS5C26Dm$`rz.l6PLamd%Ȗ$6-vܸ,FyZ7Ժ&&fFKv=^;jΏQ4Vl ;yQ͗ܥT3=ȴvm<;L(TCX%H5^fHzHY5y$ E0y(y# w)s' 'X Sms6]tIv)Z KM.ӲLBA&62O\'6ۊINT4?ۑ pO圯?rg_]uVHW`^:2ʟІi?J:|GAY8j˘w# 50X'"Cҟ߂5!YQuũte^+tq\/WҀAP&4 V(Z4`xLG_dzDeThFR_7OR8(Xu 1tK1W%E c`@KnhRH*{VԳi.b VEʩZ%BEKsOD%iOD񹟳I_Th7KHz|,Sd|XEPe4/1~3I(Bկ-!*(sgl?g;aJ%jHn.KL׋1/J71_3Z,$;d?ڻVYM+5(OCShH`/_% ڋl%a#/C ;*}Eyk7+=XN"w1&%BEzf\_TnɵDC$qLpz D oja5qo[ g6_' 6-pWrIJ۵cH:UL)ul<0M#0tl2i"8U%X_³-F0Dk}NNb~Xq@~8)zM(Q}[DgAD8L\y 4w3'b /Ef/V1xƓ1rATE`jk*V/vegA{iM,0H27]ojw~k]ոY8{\lI ~dcoVN~/qȤCb3f\ґOvc`-$j5M]΁˳NJ͐<v&y>j#[IlMNOsgKU$^%DzW1̄:DhGg_wU$@Sre4a/:k4@F3}R[öyj]{0ēqy\ګcpNhY:p#\Io,O^\v˚p5S/tڻ'脳wRɑ`}cpB攬o[J$o[|t3ɮ/wۂ~C×n'bU.ul{0LgRvY{"H{7}ꗑ7uk!:S-%3sp'|t6=~6C i\0%>v%>< o r=0M!?dJPOu"3zu9[Õ*."G4[/2 W=+"6 V.'+0v%LآEP)7/Ѯj@@'J,4":j"1Lfݱ]S%Z-^eFŔ̝vj%BPDsּ UfC̋ /MQC0V - c64XOà;VM샮/?uO<,WdWQaUvr4~ξB%Iƥ7qV E`QG52[Hى+ Ga^Fe|rmeJӝ=էuB"G ̉ @'uWO֯sxh(UΎB{OcSef8y%@$Vv'{wADT,)45 f1ӭMyŞȫRc-F=EϒkÈ+Uf*C "'3MqvMa#w 4/Yyǚˋ+Ùr:nݤ_f o$`ߨ@QZ;V [,ޔsK q}ʼnͥ~ޒiTH[4ytYd~ KMՇ%Y|І dQ?|+Ȏll6-E =*н)Qc;8VhJYŦ(nFDŝe(7< tG*Iur‘2PO_MeB\IMlCoCFlƮ ^C/K{p M窱}G4Г\} ]\m ب{ "eӗ^;JiןM x.VB,w?b0D[jc,^ikD%CKGh 7Bs؍ŦL+2o#ŏ`RCޟ^J'pv}J{]X|F|TH=-pYᖃ{̎E1w>T3AoR ݯ%sn9< Da ʹ_j!3eKM7ٽ`t+m_$٘뾾)o{2Hjh2ȁ,.ߛvtgъ&f3)rI.! }KI%)aN/I.[+?%bqmk鰭(spտ$ߤR=:'c5.i0\/-bN.~ s*11138X…*YV| _oj3s#QRa)q/dψƝ WhVv7' y _V uch=˃.F *%͑XX 1d,siTmZXH1O;\G%HlD}n5WƠ*zѣ?0%\?Ja/]4+mz4滘QC+7bB(H| )Oun%N}0T̹s~Lc lGHE[H9Ԑ"܇0.*j¼O6~XoӖCJMF#%l6G m /a@Ǡ|7Tp7 dxfiKUvhnOi 6i2 Ɏ[DJoͣ[&X ,Pg?In 3G@5%ڜЧk$Dҹ n1[L7.%_]0eח3kzXyZv)v:S(Z+Nl|hպ( szqfgg,&i僄ʾ(.7}TBw*C[_f_u44-hێv ^[r3;3rBB(YmyըPKzQ]1ɯ{^eKz<ɑȄ?"9I9N~CP\E1ho@;FEQ+szijQGV*i-G(#6KQ6xd=#v)&ҿMIEHq=] Ģ]o~e 8$`В/vc wxO1xrpu7Ӛ퇑;KZ2.&. w'7)8FnY؇N_nLp(u-%'yX/' mKɅ;$fVpKfp}=0_ʟ'h sl+>Ǟpm~@zvb6~[h[vdAQPEIkۃ+n³x*nWeRתͯYm1L /X܈"&v4+7& ~B,ô Ŋh>4{v§['0[H5>gƅnZN_=-͎JLϻP=ϵY)" 6"F$K%9B_a85RN>I \`dK*ªW"%Żrj>/ n  e[y@)5H{XgRTY#KXGaQIVRu&N-aZ'`IAz }c-7'WLUHEy=&B=y%zUԥ:"XF1IX|DKZ2~eLPԁ*mKpUY<$\jYQZ^d=*GB?A* waxBb+sH `Hj-29#; \e+mad4pf\@x)78{h)!Zc_< kf%$$CSOM/ ;0`T?XT-ɟy~e{t n}K/=r *ֲF~9H(יE|Z+pT)I:'~f,?+jrZ&2fbv^^uKPD@}G)UH:>9p"od_E޿:oLs)yd`Ҿ}Eޘ|!;' F$߳q]TT{[Gw<ԢIBv+G#|,7$ 'cGr4Q*`x+w`$NtQ9&lϭa^2 2܆5!Kփ>S&9E!ގگc=fSfV 3ܴf3)Dy7cC4FXV. E_R:L̀lܽc 72;d,Q jJY~ {t?Z_UǷwȻRÜcv7#4M Y=HEf@%@n QR;F0M~Qw61Cg'2qhJ{߲"Ȫ‚Y;&g\ow0{} taQrxonI&pU*D9K"^-1psn.?2A8".nXN?i,uBW)S%pm.!5c.8T1=Oaazqk畃@7#u\y`Ыs;.]ku7w6s|u8&'Pq^؄.$S Ýؖd6m6Cƴ= .T^dma) j{/jߝ"h!딈})u1p쥑9R^>b?_Qax Dw"+]*04iu㶝En\Dnŵ˜l Ϫ˫ 1bf5,A[!RL\ u\ثxl5HJT—96v69OyNOQ/ G&Q'ܘ* I,4/j7x|^0v 2$YT/Jz2TslS@6 ~JfKyy',XCvwpWxWeZ G8Fn:8G?ڕ N,?WݏVمHxfS,^ `p(د1`(7_̝Oʝtӹ%(uPh"X0:u>Yqޑ(R'8kw =iJ%DUS~'Bl$Mk=:QHi!;+ .;vXRm֋OR5r:4ڡۊ!kRL)*:g!@un&!߆u+á[G_0XfQw;wn4Pj.,ƫ!Fһ!w+f3ˬ8Um➜HZJKnBVi;C )]A  h*ֵSo\oK;B~ pV9ȕvH*?¯U5Iqd8ȑU/tFp Xn8}xLC:36\%S'u(=\eCh0\>Z=\wM$ z&wЛ*$JZxpʪ#o_>Q-h<)sA܈P]pfao'dYvB?U!tUeOǦDp}u7Ugx2cqYq}+bfV~g)k͗<3zZɻ4̼(2.q.En ̔0:K(P3qNߨ{>yp`K Sc4!g&͚[bS-A,O6h/q&0m`Tqf_bMYH&.S[0d>^P/[ 1ELѹ |YNِkm!X`MontΖB.D6ʦ)S?~tqFZM NGUд}3op.ep4qk΢q|~Ώt0X}HL1zM֮ۤ뉁 f)(/H!pMU7Ъ`D0SY_,r3//sՐ[бr*$ƁUED! z)Xe9l7>';:' o-qٮ4n}zMr{i'#~eiZ9ChhOoDBė=IND<ȋ-1+"o=ᘫ 9AbzcDh10,6Jک84( !vk ŒB|[@|</g)jO2n6= HAh3]N^ ԏ-VV?7,uߏn9wtI 3g.8j=ó;1EwxgߒL^ &9Q<Z&0CzX~;e%xFv]@~$֫g%6^Ņ8[L,% [A5gm-Dj޺Ex'g2L{@d773Q?N0jy'ߴ׈n9ͼ>Z:Gzha>I}\Z) :W 0kR_V`XR` Қy)Ɓ-" (l;k VoWQ Ki# (Ӆs {u X[B8is$7oq^QGpmȢ]ƙ=r%R)!h(fWgbowKn;s}̇ݐ l tvMҁVRKJuб1Ҫ$H]İqz[Eg ҥRm KK.iRUTqؽ?x?0Yv=li _Fl3Eu$c:t&M*pob< #|wɫCb1p 6 )}K~~ 3Q.N_IƘeP-jgHcJn><3tryL-ιJiLUzQ l3C1f]& Xo-%S &QלCx[*v.fLE/``f9S^Vb|4jWU0iT_p Ԁ^XDq[^Dz _7=5݈C ˜n>/I.hB)퀚f>tKP{o,/zwd<*ֿ2a^>b"[UU.N F0 eG Lf{ZTぬj>+r@Cy=ORP/0nHV4+ c2Ri'1nb!&Zc͌o= ARdia^d3odTDĀoۉFYq2 \/tw*-荙zqȠ2U=#{\q}i|YsQ ipzxVܞ9//[q5Gl:@99 oW 6ݞl@qTP?MlK-ӞdNu&CSR4trɪIHIeU`rmt\tRʡ R#2okv?|YˏwOP|lE2$'b6f*{|N~ ̮2V =3n{+ boԅvx~ HWv|PVќCG npA=ё7lAcNsǗٷmvd죏}F]D5g)e_?_iĢ>@x yXn?_ܸ#~QH4$i=-bjoA2ӅHV JQi8XО O0@ [ r*mI\#br5l[>UZ<% *n*^ց!)+²(Z3_d 25T0LݵvN(٦_8SL+ݰ3S.ooy^}.bz!˧0> 9 VNoM]ٽ_GwȾgqyM>5)ƔX"GԚA݆ZjTI7WMmaloOM!.YWluV[+e^}Ǝ=N:nrX~NaƖ{Ï%ь vV4 !H/z7Qq ml sTvQN衃P޸$Bu<7,Jp-G7 wn'niHKFO rrȾPgb~GXӀZ}>w̗ؽ9w.ij>~0W B3RVv(eTv,^"ȷah iKInj.V*kS3FA }}%/UK˝:{;lP{01j: ۻh 2!j+u|\&)9[,aI1lf#pVuNJ& \!&J{[p4xh;("=Rj vc^权=8'NYT3s~TPVtHj^iOl3UM< -%v5w:{Ke Jda[ءn\_޿_Jn 襃˒_s= p[gA|(U~޿81xc_ZbR#i,Aԙګì s892k}4WzWGÕv<+QO"&i_ӆ|B=pGcAw-˩ !5^pl>X sE 9o,2e&/T_^7gYѤ6KWI/4#7ʑd!$^KzJzgcP6j?%po-M)=.~-MmV{zЬ'.t{=3|&`PUhGy :`f@oOsZz:gYf &@J7N^}|XȈ(=]X"\'p}.XR$܈e?;-hDP*g#Т! o44PzDUu &Qmwh+f(B~C$q'E49X>532-L20nAz=ԙRG_SbC XAЖS9gδ)[cS$O2-V|L&?%gnU6;83t V>sŢc}2erb=fKOdE =ɏb~dj.`oFWI{ɋb,I3*v0eLT*?̆ī-472V&FDoS>Ò2tMu³HkZYe; 44iHx6M ael4Y2xLn[_@T$I&"JH) ۡ%063$̀972Px.Q'>ny\)7 &~r;Gm+`^k"qnw\kBwRVb}m' ]$J韓2 7]^I'S@"籞q6C]%+M5`)ha_~%jv5<,l0rTzD֔oQPxeCW8~hp(f#KeezO_of^?uݯ (SdduLy_B;4̳!71v>Y3l\waNqGPdI&SϬ)fKBrM Vl4B(}oAÅD#ސ4dH z(\"%bFDoJ?NL"%;Z5_1(.2 ̈́k1uU<؛i?N{yƒ~%SvCݎ+%xEIG!'QuZg]ٟh/bf*r10 Mg9' 3E5uf2=U,~#$c Sڟ5}?)7e1Oa9hidئSEFw5h䫴{]tcͼ|QH\BaDnD}?X\6_-.˄gI߀f^PEКɇV{={>h&|Y_qQ}@0X3D|TsYC=$_T>d4.Gyg5,)瓄yVtRq.4!R\#ƻ6u6Gb)i N9P}mqDqWy9ԫP.#\\ksRr=W=[ѬN Р_Tp5`Pp]Kt^ET/~UeuЁTAFI+n[EJKW}`bKMɝmIݩ"g}; o&-^#N͊^hLF~jqxUL!>]9}2SO NDžO1:0f:QI*"W|ᔥ[|I[Esp32 ھp f~30^RT$dFrECNO} J2 tyBtb>T^]--s MiDJQ~{軽<=.!%3][\MuaJyD2KA%)g 7?B*Ὁ4)m0:EZ.z r8Ug#גjuW2`-Vj*WWQ7nf8€G-ډv1C< `-~|C\F/Ј? n8~nz_َ8MKaQ5&nA H @1CtkAN."2Ht8R" >"_yQ7la-8يKw(HPBDiݽD!{ӉX\ m],8z%sRF!<#V R{+>wXE7:'\OtBGKl E4ߐz@QLi<Kpʻ i;,ĮYՋ=u0M;<R6 @a(bp)ɯ,4LdD҈Ж\\?KZ[!"B>7y)~^jO"2SY@ɞ; 2T'h5~حT!ݿ+O{) ߒz ^"خPKA;ti eo|&6c<'jK!+ۂ^,kG r4dY2MՈ+f߬#?/2G Z^7Y%9:_o}U>t+ډڽwt k4e|w{{i/g"р )/Gsl }1+XJ}*#+p>F6 KtJ :QӧW<vE+  ?|aC+~8.%q;/6XF2U@܉"rl@ـ;Am{ߺ}3[\aז$3h`1 {89tC\10 lHeS"S覐`mn>f`j`!M@.~C0k aA\°ўUf 55cd5)\s gM,.Ѽ%gۛޕo@Iʧ$ d)0MItJ$\Y9m<e' ՜;I3jQ~R\̀D+E%j=; 2}?͢$=G{ܤ86*?RZᮡf^\ yPS>u44q",S{dKAiZ(Zwm FZɆEIՠJ3ߒRGv{O/L564(ᵭAKn}P$Sw+h aoJd",+$%^s 7$~R_ W)SMOa\Z'ٱ8R$wD$;5:϶ Zxi6TJS;eEܡ) 6.t?lix*bKǸvO322U`ړȡ:l^Mo&~S:n;fJ/hIcztֽn}LsS]R>w.|^(R4 s}@*^ie[ރCC((%ys}n ΚՖQ쳿X2ީ.7E.1?EcW.ў\zEʥiTCf9f:N T-_e,[Д"^kP\ϓ>k|Jd:-Nlhd0>Jr?S~?+N2,hZ&TLњקIj23Kf3*^aíӆV:Gjd9ndNPT //Ti$iiթclh#ͬ?㊗Pc:dJqԋل|* Sw٤CfWG؃7+?sG_c#Ii_{rWpV۫5Y,mcD ӟҕB`͓qo Ȃ8Iޝ1(0y-G}WybZhM bx~47b #)P\%͈hfnI zͅr'?nͩg˻Q3TzY4,P$gKaO(Yee"ey#!b}Zf %k5mh(voӚ9 Onz s(v:;em.:  6?FjM@(Bace.Ivχ<2cFr:wrm1%s%`yv :Egpi-NNབྷ|~gpΪUc F^38I1W ߌ8Hu*57U\Bx> O&z;ț\lq ]L4Uѻc-~1m/J9.]KM ʁu'ogio#~Y`9|%CKG]$)2qƕgM.^"EG$i}#:fA%(S~:F]`m{\?@(7M Sz = fMDdT1\V!<Ϭ.kO ?h;j-]V%B8~pJgs ק/%1pْ NgjO4 &L9>'q)_[Q=*4XlaY Cbg8^odq0Jڒlpuz9Nm v0 ^~6ly'c&)8UY14j.=XQU`=Q/1ueiY^⿓}_eGƏPmLUDW3FHRJ4$TNRQPi<3fr*ZW R_MR}^; #x 8 ! UqRhh>.}F)rm/Z}-,/UCUnؕkb>yL/ӔP T$7Q Zy}f&&Ug8WIL +bܿ˸E} 'zJy<ljMԱ;U_Y3 " ሎ $h݃&C߻%QQQ N >/zZ\c@׉i.+&{͔VдaމO僾,L¸yu%o{ .jXXOR>{Ff"#;)\i9YwjYg(]IG$qD̚䋸" 3QNTh(E\/bU󩜹(RJSh{S6ܨe]RHYωGR-k/XgۮgSe4JAxZU,;6 w7N[[-HQAҧR(('R{QɻgoEnEY-kk eQo~'e)8 Y䶌)t?~\5 ~?NF@ޱM w*UbLF9׿Ȥ]^ 6A1:\?݄םbm,n?c<^[`GD,2D1'LG6Jn|'KMD.[kM-P9 o6qB@,.T}. 6MVZ8d93[xeXӎ*RX<nyD TT~_|Ìʓ =h~Df {(_F\Tv'zډՓGA aON¶ JTB|X@s $7h5// ta6LwN DB:C%i%TɁܣWu^ ze +?%fOqmmѳEbӃ+Z m- C_b;r0.:] { sRpxXXHP[stod 9FWR wco@nFv.xcb[FHmJ'0'sF(Cbrq d"OAUܦ. LZ<' hWf&݆`S%?$G&[%{oM<'Em9̐p^='"|`_Yz zDžqn{πU{WBGV~S*ߕ'@! c74N]0,үQ>W6ۧɫ y,OP#Xʰ.>0|;ZNꥍ?}3q3's/Xi}/Jm9늷3r2zm.9cM,WǢ}cO$VƊ|/tv=4lNKAz'eS8%n-u Fef{ZO-4Êv #=rV`!b3N߻5Nbo0^nYp߮rΥÊy3ѾC( iR0!!d=PvǔvV4`~3^oJ7v wn|b-.jj}.¸k+Ұ}T|fig&'2IiʮQ|d@1By?e25o-sMqER#iHoXjd.tp5>K:R67 ۤEh Pk)W"(PQ*i}4Xg3U ɧBA|qݨqSDZ1=eI[Eӽل 4+N5cϥxdv,rY Ch8u$*{@d'Hʕ/l8PO'bKmy8sK}!>6B5PA.9!Vw;j[WObCl2/%!|jB|9>..E>PW8eZdK_Xo3YMSYz^(cikA;R$! z_(l~> CMצK{ S փ_hKo[ΰhRؘuIyQjvrFVjiC]W@<Osv2j80crloUUgb( ZXߟ*x9:'/f4f$ T,6NFYcs=W}sٓI=5kY}+DFq +zTcP]@ kiŕ?AhBEjRA^[D~V$!)|)TeG8B0ano/4}c^fdyP+Ro^Dpt$Y2@ak40b86j|揑ahI H >t=i+" הİ% ?1UQ.T`V*(Ψlws3411)hI(MQA |GȞaz\KEY*ZV5_(@װ:>4R%eRw^tl/ ]9ޛU{jsH灉ȁ0]=qBV'CщɊ+ڔ9Sk$qOB'+Y+5(G4Q{.4-V`"bK߫}"xc+nĿ8;-w1hבBI͜c=1$-nvs $x{n=0 (@z|~Uȕ!6`gMS!(7 #qY."g#> Jv4:JdԧAR$_!g`4ao̗ȹxE9.>aXgcv.b$[?u5Tr`ȖG09Y'UxIt`#7qgOM] kp)˜G䔱|JmE ZF2UjD!QFE &}$ܘgm-fx~>~E-4!(Io_pV:kYs=|[dU4FY򦣏%<_ #kETfhL}{&鮏݉sz 9a%L܌KSݬ5pM$ 68u?aJE7fKZt.US{? p$}#d(,2YyVȗUYHVqhZ)=֒ZO!z"}[ [S.VrRnJ RT{s/.=S@s(|Y 4:{x9k!=sJ"WvsYH hW$Kh$b,0(IAXưǓV ǜ;XRKUP@odr]ܱ[|7|7i d%%Mk _2j[oSC)P~)5jn;j&Ep}4niC#zM +TSZ4 œ!W'1rpJb!~?UQNX$-Q𔲾V# ˰yk ~X@IU6AhmV$O * SWWN;!3uGAZ;Ђ Gd񑡼Н\f0!0Y.|3.Dx7(_]bXe9;U]I{u.:(x\d-#Y T̼gqZE6Ӝ*Od(:1ejf&8Ai| 2>WyT .19 T+wǙ}ooF25]B{vh.(j'lq]@B,62U}Jr\B^^F?0 tB!nX @SU»;=E2-)\B}.oCoK5KĮQ}Wjp@[miIV/H7G /5~i]WdfvKl>զm@f=?jT9Sy4fm(KE,p-NRtW_[le~d(Kit[DrK5 3X+RdP]D'ٺ_^ӏFTwoΧIJ c10&߭\N`m Yؓ}#-GHWG.q`1rCXrS:*{\Ci%C -s_ئ}xJP+*u檮ShG5gez~FxOpPGMϚ |#uسIi\Ҕӻgx *8JASE&/9E &,WELR(4P;nNƸ4$pUΕ>~!nZEW/3#|Dc#@mjΕB:%Jc"0Nh&KMq n 3ojoީHay@G1 .o k(lezq4c sjRn4w0[rlDO/>)Y3tֵ$|8F!͸f5iڪ YQk-/*-Ŗ=0~yt4E {TK@qA[=Vv:&ND%&ꑈ.,OI<4 F'd E:o֮%SeFdd@HXTx=XMh@PD4vC q ]aj&( H= Stˤo0!'FuIPT^G‰>"$*<'z3q3N?j83x]\)1kNH0~wo3=.$BjgOt8NGgOnPVƻV(="v3XlZ69l\ ?n?}yN7sGQ@).}V'"x&E`98xx*OW,:~(q75x,W1lڢl w\vc;6$oCvuP$`7S&&VeJX| o^_yn:i,XJk"@ X1%@Oe4 OA)C#aPd~Dw;U+JRPY7ަ= veA(mOhJgx*zZ26,dhZ )Z</pFgZ*2& obľ_qVsw}{>(k%]=(+# g0C@vq:֯J'`V Ӻ. k* 43.iJjūԸ,ShDʚJ kI9y=(sECuDP5oyl4t?1[gvZ>Dj@JmLpNnG9afb}YĔ(;8_[:5GUcZ8ѨM\'Pz[l ŌOćcz/q.UϯM u@K=_MLH@_oQDX9]U1JE5rh\&ЃH܊-Nf}kxqFհЇ9ģ~ ǼD#kWA9C/bj u>%khy1s0]EZ gm8I<n/_{;͋=&\Wo =6;Yr|/GdPae.LV_zi|F  ^QeԪɂck&)ꅆʧPMLb 7]FH췫=#pqYXSX69?ɒ/>:)O9ץO'Wu٦z~mn<^4{LעOu `FX)EdydF.C}BڡR>f!==&$('ㅾK5=e=1a_#e>hlm]^>f*7,V,');w;pOq*= 5#X2w2 i%:]U @&Q.MyH)`kġW)L[?"MayCtWFh\*5 S^JD}NRygz #lqtV;{Z[+p|@ShL0yx>~_YXҭwLQMl߳߅y,.4(oW4R!N5Y(=eCH:@nT"8VOk u|DZ]jeY8{P'!nztā)z]E\ c`>iܪ J\V|aW ֨DᛩH.DZX5shq}cE5nelSvbjqQ1 4!g=ؐ<0^}biG5sPzR֘U=*5K{햼ݔ~hAAdbՋmкoB]d{_E2g`=`Cm; ƯkQfz<5EStkv:CLd;n݃ Roe;1+y2_|DJ,{:Nx"uj5&48GAXU=}-x:iewMsKl=)jqǐ=d~Ҟnt d}zoo4IcƧl%gI#=ɑ-SqUdlRtNMQufF+)]0\^0h -hU^ h1KhAwvu?B(U1w,-\wVM-2l'Z!Q)3`Ok+!2PVVfw({|.ACCŃ0VܺM39ɭaFUi%{Ȋ`&lKs 8gw#@R@{ǡ5ci60zD90}F*9w ?VG$0r ZqOҿUx7B`'KvK.ɑ*Z{ >E*]lcWv#/@Klk8P9oAqSp3^GdNES4s-(a@1AW6UMQZn<@3dWSKH ==}|Lwx@+NZ7ɥy?KT.hl<8}r;Sȥn r}˞vΪ?_Sͭj j1n˻;Uh-NqڠMԠchezR9|o}PsB+Y}魊\ 1(75Ka 4l;"ie#ʽVxFD9t./Y8 }#' g  DaWK afZdszyS6)İ-eZ f?_ow-\ƠV`=qmL+!6~E9pv/hOX0sKۇXх i%/$,lt=|iHS2TD>+&K.sye4wbSHw: '69@E-1k'ڔr۫ZoktghVpu06W0ոo؄!&*E Ph𪍢ߤo\po}R1{oxe5Ɉ6nj46I]B 8_C! {:7WwzB|4dAdR 2`4L^RLR?N;-lIvM{S Ab^EF>7oUv%Xf͏mtGqzk4/f qt=7- m"*2rV ELL8!]& %ΏQFR~w~ i=a QCp;X*?V\Ў'yU*AU<*0TTPpn2$Ie-W19nݹt>5܈ȵTK pw"qǭZӇt';'aHȏ7$( j6? ܷ7)4.k6agc`Lգp˦V9=@__꩸toӏW>Ͳ'NѪ6\h uYʹ~zc:FS֕(О:J!H1wɅJz(m"_HdQ-uNB,DAr?vjY(1[lf7~AgxT՝PfH! 4VߔiXM -nX>="ힹv  d/3"+fq6ay:,)3Tʜ]̓sX}[-ͧK܈[3A~&Z^vUm-DONtLؽ #{Q{yd#3m%'CYq\ӂ[b\1ՒNAP8}Ǘtl_$@G[B.~?*6\n prY0NJp5hAԯA/n+_kڢ -w#NpI?F]CǝKJ0th ɞjK&nZ'ʸ&z ZuӆO:c-D`4ָz$.nyvME $E^,DJ炢ũ!,E]gu~چ'| <ĺF\6ER6vtfl(42`s;Mj(>Id`DvTo2{;Upal%& vC_jw S 0ygC{ Svl[48[=ݻ=i3~& - -I M^Q_L TpAjq\cLfӢ%*:j,-G,,3ab~ٝ1TM6t+z>Cl<5[P=GZ5S#Hhq.ɱS;jЕцiLP'g[VטŜI <4P8K=mq+ӾjP4<~3 *wo*e"=^|z\m<ŷGg8-ތkh@Q3M:XoTJv~40VH?מU,FwcL]h"{(Գ=NOpw!%9sh\fBwΗ.(g0[C>ۧyw4Fs3[qO4>WԬ*Yga'M68|iJ|;PdD;rtQ-QaZj.1DŽUb{ 1& ׼##-/qcgp3އW)=!g%;\s~͹)3_X z⛦I ٚݗ#~iaF!7(<7/d69 Dx.hd"wgatr!8#HEqUtAytg#F% vF/oLC5*u\ G= C,%)a{0/.xc<Ž~òMHD^?mdpȢRU$D8]wU|e:Bn1ia2w1Eb$ `K+:zKr!q¹Z%G_  PތBLq(Vʸ{PhET F1&G(NM6M  -l~1Gs\(ﱵ]Dq^\ gQR!Ts̺ P#($Թߡ1Tb o$i\(r{eW}b9 ]zy#9І\FA؀ {l3YK(ѫr0;v3rvj6Ӌ$#8RHvU[pzq[j0,Z:u(Wt5F l? w7%)wp.f5q8EGU$P&݃@Ue#s`dHM+T8c)N|9Q$p[ !䥞*Q.b ?DYA"MfI%HhQ]J"~hE*K͗2sh!O ^)b@ʫ.MqYAϪK^ rJxz[`n =D$pK SU@Bhi=hEp%$f0CIPf8iJx֓`q17dZ 0hKG+(B1"ڤ@rxT'rm<;nw=Fp.asT#wpSj|({CؖCe !BPm̍0KӺvIJu& Uwg7=\rm9 uGux@a҇)Z `/b0QTy|G(RۜYzRLZVg9.- e8Gjn=N"}{YZz[͠Хx 0&( 4 [޹o˺AEҊ?2OK7//h[hFy{"_ʖ;:z;ktVk]c~ aZ/ݫlKɅ4g">*NpX?'+OBm1Ye)DTgop2Cԝ8߽+@!gZT4A5tb RU5蔋IێgfxAG(giDgF՚,vo$}6 (3fZŗakF-;gQ2t`U:~a%{z+/Tiڢ}mBZІ|潿찦mRJ'mIFa/2*{>g(d3Zr 0D҄ v, Y[@݅v|h'sB2 4M54\`UT~0qX 9FDFTQ Tg)DJV@IC00c= 'C\BX琬7חV_ E\_5LzdgoXhoRuy@lܾbڦvkUՔf@YBnbb ؔc] hG}S:X :ߣHka'w쇉#g甂`61yCKX5,2Kdn Y 1A\Ж%Aکծx ljFnBN)r Ľ|ˉ"xY$[;N/v݌ @V\B!9Գhֻ};䞬9 ?:%͹jR_n9}["9W~sRO~\s<;8lm` ބ H !U)aQ#X;ƙv1q߹RNxj^ÉExK&0.uV|zaHBT8~:X9pؚ~ľîX++o]H:)lGAExH|u*g&BC58jj&FghVLUQ2.! lB<+Mh@^Z6*]ԴY6w7hοn݂%/w/M _\a; veRҘ{|#>k_$Pٚ`TbBa'` _a'a$hpjZv[]qPSDK%0)WaWl!}n:5&&Te# OT)P*[yzj%gli}Hnl(9{`w:D\ \AÃq2ӫ)8aU `*缤Fx1$|7Ic:?!EtZzK=s{Fpg X鰎m$cbmiVAih<S0xJl䏚Msoy dsvMUN3kxl!eIqPO"y -y6N3ihK_laLQSn;Q^#N.v*)t % .&ЁkG4^ XEx!,p&mnI @z$WH: Djh ؾ>VeWody!F5z/xKU&^ד fF8flUJnCsRښRAߍ}H}N x=+`6@<9.w#9F=3ڳyA ,O-6uNu@l]!7luJP 3KV BO{oLF;{9K,=G:_GM =,{ 񼷚V"O\$s)Zpi5>1&cN%q1>y@o=-X6YyM]oIjIdm{63V* KDcbDn(L4ysP)ΛT*9rzD"]ft(B?qcEyLq=@ղOyi G i[iJP3D(dsz-b*@{Uj̡[<%Rr(H.2c,U1h.Pqs tnPzţ ` () H'l{ۋ fGS6Aw-d;s.0n->ؐ ;r)p ` _uL$,LK uHT7jm^IiNW*őd<[Yjz7 p2GlI FSo]b*Mn=p ګ&U_e}6:7:H{ nKJ/佽C" ʓ0Y nEe애y5gxQi')}Z6؏Gu>~TO.;MPu8b)>GkBZ~҄x|Q ?CPS}?"&k_4dJ=.Xao϶\ty x jM+ vJ J㨡&xkv _ߢ}Xו6EѾ*ng h`Z ຌg`#dJuiOdTu}cf@%@@3'=:/<i rӉUւ&T)kXpOB4ҪŽ2f4LZmJ㨕%1OM+I.{iFAw-C~c2rQ4juZohJ-ZR )wFmqd%mi+%!/%^,$d@(2@:b1OOK-eH; 4EZڹ5'ڳMuNۓ?{qU_5}ҐRΤ Μ^[8 IXNXw+ q~y?7JlTB99[TmvkB)X=xVK0qje wҳԠBTJQ?ܷ{K@A6(qݰLn I@G:=uinԣ?erug'Uks`( 2'070,U V=ť蟸 t)@5>~[G |9ڽ]ʟd%3]b ~,?gYZlW$8ka!bOQjB Li/M?] 3>9ݍ2,Q$dׯ3[fvyA ڝw *o2|H3U WǬO8ec Ob{77f݀^!B.ɍ]FaB1-I7DH?|+-cQ$Y$- .@Ն BY s7cTMʰ8B Oqx&*ߧȚq;o]Y<)+YFy~cg!3tl/Zl A'c,'\Ђ]s⊝S]YmTHƼI/L xஞ[LT~IJs?}U@:.\iYUƔTzV}Dz)q)W;p}y}ht~S1 \*^ekx>tN%+ FG0w7~m 3p{;⑋:Yސ99l:O-4 lhpD<"RqIuf8C>[*SuL Kf0S7NC/_(qh?HSp$Z1q3ęF ӣe3D@2H Ѿ2})l.>ݘhc [Lmż]lblmD*^&¼3|^0Nt =a #R!*?}9VnFms$Xj؀c7<M5/EC]9|wƟ́cG(Gpt@:G: a/#*,? vԊ=@ ! P}1Q)Y7W(' ` G3s߆5^dx 2g5 l.ߓF+#o*RH,dXf:eCtqE*o)D#+;%=V?f<1der.uX7SVlg-U`3+O7?ZQMtHaمHWC Bzz3/+kfx&l/tų"I Xv SM$c>W#lC}q3vT֏EM dK xmXC;B_ , ϸ1#<Sl8[X1ji97!+'qB%+$QJޞJ^{W̸oyVپ9?v uPv/VpUt8m~?+uL`w{5< 9_XeoBry!r%ܘgW4 OSZ@˕"ctU<7 ?ʆWS4#k4^9C4/a(=eMD|;"?܃e#LE#}tݟ(>6s%BnhM˰O= ngòe)u$nv#QRբH1|ZwEָq >/b џEhS ǩs" rtKsoեG롛2~&^?z~OAen}u+wu-M3oukGEOťFԍErb&|*Zz7]:"mvHϣ^pY+D~ʍA nͥP#*#?t+`dzߕ5#n㟨3UNEE $6)էSQ81"Gx6`qXF,ӫ9:|ND\ 0+ir {\.)[mHq9$ ݷAmILہCdɕ;o̗^l n%z!in=__+̶Zrzj"[-FWjdSxжZWg^zNl y*R Eqj{ڷ9/v¡`Z'Ueʇ#3ʒѱȘZN+@̰=+ߋw61>O.p+2JӠ 9H󻪖um#d!m&1B11B6~\,z:9uNSE]B/0gjSPHv{Vq3͒/R]ӆ%WCT0i7N [l @2nSjD\f}‚唆?~Rc=QʮAGV{PeF~"Hl0vfhTA @^\HBP0=h mXJ":dޱI‘kMŢc(q\  V 9ؔ4A|˗BͮkT)s GDƖ6y秥FDo{Ȭ.$Œj*}(bD✨ Xl;*˩p$՝ccu~q V%ORZ>}sQ9eIAbAݩQ r?xQn!F-ǶPh6)oUE:ew(|5&O*DmE8j"ЌryC'AK_C5LX9>Xh& ꍲ:89kDx)ܑuK􃰈-kXtE b}mH/Юd#7D8E5(cq!0Ly' y|t^ v[G_q{_IlKR}AʻtsQ-T#VM4v|ԓ 4:-{zA\8s8Vmo;uf+UXx~΃'73e41G@\rf0'}jXe̊Be&F^J~x 평?ܿ@Xwt{uKQי=plx[T/3k:h*ݘx#9h}B>XABf``#տ*Y/r9gQ巂2+kDbQb19B?rA&70HddQU鹓>2;u.wYP)w1!r'!)kmKhIu#wCA xUo6B5zR2lJ2K]t8PEBie+JiȀ)mDFds 'j`$@%:le!;2'(HIwn:X "țR L7[2A<^@kb |#2Ո?;if%0CS{6!ϥnٰP_kr]-x+B紓5RXg7K$ ܫRUM1"QȤp[k}=EI"2tdJ3]:%W_W?_a MYKb9}STw]_;\[_Bl5T \s`Xa$hM!Vš/U%TcfjT<%**'tlb0L r륢CIã} v` i܆ڄ,ĕn,{.`4:WUX vb&UTͩDJSq[.`[; ?Eѡge?}SMw׽`-:~onJa-D~ +xcޔpoldzLp-CvFg;*R^KYumaSۣaFv]U.̛=Տc+(,K"\H~®}2[Ѥ"Ȼ;P꣇Cg JY7I'ف~>Ԙߢ҈.WLt@qCjx;u'eq5 0i< rrD 6_EO%f67낈t Xw2%MnT`oPU-e2T)-[N7p=w YY MiB4]X V9CӇ +x9@5aF(3ҨdtM[c"[ybmctj+lS+kQ-[x췎L6HE0l#mӘQQvۤ "*27YN(KT:'&ہN6NǢ;?P`fM<`$/JG=Xo'“9Y&iBeֺx ^Ԗ NQlw𑾺 R YO@4cNo}4ov<z[ٺY&e%~5 pyWrS2B WrH+`~vHeDuFdXѻ~oq7K]ǫ2S:YsBբ+2*Rynj #" ,W֋P4L N#$?olDN'[vdp.sY{3Jۊu|u,Y\[0H (+/VtW?k'A !2 QCnRqjURI2 ߵk;4)zK~^䔠GXFGwH@eu2v,;V'#^7N|^.Sp9kj.ZdibZ/XKrے;Odc ۤ*䛤+Y3J힟+w "نV=ّ7vLj;c?̪<ZY%Wr%Cm[&Qٲ:f[{/WhyBAbb# )vIwy;rcTYYLjLON!^&77 ]M腖a1y(rwI.J "˯] רUsstKpyW?v@GftI&Hae";k¤gs]Xol.gR%aT#3Ύ6/~$ ӄ}Ѝ痿jLmt@4Kkp_:.ya$]56 3fFa1Jiiܐ"$n!K.ʇ;)ӿmTaGEji\z@q*EGT|Ф_g]7Ŝ^4al71{ *kAlUA$g%nqG C+O9PKOzq{E~UBfj9)1{ ̽:/>pЪ+Ok6.YUύmކaJw,{kjJZze3&zY/ѕ 056sA07_y2G4 ($ׯ!{,cka3 h4⯶@ߺ3'i=/<Nm AhEx=/jICiRJDq>mԴ.!=IxH^]䩁1Fd1l =]MeS ~l56VXvje,r0otq\GhlaEbԖ\~2%|"XBS0]`s6" uliyd'o(TZSǣdqTEs!5hW[L1u,͸ lTK;LŁa6v2-W4hx(!(݈4XVnV~"3d_iiD 3foĵ3/>BT?Ra~Qy峪!xjˇgGq$yƐ;B_T<)ug\21S՞62VqIDr'3=B ]qX2Gvo. < Q~/CeYpѻJTKW_q0я0` 'EUr 3,C_D5U ;.·yGB(xKNn\aAA8[ qsXao1k '.8qEDdM7gLRs y.ŏ6q('S G,aF F\G n4X aE^EltL_[8J }>{8Frln\G03\?XP t2@! aako*n ,o{/0zv~:jXcXuPQzmT^ybq,Y=->{>o,|K/! N: }EU'|&JPh 3"׋gh!ܐ_P p`h /ګ4uQocD1:+(0Mr^U>[䵢[8@G|'L%e 헬7HԾo*k7)PUIoмIS rN HO!ų)ZFMt$9oRwu&&Ê.6L؉JV:mO1_oU}C#x># GhEtLR:D,*mrAC~CpWs55BG]pUGLezBPM=]lj=n @ΌT'5 3L:.%7G,|νNgŞ=/`$!|9[-Cvŷé.z[Jyf:fm#9e YGEhIO1Vk'mOF~A2r#O4FJCP,{oJN蘿̞~{@wϫeHQg־օ^ޣ˾~sՓ'cH7xctɏ.p#}pJ夎86W-wmK ʯ2;UN(ix۔f};ܰW+fKlfK@g$QI2N4yM~6XA|xɲ3*ϳde'ȑǖF!AԼArB#P)Q.ߚ 9&yƻ.oOtHgbg*MY6װ0`ãeݨs&ZiBJ~gDcp2[m7 ,QkNj0hg 2f._!se;pJ"Ae (?8:ʂ@Ri}<7")ƯIdE~8&8+Jv0WVB`U\IʓT Dj1GE-Dç]F?GC D33bVZO#; "-ѣUP~W-s^w;4(+\/s+>Id;^ gI1$W|#'恛dR3n "Sh 12YqA/HcJ8j$ 1UQqzF HKY#.#˩yxIi86=DёՒN} $Zk |Q&8 \-hvmva`XSB }KD^Fx<Ѳm4If(6Ҵr0R< I|p`VC>#_f:+P}1GCT^i99Q {jhV׵[pAL$ѫqԟ(\KC]C:B$?4?a0*,:` 26Êe$_ezǕfK>Bԟ88 _Vp<;*ݤA: €)g3V"ĨA8$} oi˼hTYVTEgi^ZQ~G﨓ɗ"&[~4hzOR'+34Pf!tK/L`̗09"Einpڰ$h !%75$K(ћp)0z~ӄ!F/ӮWgO>hso$Z} ï(QkRa bʋfSS$va,̘wH)I.)ȯ. H0IeID2| I_ʎ%@56~| 8`+HX?|Gф: !SCҳBow{ǭHd['niF*:,돤 \mv 'B.JYC벀;U]m^ 0DS|M21{~$po$ R\x /ڮva֛wJu>R5_,˽jQuX2"ٱ!7zN5Ufcit` v6Vq7S ci<]zWWI5M'ꑸ.ZJ?b=BC)kT,7*Zi> ~vyD0⨙t#Q9WHn0fOb#N]9ZRzo{LX|Q|ULYP^\k֏eRX@bJaP)(?UQfL1T{ IMz|-dۯh$` i2v92V"yPulŮz\ ٪R=Ek ?; ( uc: !NN.ԹRG_&d z"j464FY;n Ǜu3ۮΑ.\2?#ǸKI~574A)dMYfcՈo~ō hDJ/Fj#<` J}5/;]6Y=pysoY'!\I- 2fdKqURqR\w)<^(/ H[# )}%7^ڵy@$RA Cr.]㐖9+,Iߜ''F5h_2 ̌#&qDKbL(N.; ʢ˒1PTIjL0(#$œG޴o`5NNi[{_z,֥l \ ˙ 4wzsTȽG0wx8J^ԖGM+pp6v'ANfqDH{JS4I2wh3O踐 e;UC͙;yT"*\S؃|YR)̼bRY, ۂ^U>C1,[~crƆ}`uQnO31IA9c&)a]xptp0(e#KE}5)u!>wo"A9lS]۹eA a봤tИ`QqZ:}--򈩶*.AY2#eY;ϝ"Bw,9*7,1^ c_#~7KkF@aEsKOūPfSc#LJvCKfk &M^7AqYҺxK 6ز|aI:j0>1< CØcJx'פqyv06v*l+bN|l DWQl/ಥ* yv8s7=`czK; '31@؂2L 3VRԼO sR3*ęաv$kuH?ǑPFZ2!OnMZŵ*=4^W]c5)fpƽtvMdYyu7vВh0I * S!R?r``_(f ֋$JKBݼٙ=}w~X ҷ75,s?e,#۴dϝ[n YT qCw&_Ñ',aD]zZt o7hFWu2*j;F}%o4>©TGn:ݰ8&GC}\^b ΙzZ0+6x˿}>GkWc >EFi hT {C2D-XᨁXcWCE:VxQ1P@F%+ʥtr6< mbٓ4gA)vl [41"oZbCCR.{!I! Y&aT5K8sP*O]l~#_5,5׼XiLMW`xeM=%{Gb/x۵F4prvʦEj(# - @d£cL SBcDlRFcL PTɇ[wclSeIn$an2kȏ!d 8S6Q\ =~b^=mɴ\:Fȵ]ҁ<jU2l0 CSj *m+Ys_&eLyw&/|m trGFW HRkWT$~/Q^sS|K|=xX6`־_'M{钸"Ǔ׺LJ1e*j`'h_}8U6&g}RC_ISGZzo8T^=ӞSHniz9m:= $[J!ˈ҆}i6Yy,_ #$=Zy}Ozlo3 :٠٪Fئ\(b6dhb~w9Q#B7o-.ol/^uj_9;9Kٺ;yOib_.K2t=JYn:Ѡ39VBJumdzɢ{ +j|:Q,'G2ROF ʠk {t> 8<@30]-jcqs=K}#1IkR$z' VW|J{νJZ{WWѹ, Bޥ4?!0]lq$ ޫ%FӦ wta~pp&)Gt2dWyq,o^ d\ O:K G:7(zHvk4XܹPWyr%&=N*=VV l 0$Q1KIAկ 2c;F2uK@d׾;g_ڇn{NdJܮZ9ب^R)Y|yIzg?Z\#{`G,\{G@xR7mG=  v}TZqbfc7|׌^C-S=ShK[W|6du0ޱGSW?̽Q?˲9=L{L~jeZQá:+UnsBs NM>^F+nl/8 hU,qհnc싿$m ƴ/; wyqoVpzXqy D$3c7*|6Iߘ;xaGW|*Nj*8&;O؎`sıH PqsKM]k >f%;TVd| F6JpAf~EgD!i|ۈ~?Ҁ8)ucp4mje ϛߤJѥ6h[p x^!rqTπ+p8Pͦ*TP&GPx T.̕5F64ŵʹlZ";`C(1 ׾{ԧN1Aϓx7QfX@&Ot 뤉Ml0ncM{}oqg1tEA 7L BcmМzpmǀ( 4F!S-3,2+RZ sm)le^W )>O}d",b}')m ɟX?cջ&Y;fwPo[aDwZ|Q(7}ESB_.On= Fݠ/>Y[B~$Q#MVKL$T]̙I=pXwΫ }X,g02H/b"ouexo%3A g}~EmSe9 €=!'Ǿ<'X* r|!v4WcyeO;[jr5 w14` ɶhwq!+=|!W:L x6Áv z3X;] fL ZϞ$98EcSK?\1  .T{T+[ŲG}`n,pOHO;(9{% ft r٤#{5Zgj 05o=v:_65ᑍnt,jfcŋNOp3 YԘkASqY@n[}irx*M#2Wڮndq`LdA m7˃GHx/hp7f  A/-ElΰcUlCl4l[Ӱ ՟¢+D:yVG)pF d@d;%22*&ON[%xGN[Do~.ÖaB"ׄ;ԚE3Q˿Qt' VzY@ 5:*j8 ZN(&Xa4E,6}{hfm7 'dhr\2vyʣ"N?1Ȃ'M3`{ݬD)vǵJO \ZWf2 n;Ρ;L}=sűkN%8>%54ȧw:B %sA56Bôn-$E&dnĺVy+ɜ`\N+a9dQ5H9M2ԡI2_/z @L ;fNA%u,A v5 mJY6[݆MINA6d@7rׂ&qOP}AV6RshNRe)ӑ8aPg=䤓o#6/ _Z^r0Yw5ߔt6:V +$E2ȟYM48fjBG\5}uld~ JJ_OG@)?܌Dn qb JTo^lD VpfI]#OKnY (Cg<+@.9j**"N-$S_:NR*)x ݠeJ$iъc)XBhj9zIQ=ñ^Xzhr&G:`#TCTuZ/PuqpBX߹gnw#߳aѨ칆alb iӔ9iwĪCz 6^c`H186v -^)i_s|Ūi\Xފ^!@@ jLMe)G%vjK Yag{i 6L?l%"We/a>eJcsN3kTGJ-DvcY>RsPd4f@ejɶ@=O]Dm}L|ďzVuO6< r䯿AsvO߈ )O!arE2ARC^[9Vԗ*)*QXҚw @&xB^8/o./Lk[Ex'>eA[T .r:;jSN/q@Ɲr6r[\,V>`բX* +`KrJ~(TNxODuǩMւ<%@4o`itW=bBК8_zo r&HrN|-|l\w@Ves\vεߺosY5Y}IU ?cA.ݔݙ$1ػ]%/^D(6;V?$1(4>"} *78sC '" !QMZKnu.ö g:OWmŮV9 﫿3IYyH#cezT뷄yeRXrcG[tE+KG@Ls~(]}ꄍM-u7MD˅_M4EG";ir[h8/mLMe`js;-ܷ+SՉxQXv~ԣT"KPIٳ,(ҭ{ aA[p&H ,e?v0t`C˿,ġB!;e ϕS EFE7 ; VzI򋽀sIr/ύz3#9?T GaIAS>XցbC~;̀5D^* 6(~}7(Чѯ=5:T ?lTMZ6oỎP~ :SF"xloC1TD ;&mfBt/YG~:HRg FQI_ 4`2==Yh!Kmm>(' JnG>+h/Yߥѡ3 W:xBpꇵ? {fCQ`TMF]xйp~ʉ\R%#`KĢʲN][%,,Y^[A^ :4#tɀ\ֲs.1<1@:< V"|!>{ojx0 R̝I `#nxJ)kT/ ڙ"I.Yɲګo" 41-0OÝMHYyn<+ 7=HlD 4k'Zvb$^H ]:h~PV:0Z'3懜9)dYeq\?!W$.WѺd%.IQ[l;akt6cO M k+ zQXX?Cn3DI7΍L D&{pTAY%u+^ÔjYK[*l/F]XmxY-+ނ(Ftصeeu]` x.ߠɰsQяH*R5:a.54>.H!sl!hEC}֭iVFS&frmZ׸4U@kTAd{ks>WDќer֬mps֪:]3Y[qKVE0@1 5Ih^ߘ4",/66 ++v"-K8؁zCI+r i v7N;;x){9׈dj)NC8`[ 0ӲHYl|o1GbG"dК"!{=2E)Ѳy&.T^Fy&W @A0J򵀹p)RBg<\QbBn0DIz[Erd,4ܧٲϱ4{p'Kiȗ)h*AcΨ<cT ѽ&kh$|=l(;̼,Z^lΉ/6id;7 A'?o}v&M,g-y@C#Q{L6RjD4؈4)4\ 0z\'2 F]5p+_K#+pFSf^rNdfִ3#nUiÇ2k׌,/]?W=l+TQSC{˔QqgMA ,wBC.cX"cy4ӰzЅ r:NpQ3(+dmT-[)yp!!g[}OWa'\hо.Z4)bԔ!,Un}Yr界u&"Lk='a-nM w]+^1K3lh CR 7@ƺGgp?){iM%q]upp_H=fhH8sY6`[oc$WU#[9H'`_,#'#/ߗ;Bđ$OzxDN1?BTJg7X'`|n/Y:Xa܋kZ~h iƥ n{iZ0uF%ML0I*ܽ/Z]@/q` x@DrĤgo&G=JFav'^\Yyޒ.#kи{&ٓWn,!pSE^[x 9qN[<: >_فΗMV@rNDBXгlrő.VI\|#]N>5vbXj>'1DZ?]p -9i:qDc6פֿ=B\xKv_zfaΚ BUV;tQ44Vw $bՇFLS]Ҥ52iHwb|I |Y2rHvL 5kl:h$Nproݬ0zYq'x8Ad"xO32w4-x_4$*vڒ17sj!]Y̵O<|T c:V! Ƞq"jceLp4 s`&Y?Ifo7+/Stn"i"ڦL齼O>hj+!dQ^Jayik듳VbޯT1 k,>Xf>'&<5z>Q)T]U%(>RsjG=)(TьBc\4s#\MU?C6._Áł{y klNxITЩ!GQ7] C\gm%U nԔ^>sض\˕{{k e#,x@5K m+H_SI4LwdߎS' .b\$摒d_lbbg yaSy%oGZ-fur#3 ȷf2_jn~+ʆgPUbFY {ѽK0oKYT8X5*OrdyNU^d)NzCGKK}L}5SPƤd|jƳUG!p.q6J4g{[h4N;|ANVou 'y˄ẸPDH%{vlswWpDL[;]MgfY2[+p8if}Xx7F/}E>Xڲ,Gf.]k%{uLP7{e0ྀ>f^.6T w;e^ _.V&ݏ"2 ۙ^NmHzNcTF  wO0;(u &euttumhZ)Lo_ [$,PM:B}ϾL6Èdr,Xw~HppCvOdaS$ u"ek4t [wݝnl,41i|'y-勽2Yc2hf vP,x)/zZ?[\3 'Z6J:^ 3X/$XS=̄NUs&5TR5Vf{|coRGZX~ z"nyFi;urI3 oۥ~.|Kp6ƅvӱFSu1 ,I;3` &iͬ#/_g'p25pQF] FnUQGWSsP #i߳:zBA[V, UGiRD?0C4,[EPhJI.ģ1/.ߗ36o"V"#dɂuҟ.3r6|po.˶+?it6e1P.23¤Ď݂5[[!i{9~~HYQxo)Cuh_$4SZ;3puYՌs,A5= ?`N|hl5+FZxu5;hwpfC{q፟uxY zf8 ۏ#$xBR4L` i҉zVRlY \[F.}氺hF[]tӾpqd(I"xs@zRBZ^ՑM;A*2+ dxa[V)8{!;&dŐT+ϴjaų{szcn걥0b|(58 ocxBXpIqk9kkG~pI[S :])@B]+ư&2qLo0ᩄ_<ƭ" +[,鼞DrÈifӑ8>gΛ%u4қ >!OXb9/[LCEl*?\qn%B6uQ4%:ߢFs+:)ہR޺aՕYp+A#!Vf2B 0N W AiM>w, /\-u*Y;yT>H˓?FpW~V"8"O=";6Y|Y]ܜNPa V MfJ huj) Vsd-y ) l6OstcC .L M/Y*q HVCGXuk!b^nV e4mH*$֟E:OFȓRD]_[yqC} z5eH (d~I$6+QBZ%-1kY-`&5Ďڴ)ȦʴwC/-(JdwcjMl'JXrA) K%zsX`lrN9^L6Ŝf/1V\N&[Hb_rǟI:۴:\yKˊƸٞq=d}Ru+N1Fnqp΄3&mDJvtÝ݁ `w!UUVHhJD NffJ [Nj@`rpIi@@}z韔>*"VXݛIxA}gCxy3$齃;$JzEz*8 0= X2LY@VPXi&hr{*S%.Py030OڎO@w[@>Hz)YxXEOE'{{ݞv=6'vCT9R;NP|"vT tf"4<|gmg\4;&-B(_c4@6z요H\gf;MeAirαg4:櫸wNB. kr4E e]eTW]= 9= i` p tdp)4bLGϤ:6ZVl-L",tзLȐdOU %A=Q2G3 +.='&v?͒/Iٳ``-q@!-aW}s4HAڠΕoSM'_~wtOЅҶ>9r2ZW\ 򔙶;;T Lm& R 9Ҥ*xZ(YGP,k tIuj9xK>[>(NeN:f_IKADnMݼjaID">JtA 8A(s&ܗ,gG؁O›A%eA0-Y7/J:Hf);ZGLE^%_ VfFhE]aRpDXC9o`;탮BwcD~9뗋,7WUɯ>@>|3m8`r^bc]́꺽6`\{ N'tk(d"OWEoꒊNJ_5I*ٴ @W8`9c \x! F(a{lw~Nm\YfO ,A@ "M/KÜ{=?e#T( A< T['bdo/'D–Kv07pjyaS'S(䴬sN_7fOk Ў|vs샂}Y;Ol6XV$\VA:>A.WYmu/v(-lsGU~l1A q0=$I!jۋjtfxb>8O %ǫF=8-CC$.2EGg& BW08A]2waoyt<>!Sis'\ϋJXGo|$U* clwSybL{/(=D],bY ̯i_y|/OF B;0:`@O'N\|0._+f>T{yQΒ vרzk_\:Bčx9"-7*սSFgs]6B @(CHswbHSbwDg.|s-(>jfԙv^`L۬jc2jtQi!qG:TPhWU~#rL,Lco 5Gy;"oS{АS_tik*CȖXHxT11*c@UnTu0y1,k3CDZAWq߮rmm<)GP8K AEsm]ZtNKI#A+[ѯf1{u)Ӎ>JiË%f5V}GI^[Dhn9 vf>BU"J"\XTd|3 ` 6Sw,̻נ"L\sܕyB˪7p'tJ3K۱+6~̑9 61duV U |>GkHX)JJ&Q[pn9*>le+ba.u!I܃A5X||}pơSg^^=6~.Ϊ*L6LϿ/45{KeO|!|$6.e}r$)yt}oq\4L*;nEĚܚtqq2>`hly_2Nx [w}̆"cFu؛ SKR֓s(x^⦙ȗxwfqg$[Xr]kJUz}XgR[)ʴs;$^• v10PFrz-l]'Wyc3k_xݱbbRn&571WZX@4z5mm[i7V}D6QgD”>R{wӦ(pnq[H _[:sz~*OiT_|9تяe{ [>eu)pj2$z~{FkK_]Zwe6I4<('rz⼇ YsJd1SuE.cU2] :/ІXTi+s4N27R @$eb@6ݦ`9RCEZW@\4dntMكoAumF&x0i9eϰ>v}F ѡWxMlSvB: ~HT)QQ *T Zɳ; K .5 93vնU1˵j k o4  h|p'~9<<,gukۡ( HҐ"&8ҐSߌ.ӻvө+!wRCmO;ZywS1#PGcǩ],l"QcN(ii-.#*ocmۚa՛I`ŗann2D9Fa$3l[L\Mk+k{UsV:-/lrxVodw-(Դ@pISqQ鄚xs{jvf*c)97o!zoa#P PI'+g_n7ߩx QqWkvk~8 oپe6Yppaɘ)\Ys"j&&=Y˒Aoޫ%mr}@:Vhy5Q:t\N2oP'+L#L' J/bXmZF l{3%)[}g~˜3k?1rFk"S2!7x}5 }x:#;tjnR[u ?R`+$ #]| %pRܪ#H䞉;DqJ("MMOu6ʝ=E9 ;69'Pɴm H?3vǗˤ8Fͷ`΃EBܴ.Vr6U>޻#Xyv ͜Zk+wn!wfhw?v5$sĥº?:}^dQ=9$م@JA^F;@Ol)(񪏓߫alEA\A5uyAcK& j d:Mѕ-~"a]j_<ⅿH@sfj?< ˎ=ItvφkO-uo{XX|STUG(ӳg\V)i_ʆSK 䘂.K뺤{_{ZL6더bb:gqB^>xrdY _-񿛺ɢBPlE4@i׊aBl΁G iLؙ 83rS6o=.dF@rfkW.rl݁gd$:CK^CuBu2A Qs*WB.>zHոJ".n dgWTHkoQp k ETYތqR&4щ]E5PCQCȟʷD 5hX@li 5̋[(2 JL2% eƧHZD#_0 H5ES6Ulue„wF-xpc/ȏ#c)1܅Cje,-JG bu^78N s1?܋YBKEitu4Nf!` 6R=S9B'rx@T 9G=[xI+ẗR:A!_cW-b ։쉺 ȄdxFdZc管~m۷|rYuclbnZ[wϣ\.i6Ὓ2OF*2=Y⛺akUX6cCJJf譒كxyqvh:8᱉V6JN/+T/zh컮2X)"$!-+ʳEYOcMQ01SV+Dޯ)ʞ(ՠyƜhZq"`gCbzJ}4"$TQQK.=s`ks 2%&{i?VBG"'?,r y>5<#᪏ XB4_Uw^ J)cu89hkǂ6 @ڭIGgXvwEP2 i&AY\@hnP$BN"yr?Zt x;P8ھ{ `H29"ݲ`/x߇Qd.k*jKX06sQ\$^"Yn.r{F)%VJXgдXRDNomŇ$Ype(ʓRΣMҨ Ccg^~$.`RE'R-ȑPGȏ@Y6DqrZKyo1" >MN܀!'bF'xK=]+A={~w h ltg$%; r츓6@3g@{VaGvHȏgO#YgZ0!i-hyUFTI# &XmwU >9,ŵK2Wn#M[WM5vҾpDp-?4.ZH5@pv{}8'9aunF$uUf`- [o! N2`{?BFZDT/:y',ƅXw#]\բ-E\W/<ǃ\Tw m[걯T|]TnSup墐 &z'a / ljB-أT%kXZEil1tj/DZ3kMV-ΝYcyE4cvnSR*cgO Ri`E,œL/fd74bO%&0 "xhm=uXrחJ' 1pPHɣ=!O,!j;zL㨜1i 1o"pT'}1G^#01iGga9m^6}9P8m?tW86*E4A-1 ,G*xA giMs}Lr;>a U‹$P 9C6t3'n*̰ B4ս d`/vv42@ \P2hY/#7@gJ*>Ms3 ݊kʕ4 w&zecY2'L65yUCQ'g\nvU24Ur4PFqYLʑQ:SnZD{pgȷmuvS;}ƺۘb:> r@_fmsY(Tj1L C;R _t.;uYuY!f֥<\ @_P5`m"k>;H@C:TT\ ›&7 ~'4MsTwvȳn2K?۽367$« ErKzFHVfXD%L ۰J!mñS莮Q'd;=TFdu| XU<srpG%"z.Z#]l"cGƚU Ϧd H8D?׆sBJژ,z TDy Uu9b^7ݺxe ]Db@ 'hK]._] ,p&R(I7^Nџ J,{+#5"xK9nX5k <;?JX" ikC}G*;gOZ3Wn܏留eY"IQ{CGCTdZl臕Fe C#Kv( :CU`lvuI V+ҷ%@.6!ծYhGRr~TL=UOoT7\Ȫ: )lMv= HTvwvW5AF3V8fxz1}tK\6H4t7qE[Aڃay M˝g˳CL:WP/^mŘ {a ߛӊoujߗ.q :'$Ǵޕ{;s)o6)-YִP =Hq2~ni1dlMc^b0!P4t% eG˂ ̰TS{ˊ{y4vwo_8g:;RrM@~ϓ;fwkA|iR&)ʅ 8` _uPjv§oϐï\ EL* iH ytz:/ok3][X*KBJT|c?e97(ך6_⑥6!䎭i\Q,}&.)ĿJ|ՙRϾߚ >ĦpX'n|*("AOUU|1?=Gir Nx,s:8l ptkљFCM~D aaҿ5Y՝ _&{Z|V4۫A.(8(cvϴ- VD zܘ`z== "?D,23a H|UAcIQZp}34 dDlnЂxB/(I(ӓM{ܝj#JyK;h j0x%W= k4g5!oGzbnr1ݷ(D>b пq?/sJ/aY$r;b=q ?cXOX'缃:رLPtUpmwx#kƲ 2aZˆw;mT 1= (Q0BaC͙(c6tM琔L71Fj ԱrL'^urp޻9:=H`^gadTZv Uzԃu>5; KG63$HRN kѯء Wٞ#`ʥʆ^v2CEVU}K 6Amԛuy2D u9^r"a:3kkSiR접&~a,q(\EQ麨l(Ev,d CKʦ] F pW&LPd(mFraB713q%[ˠi2 y[H*O.a:"Z2cC97@ޤ š,ؖDނ_`A! L&by#k!My?WXLS~*% buf"(-w`t5C^}ظ+Z~};o3>@Eu0cGοtVH:*B1tLYEj C5fN^Zk>B`_3+>}1z;^4U "ILOsʈ+Pڮ;;bdv *ho |Yy|}X=#]j_9X[7lѸ'SƻC eNi`DҖb8]Hl1"2H$2+ 簷:ͣUX@_ b}) /:>ey LB|卾'>,1?*ӻs 'Z_y1-qYL|$*C 8Ôr}Zn KiTGq]覝lCi ӳh"0`WamKYI'ߧY-9<ةڠ3"R~ C6zڠ=Fmǹk@͸(\y\ -92k9s0BbT~"+YObOFA#Y=?!Lm!Ealt;խ10SweMĀH,1b릲gP MTN9*(NԑjDfIHU/  D@jƽLq ^]/Jcf +Grw?.F6SmlCdcY0 ,>Ε2,_ Mv~V_v6Z!H:"e+Hqo,fSz=]ҕ;'px}Mِ6xA43`-M=^CuQmm=2!t0 ƄT-Zw!U$ nYjx?w Kg_ai%\<̕>&\^zH#rIu8.":8i1 R$x&o)y I$?J':>PS4)󻩤щ[ ZS>U9;ր~uEs!%Nd"+TcLtz:z`ߔI'C0jW(lcS|{%8pM*'̋^lw3d|txk whNAOv8-Jv.4Fjgɪrק1CWLGKW^JBug]. MJ`ӝ1K gK.S7@FR {~Nsyr@O|@Zڭ9Dk*}VC\V(!&}ːK;~7dÇI.j4_n /[QmXlD(r-x̋xYEs(Uޗ<E3IIB "n u9?(>ƌ{ 3nEmpL٪S$߀|%k0 l2R԰nfdsnL ]̆O?&ޅ1;iW4$^(); O7in &nq- K>J՗ȋ82VJbP(NW@BaunPoQG23,W0Mi :@ CQq@+"+pp@cyk"E`&\ܑK4w$q ֎JHod}ʱ2a.gm/ $躀/bXQ {7xk6(.1 yY@iٙZ@ͪŊ'p,mOrP5rl^ @{X` ڬ,Va1¹{dMꋪh)h8qv! }& "H XLz%\0KчP[ᵉ. `?AeU[-PIrV Wɐns@=⫴C˄E4+><,YbSޡEu!GuN}GwIJmIBM{ҽ8ې+6I_/֧$v#jѕj( ǒqMbu\#PٶlOuѝNh\e ŨO'΢rD )9.sΥOWd<$фdqntGp"CDU A?71ΐ(K XKI] wnC|1L SX_\TAϑ*3'sU%Fnw7 ڸ~:vݼCM~Ϩ [??9wh2{uSXa* րʂfE>DL պ]nݣZjDÌfV zվy'\dJ0[R ^O_ye4DB$|Ķ!ǧ8`1#&<h,.dT%4/}iSr2zk1xI:t!RQLtgʻJmѩKǒ@\aX 6iuqQRy?x L-R]gvtOԨC)SLZO5FlP)EE2d=C6ͭ`'@97_cjJ1 5ԯ^jMɏʌSǖBeP߽!%m8wd'YгF:_ :Ϡ }ر.&~I񰷸gK|#;\_ps=vsamELVٲ~)VFfQ` hE rQ*QERx/_u\>y#DĜFJ0K1RVH@v5`1`YJ9B 1W5|ky௯qI)H¬kjcq 9e2kI<äŒEWTEJك>+%ELE ݜ7;0ț0bdqC{; ҇(\!|a El;~yH?"\ o<lY5trV\B鏺`p"qk}:}fo]sKk9ے'tK_+܃1U^U l̃QVR CR4-^.zq${S64J|ڏp'.J@5.a17\gcnB`lUh.ab>L BXh )*)jQ+P,uF=/VP )iw Ow*PtpGIFm۠WЫn;DB=snL.XV$M-^1VXdƯ us+{+(e+cqnE &Bu76]NK0W90_1utsW۲B%y~qj,J,y^"n2fWGĠk:)K]lX淚q`;0p`M-]zF?; f?) :k`?G)ciwA~Z%$+􂠽PPohGzl̻-.){Vy-˄S44-s} }IĹ}Z'_oC7RQdogpdPvcL߶#aqKã4Es"qFOA$Qv`%\LTSoCplձb# ̓'hf1$sy 9!/1g"e-5b blp}`(W DKտzWRCG (HSnFٰs0TuǫejSr 6·T_]ܾp_&2qJzCȨwsh}PƗvbaRY>XxM;SH"MC:+]#!GHl f^9vO볢i0.]t:Yk𶸓خ\m3,Wnf61IaB[/TWQMa8 !n?&CA;db,i.EXh[dG:foDKW;j6qV8 V_#4)/%tiHyZ6ף͏)jˍ@8gq` CpQ䗈ۗ!i|V"c0a`uCRgbǒTC"W @E+ם|B1{ccFKk,'VH| Pd#[>]<5IHMM$g.Ge8,p2KbDk>[HJ5%`Ms-Ex ݭb FMOGr8E o$%v'5c5#% eG=3enl%tE-=-~<% F6 7aRI+Fg1oNO>/am*B<"P`(3:1 9=2aS ±$rӣ ;th>Zp\=Z_+`ψ<]nu aHxR[}s.A?<\*%5⣚*H1]"NJ>ng{)gKdbA>;}Uil,; tfͶ,HQaka<&@d->c_!lf)onOcz :}FvH'(KiZ߅ gun:, yUwiƑ*=ƫsdf),d9جIrb7vkWi 5b3cWq7'\W&pT8쬮0JZyJW7ѷ;ϙh@(:& iZC0gw#g`O m= ?$;Xs՞ʇjn*qZ߀bpe#Jw(+YnM(w;0}II|ijWUK5 _P#^mJƱ; '4H=j̥/b/>ގ +Z$ M3̠c[DheO)yG^SyTh6uy2ټgg4 f p,aYfެm* O$95!_qk/F&K[Yß(ʟf( %vƣ=<#'<ڎ2CGL]DZD ѲpL2_(0&RcNoª'+}0`>{(m/,"l/o{):;;)Eû)X2.ܒ%[a/}HrYs82BdE {dL7A{㩟 >iE0w3*\Ec=~s bfPx-rh *8 D܄R u\x)ayIՕT)6D"t/s;lSAJνK&XTo1^x [FRZ7@1%@IVqpsEZ"5TlBU*g ԙy aDU{9:L&$гg:%"/E4L֫cz?zc+x(4JwZsnV`4L0o+x!d~zDtifJ˝d=.rcLH`N 6u\ -{Ld~ބ,=ٵVェ=ѐv\(`fD`MW 崷O,zع:fhtJt3`=l, =SIٓ21 nj-qG81N^ .pF@v2v k84@ro=%Az͊' TRm_R˦E>H͇(]ư3bK\QU h;2 Wf!4ZĒ9Mx@ydGxhaћf LO2p1di5HwҞbn6?%)$~z) Dו|l8/ihg}L}P|iLo!xϰ)I˲=qeυbC x?64t~SO` )+cp=K2 B ӟlհA@|cq^T%sJ ,Nޙnl/kWRښ+~4YDBwpn]ڡ_]V >^X}aDȅ֔pSD5|p"S$$)7bǏm,m;toWγZE\# (ڧ&Wm@Gs3{EG1PƗṢ~ȣ{[; ^&&܊4w|r5 *J=JGL5&4bō"eۍFcLJG(8fGC7^xGkAa\4/e2VIdUjh PD^q*) !69xk[:ͥöӂ ݛӥ#D9tr<KAWO jS+\<+]2Hĕ|׎jDzAYٳwӕB Y*8UDb1TS}nL=5w_%6xQ,c@@҈(-!GMO5%ڪq—U<>B TW̚y㡊W-ڴRZ]S-"7FvY3C;O'*݁l+3bE%_z;-sWn(T+7P?jaœgtҿ`/{|,QʱΌhk; !XEVfz  5uiƐ%adu3iX_Z MJ:T4,j-(Mř)U=:@Hr Ô/2d7=bCL%/g!#1C֥ʯ1ɘu <$gI7i}<,):in/8eUHaS ,)Vr!Yjɥp0cH2EWM IGqs `iviYM WqZ<ZIߌs@q ⚰.R."S ΄) k-[2~[{Ũ(R[dٰ?DToWc`Ќ7 [S 61 ]ok^Q3йE]re‡T牎_%4<a}{ٽ tGFmA{R)b zx!MalFf-`@q2@>G!ۡLmwß@m>G|ر^b@ >*.u'<2,1NTB(9MPzU~LxF+TO?HAi\iY 4%>~5fE%j3*V0?xVZaIX?g'dؼa2Eʺ'u2 U]4gnM-D)SdbK@ e|T߁DB;Tؠ!0?ۛѭ/HZAy^(>V?eU8ʼ Um`U)6ͬ;f|Co!> vU YPmt[䝄@EF:U$'LJiewI4Hr<9PueIF 0XF/{焮`]?l43,OeT\/R4v_0hxWFvT¾KK/ŬZ~>v}+w; HH^yNޘ_=!(XT_ck:M C7f8 2Ф(,0Uf+E z3Vx Մ=pͺMgk{nzM%+(qqIL4sH@ۯiS'eeAv!{*o@^Grm^zuŐfc11JttըP#P틼|)FN51 KhHud:0ulW M΂H㘾I' TWY}냢wSxחKko!d>6Kp}!H8׶_Ro*dEQl=X,+)u۹}ߤ ,r,o(T]IrHʛ_a/k}pWyT=GL~T97%(4"TPj"YSEqݔ<73~dh>*Hi*Ų{&c|jdUV$;j8$ heB~ΨA܉nSSB8k[6f*ddCc~ۈ_gnFp?+6oS¥Q*22lS C$duAC:J@MVv?^b;aڊmr{=CTZ,{fwʮ!Loɴ lMKuٖeU> PtjaVfKSD|x0nP*HHp0LaEEea}|t0cr/ XA!JNJfXJGEg@hV0$-ӽTu߳6) m h BHGzXt}۠˹ xmi&) {t/*vfȹM2)ˉB>7_%#3c6z3o e72eK@]*_߂wtX5G{Ž\ԻȖێf!sg }Vz՗"PM#4c?QZ_cs8װ֓3rD2VBm^OLBHf4Lf?x@2U} s's!k^J1F]x:Y:]*D#X7EYHea2ʢent0yNIB<ѓ-+@6D%%jw.c-~%hm9Kɖ*R4F~d% ︀KO 6-XZAꫂS"h@İIg!|? w,>zH2WF?x,]嘼V Az`b)k^$tGҎ5YεjN-<_e [wv.bLq {rgD ȢJaA)/4dٿM4 Yܰ]tU4oXWcydŴg7AwITJ˺<4y?󆙁= +(eֱ^Vt+PG *;܌U%cysB!ʁmy gXˌ?FOI 4@>è̆y3LV-ЅD@KFڃ !"$3*T= tP:!(jzd~^WM1~lsc(qqfazX67yoY6TmqRW+(2ws򫇼 &`=)A GU/h?$D_ eISY B -aKݝJϏF2NT\\J31;Z} }Ҟ .G+(r*WWpRJR{ f2%Ҍqk5XVjfw.hQX:`\ \eѣ)x~0,wI&W]|U^ d(&t+uWM+Bug, 1Д92l_*TGm F /F ŗaM8׻J6jSإFni_t»7Tk~r3"Y?;>#&fz+%,sPEz_C#\Zkݤ|חt$}դt!83_sbwn+FxH Bdzd!0Ϙ.z,nLd|5 -dYt{/J=FaK_z\ބ]sz&ڨuLH\L]ToǝfCܑ*A],2q٬9:aIK8Z#_Av/;SJLmdL^󎚟m*ӪnWjSU0f6U(ari(b |v s2gqAol > ܈;[|ƑMq{||r;!G$8Sj+EQd3^& -Q8nK_I},\6^/9Zzo~WbL;l1lԵ31r)̒\&vFUu ;p+/*MYϒF|ZRQ 'la5^GF.?5Rl {ډ?Ӂlx)hhMBGJ28*&/*:OY9 \r cv7Zꭔ^w[B3\8͟Ak$ҒTO>#VT,ȌX[Zc/9ٶ!ήn*jk֕ye??Do'^ TNY_GC 2;NswvT uV fr8 Aw{N׳%CXID ӗ8U꽘a".Ry K\gE5{hxsUv j`r0 9p-gmUV $ ~*rk@(`76x;陞׮ J_1,{ע|v(*5r/ڢt0@CNJخFk0/:}LSLR^M@LO0ԕcûL*eV}QCWGHV<(pS]6y۬wf6{ϻ,QJ EZx2ii8Mi ֖}*@EfZ^@0Q50Uu, ~ԯ$}3W!jnVwTVnsICv yV`ӄUQUp ` d>?E>\-?zII0wkoJ6@HvD'w?F8+i^B Wv@W\1Kݝ+i[T%  IKA-@,s;J(J5CO"ꙵu2ʋm,-hC1GvHFnu kgiٶ%s^tK"A A}l66e>PzMWMoe0쎋4* /t^z/*1%yMU, oFj{qF,W/[ӑ,007 zzwJ>sɝE~3)wJމO1 }5 D{t@4fk>nN[EAS#L]碵0ĕkQ{怘!SihډHC w\_~bOMjcaNR|8j?R7 lcr̥ ljNU);N^0a_N|#@PTɢ5`Ϸ}QMçqwaoWyl0Y`eIZŽqAPpuRu~$n @0NUۗ& l+Q\(- k1z26thXJ֬w(VbBXpĞ`)FvrDdJ1o ԅN 1$^h9x)Ĺa61anDWw7b\pkTCO0sWz{!M͔P@R|kw.t$Ij~M;JP.4CI}5f|Yݼ"b*5sZs1N+l'M/sK @mBa_Qqƅf#vA.|U6&7%BVmϚXʍqީׅC@Z_*F!~EgT+P9mNZ `^zui# /%Ztyj; rQA=̍!Clr!bԞVcky642*mc0 `PMDw?ǁxLB2M3͸WW] r}kv N%H :a6U#7O9`e-T!m\᧬}LARwo;TyJ`faJ`T;m^T:E.j)9wg"odL2>{h2L >I$#*ECPg\0MPDZqƘ&wYrY8A2 h$nm!#LAu.3Хs]WK/Vˇ_/`_<")} e$/΍iY$nr |BdpfcFl<:c!D:Eڰ@$'% "%}up*x4 ^:@YR Wc-"rQBM5;[B%@#v}g\=3k 3t-,?̱4iU':;m%ha1rS-5 BUJ<#疀y|Ϫg~ů3َFUGtmlbIB*FmoظLufu.Iiϱiq=jCu4تW`*r)Ll@"a3J.|RhQ(^OxRklHy?[0@[3V` PhW[/q V-'qbF$.a{'nB N(۵8NylS4!XU~¥BusP7pv ?Ts?_,IY4 f1_bs NȇSt&* !R(^v`f %x~]8[aݮ*)2VB4nDPɨ Ve=KkRi]|OV\ʼ3ߏ[rDN:|:qҸrk1XL^&ux_27)O9i\P5C*'Ή;"l!רT*XhN 璃?AZژ+۾~`D+$#2o{狀kCWgJ)J8S-%V^~$WKa tVo"AngQSY=x6L8GhXz%]p6񏌗@ ^]yleFm-ic0 U08$3ChVsA bBNv 5}%}jôP%,;bMM:3*蓔G{cѿˈG18`ցT2XuZh֝ԒFqr ;4Yv {+ ?pgH0B4) Ja_Em:)3 yp-Ruuxt#U|n Y"F/\k "QG +nS)@KY҇!,Vx`I<! ump,Lo|A*%c`j-> u\Z\hLEڬ=%0AIRHWFۈƶ$vupOjcȂv)Q`vw=g0UT'cbؖ!OF%"ES|Q,L;oDmb Q^VuQ4YFnDoő-\f5mdxx+j&7q2DZ@xB?иUA]tc9|b{ȔgWT LT22~s6ld>}`G}i44i?Mhs$Ons4>S%WEL`h'zV<4wvĸ"PApzRZW NIc- afR L|OH{A7ɌІ"DJ*r>aYo}AWI9ۖ``zw"ggxԲȀTh ^؁|kMߣ0~' $jf΄;cJ `P B(rJlb_lq|{H~6c&fsf"QW9Yd dfa/i.+.w@:g.wvFw߾1: BGڛ2't_܆.O/4.0[&ԩgQYJ JUG GtpQ2@kqHvvsx#Ngy[Pl0[NFM{r/w)?Z )wKІլuue{K_j'sƳ]' kF;6#- GX unmmbq{x~1o@!̀|ah Pl`c~Ⱦtw/+[\F<{hcW짶n=3NׁH#fȑz\rJ$,5Bi}-+s+o T~@EXwȾ#Wy'+hy':OIKΌAƌI넨7-<.N0}H!c]R6>[m%QL/ycrQ~J̈JSϑB ai{-U.PiljcyL@MeQYV8WՔ72@Ǎ7L:nobSi* "i͍iV hcxe9P{ 7>s6z3Ó>7Zwy!}E;&X{^K{շ HA%l'| b*t/rsS Du4!S_.{foas*t50>#B3rrQSF}XN PtA>8Ϯ\tu#~dw/:ki^yŵkA(硥DHf:/ö5x CMwсA}:&0G72?Zt-;,qg`H?KĐ.( ?5{1NvH+nV?έ]QkQ\ Շz|i LhX#FGC7䭑o\dфw jʵѩB HS, `àg|OM":;zi՚sGP0v8G)', ~pLdS]8![Xahj~s`7GHnY:"Q-Vl]∑TIJTZCU{]Fn̙6/ u.I8zl}Gߜ\_&}}h䕪_/jZ}eXqu*9*lu+nm"R6q<ܢ)KЊa$oq&.n>D/RY{x*;\ԑ b3GҒX/Ӊf~! LTf۽h2ͥDމf޻@ok>˜&eSZw+CIbfzܼ\Nv}31[j . = J e  :yt4J zW+s^3h~ĵ5|5<Ԃ\-Ɖ.^:z}0#*Ǩ.Ɖ2x#l}O)7Ht*}SZ!KP-b[ ГU*=F&iNϖu4\;oXP{PK*X^0MnyEo 1kPg=B cr{Ce`7{':Lyf/ؠƸ5Ь)xkzK8JT,`GGRFusά$òKw ,ZHeX/ "p#kjxeTێc.Z7HhlQ6/$?VRf&o9?+f0U1E̔#T Txc ffQ]Cmet#v1l~ uF6~a@ׇu$ڸ` w*.Y񴁨8h )̜0;85^̧tWxM\A~>WQX5*>-`Fs:}& =74B/ ö|#F6PF6st rDĐq m0ASP0ƃe rM |YEh5jx] ƸriADE/(d uA|=V^fGppxpXo^ $T/P@^?YNo ߚO tzGM"&Ǩ 1ʼn?O!j<w;Q>Ĕ`1g"{"lloTW%e&t~(kD0;eL%n|F.NGyFR9- טT5yPAl0^ !RRx|Y"Wx3 ؇﷕+ͽ ޽$LX(0Av0^Oe/Vs%- @ʁiH/&e(7 zz&4+iM7*bjpحW/hb3.8% DŽXxYX8ʹEq:O *S.˨~h;qIn +@"v]pasX] 0KPRy6?3UgUpp 0PNOspcjĒ_q^ r Ž~n5k=pE!>}dlBQ5Q+YD\m4]"݃c!]#/ Ҩ&C41C"?y:燱׀ pRC"`GO !{e1Ҿ;Fb6:M1q&c/G"/MWR.rEg"a[ `'fiGmYݹl|Ѓ95W ̟2ֆ3M,8myqaQ:\0MU(uƊ9w>:715a(-O% +=zmKeTq,iVv9cN[> tyAi;CcJ.s)Ѥ)-@s.$2'ڟo9v stTrsG"+$A*Os\#eTk@d{`&G>7@R~]߹DLss_Mz/O|z% 7d8n#Qzh޹sO@~e05d7e#L8ѾR!yJWJ-n;JKOPڇ1@]YapYmX΀A5͛\JpI;h&*3(pz-:Y.w?䚝<Ѐ$D94vV[; hH~PG&J0|:Ռ"ȠTպcVNZlm%NA!ajGCeT8EfαEwMb'an0d@V*5>eC}LķH!BLk:9i`&quhw&u(9Yr }h?eHQnB'}sCUY7OČhbRCMY .!@a C /U:EBֶq#%S}#6CsψN='z8m'ZY1ud-JcuVWJf@(5ƥdf/ `Kq݌/fHBl"hP6p7΂GYsC~n X?Nt+eibLqL-, ɴߨ0JD2ho&zZlO^Sjxyjm~y@/nŃz3W˜)&Q'P L]}hZP$d-=BA JJgv<./}~(8(ߏS 9 尰Q^ץczP hP-S@Dnqq$Ӯ<wu-1P 1Y:8q&9PUo c8o0 ߆44m2IZtt7m3Vtb#fg_ ῿fȾERWp&D@(1N2%W6oRsï։GyU#^YyL81r>&L2^9>,bV($+'mϻUL)A>:.Z*Z1ȃ~S(Lz^9؆>.&0ZTS%^Xjh蕁!&lR7N j<{1Vx#&3Rf:}\La$',Թ>(ǤKg8Dxb<;/hW B^}3t(yP=(;k`t6`N",]2`w \sM~ZNTuCfNHB 'I|A{:g <+@ԙB =1]e=:*8oC7!"ԋI*@B\.V`vʥIQ\t>JKTKqX[RbK!-.ߦ׋̙7%m2>:G@D~E6-LyPrL_0'ݼ>˜:OL*5U9ֿˁM$` U0'OV ՘B&a_XNBB*h}C _$]g !?rC 6`Ɖ+%;JpfԄYю ny[˽oRkEHv~5Dmx~B*1K0PbQ V[=XaSt'\ B n3X QG`\GoA-I'A<|9rB;uSn]VAQlӃIkP|ZmD#foRJRws~Zg`ٙ3>!1R4_Cn'rDF,N\q/k6sEF}1V֓erEz;o9?%BP%{`f>9%j̲UB#Rx MQ8knjA˓alҔ_9ѽ!| k@Vfpu&Wl!cc#_U7Y֌ 6_[,PlxEEy K-jnb9tGShmƊ%RǣgMF?h:z/.rGL@v+5M3ŝQ⬯nzT`qr-Ȉ ԝ{oC Nښۋå3;& N\™]_")@3 9eGc{P05m1 nGƒP#ZE&䤅RU_iNffl. qͯ9}hx`XU lO׃d$8,Z?G˂A)l ttoi)Sy cKԦbpB3)%@}8,o' [oywZR|߂@XW;Vui 3tgHT ]gkTaXll:a ´$-Z[I #d%2WR"ȏJ`ҌOBb>;Q4oځJI:*`M81P,ٯN(C( .z$*>*d/ s~=%+&eMt1jӸfd!m!R:z~Ԓi/O{Uec] WSKݰXCQD_acT0Is?!/um8xxdպ2=hQ`uؐeFC?lI튌w7z317H4c TcR޹ ˶cn`!ZuyTfR7Idf d:7xb[pA" wj+Vj:{v7@:5/Nphk_eG!O_Gh=gG$74OQ@% 0 hp2M-ȂWN!f"U(/ =]Q=VP{kWTv{Z箤HT|bqF\V9'y8R׌U°?KQ՟LYOIr vے,0"&'ݿ/qM&> Gc-<-l1m@rKȶp)ywa`~pԋ<8F(".sŹ|pڽ=־;0L H hA> |._1_ _ W/zoWg%,{Dk 8W0@~x C=U; s@AQ6>w.Ve2,0&Ɣ т#P.1q])VD*ft#Ҹ]s&ZN@$n ${\yݷ_8=TǔIJD= KR$ G0+ 9f%b7x{F9 ^ߥڱd k-[0pAE44hu1{-ؓdb\S:GTS+`Ĝ /OFnmvs~qjX}~Q:K+ၮKL#[{-Y0gR!=FKP'^{iNX<٨N?S$#]8@4ki}! g[$хmoR${Q3> dGKۨ3uQ[m Dc0d @^I^ڦc8l&P1hw'SQԂ'bQJi2s_ SrLWlXx=Z[} ֡VNwc Ʉ?kM!ޱϫRC]M4*x#JseRbs6)_k3:x\B ^0$n]rSosﭞkIî'AzE:$Go&s;u5Yr:0>,W]WsvsP} R$3aG7uLo BGnȣQR#eCJ߈Ejln'x3>:Os9S-xyVYq an Ƙ.~(D*i%_[ wV+o 1?Zku:@8UA=ґz^=U/ؤzLJ!DEb=h?k5,;jJޕ(ܗ|{Mo?\#oD=)_6W0=%f v FP*1ƛC0(Ew͖/ OِUC˳ƏC@7N>Iޒt3hEuau8u{lۥ[\W&^#@o_-/?һ0^vH , IANK wPM٦j˴Nݕ nuHf_Ia/cuȝ5?g4%gez"j_>5'n-' VF1QAKEkU$'-Y脀`Zw2аXDU6Ɩױex{8δ  n᪬ֈޘIKqAtã-'1CQQuU=OJ*˛sn 9t*!DXtj^<бYNئ-|D!yx[^iI1l2fd-dmFv_7j>`&C1XBm"82vL+3.a*kwJ^9lP/}Cs]!)kwT 3nLŏ Zsx")f_KL }X>@}@lyl7{umڤQS2~; a9ٷ*2yNVr*> LG3R^~n}WT`G@rloO: , @E3)lF]=`5g8"}7y8jIӭEWs*77 5u>Px@gڀNBYp1(TYFG\Xá+Tm땗bb[ash!>'j)>i0M&8PT0w(A}C7pf=ݵݐ(߬36KKC +֐1 9f{^e,|zOly0a o_#3{O\H>{n솳/5ҦhR}|5iu(DX{ca1n $vbriMX!!k|DVQJȊhW f(r"-Ns_I,iAĴ1̴+Mz}HN1P*#/$GF= EqTOD#L.nqXF8d܃YaY%ߡkSu`ܶ=B1S{oS`^%A5ws|XBisJT]rvDII^*|Ρ>PP&V7d1ήnkƐbdK,n͒F HfN!$vIJFrgV#'#Wv%^uJٟצ޷Fc'm.Q(.mAH'АD_Quם-Zsƹح,lsť(1jaTLnV44w+;9$l':-b #|P@6s}%0'Ԥl먾eRn,vKMjDdxή!RTʀ[cY$a/Edf|DZbΡr[0E<|lv`7P-[H&pD Y)[UoROJ8lֵ\Gҗq~rOhZFKH-?DZ7tZcٌ =7MԖvnNm=!<"* uQnI8iھG̺, Ujj: zqiŇrءaV(y8h2{&|o$/UɁ4 T'8.5$_4()/sKG/3ȮɀeGK2 ѿ6Lf<jfTԕUq꟯БmWDbԈRVICOi,ϳ0F1AuB(f t L,X*5xvaBC.N]o+'c@UW1(>Aۖoݷ%WV ZsܤJl1ǐtoa͛98g/_8 :a QhFq?2hcUDrPE$wV8]4%woRݰ~lIg3gGpiҾr8mÇ:γHZoJ['U\G@u` WM U`5 #e里AHuD #8-8yԦ VawVF+GuXMJ2xm>+jw:fHn`nz`ݷ:$wROiQQ~âѕx.h׊ampp{{$l`4ٙ~bIV-֠_xS@ogcL ek~zh$X\xL}GENr7A$Ja2Z'L_)fF iΛX ƶs\ˬAjYZ"5ޑ|~B;?{v|޸ťb_ڈԇ3,. _хy,2" @fq<7OqdJS`//yj6*&}?}[qMl,ʞ|"uI&nRfs"acGQq;1H"[("~Šk I|pIƳ3sJ:t/Gߴp#.QNvsLȎg5âB2f Lle /ò]$knwàKY5mF" UNO i )A_97T ]/OÖ62(%8p`0Lrg:tuƤ;&Њ{zftXO E쇽-ߠYm,jjm!!ݻ B9t'}G_{%5H9rd:m(0D>^ZRNd"ͮn=?#{?źČD$Wxv[C$ ݧcK%tqONS5 8ʥZUu?Dm vI7KO`z 3;dstg1헿"`2͏^P?b톝3IOjtp&q=9nȹ^V)bpp.Du@.'e t KݠKKJE]Rd02HțЕ6A^5'a%2]-Ĺ!_Is©/Rmx WY%'aP|"u&f-W_%gkiW omڪIg AM|pw4Bh@90 A/-IǣVkU-9[gc,js</:/A/#}K/崡Qz .) ifH=C ڮ"%'R,a;QuX'!>M햅 bUL#>P~ ͧBX-Tb.L͜<|[ڄ6Wo zZ>;.NWMgWuMT&jW'ΜuѢCMVw['ri{灝_̍Y_`9Ib,vgl}5KfQ6Y/+mDP:/# H_O*^ UG3sBbȊWbuVFvP'Ԛ%lrD\$I|Ch-A\ڻp0׋t-_0BQ*aZ`$,Mw4!.:2,1cLy"Jg7, VYwvQSE 3LإV;%ApH2Cp06ڨ{K8wNoq6L" N rF"x$G/c<٣u"}]dάjsfł^UrAJWgi~e)݈(940HM(v (:COtd2(-)&;ohGP[jϙQ_P`|̤*DJ%RLJ1) A Ҳ=($B(!\niQ0mZ(5p>sbm D!3A]@nK# RL7)w] ;yic&`F]l?WE V+ʍawE{cu&ƒØ馷aҥٟ.f6'٣oMgxJz7̏Ok}NBR_\2XtZ$}kUwwtsA=ZBT FBP2o?)=> xc9Vzб_ht]>i}C/:h[A>aCP7@SB\, +L'Ѿ!48'#EOڄ. M* ZVXT'} 8S9B?+j~&+z \mzZGNQ{a*<ƥz [huU&*vb cfy}؜tpTghXma9D?}⑔P*7^KOW Ev&IV$?Ah%R"_Zg`b~|YM,'8aY)9 ֖*"4Veb%Kx|2hMy\*9NFYB2vI}!gQgwnrz? IJp Gſ+EM7LKTi=HbRGdTB=|iGSQrج˪<$ɈA1bZթ\ޛiaͬ1ʫkȴ~V;QJLdtzmo}TSx&;EvF i05]R-u)eSWlwˬܸad^nG뗫ϬEvd!yКnVVL@ )zA 椬w$<3`@ݕ,l; #&fj1 ^!N$0ٛk Oa_CV#R3#"LjlGq:tD$9; |?踕 _ݪ\N=8'䁫 ٱ:WghI*lARk^rq!`,N*=˃L{"87Dڢw{ĮЕ+,g8,Φބv0- P\2)ruqf=ԟ/IAϽk! pܴb)̇]jwV ;%)0ȳ6oοj9w3xi[~e`#h`e!:39* A}OYnҵEV,k+ Gh,|8`!TNB=jV\-xr=t5\-Dvzݭ} ir: Z6N:]vufu Eg&ss&Jr+;,=Ovun!X+j^h2y?984Wb8dIaj<5t0YCT'*-`ITxx8֒ wqWwN8FSh2 q8P4r;`ŭд=0 4Yq<Ʋ&DاM]t!uūSrQClM2$1UKW3.N,q.tuP/Fzܽ5scSߧ'_ྑ3DۍJxBj щ` KUvSbO*vKSR/9^Q.iM W>]+Yn/UՀ _\͋b9]} r O6ƴhD#b~0+Y3ZYP嫼+XqՍն||6rفLi7e<(zlO!ߑd<☙vwgjdtӰX&ݰA[9}0-C\nl"8b 蜵 !˪Pmz1swQ `IV"E)b2xcd>  }J`qoA,if r";|4i?(o;\;UB)]pT/Ϋ\D7.%tNzG+$f'of,P|"y(85]8!UxYW*K%_{Sel+]`5k"ӨM>[#}S{.*}ӄ/5EΨزIwy  #kQ% /C3VlqH޵TWMLxP)B]>T"+x@~N80A`q7JT" -!ITɲ-2(bTŻt/EF~o7zh:LB!O&8VKnLygHs-}BBW5\jwH$o„mHJba`5 y:9.OPzL;TbCc%G ?ZH4M .%B"!2"M!2I2'tBn&AjҺV{ )t]Joz1LgNЎ59d4=/9\CJV-w^6ٶu_ɭxrpn:.'iW\ۥ¾d%ꀠt`2^XqyۛMA/B/ki!2j/5$i0߆JIcҁ(@6͠k׌ҔxlQMPz4^L d޶~X1Yeȅzmf ڵ[fs|27l)_0=7@`~6$/˿uH&@7W\,bZH%$/$pWի=>V  F[wʎN$4MY$}j}-kuxT4k#mۤqFt1Ll4.|ɛ& dvJi^P`KDۨHsIu:քD8E nuH.1̄HA6Ny)ߐ/KGFXc< ]jM5fS!UR/͚z2dnEC8߄K>/qyU=s@4#`+|Do۰$v 0I5W%6  H4;v Xd"`?|NZ<{B3&Fyʷ= r QC70qCYykֳ~4vsǕTbm oZ3\rcY)njN).R>'WTvYErP[Wd<1F~3N8eu5,qd@o:m\NUMN P142|+͟fn;MC9uYɋQIu)_.c*VKSj{|lc0ڃk{r\x]xoKSb)t;_1A4N+3>SkQi.j;pι|f\~&y3Hn<¯u tS7u۴"=چ&?{iR `qb ~'1%^NȭRa).|h-{qj e(JC`qj8 /a")7TĨ.Z vnlTA"HxĤѬ(!?DGҶ+k ̝^olkev ?UB01!J?qyг-)(lb]s= <y߮nH $gfmQ & RKu=. >EGƺ>"CNwjOQ_lT^{K*d $!t?%h1+1m֑.F3DzHImN+ws5b]bM}O%|e~w}w!N󕹘.NZ-n퇐#<(H] ۯS)cB7iG*2s HUvy[X52Xsd9Eg p:QԖGu~;}vpYsDUQyH^O_h70C n{27_A(70x8@ ~Z:Єk˓ciH2[a8ൗe mC@.pzTN}ps:ɒ7ɠӣx fј[-r c >PhĞ]ݟ)$_jU.䚶އfI^pWr|Qc Q؀PE dZR!rW(v/(D9 ;zug(\ VS^N,y4?㑮 'VLwOXf&niRƊszA&pﵥkx\hR)3p@dbsLEon(r"I(^^x非 cfC4q}^U74ZdV2ڲ#.F3%b~gH5H'Kܣ|j/jDxoܺpgG-@TFhp;6av6 FcճmSUI9`NT5xbB{|t!fE##"n(V2 % t 43Qàt zHMOI7aw҉$,e# ps^@.-X9*d~ Q#{OztmSQ4-P. dMUd1 ߘ|8ū= 뇙we}9/տn>/zpg.Fxx@B>N`s i~o<ێ\qRvSh}&.P uåhbs*bvtK+(}9Жax\ĸjCrt,d ;+@WkT鈃#ֳ^Mf.VYwXҟ<Y.Msh\=wr}i :2hئ)jp3KOFC1C,`W7M2 [$W$cD=YYRb_惎fż(EAo#gdAT ~`:7oq?4AWA_:}8==ȲyR7* ! :wՖZ++{ (Zc'`,3D<^ة/CU/vj] l.}/*ԆgŻ[SQbѓO9.2z3Xk ''E+gG+{#gw >C@߂4I%Ǐ @TJu*9{D"'ͺ|ctWu٥dU>e㯳I[m]>6gVp`Ug2p,ֶrMRA$/Y9Wx. EQ(2 WmYzRx^9d6^@6(g Y1{|[KD]2m' !}wɉԏeђ[ ]i(mOr˸P9Q@I-p#v#ȏb#xC}fOX3ꂎ*( X" T 1ݜʜLᣏg-5 *S&":o!lo\JuFY11eW ØUܩLVjz[v5عrKBx ^ IaM4;G/L:j2gb+MJ)yx?ts/)snE&&;zC)+|7&sC 6:$!?پծD4iZpZf3}Y Vi {CQ5x}_[_KNN]TjCe![]ɛ}oPiE A9ӡ,y7 02޸RʌC; ̒ dmW(‰LuݵntJ+.(M!+q}(6G3\Y L& #1* sjT˴;$e=' \2 syzuz i,ĽҵOUmX E,$8`Ζ8O`;%Xw FAμk4Mg_)s҃)⢡0y'O?&"z)غw1)f`|JFL˚zJ9FؾYԒȠh&V*j s`CTR]ϬW5Ƴl-Sv#҅-~t^8Y6(,)ZuQP%N+wa`jR^s:ǿCk^򢤊f[PiP;CJ\!`2\My(uP#/K~Pؾ̢#o )#sdqeoL ,kd&.1uG\/ U<.6[:l!h{"Z݈Yw;(F Qaļ&P>uaêxJdWE:q8PXylw) ~I  ]~gm@Bh}h: :bX:k H{ADހRBNJrjq]J  2ps-YG_R^-8~̒ە~O뙬 #*;x-Y{N0k{=8KV뒈Pٜ2 ^xQ!Zy*^AILuI|糓jjJT .Z.Ncd,iχ2Y>,MXFQ5yVATRzm%sg1^wxVcju߷peV$lhA1?lgԖўsOj"?d9 4ZAҰƳ[3DF;;ebA8FQ^^\G.J$?uƥ,}7!q{@*]jK։R9on4Dʢ~T ;h)I*DQE"q i-EIĬgt$&!'9 6DEoYU -bs0}6rDi#>U1"le4rCyy5E1ЗC lCsɭ5d{`}@*LrY^m]R;&s6H‰tBJ{w|YUJbs.^ nlBbTrʰ& JݩPRi?.ХR:>ɴ|6EJhW]AlLGnsҭfK>/pD:*ZXLH+W)u½.!h&GJF|壍uA)oFObTjy2_*r)KG{5M;fĤдƽq[yJN#$[x{e=\pnNl9=#ӕ T'K؀{u\L$x= N/(gUD_!MjO#VZÔӪi wnrhIM0P,Эwrin/ʍ;q1CK/Vc>BmxlO7ߙCʮ 72s,vrU;kl9y;il5G=ru7Xqlj㿝mk|"-a(+t\q&Ҝ]YD+Ȉ[u!V9=.@7ƠKISE:}UNFT%99^£v\֒Nx'Cwlp#~^?>$Nڭѹ!*'."G53-ƷobgV9a}`ѓt"\~||be7I(Y~85 (BQ{MV `8B3E4c"=Jf,zrh0u}9Y!wo@OCLRU"^gn)Jd1,Iˇ +, ⃚;Z=Xf/?8jMnv1 lN :cW֊BP=-y-RǨmO\k"PgF\yr!k83(Wf 091fkhC#S3I'0 v{A]IyZ$O/r ?1&AwӂTY4C*i~0USchi[p|Bz!P#B_oG Q\|a :=VOm8Q2 $ÕȎ-~ u%&Zêh|\yO'QspQV j^CD \ݠ{sˆ%0Q k^ĥL jWcXwĸf i;lzޑK`ܳ),^L=f 4jѡ6Vy{~y=E}DW"1SrQ?g0S%mL@^jE^J I.7R1⻈w?[5 gF+>aݖa JKwG}sQR ^b?;h~nZ6ǖ8ʴ]Yb{ r݄jnj|:8$ l:1վvY/];(zKsWΩ7^{^ܞQ<}a@f0BX"I(k;Ps`U:c6cF7#?YȭV&oWӄP9;sP^p%X[U{ыׁ.V1-x55>f6VO!Siwq2JSŀ_oq({T}\;f5Kx:Ӱg'zz2h<ʑ<.V6`CR磹̼VF߸x@U=#KIRLܑx}@ d^{ G-wTp'[P)j2|8D[8Ϸ::ᦵ8HFk8q;{wGVȯ"~juP i(XɧE٥'X|S$Fl|xij=y=շ*oW; %lzІ%L{2[!NyOg{6e15N T0* gIsl4M4~'ݔ8UYn͢ɚ]+ޅ!g&xDh9SIP _0mU,$8T`3zđ2u1 s58:x|R-U{r$bp|ԙg'[pz*2  >W׶jo"'+,0*WHd~kNxr|ڏqD'Fݖ "HkV}=|wvZAZIoYUogٖIi}ז&Xm ( E@#A^X+csR!"Vo1aR;e|ɆC"gQrJsJ" ™&;Ɂ.<9a#!HGF -0Le^\a&߷SP}dyRs'(S'9$V$9l< FNr!Rq/m$7nCN]aaQ} u:nL-;U~e᥵4:fLҁbaLIoJVwH<"Db4ol ;s:gyFo9Mߠo׌O5Q crfqp!L4o9P֏ u\=uUm╕*SL,gGxM6P>n0UQn'mr^`q%/Pm14Vm$!x Pkpp>>WѪM@[4P2boc2!I4w3w3?8ֹpJ2ʭgv[s#Q/S R2G(<\8{@+՛U v|{V ٿ$NYڹ&hq`!V59x /.9#S|n(X.Yr/f ,O%G-u{n/vfǭa6i֕nCjڵ=ǔ/#~c-Welzbtm |&mwN gs -&ݓ"CJbQAmvvӵ^s1W<*/7Sw%- zQ9m^MB&i^PYrud_{`/If wPh.F]Ӝ>#`8yBT>nY1"7ȒK4mɕqIs{՝I<v3Їļ?:Gqf ik/u m%vȠׇQP^*(fW,2z-PAF%E$B{|,xb Y+#ޕ^5uDE-jxoL¥\*m' hSb{$D>c"F)@0rScog>T85B OA vIr+ok07V涭*jYMʃd(c26i&(@{ ȍmU6fx^d4۩Ij/ &c㓢 ]XYCsN!`xѾJB /2A@5_UK]/zFYI3~*ۤKOPl17*]>C_mF _{ :075,~Ĝ~B*{._o`ko+K`A0KX\ 0r޹?B푟@Ufܯ_{u"~,|cӋCkFU\ Ksx(2XTp6td5h *_cdW ~_J"iS KMi@WC;iu|cX@݄e7;ib-9X149KZhgGI1` +yOT:v>7j4$LRCpOiD$XvFݣIvd9wa,k-fKmA=B,<;H$1f6-R4u@_%|&fm*H+P HP< z5 ZZ9P_#HSzkAPJ 6E]/3lt :(Lzi 3_[r[7LU St]'| 囎^T%VD#}E=}8s ϗg_mw\1G7MG_j_݊#iN1h_YQraLZٚҖkPs#9}UȬpE ?Ԫkid%F(r>Lڷ;E2xoz9ebQtTe~Up fctd2WHݚpmRef2[D$ȍsvqA벣Pqrk;IƬϳq.f Ő_QYdx>sKuΎ\y?L<Ӊ|l"G\?;ڧ͜ HSQ`` mhc$!e !OB q~^ jr%@OedЌ6Ll4@u3h S͕;ՏM1_7դj:V,ѱa >zL؈Y`P뗵1;쀔60lT"Iu. _;]( RLU6CZқO#8#)M֔ԔG@~p a WwU?i'H3.3_9jعnm] U⏴r谶aņFsǧ^ h<8iiGvr8M?$JC Ӓ(RӸL\` Q9(([2Qi7t8 .^nXt$`@!|h:[qOYi c!`ۄDV:uLBTU(z,\/>8 hJbil~LAICjX,e>Sk5Vh,M.͙͉ƍէ,&"+@ N7JpVm]O6vl\[2\F]0.aMVV.hZnKbQ×`eqg#h=aA>ՀgP:vCˇ.~Kcm5^ej~P*m f鿠OzehgQR^neHY\c!lo5f/l c=`!PXYl:}kN݉NY40,;wa}"zlj7`9DLL CHSq.O[-ڝUƖfw1Y6寺jfFh-LM\}Ckewh_/FcغM3.^TSWf= nm {DM^% fGw!!-utheT0m8!C4,KYsգzW7= M~QEGj0d¯28 K!$ Yw`^TpA`eaO&M=1z/ Ŀ{"L[/ }fChLE!tY1|AY Rr|% !^0ܩ\fnGw5d2lADEFװp[:Zm-_# ,p̧Z Jsc% -xDg\k}iH-mI:3bplآYY\]>:hށ}叴=zil>Z>MZw&9E]L&7+Ž!$X/?;;Ikh-D\ے._?쬮ɴ0%+@O%\K_};Z2OIL)A{lBzjƚ#)(=ArV C)\c'CM3ۉaW4+aϜ89Jyw{t U$~U/pa>V5?mtqˤ)^[:m= y% jD?,Hjp6 -\%!RbOsu~^W'Uk])އl>@ǘdٹOG67&a@L[ZZUƣǏG! fGbnր͗LGDJ>7Q}?xT=1n:Eu2N\hxQ _c h3)%[H7/v6vc Ptp ^`D֦ "`!*aj`~ qe~vlh.FJ:qh!+*9.@$츱r`W ü=#Ef?x1njy1vvec-o$hlſў,6_N9QO-$]Ee 'oXxD= ˎ/:+UamD\aQ@"J3#9»> i0 M; k庬m;}* Wt)GQx2_a7[GZj=@doﬕJm s9 :]1yC;XYQ {b*%8G`s V$ϩV>gh<)<yAB /FGhV~9 =7Aw<ٲhߑ Qs8+ZS$N(*fze)R}Xo~&勞h?옌풎!O*:×w:CVW͆h \E5N?^=BRi/%LJ?LО:I mW$p67,us:ûGZ6?m4;\g)H.\C|ѡ#Y8WExYc O-i>eUüᘱRʚ@> | %y[>?˒_)?G12(NxP4tRjxܣe OXkLy D ]0*w7s-A+UGZOfa!vx*| W8Jo"];OBWJ$IGaYNÂ]C>}j?_*k,'ȣGtE _f+6O ԛgKB o?i!e0*‰֤F D6StsXE[4'KiȼB؀/# ch;-* [&d'-K/dHy71%礞:!r{X51hls?IfvLt{vQQ(9ZtK–'hBjߝ}YKӋoYdWT-PV!1+ؠAf쳾dٲ`r%&jKU8Vr S;gp,o5z߅Gt_+xǯȣxK#:c\r877Rd/ɳ0ݤ{8 4Q.Vas*IdNBϥD="k2/jhaPmbZϨplE"/\N9d5C}%1|x&``l"5oٱbvņ}P ߸fLJQdN{FWa@^1]Hl|w!h ^'~,R5܂ZF^ȪN"cǿRI{`&D#s!Mb`R|jd!@V#vhUw]>Kt_l.r2d ٶ҈mhyG|ZPS @%+gNʐɛBZ#ՆQ/ Snaи=?p_[TUCH%m13-FeKR;{@\,~UL;z+IVT q g<` 8V1kMv%b!H.dl,ФkR^ 3kNZX)fPo{xBz)e=ב-;_M>)= u@/eEIwO9oHLGQz vzW@|3F} ϴpM )YpN᭲a@Ѿ̠RD;xz:6!>KN! 59nBmYKz-(;nVsd lD&i?4i$e#/4Džx0an뤊 1L8:g7u(z%ܜ^&g}aYZ +ISǜݛ?ܣyq*Au{ ." vA2][tĿ_+ S TM.Og823@l({ey165ow[#^THGL޵)Ӡ#-qWc,:Q%,c2B4)Fah+$|r!%{gD=tU R"qp=>d 3E葽?ʿغx[mSJ+ S1`-c/D̷@㟋QS -x*gy>q]IhIa*РK">oxKP%s#KZe@80 tSn̞ЀjTci(;B;K~ɉY<3(\.nn[|ឥ*In F^^mP巿ѲucʩjBO--!0c)n4 eƸW( Z7 8$'Qj;4\O_o73\E[T+LN>\\6dsׄ U%7(ӿGoXg~0XLV.S~aS>l- W="3 LrfuӌWhS9ӀSFT~6/ xsٛV=rl,Dm}i/dxpCɎg\`tٻN9{M2DDHPUR﹃0|z}P~X'L,CJsDgCU^>g/Lut6q`ȯXq)F4KhM~JXr1xԨX;}]<ʷ=EMB'V]XNS3}aL&yo'RaiJ7W9e=Nĝr(p')p GԹDdf:Q:ۥaZaλXJtԔٮ#JϷ)_Ah+)Oty?*I g 5=Ӌ܉%8g@ E|/%Ok:cl%1.{ CʩcT80@n&gl@c4\gWSW3wW~jkMA:U 5fڬmBqr3>N#/^Da?{ʬ 58<.ى̠AK$};LůZXoXfGN_k}S 7F.E>2ݩ0fC$L~ǹ%V&^[h0sz!`ZcdQF.i n1 5.@Hs ū# CPuϦT JjzV@KDb>P&k]1_(<#jc\r= 8˻2rx `P}\}ࠥYBuiBi0,.=w (:,.&UmEFݦcG{HvT;Bލ6I>2p}z510 1Z ABaI}gG0gnV6\؞<Ճa =)@BEp,u(ś%gប5sA!8P( eꓶeKg 2Hƛd+ByT/M:>nJ- 6NRyvqtRU%s'$ހr<{x4ŕF! 9]#H#㭅.iDW-Yz*>kHQI˸.rqB C0qਖ਼}jDxc'rs%(eĬIU3гǼAh:inћ jitE(1L4 E_]:(!߅}Խ)ꑶy0kv=xb`K5Aq‰+^Hm"[qç]R$ ғL726<38o+0BќM)xhijߺڪ-9stj䶺u&[&"$E^A+sF-t=bLr~+%VwBGI){-ExHbD'f]A u ܆WV[eTZ%# f CӖS.E[:Jg&J!?U{6ED]h`GsS]h@%ȉ%EblĥRaʶH5w`w6W\SPK!2T5 S2}Q}azz,rPF8o)w*z&f=$ڽ89ӚlDz(l;߽[xbqB$NmaR{;4yJ4`KqzUxY"Q#W&B-u(qӢ\+0Fi3JDž8 R`{Q ޿׋:3 2TcSQ̭(T6kHD1R۱ۄ̏|5mYc+sì=0j4׺8N;Sմء\u,XDLɕ7Eʀji2I- >Z1]6)MEľN{ל#nŒ"1|/Ý60gZ]`ԠW` +QpA5И W1ҩP,rC0;sOzW0yk:XzAl-7<1pk|[48P_̔xdT Q'CxtrK)as{tb$c=?2_4|,]8`;1U=i;Uȓ ] Dȅf?wf觶2*@ufHԿreVq$8&e+1=ĬxLj6Z=DŽI Xj j/9J{?5'~`#5S ԛ|ANr9-Nx lMWhB%/UqSa>[ÑN#7O>B 1!/u~, Υ[.koD/am.Dw dDSSmEImտ+.QP)mkyUwoxf(c(#:{Zd_JqRRJ$v`lajGjY̽"s!^X9y׭~bOFs~FxFS3~ej[j|y6NhC`)xcO&G\K1^]ް>õBY6Cr P?<,m}r,f`tqA]5 OCL'=`r:#Ԍh9 0/=os LEF"oɣa0rnALx[U{+H~^R4ɖw %&)GҚ]3}>$ݷĖ1CvS j[BjgXC,Y F]!1ALɬL\B6~bgH]KDfųOdI/sqO/MTM*sZnu ]CTеcWb[wtBUӤ0}S\jD,fRĠIfSִ#pba!.4Ow*{HaI(.ZկZ~['n'U^RU=~Sc:z.>1¡j^T9T*̝0Aj,h,^uV XuL-EA4>Kqw{1y '7R˥s3[ 3zLSc4|~_ka3;J lk/ HH8W( Jوm6 n7d:G{<4>Fd:tYk~OM!z5ZJhTNsw0?t2C^A@XjH R4y2 ;_0bԓEGn9>G9=nZl6f%"A/i Jtm\-D Ƨ<=.fAc0wUC@ 3Om6! dўa󸌡]bYæVmr:oo=G*O bf,DS#@(>w/Yj8m/rZA{ .s7)IJxE2˯S)K[$Cn.S'tV H@\{ydVf+Tc$40P|}O;)d p.;yV )Kq2/4ڗohf|J .-=[Sn\;yAojD88{+WWwH_˵l\0wN5\i&Ʌ6a1 0p*Y!HT&K\3 B(L}xc@*DÍw86/~D}]"j*f:p7zꚨ'IK'U+bdRۍea+}&LNe+* mCX9CW|∏9ECma_^)E3iSH `q=Q9b Qv#[-8ɞ맠& lsMxb>34OM8I7̞1M_1*fW HtF_ErA#;c0 ʨqc k*~2aiV!|S"*!ȠQ:(/gBRn> OqgJxOND62Fm1d^ :\ ҙO\9?M$!ҧ/QވBw`N7"*]ϤԵ]fAVnv[{>l޶0Lȯr] 6>ƹ/| oȶsa췩 >Ax0b` u)[0 xVљ $;>Av2u),E c6~Ĥ菮odf4'zw{xd1]/LfRTHqkoDÄ |. a^QXukI^`#7_fŐ<3H21uJ*m;+IGUm][Th#c ڪ̷0clOmktgvJj_PJoRJ')BxNw`x96 q'pZDž;;6ܮwzjkjr٫3Kk!#9ˣn!u܍ WІw8!%vJ%Yc&oFij/}|<祽BUSyH(_]G@\˞{HCGs8[hRORM6=uPng`Q<gxYbّC9L.1X+=bk-/t|1;IƨK/g 1Qts)AlQg~Tac7@pmk`хmۜKlVIcxMO#)k,qGM0]4U8R̗C' #7og,ޒFz10^>Y; 0G&*v/Fm~wjY>A߷3g!)ƨ͈AʗpX C/:tUJWѼ@!`ryɌw KKa<բ.՞ }Ur>P_։4-Stkg*F  ~Uz ~R9?xlM'}.q&nw}zAO9[rG Zp a7%X [,ܡ9Ք[ eG?pmyc)G*' vYvkWjuU0G9blS EU%h`y201qQ YH;jFkE^XCT@'߈.%o~7ɡ"lf@k~y[QtjHH{ր7iG״"!Xp̺:+сɕ"AfFl#{YPxnA3FC|@n#p E7ri!ʸ ckUM!#G5WxWbB%ϱp59;:PENoӠ6 9Yg^ i%dZy"XBR| {͎܅w\ )~ҰU\DqB[jVLS_=aOEH ψטL_Kڃ/>MSC68s&<C`5k2o("u./ f5DB&[djB% fzl'H:P7b,WJ:\-J{ mQ_7ԻQr$ $ @ˮ $w{PI$ 0 cY1樼B>:T&[g?}c<-t;WٝIL>\KtlQ X̖:-dKFD eR.i\_'+_#]\ʖ{BOhHi\$!*xՓ)}?@&a"ߘـ191GblK3Ki3^B>e[ m-HMR+OmcE5Ӟ.Ppxh}>B" @!'Aq}Ytd"2ynPh!©{;tU>/X-%5Lc'9@LM*2:.<-FKn# ;dt_GKݸN +9D埍0rfudh)*}3Ҡ6O^VoCeXTbu2\~dcnc?_?/)< $|Ԝܟ Hx^lOBf9%W]G}[:7 Dgq#/g|qDmi-9:/}* |- [D4xO1jE JГZ.YN¨ʾg<0}g@8~cDdż]MtHRCp[B;,*n$ (V?jд=|gj+ik[~(%5i)No@'_3 (deXwVq,ȅ$ |La3 86NϾaXB;'P:h OS,~Fߕh.[h uޏs}\܃jzc4/XX'gQܶ'ݜgǚuw+γFB`fe "T&dLX w8 ̑=uVG`hMRszӽ&˯:vF ! X:\8N uCR|irʐ$-pd˕ B*]<%Bµ9 xrwHJ9`-8ʬutq/`U66%{$<^ GGhl$R^خ-wSb_<1=JZh7XAqЁO[+tZJ8ZV\Y EM7jk7/9Pnn0j% gk.B_hwvUW*;HbYL e"08,5^ 96a+(a QΒZ&}jq+LԜi)zUPmu2"nǓꮥn?H^V;Z5>f*=kcufɫ>S eK6+QRH) >sH9c^!siҊd̴lWP2 ;\,Ue>b=!UPc'X0 N!y5'(ɬ%<8#xVWӛRf>wDnCQ ;|1:;%?kP)iyr c Fĸ2)PnK<pRe U.yczWx;k7hFcg(!8 PW] 2&ks}ɝgD ƒPFQkC|dZc[ZcF`WZ&2֚Ĉ_g(v%> sjpuնK}QhW0n=&mDhns"*_=™]eBu/“Qv&"G(2)7l+e0TwTáזԿWh&gz1>kdhf.@{N{e]8/%lDT=4S[B?rw(VS}zqhVD ,WP}t{F=ecU#*Lʤ6cZ1K( fcW/aeSU?v +m7=aJ]Gbv!rOY05ŀge ]n$ዓ]-?HϡVKd_(tryP(/_үc9w%ٛhud1`W2hnl]^y%z:Hiy}ŝ/tdGT_Mxǚ} }k2Eou.e\@/RvXkRB!ZOZc2DVQp*&B[2+?CJnBU)J>T}}y^+˶y )鉢hr4H Bb$c~lCܴwVO"+ieìo7pyf(c=^0TwT;%;S fP֫LUb9?!j/sXK)+Iܺ$L){iY¨#`8 G@B VǗЗلuU|>}׈ `P&kXsB!itZ?.W3e0?l/: 0YVx: QHKk;̣ y$e/1 TP_ӻ dBaIƪT#ҟt+<dh5R{nX^pSF($2(%ByǭvK4X="f7]?lːo-pLJ䜑g׭d,mAxn_ZeG}sLݿqq.Ujt:ӓ jMh)ahrۈ p嵇QjH,MA"Xr뫗F!gZT")g+ mקQza*OtOm?_gP7ϕe䁝_^*c'@rlIŒ[J\#{n5{v¿ύ1~'R-wN: X_k?d`kgBGUaI[¨ӿ}9؟O{5Pʋ'5<.GX%'P~l$/^p-myRQU|Rk/km^g-iɲ M6VrB\=0n?hjn| w"{XY~•8^mm ̗xgV74;+Nym Gy/|M\lEsW U5 v{$P/rWIѼMЖPLtv뙗IeTUE-լ3GH}ၾIaWM{sSBqd?49[Y_n}C,^YQ1׀aK#Gu d)s='`;QcT%1UgCw8|VgVO X';ϰs΂[ 딜gHa|?wM@'\DA1;mJ!ۥA/S٣pfn'{Tu"!#~^qz[z|kXyVdk<>{ ;ay@ك׮q=B;xp=vgfH?z[ק]/=/oL+ZJ53TiߠXd"s}ђRF mͺ>?euEԘW09[|& 2& %I 4 $.]j !:4.!nsN(/ſaFE꫎` ˲,)GqȄ!H’9b=֊9g11AZS OL+ #Ջ%#+״wj{6aYYJ}!ID ,ü݇rr@ia&g< :p!Zm:pTv~PUbл^tO|b膱ȦyDLBRb֘}zWa䶩!|bI[^z|px(~B|D~lRq5Q1GBM2nJ>ߏ{Q̉uϋ泮wr,NrȓOJE Ue1S%T'hE #];k :VC|c $e Ķ.K7D$}:p\w ?":r̞o96'Kٞeosފ 3,מ_@"JK` e1i+}kr{.QXMMTx(A%>xl>F_놼z:[.4һg>uk .f.Tre'#yZ;]j<]`HU^#fwH8Opaټx>]0`5HvY!W]Pe]zrf۴eykHg,Pb <-*׹#=~o@Wu(uQU*1:(9鿥y}j2pMԲ8ua2+l KVpw?|9lA 0_'[W=iNr(MB"j^$d&2yo7Lx)1PRu80ư],cK˜E Sm;}wƾl?5%M[XLZ& <8I͞+`$4ҍ:YqrdAxr+Ȧr B\p9-y`]g#sb&_J-N$IF{@dnak5FUDM8#\kKږVz2S7ZiqU)YLk/\MS.U%SM宂GUD,b4ZA bb}/^%bj[uᏦL) 2aԱ.z 5*AqH;¹d&syMx*64ݘc'_3cH\߰.L$>Lرً y2 :&~ ɛ9tw$D^ `"W$Mu_!moGʤzT4~hnI P '4+G*a+Ib˺PF35au Z" oQ~=HmbDe4NXYfь^?G_ 3M9Ȍ} "{W}&]?mB2u^e),+uNȼ4w` 9Vï,ɀ#KB4)bJeA%a-31nLYCn+` g^Vv+F,DV[r(#"=+0Cal Hѽxp-߅Ed5'at1=U@2c "Mzm >>q.,C z2Ʋ8G`l29[;1_ ٭^nkg/|YMaQ1-6kA H9G%J~U=P{E9gY+Kn;+';NHLy~ `3T<X)3hPI(]P٩} ! n0l8\o!i#~&!nσ~7C}>GYcZeXsef%I 7 XztbD%Kh~" +1 N(ڧi;|lqGܐd3R٠f??ݺi<Ұ Ij&=[ҍ bx d-ޑf?/?T޹5A9wk;B#x^_'K ibCJ;O/fxciL +,ۜu B畨$s{|]Yl0>]7筁iWEF1A˹N-5N RsgIfa7BAmerpBegJW%E2@3(@KE:*{.}XlH׷lj,{KiNejCim*0%ݬԞgt˽ɧ-)*e-9Mx1d'ߍ|>@iRŠ!:,B^Nr4P%̡{%g֙r?i1 Qz,YPxY8ԣa _ UG-ΐr ?ܴ,Ъ!sƏNmotptD\*# 5>>0z9`ryYLR<{*$o?+T俑Q_Y5qVh-5TZl Ϻ OxmqP]oFN dǽUݺOɬW}濎W߆C>3Q%br7>˝|Mf辳G'P?F"ZzCjDGKew,\o\3Vy ;7'3}^NS`&!Q+Zld][06n rÆ3-c2Ƶ È*i o!J`}3uFh9Z4 7P/Pful"i5+T"t'j, |}9V'̈́J{DMb B'׎4q-u7)C:>_%!W8(+fM n!ǹLCc"3va :v0B8AI O Y$Mk*"uh]C e1 L o>J9-z"C,_ިڶZ.; o|kMi3U9UD8IJ?/ ա;ZW$Asjjث:*m +TfN+ϵ!<^s9YzP&xǿ4ËaB`*_+J6-مjz-in/iQ/Б]3E]w{6ǭlv }XG/4Ԩ-o 1'r6LfTY.YH{.uۗP^ '\.f&jhbpۤvTe#ɠl o]&43<ºoJTOx0w 5+6rOoeq≮2p`0h=" =bW~>pĚ~N0YΦ9ěziK5I0@>"!oPt37Wg2߬B20 pHr7 3voԴā=@JG  CM5O.1yw }pr8'lPz.р[ف2خ,w %*-@a8PRHۄAgO-j7Q!;Hl-4c#D-Ï *n~,cQw"҇ 6Z99{ a}ۜ3Q {pU\}Vf)ʫ QqQ~ upP[+Kqq?Sf*2*F!<~|:~iwA`rK*†0&? zPb`kN7nq(P[=T1tdI_8iR3*mcއxܑPҚcP9}IH[(9 "o9ރ~ x5Ss (6ft3دȶ")IcDڼ9I7!O|2.G=*f2oebFd{U;3I]-lV 2$ȭ6f#S:J$\J1龣G}jH" F*MؿJ'B(* IW=j渻-K1W_GT/f%Ky[: _Ӣ΅Dۓ+V?}˂)l`h0>zl4⅂+y9}0`ā'vW1[/ѣx/ϡn-(SaBp FL4ӄT,OB27EYjj!% y2u']&Tv硴?- H7CTxkG 7џBP7)Bxa~Gꪓ،I$qAF4iH"Si&}@E&Hc%6eQbc¢3I1>$$I!Ѻ0 AOG:no#frvߔK]Dб`eqC|Ii>gl &7ⴡJI߭Vi]Y6X]=|,2UX֨n \noM2QYTёXݭ\ymb>)h(6>-_h%IxWT0r}K:ZÀ]4xju<3Hs-x̓+i`]­hA ITY۰)i;*bOfaMipi+^NNw\wn%0Ӥ1{?ի0wNxw~|Yz;ɽȺt&Kӑ"N6`ISE.u(7y M1OANS=/k7U$Yݩ Qg Mi}#\Ξ8)ضnznਨw<}f! p@1[S=fŐҗ:S먉 <V^a8/lpQ kH;2E]-&`-[d nO@jd´[F ™T',mRG^6 OQˑO!O5ȌQmK.SL7Ĕ:{Xciܦi>}(k 8N6R'"iFlX8emZ3&srZg&DFM$%`ՊT_jɤIՠ9 %! c t;kϤ)۬꠼dRNն~G%0z&\B~dTR؈&&`b~ɶ;y:NLv>-9g+tR և&#;(oe!:p*kdIlSԧ 42p1xH " +yAT.1u>~M %܇aa +qw/D$a0ιiR#<Rzn ɦF2A̕цįD~fͲ#eQɖn؉mXE@t-f) K.⥘ޫ?pŧ8_#@>O2 rM9Mb6՝ 3wukB8Ux:7&r` Qf*/gJq% dprS>qՖ6L^ @pmd{.=hTu 9_H Z);7Љ[BjWԵ+ހd%dC7Ὲȭmx)pӾOVJl™ry9S)t<FAowl]2ȅI+Np1/Xc9՜Xj5oIh2 Z$E r}3-cV +R +hS=USPU:aVRa_Q7 BΝ߇hCWb!xSA*tYXV!d;C9 |1ޣPQۘB!gFJ:>AHg`vF`"pauL >[b C c _ӜmRPg4ՙ6gS̒9 ,eӏ_?,$Q;(=]GђÜ%P:-fcb1Rc;`1VWR5]mfOzxYtA nz> spp Bߟ1ڭnaIdU:R48c˵ ZE[GY "7_O=mRe{M`޶^eߪәq6Tj-k-$z.)'Tn2nB<6kPE~LMa,{>mum \U/ E{Øqb# Eg(`Z-DQF%;{5%,صp^Gq2$a(^~ P YNAMy߂PS #|N]5fz1Xw%Mu sYs:kQ\fSmqd׊̎”Ycxbn@ћx͐M#^TZ*iY{v:F^4$ʸ\ܤɁz1꘳pMRFu%V #pk%|k[y{ȈP21UIg Iw)yUҵVx1xsf[DyziL$EOPY3c` s@P dA1 MluJu X+c!K w2UՌ38i߉`*Q4d,H h18$oЭG<ԥ7\ %l="U~P!oq}Cj_K[;ux.9\9iD~Tq[ %lǤ\X-Nu&KAZIKoRRP=@~#dn.C xHvz)wxL#EDo3q0W1W0ph/5T+! }6PPMde"=d9z *멸`뾔Ro7JWC&viT&g:YW4 'Bhxv dǬ4HZ+ٛ2]/c`NtlE4PԶŴ }wXp=sš,iDzCܙ]50Aq;0)K5NE0beq  ~@|q糧E94"+Uy`4/Fw8<,+[[m43vp  ^ ,eف+$t"2tl X_B.zznSZTF9s/`J1n$?=f4`gc',!A4}X\ v8ZRCf / ؃; 51Tzi%CʑJ(;Kd~I5X|v)qz^ )OP7C:^xO*xL )> ;CL[;:E"uI$dn{(2- lN4idPx=h#|kPŲDxZA1l.eZo[sqG¸諥Cv ,*9~X]{Rcd̦jq#rDRwK *BiQLS \\r<>tvh⇡L) +uE]7uMxC5С(VOBQ4+ W) /TrN ;i&۵3 [Wrhat'!/׿$eq@Dګ9Gcah?N΂<0I}l<çVCbpM_2YV^>t7tQ58 73 Q7k(觯I@ۦC ;3$`Bד+.^\? ;0!F%>Xo g8??-H8tQGMӖ 9'ƪ5D`!q^OIѼv,Z%yn[oFHIt2nJEl#rgIvs-—(F>SKez=)8"4k%(EIQl#h͈QL׀j t Ԁ .֙+_|x a~Ws+ ,A{("RDl/%ysrlXn_W}!;0lGkגsjeqo0SȓazdI+aJ\˳vnr6ZΊ%fZ'~57`vq,J/po;RJy= ni NF"aDϕ# ou_6'[i 5RZ*J;2쯩/u؎6iRF!Hd ]. ٯjM#2'/B>'رXI5_+,`rAUa5}2;YqB.rN !/ Bj Lx1Ni0``-kK䶻~Z}?\Ms.މ 2Ou.:y.dA|:"э Ahg7:AZJ jNAe9p wz/ytUE$}QppkOM]v<8 z6RÕnE@ ~N΀^KfDp5a}>pQíX%ApMЙK/ʛZפwPCsӁ~Zbqڲc2So_(}Yuu]Ccbn]-Bޕ#(i`=0Stap$DVj "f8(I<'>0 YZNMF/man/0000755000176200001440000000000013620502674011452 5ustar liggesusersNMF/man/cophcor.Rd0000644000176200001440000000404313620502674013377 0ustar liggesusers\docType{methods} \name{cophcor} \alias{cophcor} \alias{cophcor,matrix-method} \alias{cophcor-methods} \alias{cophcor,NMFfitX-method} \title{Cophenetic Correlation Coefficient} \usage{ cophcor(object, ...) \S4method{cophcor}{matrix}(object, linkage = "average") } \arguments{ \item{object}{an object from which is extracted a consensus matrix.} \item{...}{extra arguments to allow extension and passed to subsequent calls.} \item{linkage}{linkage method used in the hierarchical clustering. It is passed to \code{\link{hclust}}.} } \description{ The function \code{cophcor} computes the cophenetic correlation coefficient from consensus matrix \code{object}, e.g. as obtained from multiple NMF runs. } \details{ The cophenetic correlation coeffificient is based on the consensus matrix (i.e. the average of connectivity matrices) and was proposed by \cite{Brunet et al. (2004)} to measure the stability of the clusters obtained from NMF. It is defined as the Pearson correlation between the samples' distances induced by the consensus matrix (seen as a similarity matrix) and their cophenetic distances from a hierachical clustering based on these very distances (by default an average linkage is used). See \cite{Brunet et al. (2004)}. } \section{Methods}{ \describe{ \item{cophcor}{\code{signature(object = "matrix")}: Workhorse method for matrices. } \item{cophcor}{\code{signature(object = "NMFfitX")}: Computes the cophenetic correlation coefficient on the consensus matrix of \code{object}. All arguments in \code{...} are passed to the method \code{cophcor,matrix}. } } } \references{ Brunet J, Tamayo P, Golub TR and Mesirov JP (2004). "Metagenes and molecular pattern discovery using matrix factorization." _Proceedings of the National Academy of Sciences of the United States of America_, *101*(12), pp. 4164-9. ISSN 0027-8424, , . } \seealso{ \code{\link{cophenetic}} } \keyword{methods} NMF/man/ccRamp.Rd0000644000176200001440000000033013620502674013142 0ustar liggesusers\name{ccRamp} \alias{ccRamp} \title{Builds a Color Ramp from Compact Color Specification} \usage{ ccRamp(x, n = NA, ...) } \description{ Builds a Color Ramp from Compact Color Specification } \keyword{internal} NMF/man/fitted.Rd0000644000176200001440000000721713620502674013227 0ustar liggesusers\docType{methods} \name{fitted} \alias{fitted} \alias{fitted-methods} \alias{fitted,NMFfit-method} \alias{fitted,NMF-method} \alias{fitted,NMFns-method} \alias{fitted,NMFOffset-method} \alias{fitted,NMFstd-method} \title{Fitted Matrix in NMF Models} \usage{ fitted(object, ...) \S4method{fitted}{NMFstd}(object, W, H, ...) \S4method{fitted}{NMFOffset}(object, W, H, offset = object@offset) \S4method{fitted}{NMFns}(object, W, H, S, ...) } \arguments{ \item{object}{an object that inherit from class \code{NMF}} \item{...}{extra arguments to allow extension} \item{W}{a matrix to use in the computation as the basis matrix in place of \code{basis(object)}. It must be compatible with the coefficient matrix used in the computation (i.e. number of columns in \code{W} = number of rows in \code{H}).} \item{H}{a matrix to use in the computation as the coefficient matrix in place of \code{coef(object)}. It must be compatible with the basis matrix used in the computation (i.e. number of rows in \code{H} = number of columns in \code{W}).} \item{offset}{offset vector} \item{S}{smoothing matrix to use instead of \code{smoothing(object)} It must be a square matrix compatible with the basis and coefficient matrices used in the computation.} } \value{ the target matrix estimate as fitted by the model \code{object} } \description{ Computes the estimated target matrix based on a given \emph{NMF} model. The estimation depends on the underlying NMF model. For example in the standard model \eqn{V \equiv W H}{V ~ W H}, the target matrix is estimated by the matrix product \eqn{W H}. In other models, the estimate may depend on extra parameters/matrix (cf. Non-smooth NMF in \code{\link{NMFns-class}}). } \details{ This function is a S4 generic function imported from \link[stats]{fitted} in the package \emph{stats}. It is implemented as a pure virtual method for objects of class \code{NMF}, meaning that concrete NMF models must provide a definition for their corresponding class (i.e. sub-classes of class \code{NMF}). See \code{\linkS4class{NMF}} for more details. } \section{Methods}{ \describe{ \item{fitted}{\code{signature(object = "NMF")}: Pure virtual method for objects of class \code{\linkS4class{NMF}}, that should be overloaded by sub-classes, and throws an error if called. } \item{fitted}{\code{signature(object = "NMFstd")}: Compute the target matrix estimate in \emph{standard NMF models}. The estimate matrix is computed as the product of the two matrix slots \code{W} and \code{H}: \deqn{\hat{V} = W H}{V ~ W H} } \item{fitted}{\code{signature(object = "NMFOffset")}: Computes the target matrix estimate for an NMFOffset object. The estimate is computed as: \deqn{ W H + offset } } \item{fitted}{\code{signature(object = "NMFns")}: Compute estimate for an NMFns object, according to the Nonsmooth NMF model (cf. \code{\link{NMFns-class}}). Extra arguments in \code{...} are passed to method \code{smoothing}, and are typically used to pass a value for \code{theta}, which is used to compute the smoothing matrix instead of the one stored in \code{object}. } \item{fitted}{\code{signature(object = "NMFfit")}: Computes and return the estimated target matrix from an NMF model fitted with function \code{\link{nmf}}. It is a shortcut for \code{fitted(fit(object), ...)}, dispatching the call to the \code{fitted} method of the actual NMF model. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # random standard NMF model x <- rnmf(3, 10, 5) all.equal(fitted(x), basis(x) \%*\% coef(x)) } \keyword{methods} NMF/man/predict.Rd0000644000176200001440000001161213620502674013374 0ustar liggesusers\docType{methods} \name{predict} \alias{predict} \alias{predict-methods} \alias{predict,NMFfitX-method} \alias{predict,NMF-method} \title{Clustering and Prediction} \usage{ predict(object, ...) \S4method{predict}{NMF}(object, what = c("columns", "rows", "samples", "features"), prob = FALSE, dmatrix = FALSE) \S4method{predict}{NMFfitX}(object, what = c("columns", "rows", "samples", "features", "consensus", "chc"), dmatrix = FALSE, ...) } \arguments{ \item{object}{an NMF model} \item{what}{a character string that indicates the type of cluster membership should be returned: \sQuote{columns} or \sQuote{rows} for clustering the colmuns or the rows of the target matrix respectively. The values \sQuote{samples} and \sQuote{features} are aliases for \sQuote{colmuns} and \sQuote{rows} respectively.} \item{prob}{logical that indicates if the relative contributions of/to the dominant basis component should be computed and returned. See \emph{Details}.} \item{dmatrix}{logical that indicates if a dissimiliarity matrix should be attached to the result. This is notably used internally when computing NMF clustering silhouettes.} \item{...}{additional arguments affecting the predictions produced.} } \description{ The methods \code{predict} for NMF models return the cluster membership of each sample or each feature. Currently the classification/prediction of new data is not implemented. } \details{ The cluster membership is computed as the index of the dominant basis component for each sample (\code{what='samples' or 'columns'}) or each feature (\code{what='features' or 'rows'}), based on their corresponding entries in the coefficient matrix or basis matrix respectively. For example, if \code{what='samples'}, then the dominant basis component is computed for each column of the coefficient matrix as the row index of the maximum within the column. If argument \code{prob=FALSE} (default), the result is a \code{factor}. Otherwise a list with two elements is returned: element \code{predict} contains the cluster membership index (as a \code{factor}) and element \code{prob} contains the relative contribution of the dominant component to each sample (resp. the relative contribution of each feature to the dominant basis component): \itemize{ \item Samples: \deqn{p_j = x_{k_0} / \sum_k x_k}{p(j) = x(k0) / sum_k x(k)}, for each sample \eqn{1\leq j \leq p}, where \eqn{x_k}{x(k)} is the contribution of the \eqn{k}-th basis component to \eqn{j}-th sample (i.e. \code{H[k ,j]}), and \eqn{x_{k_0}}{x(k0)} is the maximum of these contributions. \item Features: \deqn{p_i = y_{k_0} / \sum_k y_k}{p(i) = y(k0) / sum_k y(k)}, for each feature \eqn{1\leq i \leq p}, where \eqn{y_k}{y(k)} is the contribution of the \eqn{k}-th basis component to \eqn{i}-th feature (i.e. \code{W[i, k]}), and \eqn{y_{k_0}}{y(k0)} is the maximum of these contributions. } } \section{Methods}{ \describe{ \item{predict}{\code{signature(object = "NMF")}: Default method for NMF models } \item{predict}{\code{signature(object = "NMFfitX")}: Returns the cluster membership index from an NMF model fitted with multiple runs. Besides the type of clustering available for any NMF models (\code{'columns', 'rows', 'samples', 'features'}), this method can return the cluster membership index based on the consensus matrix, computed from the multiple NMF runs. Argument \code{what} accepts the following extra types: \describe{ \item{\code{'chc'}}{ returns the cluster membership based on the hierarchical clustering of the consensus matrix, as performed by \code{\link{consensushc}}.} \item{\code{'consensus'}}{ same as \code{'chc'} but the levels of the membership index are re-labeled to match the order of the clusters as they would be displayed on the associated dendrogram, as re-ordered on the default annotation track in consensus heatmap produced by \code{\link{consensusmap}}.} } } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # random target matrix v <- rmatrix(20, 10) # fit an NMF model x <- nmf(v, 5) # predicted column and row clusters predict(x) predict(x, 'rows') # with relative contributions of each basis component predict(x, prob=TRUE) predict(x, 'rows', prob=TRUE) } \references{ Brunet J, Tamayo P, Golub TR and Mesirov JP (2004). "Metagenes and molecular pattern discovery using matrix factorization." _Proceedings of the National Academy of Sciences of the United States of America_, *101*(12), pp. 4164-9. ISSN 0027-8424, , . Pascual-Montano A, Carazo JM, Kochi K, Lehmann D and Pascual-marqui RD (2006). "Nonsmooth nonnegative matrix factorization (nsNMF)." _IEEE Trans. Pattern Anal. Mach. Intell_, *28*, pp. 403-415. } \keyword{methods} NMF/man/show-commaNMFOffset-method.Rd0000644000176200001440000000047113620502674017003 0ustar liggesusers\docType{methods} \name{show,NMFOffset-method} \alias{show,NMFOffset-method} \title{Show method for objects of class \code{NMFOffset}} \usage{ \S4method{show}{NMFOffset}(object) } \arguments{ \item{object}{Any R object} } \description{ Show method for objects of class \code{NMFOffset} } \keyword{methods} NMF/man/nmfEstimateRank.Rd0000644000176200001440000001647213620502674015043 0ustar liggesusers\name{nmfEstimateRank} \alias{nmfEstimateRank} \alias{plot.NMF.rank} \title{Estimate Rank for NMF Models} \usage{ nmfEstimateRank(x, range, method = nmf.getOption("default.algorithm"), nrun = 30, model = NULL, ..., verbose = FALSE, stop = FALSE) \method{plot}{NMF.rank} (x, y = NULL, what = c("all", "cophenetic", "rss", "residuals", "dispersion", "evar", "sparseness", "sparseness.basis", "sparseness.coef", "silhouette", "silhouette.coef", "silhouette.basis", "silhouette.consensus"), na.rm = FALSE, xname = "x", yname = "y", xlab = "Factorization rank", ylab = "", main = "NMF rank survey", ...) } \arguments{ \item{x}{For \code{nmfEstimateRank} a target object to be estimated, in one of the format accepted by interface \code{\link{nmf}}. For \code{plot.NMF.rank} an object of class \code{NMF.rank} as returned by function \code{nmfEstimateRank}.} \item{range}{a \code{numeric} vector containing the ranks of factorization to try. Note that duplicates are removed and values are sorted in increasing order. The results are notably returned in this order.} \item{method}{A single NMF algorithm, in one of the format accepted by the function \code{\link{nmf}}.} \item{nrun}{a \code{numeric} giving the number of run to perform for each value in \code{range}.} \item{model}{model specification passed to each \code{nmf} call. In particular, when \code{x} is a formula, it is passed to argument \code{data} of \code{\link{nmfModel}} to determine the target matrix -- and fixed terms.} \item{verbose}{toggle verbosity. This parameter only affects the verbosity of the outer loop over the values in \code{range}. To print verbose (resp. debug) messages from each NMF run, one can use \code{.options='v'} (resp. \code{.options='d'}) that will be passed to the function \code{\link{nmf}}.} \item{stop}{logical flag for running the estimation process with fault tolerance. When \code{TRUE}, the whole execution will stop if any error is raised. When \code{FALSE} (default), the runs that raise an error will be skipped, and the execution will carry on. The summary measures for the runs with errors are set to NA values, and a warning is thrown.} \item{...}{For \code{nmfEstimateRank}, these are extra parameters passed to interface \code{nmf}. Note that the same parameters are used for each value of the rank. See \code{\link{nmf}}. For \code{plot.NMF.rank}, these are extra graphical parameter passed to the standard function \code{plot}. See \code{\link{plot}}.} \item{y}{reference object of class \code{NMF.rank}, as returned by function \code{nmfEstimateRank}. The measures contained in \code{y} are used and plotted as a reference. It is typically used to plot results obtained from randomized data. The associated curves are drawn in \emph{red} (and \emph{pink}), while those from \code{x} are drawn in \emph{blue} (and \emph{green}).} \item{what}{a \code{character} vector whose elements partially match one of the following item, which correspond to the measures computed by \code{\link{summary}} on each -- multi-run -- NMF result: \sQuote{all}, \sQuote{cophenetic}, \sQuote{rss}, \sQuote{residuals}, \sQuote{dispersion}, \sQuote{evar}, \sQuote{silhouette} (and more specific *.coef, *.basis, *.consensus), \sQuote{sparseness} (and more specific *.coef, *.basis). It specifies which measure must be plotted (\code{what='all'} plots all the measures).} \item{na.rm}{single logical that specifies if the rank for which the measures are NA values should be removed from the graph or not (default to \code{FALSE}). This is useful when plotting results which include NAs due to error during the estimation process. See argument \code{stop} for \code{nmfEstimateRank}.} \item{xname,yname}{legend labels for the curves corresponding to measures from \code{x} and \code{y} respectively} \item{xlab}{x-axis label} \item{ylab}{y-axis label} \item{main}{main title} } \value{ \code{nmfEstimateRank} returns a S3 object (i.e. a list) of class \code{NMF.rank} with the following elements: \item{measures }{a \code{data.frame} containing the quality measures for each rank of factorizations in \code{range}. Each row corresponds to a measure, each column to a rank. } \item{consensus }{ a \code{list} of consensus matrices, indexed by the rank of factorization (as a character string).} \item{fit }{ a \code{list} of the fits, indexed by the rank of factorization (as a character string).} } \description{ A critical parameter in NMF algorithms is the factorization rank \eqn{r}. It defines the number of basis effects used to approximate the target matrix. Function \code{nmfEstimateRank} helps in choosing an optimal rank by implementing simple approaches proposed in the literature. Note that from version \emph{0.7}, one can equivalently call the function \code{\link{nmf}} with a range of ranks. In the plot generated by \code{plot.NMF.rank}, each curve represents a summary measure over the range of ranks in the survey. The colours correspond to the type of data to which the measure is related: coefficient matrix, basis component matrix, best fit, or consensus matrix. } \details{ Given a NMF algorithm and the target matrix, a common way of estimating \eqn{r} is to try different values, compute some quality measures of the results, and choose the best value according to this quality criteria. See \cite{Brunet et al. (2004)} and \cite{Hutchins et al. (2008)}. The function \code{nmfEstimateRank} allows to perform this estimation procedure. It performs multiple NMF runs for a range of rank of factorization and, for each, returns a set of quality measures together with the associated consensus matrix. In order to avoid overfitting, it is recommended to run the same procedure on randomized data. The results on the original and the randomised data may be plotted on the same plots, using argument \code{y}. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } if( !isCHECK() ){ set.seed(123456) n <- 50; r <- 3; m <- 20 V <- syntheticNMF(n, r, m) # Use a seed that will be set before each first run res <- nmfEstimateRank(V, seq(2,5), method='brunet', nrun=10, seed=123456) # or equivalently res <- nmf(V, seq(2,5), method='brunet', nrun=10, seed=123456) # plot all the measures plot(res) # or only one: e.g. the cophenetic correlation coefficient plot(res, 'cophenetic') # run same estimation on randomized data rV <- randomize(V) rand <- nmfEstimateRank(rV, seq(2,5), method='brunet', nrun=10, seed=123456) plot(res, rand) } } \references{ Brunet J, Tamayo P, Golub TR and Mesirov JP (2004). "Metagenes and molecular pattern discovery using matrix factorization." _Proceedings of the National Academy of Sciences of the United States of America_, *101*(12), pp. 4164-9. ISSN 0027-8424, , . Hutchins LN, Murphy SM, Singh P and Graber JH (2008). "Position-dependent motif characterization using non-negative matrix factorization." _Bioinformatics (Oxford, England)_, *24*(23), pp. 2684-90. ISSN 1367-4811, , . } NMF/man/offset-commaNMFOffset-method.Rd0000644000176200001440000000062613620502674017313 0ustar liggesusers\docType{methods} \name{offset,NMFOffset-method} \alias{offset,NMFOffset-method} \title{Offsets in NMF Models with Offset} \usage{ \S4method{offset}{NMFOffset}(object) } \arguments{ \item{object}{an instance of class \code{NMFOffset}.} } \description{ The function \code{offset} returns the offset vector from an NMF model that has an offset, e.g. an \code{NMFOffset} model. } \keyword{methods} NMF/man/nmfModel.Rd0000644000176200001440000003635713620533602013513 0ustar liggesusers\docType{methods} \name{nmfModel} \alias{nmfModel} \alias{nmfModel,data.frame,data.frame-method} \alias{nmfModel,formula,ANY-method} \alias{nmfModel,matrix,ANY-method} \alias{nmfModel,matrix,matrix-method} \alias{nmfModel-methods} \alias{nmfModel,missing,ANY-method} \alias{nmfModel,missing,missing-method} \alias{nmfModel,NULL,ANY-method} \alias{nmfModel,numeric,matrix-method} \alias{nmfModel,numeric,missing-method} \alias{nmfModel,numeric,numeric-method} \alias{nmfModels} \title{Factory Methods NMF Models} \usage{ nmfModel(rank, target = 0L, ...) \S4method{nmfModel}{numeric,numeric}(rank, target, ncol = NULL, model = "NMFstd", W, H, ..., force.dim = TRUE, order.basis = TRUE) \S4method{nmfModel}{numeric,matrix}(rank, target, ..., use.names = TRUE) \S4method{nmfModel}{formula,ANY}(rank, target, ..., data = NULL, no.attrib = FALSE) nmfModels(builtin.only = FALSE) } \arguments{ \item{rank}{specification of the target factorization rank (i.e. the number of components).} \item{target}{an object that specifies the dimension of the estimated target matrix.} \item{...}{extra arguments to allow extension, that are passed down to the workhorse method \code{nmfModel,numeric.numeric}, where they are used to initialise slots specific to the instantiating NMF model class.} \item{ncol}{a numeric value that specifies the number of columns of the target matrix, fitted the NMF model. It is used only if not missing and when argument \code{target} is a single numeric value.} \item{model}{the class of the object to be created. It must be a valid class name that inherits from class \code{NMF}. Default is the standard NMF model \code{\linkS4class{NMFstd}}.} \item{W}{value for the basis matrix. \code{data.frame} objects are converted into matrices with \code{\link{as.matrix}}.} \item{H}{value for the mixture coefficient matrix \code{data.frame} objects are converted into matrices with \code{\link{as.matrix}}.} \item{force.dim}{logical that indicates whether the method should try lowering the rank or shrinking dimensions of the input matrices to make them compatible} \item{order.basis}{logical that indicates whether the basis components should reorder the rows of the mixture coefficient matrix to match the order of the basis components, based on their respective names. It is only used if the basis and coefficient matrices have common unique column and row names respectively.} \item{use.names}{a logical that indicates whether the dimension names of the target matrix should be set on the returned NMF model.} \item{data}{Optional argument where to look for the variables used in the formula.} \item{no.attrib}{logical that indicate if attributes containing data related to the formula should be attached as attributes. If \code{FALSE} attributes \code{'target'} and \code{'formula'} contain the target matrix, and a list describing each formula part (response, regressors, etc.).} \item{builtin.only}{logical that indicates whether only built-in NMF models, i.e. defined within the NMF package, should be listed.} } \value{ an object that inherits from class \code{\linkS4class{NMF}}. a list } \description{ \code{nmfModel} is a S4 generic function which provides a convenient way to build NMF models. It implements a unified interface for creating \code{NMF} objects from any NMF models, which is designed to resolve potential dimensions inconsistencies. \code{nmfModels} lists all available NMF models currently defined that can be used to create NMF objects, i.e. -- more or less -- all S4 classes that inherit from class \code{\linkS4class{NMF}}. } \details{ All \code{nmfModel} methods return an object that inherits from class \code{NMF}, that is suitable for seeding NMF algorithms via arguments \code{rank} or \code{seed} of the \code{\link{nmf}} method, in which case the factorisation rank is implicitly set by the number of basis components in the seeding model (see \code{\link{nmf}}). For convenience, shortcut methods and internal conversions for working on \code{data.frame} objects directly are implemented. However, note that conversion of a \code{data.frame} into a \code{matrix} object may take some non-negligible time, for large datasets. If using this method or other NMF-related methods several times, consider converting your data \code{data.frame} object into a matrix once for good, when first loaded. } \section{Methods}{ \describe{ \item{nmfModel}{\code{signature(rank = "numeric", target = "numeric")}: Main factory method for NMF models This method is the workhorse method that is eventually called by all other methods. See section \emph{Main factory method} for more details. } \item{nmfModel}{\code{signature(rank = "numeric", target = "missing")}: Creates an empty NMF model of a given rank. This call is equivalent to \code{nmfModel(rank, 0L, ...)}, which creates \emph{empty} \code{NMF} object with a basis and mixture coefficient matrix of dimension 0 x \code{rank} and \code{rank} x 0 respectively. } \item{nmfModel}{\code{signature(rank = "missing", target = "ANY")}: Creates an empty NMF model of null rank and a given dimension. This call is equivalent to \code{nmfModel(0, target, ...)}. } \item{nmfModel}{\code{signature(rank = "NULL", target = "ANY")}: Creates an empty NMF model of null rank and given dimension. This call is equivalent to \code{nmfModel(0, target, ...)}, and is meant for internal usage only. } \item{nmfModel}{\code{signature(rank = "missing", target = "missing")}: Creates an empty NMF model or from existing factors This method is equivalent to \code{nmfModel(0, 0, ..., force.dim=FALSE)}. This means that the dimensions of the NMF model will be taken from the optional basis and mixture coefficient arguments \code{W} and \code{H}. An error is thrown if their dimensions are not compatible. Hence, this method may be used to generate an NMF model from existing factor matrices, by providing the named arguments \code{W} and/or \code{H}: \code{nmfModel(W=w)} or \code{nmfModel(H=h)} or \code{nmfModel(W=w, H=h)} Note that this may be achieved using the more convenient interface is provided by the method \code{nmfModel,matrix,matrix} (see its dedicated description). See the description of the appropriate method below. } \item{nmfModel}{\code{signature(rank = "numeric", target = "matrix")}: Creates an NMF model compatible with a target matrix. This call is equivalent to \code{nmfModel(rank, dim(target), ...)}. That is that the returned NMF object fits a target matrix of the same dimension as \code{target}. Only the dimensions of \code{target} are used to construct the \code{NMF} object. The matrix slots are filled with \code{NA} values if these are not specified in arguments \code{W} and/or \code{H}. However, dimension names are set on the return NMF model if present in \code{target} and argument \code{use.names=TRUE}. } \item{nmfModel}{\code{signature(rank = "matrix", target = "matrix")}: Creates an NMF model based on two existing factors. This method is equivalent to \code{nmfModel(0, 0, W=rank, H=target..., force.dim=FALSE)}. This allows for a natural shortcut for wrapping existing \strong{compatible} matrices into NMF models: \samp{nmfModel(w, h)} Note that an error is thrown if their dimensions are not compatible. } \item{nmfModel}{\code{signature(rank = "data.frame", target = "data.frame")}: Same as \code{nmfModel('matrix', 'matrix')} but for \code{data.frame} objects, which are generally produced by \code{\link{read.delim}}-like functions. The input \code{data.frame} objects are converted into matrices with \code{\link{as.matrix}}. } \item{nmfModel}{\code{signature(rank = "matrix", target = "ANY")}: Creates an NMF model with arguments \code{rank} and \code{target} swapped. This call is equivalent to \code{nmfModel(rank=target, target=rank, ...)}. This allows to call the \code{nmfModel} function with arguments \code{rank} and \code{target} swapped. It exists for convenience: \itemize{ \item allows typing \code{nmfModel(V)} instead of \code{nmfModel(target=V)} to create a model compatible with a given matrix \code{V} (i.e. of dimension \code{nrow(V), 0, ncol(V)}) \item one can pass the arguments in any order (the one that comes to the user's mind first) and it still works as expected. } } \item{nmfModel}{\code{signature(rank = "formula", target = "ANY")}: Build a formula-based NMF model, that can incorporate fixed basis or coefficient terms. } } } \section{Main factory method}{ The main factory engine of NMF models is implemented by the method with signature \code{numeric, numeric}. Other factory methods provide convenient ways of creating NMF models from e.g. a given target matrix or known basis/coef matrices (see section \emph{Other Factory Methods}). This method creates an object of class \code{model}, using the extra arguments in \code{...} to initialise slots that are specific to the given model. All NMF models implement get/set methods to access the matrix factors (see \code{\link{basis}}), which are called to initialise them from arguments \code{W} and \code{H}. These argument names derive from the definition of all built-in models that inherit derive from class \code{\linkS4class{NMFstd}}, which has two slots, \var{W} and \var{H}, to hold the two factors -- following the notations used in \cite{Lee et al. (1999)}. If argument \code{target} is missing, the method creates a standard NMF model of dimension 0x\code{rank}x0. That is that the basis and mixture coefficient matrices, \var{W} and \var{H}, have dimension 0x\code{rank} and \code{rank}x0 respectively. If target dimensions are also provided in argument \code{target} as a 2-length vector, then the method creates an \code{NMF} object compatible to fit a target matrix of dimension \code{target[1]}x\code{target[2]}. That is that the basis and mixture coefficient matrices, \var{W} and \var{H}, have dimension \code{target[1]}x\code{rank} and \code{rank}x\code{target[2]} respectively. The target dimensions can also be specified using both arguments \code{target} and \code{ncol} to define the number of rows and the number of columns of the target matrix respectively. If no other argument is provided, these matrices are filled with NAs. If arguments \code{W} and/or \code{H} are provided, the method creates a NMF model where the basis and mixture coefficient matrices, \var{W} and \var{H}, are initialised using the values of \code{W} and/or \code{H}. The dimensions given by \code{target}, \code{W} and \code{H}, must be compatible. However if \code{force.dim=TRUE}, the method will reduce the dimensions to the achieve dimension compatibility whenever possible. When \code{W} and \code{H} are both provided, the \code{NMF} object created is suitable to seed a NMF algorithm in a call to the \code{\link{nmf}} method. Note that in this case the factorisation rank is implicitly set by the number of basis components in the seed. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } #---------- # nmfModel,numeric,numeric-method #---------- # data n <- 20; r <- 3; p <- 10 V <- rmatrix(n, p) # some target matrix # create a r-ranked NMF model with a given target dimensions n x p as a 2-length vector nmfModel(r, c(n,p)) # directly nmfModel(r, dim(V)) # or from an existing matrix <=> nmfModel(r, V) # or alternatively passing each dimension separately nmfModel(r, n, p) # trying to create a NMF object based on incompatible matrices generates an error w <- rmatrix(n, r) h <- rmatrix(r+1, p) try( new('NMFstd', W=w, H=h) ) try( nmfModel(w, h) ) try( nmfModel(r+1, W=w, H=h) ) # The factory method can be force the model to match some target dimensions # but warnings are thrown nmfModel(r, W=w, H=h) nmfModel(r, n-1, W=w, H=h) #---------- # nmfModel,numeric,missing-method #---------- ## Empty model of given rank nmfModel(3) #---------- # nmfModel,missing,ANY-method #---------- nmfModel(target=10) #square nmfModel(target=c(10, 5)) #---------- # nmfModel,missing,missing-method #---------- # Build an empty NMF model nmfModel() # create a NMF object based on one random matrix: the missing matrix is deduced # Note this only works when using factory method NMF n <- 50; r <- 3; w <- rmatrix(n, r) nmfModel(W=w) # create a NMF object based on random (compatible) matrices p <- 20 h <- rmatrix(r, p) nmfModel(H=h) # specifies two compatible matrices nmfModel(W=w, H=h) # error if not compatible try( nmfModel(W=w, H=h[-1,]) ) #---------- # nmfModel,numeric,matrix-method #---------- # create a r-ranked NMF model compatible with a given target matrix obj <- nmfModel(r, V) all(is.na(basis(obj))) #---------- # nmfModel,matrix,matrix-method #---------- ## From two existing factors # allows a convenient call without argument names w <- rmatrix(n, 3); h <- rmatrix(3, p) nmfModel(w, h) # Specify the type of NMF model (e.g. 'NMFns' for non-smooth NMF) mod <- nmfModel(w, h, model='NMFns') mod # One can use such an NMF model as a seed when fitting a target matrix with nmf() V <- rmatrix(mod) res <- nmf(V, mod) nmf.equal(res, nmf(V, mod)) # NB: when called only with such a seed, the rank and the NMF algorithm # are selected based on the input NMF model. # e.g. here rank was 3 and the algorithm "nsNMF" is used, because it is the default # algorithm to fit "NMFns" models (See ?nmf). #---------- # nmfModel,matrix,ANY-method #---------- ## swapped arguments `rank` and `target` V <- rmatrix(20, 10) nmfModel(V) # equivalent to nmfModel(target=V) nmfModel(V, 3) # equivalent to nmfModel(3, V) #---------- # nmfModel,formula,ANY-method #---------- # empty 3-rank model nmfModel(~ 3) # 3-rank model that fits a given data matrix x <- rmatrix(20,10) nmfModel(x ~ 3) # add fixed coefficient term defined by a factor gr <- gl(2, 5) nmfModel(x ~ 3 + gr) # add fixed coefficient term defined by a numeric covariate nmfModel(x ~ 3 + gr + b, data=list(b=runif(10))) # 3-rank model that fits a given ExpressionSet (with fixed coef terms) if(requireNamespace("Biobase", quietly=TRUE)){ e <- Biobase::ExpressionSet(x) pData(e) <- data.frame(a=runif(10)) nmfModel(e ~ 3 + gr + a) # `a` is looked up in the phenotypic data of x pData(x) } #---------- # nmfModels #---------- # show all the NMF models available (i.e. the classes that inherit from class NMF) nmfModels() # show all the built-in NMF models available nmfModels(builtin.only=TRUE) } \references{ Lee DD and Seung HS (1999). "Learning the parts of objects by non-negative matrix factorization." _Nature_, *401*(6755), pp. 788-91. ISSN 0028-0836, , . } \seealso{ \code{\link{is.empty.nmf}} Other NMF-interface: \code{\link{basis}}, \code{\link{.basis}}, \code{\link{.basis<-}}, \code{\link{basis<-}}, \code{\link{coef}}, \code{\link{.coef}}, \code{\link{.coef<-}}, \code{\link{coef<-}}, \code{\link{coefficients}}, \code{\link{.DollarNames,NMF-method}}, \code{\link{loadings,NMF-method}}, \code{\link{misc}}, \code{\link{NMF-class}}, \code{\link{$<-,NMF-method}}, \code{\link{$,NMF-method}}, \code{\link{rnmf}}, \code{\link{scoef}} } \keyword{methods} NMF/man/inplace.Rd0000644000176200001440000000176713620502674013367 0ustar liggesusers\name{pmax.inplace} \alias{neq.constraints.inplace} \alias{pmax.inplace} \title{Updating Objects In Place} \usage{ pmax.inplace(x, lim, skip = NULL) neq.constraints.inplace(x, constraints, ratio = NULL, value = NULL, copy = FALSE) } \arguments{ \item{x}{an object to update in place.} \item{lim}{lower threshold value} \item{skip}{indexes to skip} \item{constraints}{constraint specification.} \item{ratio}{fixed ratio on which the constraint applies.} \item{value}{fixed value to enforce.} \item{copy}{a logical that indicates if \code{x} should be updated in place or not.} } \description{ These functions modify objects (mainly matrix objects) in place, i.e. they act directly on the C pointer. Due to their side-effect, they are not meant to be called by the end-user. \code{neq.constraints.inplace} apply unequality constraints in place. } \details{ \code{pmax.inplace} is a version of \code{\link{pmax}} that updates its first argument. } \keyword{internal} NMF/man/terms.Rd0000644000176200001440000000665413620502674013106 0ustar liggesusers\docType{methods} \name{ibterms} \alias{bterms} \alias{cterms} \alias{ibasis} \alias{ibterms} \alias{ibterms-methods} \alias{ibterms,NMFfit-method} \alias{ibterms,NMFfitX-method} \alias{ibterms,NMF-method} \alias{ibterms,NMFstd-method} \alias{icoef} \alias{icterms} \alias{icterms-methods} \alias{icterms,NMFfit-method} \alias{icterms,NMF-method} \alias{icterms,NMFstd-method} \alias{iterms} \alias{nbterms} \alias{ncterms} \alias{nterms} \title{Fixed Terms in NMF Models} \usage{ ibterms(object, ...) icterms(object, ...) iterms(object, ...) nterms(object) nbterms(object) ncterms(object) bterms(object) cterms(object) ibasis(object, ...) icoef(object, ...) } \arguments{ \item{object}{NMF object} \item{...}{extra parameters to allow extension (currently not used)} } \description{ Formula-based NMF models may contain fixed basis and/or coefficient terms. The functions documented here provide access to these data, which are read-only and defined when the model object is instantiated (e.g., see \code{\link[=nmfModel,formula,ANY-method]{nmfModel,formula-method}}). \code{ibterms}, \code{icterms} and \code{iterms} respectively return the indexes of the fixed basis terms, the fixed coefficient terms and all fixed terms, within the basis and/or coefficient matrix of an NMF model. \code{nterms}, \code{nbterms}, and \code{ncterms} return, respectively, the number of all fixed terms, fixed basis terms and fixed coefficient terms in an NMF model. In particular: i.e. \code{nterms(object) = nbterms(object) + ncterms(object)}. \code{bterms} and \code{cterms} return, respectively, the primary data for fixed basis and coefficient terms in an NMF model -- as stored in slots \code{bterms} and \code{cterms} . These are factors or numeric vectors which define fixed basis components, e.g., used for defining separate offsets for different \emph{a priori} groups of samples, or to incorporate/correct for some known covariate. \code{ibasis} and \code{icoef} return, respectively, the indexes of all latent basis vectors and estimated coefficients within the basis or coefficient matrix of an NMF model. } \section{Methods}{ \describe{ \item{ibterms}{\code{signature(object = "NMF")}: Default pure virtual method that ensure a method is defined for concrete NMF model classes. } \item{ibterms}{\code{signature(object = "NMFstd")}: Method for standard NMF models, which returns the integer vector that is stored in slot \code{ibterms} when a formula-based NMF model is instantiated. } \item{ibterms}{\code{signature(object = "NMFfit")}: Method for single NMF fit objects, which returns the indexes of fixed basis terms from the fitted model. } \item{ibterms}{\code{signature(object = "NMFfitX")}: Method for multiple NMF fit objects, which returns the indexes of fixed basis terms from the best fitted model. } \item{icterms}{\code{signature(object = "NMF")}: Default pure virtual method that ensure a method is defined for concrete NMF model classes. } \item{icterms}{\code{signature(object = "NMFstd")}: Method for standard NMF models, which returns the integer vector that is stored in slot \code{icterms} when a formula-based NMF model is instantiated. } \item{icterms}{\code{signature(object = "NMFfit")}: Method for single NMF fit objects, which returns the indexes of fixed coefficient terms from the fitted model. } } } \keyword{methods} NMF/man/NMFStrategyIterative-class.Rd0000644000176200001440000000473213620502674017072 0ustar liggesusers\docType{class} \name{NMFStrategyIterative-class} \alias{NMFStrategyIterative-class} \title{Interface for Algorithms: Implementation for Iterative NMF Algorithms} \description{ This class provides a specific implementation for the generic function \code{run} -- concretising the virtual interface class \code{\linkS4class{NMFStrategy}}, for NMF algorithms that conform to the following iterative schema (starred numbers indicate mandatory steps): \itemize{ \item 1. Initialisation \item 2*. Update the model at each iteration \item 3. Stop if some criterion is satisfied \item 4. Wrap up } This schema could possibly apply to all NMF algorithms, since these are essentially optimisation algorithms, almost all of which use iterative methods to approximate a solution of the optimisation problem. The main advantage is that it allows to implement updates and stopping criterion separately, and combine them in different ways. In particular, many NMF algorithms are based on multiplicative updates, following the approach from \cite{Lee et al. (2001)}, which are specially suitable to be cast into this simple schema. } \section{Slots}{ \describe{ \item{onInit}{optional function that performs some initialisation or pre-processing on the model, before starting the iteration loop.} \item{Update}{mandatory function that implement the update step, which computes new values for the model, based on its previous value. It is called at each iteration, until the stopping criterion is met or the maximum number of iteration is achieved.} \item{Stop}{optional function that implements the stopping criterion. It is called \strong{before} each Update step. If not provided, the iterations are stopped after a fixed number of updates.} \item{onReturn}{optional function that wraps up the result into an NMF object. It is called just before returning the} } } \section{Methods}{ \describe{ \item{run}{\code{signature(object = "NMFStrategyIterative", y = "matrix", x = "NMFfit")}: Runs an NMF iterative algorithm on a target matrix \code{y}. } \item{show}{\code{signature(object = "NMFStrategyIterative")}: Show method for objects of class \code{NMFStrategyIterative} } } } \references{ Lee DD and Seung H (2001). "Algorithms for non-negative matrix factorization." _Advances in neural information processing systems_. . } NMF/man/NMFStrategyFunction-class.Rd0000644000176200001440000000334013620502674016715 0ustar liggesusers\docType{class} \name{NMFStrategyFunction-class} \alias{NMFStrategyFunction-class} \title{Interface for Single Function NMF Strategies} \description{ This class implements the virtual interface \code{\link{NMFStrategy}} for NMF algorithms that are implemented by a single workhorse R function. } \section{Slots}{ \describe{ \item{algorithm}{a function that implements an NMF algorithm. It must have signature \code{(y='matrix', x='NMFfit')}, where \code{y} is the target matrix to approximate and \code{x} is the NMF model assumed to be seeded with an appropriate initial value -- as it is done internally by function \code{\link{nmf}}. Note that argument names currently do not matter, but it is recommended to name them as specified above.} } } \section{Methods}{ \describe{ \item{algorithm}{\code{signature(object = "NMFStrategyFunction")}: Returns the single R function that implements the NMF algorithm -- as stored in slot \code{algorithm}. } \item{algorithm<-}{\code{signature(object = "NMFStrategyFunction", value = "function")}: Sets the function that implements the NMF algorithm, stored in slot \code{algorithm}. } \item{run}{\code{signature(object = "NMFStrategyFunction", y = "matrix", x = "NMFfit")}: Runs the NMF algorithms implemented by the single R function -- and stored in slot \code{'algorithm'} of \code{object}, on the data object \code{y}, using \code{x} as starting point. It is equivalent to calling \code{object@algorithm(y, x, ...)}. This method is usually not called directly, but only via the function \code{\link{nmf}}, which takes care of many other details such as seeding the computation, handling RNG settings, or setting up parallelisation. } } } NMF/man/nneg.Rd0000644000176200001440000001230713620502674012673 0ustar liggesusers\docType{methods} \name{nneg} \alias{nneg} \alias{nneg,matrix-method} \alias{nneg-methods} \alias{nneg,NMF-method} \alias{posneg} \alias{rposneg} \alias{rposneg,matrix-method} \alias{rposneg-methods} \alias{rposneg,NMF-method} \title{Transforming from Mixed-sign to Nonnegative Data} \usage{ nneg(object, ...) \S4method{nneg}{matrix}(object, method = c("pmax", "posneg", "absolute", "min"), threshold = 0, shift = TRUE) posneg(...) rposneg(object, ...) \S4method{rposneg}{matrix}(object, unstack = TRUE) } \arguments{ \item{object}{The data object to transform} \item{...}{extra arguments to allow extension or passed down to \code{nneg,matrix} or \code{rposneg,matrix} in subsequent calls.} \item{method}{Name of the transformation method to use, that is partially matched against the following possible methods: \describe{ \item{pmax}{Each entry is constrained to be above threshold \code{threshold}.} \item{posneg}{The matrix is split into its "positive" and "negative" parts, with the entries of each part constrained to be above threshold \code{threshold}. The result consists in these two parts stacked in rows (i.e. \code{\link{rbind}}-ed) into a single matrix, which has double the number of rows of the input matrix \code{object}.} \item{absolute}{The absolute value of each entry is constrained to be above threshold \code{threshold}.} \item{min}{Global shift by adding the minimum entry to each entry, only if it is negative, and then apply threshold. } }} \item{threshold}{Nonnegative lower threshold value (single numeric). See argument \code{shit} for details on how the threshold is used and affects the result.} \item{shift}{a logical indicating whether the entries below the threshold value \code{threshold} should be forced (shifted) to 0 (default) or to the threshold value itself. In other words, if \code{shift=TRUE} (default) all entries in the result matrix are either 0 or strictly greater than \code{threshold}. They are all greater or equal than \code{threshold} otherwise.} \item{unstack}{Logical indicating whether the positive and negative parts should be unstacked and combined into a matrix as \code{pos - neg}, which contains half the number of rows of \code{object} (default), or left stacked as \code{[pos; -neg]}.} } \value{ an object of the same class as argument \code{object}. an object of the same type of \code{object} } \description{ \code{nneg} is a generic function to transform a data objects that contains negative values into a similar object that only contains values that are nonnegative or greater than a given threshold. \code{posneg} is a shortcut for \code{nneg(..., method='posneg')}, to split mixed-sign data into its positive and negative part. See description for method \code{"posneg"}, in \code{\link{nneg}}. \code{rposneg} performs the "reverse" transformation of the \code{\link{posneg}} function. } \section{Methods}{ \describe{ \item{nneg}{\code{signature(object = "matrix")}: Transforms a mixed-sign matrix into a nonnegative matrix, optionally apply a lower threshold. This is the workhorse method, that is eventually called by all other methods defined in the \code{\link{NMF}} package. } \item{nneg}{\code{signature(object = "NMF")}: Apply \code{nneg} to the basis matrix of an \code{\link{NMF}} object (i.e. \code{basis(object)}). All extra arguments in \code{...} are passed to the method \code{nneg,matrix}. } \item{rposneg}{\code{signature(object = "NMF")}: Apply \code{rposneg} to the basis matrix of an \code{\link{NMF}} object. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } #---------- # nneg,matrix-method #---------- # random mixed sign data (normal distribution) set.seed(1) x <- rmatrix(5,5, rnorm, mean=0, sd=5) x # pmax (default) nneg(x) # using a threshold nneg(x, threshold=2) # without shifting the entries lower than threshold nneg(x, threshold=2, shift=FALSE) # posneg: split positive and negative part nneg(x, method='posneg') nneg(x, method='pos', threshold=2) # absolute nneg(x, method='absolute') nneg(x, method='abs', threshold=2) # min nneg(x, method='min') nneg(x, method='min', threshold=2) #---------- # nneg,NMF-method #---------- # random M <- nmfModel(x, rmatrix(ncol(x), 3)) nnM <- nneg(M) basis(nnM) # mixture coefficients are not affected identical( coef(M), coef(nnM) ) #---------- # posneg #---------- # shortcut for the "posneg" transformation posneg(x) posneg(x, 2) #---------- # rposneg,matrix-method #---------- # random mixed sign data (normal distribution) set.seed(1) x <- rmatrix(5,5, rnorm, mean=0, sd=5) x # posneg-transform: split positive and negative part y <- posneg(x) dim(y) # posneg-reverse z <- rposneg(y) identical(x, z) rposneg(y, unstack=FALSE) # But posneg-transformation with a non zero threshold is not reversible y1 <- posneg(x, 1) identical(rposneg(y1), x) #---------- # rposneg,NMF-method #---------- # random mixed signed NMF model M <- nmfModel(rmatrix(10, 3, rnorm), rmatrix(3, 4)) # split positive and negative part nnM <- posneg(M) M2 <- rposneg(nnM) identical(M, M2) } \seealso{ \code{\link{pmax}} Other transforms: \code{\link{t.NMF}} } \keyword{methods} NMF/man/match_atrack.Rd0000644000176200001440000000056513620502674014370 0ustar liggesusers\name{match_atrack} \alias{match_atrack} \title{Extending Annotation Vectors} \usage{ match_atrack(x, data = NULL) } \arguments{ \item{x}{annotation vector} \item{data}{reference data} } \value{ a vector of the same type as \code{x} } \description{ Extends a vector used as an annotation track to match the number of rows and the row names of a given data. } NMF/man/aheatmap.Rd0000644000176200001440000003615313620502674013531 0ustar liggesusers\name{aheatmap} \alias{aheatmap} \title{Annotated Heatmaps} \usage{ aheatmap(x, color = "-RdYlBu2:100", breaks = NA, border_color = NA, cellwidth = NA, cellheight = NA, scale = "none", Rowv = TRUE, Colv = TRUE, revC = identical(Colv, "Rowv") || is_NA(Rowv) || (is.integer(Rowv) && length(Rowv) > 1) || is(Rowv, "silhouette"), distfun = "euclidean", hclustfun = "complete", reorderfun = function(d, w) reorder(d, w), treeheight = 50, legend = TRUE, annCol = NA, annRow = NA, annColors = NA, annLegend = TRUE, labRow = NULL, labCol = NULL, subsetRow = NULL, subsetCol = NULL, txt = NULL, fontsize = 10, cexRow = min(0.2 + 1/log10(nr), 1.2), cexCol = min(0.2 + 1/log10(nc), 1.2), filename = NA, width = NA, height = NA, main = NULL, sub = NULL, info = NULL, verbose = getOption("verbose"), gp = gpar()) } \arguments{ \item{x}{numeric matrix of the values to be plotted. An \emph{ExpressionSet} object can also be passed, in which case the expression values are plotted (\code{exprs(x)}).} \item{color}{colour specification for the heatmap. Default to palette '-RdYlBu2:100', i.e. reversed palette 'RdYlBu2' (a slight modification of RColorBrewer's palette 'RdYlBu') with 100 colors. Possible values are: \itemize{ \item a character/integer vector of length greater than 1 that is directly used and assumed to contain valid R color specifications. \item a single color/integer (between 0 and 8)/other numeric value that gives the dominant colors. Numeric values are converted into a pallete by \code{rev(sequential_hcl(2, h = x, l = c(50, 95)))}. Other values are concatenated with the grey colour '#F1F1F1'. \item one of RColorBrewer's palette name (see \code{\link[RColorBrewer]{display.brewer.all}}) , or one of 'RdYlBu2', 'rainbow', 'heat', 'topo', 'terrain', 'cm'. } When the coluor palette is specified with a single value, and is negative or preceded a minus ('-'), the reversed palette is used. The number of breaks can also be specified after a colon (':'). For example, the default colour palette is specified as '-RdYlBu2:100'.} \item{breaks}{a sequence of numbers that covers the range of values in \code{x} and is one element longer than color vector. Used for mapping values to colors. Useful, if needed to map certain values to certain colors. If value is NA then the breaks are calculated automatically. If \code{breaks} is a single value, then the colour palette is centered on this value.} \item{border_color}{color of cell borders on heatmap, use NA if no border should be drawn.} \item{cellwidth}{individual cell width in points. If left as NA, then the values depend on the size of plotting window.} \item{cellheight}{individual cell height in points. If left as NA, then the values depend on the size of plotting window.} \item{scale}{character indicating how the values should scaled in either the row direction or the column direction. Note that the scaling is performed after row/column clustering, so that it has no effect on the row/column ordering. Possible values are: \itemize{ \item \code{"row"}: center and standardize each row separately to row Z-scores \item \code{"column"}: center and standardize each column separately to column Z-scores \item \code{"r1"}: scale each row to sum up to one \item \code{"c1"}: scale each column to sum up to one \item \code{"none"}: no scaling }} \item{Rowv}{clustering specification(s) for the rows. It allows to specify the distance/clustering/ordering/display parameters to be used for the \emph{rows only}. Possible values are: \itemize{ \item \code{TRUE} or \code{NULL} (to be consistent with \code{\link{heatmap}}): compute a dendrogram from hierarchical clustering using the distance and clustering methods \code{distfun} and \code{hclustfun}. \item \code{NA}: disable any ordering. In this case, and if not otherwise specified with argument \code{revC=FALSE}, the heatmap shows the input matrix with the rows in their original order, with the first row on top to the last row at the bottom. Note that this differ from the behaviour or \code{\link{heatmap}}, but seemed to be a more sensible choice when vizualizing a matrix without reordering. \item an integer vector of length the number of rows of the input matrix (\code{nrow(x)}), that specifies the row order. As in the case \code{Rowv=NA}, the ordered matrix is shown first row on top, last row at the bottom. \item a character vector or a list specifying values to use instead of arguments \code{distfun}, \code{hclustfun} and \code{reorderfun} when clustering the rows (see the respective argument descriptions for a list of accepted values). If \code{Rowv} has no names, then the first element is used for \code{distfun}, the second (if present) is used for \code{hclustfun}, and the third (if present) is used for \code{reorderfun}. \item a numeric vector of weights, of length the number of rows of the input matrix, used to reorder the internally computed dendrogram \code{d} by \code{reorderfun(d, Rowv)}. \item \code{FALSE}: the dendrogram \emph{is} computed using methods \code{distfun}, \code{hclustfun}, and \code{reorderfun} but is not shown. \item a single integer that specifies how many subtrees (i.e. clusters) from the computed dendrogram should have their root faded out. This can be used to better highlight the different clusters. \item a single double that specifies how much space is used by the computed dendrogram. That is that this value is used in place of \code{treeheight}. }} \item{Colv}{clustering specification(s) for the columns. It accepts the same values as argument \code{Rowv} (modulo the expected length for vector specifications), and allow specifying the distance/clustering/ordering/display parameters to be used for the \emph{columns only}. \code{Colv} may also be set to \code{"Rowv"}, in which case the dendrogram or ordering specifications applied to the rows are also applied to the columns. Note that this is allowed only for square input matrices, and that the row ordering is in this case by default reversed (\code{revC=TRUE}) to obtain the diagonal in the standard way (from top-left to bottom-right). See argument \code{Rowv} for other possible values.} \item{revC}{a logical that specify if the \emph{row order} defined by \code{Rowv} should be reversed. This is mainly used to get the rows displayed from top to bottom, which is not the case by default. Its default value is computed at runtime, to suit common situations where natural ordering is a more sensible choice: no or fix ordering of the rows (\code{Rowv=NA} or an integer vector of indexes -- of length > 1), and when a symmetric ordering is requested -- so that the diagonal is shown as expected. An argument in favor of the "odd" default display (bottom to top) is that the row dendrogram is plotted from bottom to top, and reversing its reorder may take a not too long but non negligeable time.} \item{distfun}{default distance measure used in clustering rows and columns. Possible values are: \itemize{ \item all the distance methods supported by \code{\link{dist}} (e.g. "euclidean" or "maximum"). \item all correlation methods supported by \code{\link{cor}}, such as \code{"pearson"} or \code{"spearman"}. The pairwise distances between rows/columns are then computed as \code{d <- dist(1 - cor(..., method = distfun))}. One may as well use the string "correlation" which is an alias for "pearson". \item an object of class \code{dist} such as returned by \code{\link{dist}} or \code{\link{as.dist}}. }} \item{hclustfun}{default clustering method used to cluster rows and columns. Possible values are: \itemize{ \item a method name (a character string) supported by \code{\link{hclust}} (e.g. \code{'average'}). \item an object of class \code{hclust} such as returned by \code{\link{hclust}} \item a dendrogram }} \item{reorderfun}{default dendrogram reordering function, used to reorder the dendrogram, when either \code{Rowv} or \code{Colv} is a numeric weight vector, or provides or computes a dendrogram. It must take 2 parameters: a dendrogram, and a weight vector.} \item{subsetRow}{Specification of subsetting the rows before drawing the heatmap. Possible values are: \itemize{ \item an integer vector of length > 1 specifying the indexes of the rows to keep; \item a character vector of length > 1 specyfing the names of the rows to keep. These are the original rownames, not the names specified in \code{labRow}. \item a logical vector of length > 1, whose elements are recycled if the vector has not as many elements as rows in \code{x}. } Note that in the case \code{Rowv} is a dendrogram or hclust object, it is first converted into an ordering vector, and cannot be displayed -- and a warning is thrown.} \item{subsetCol}{Specification of subsetting the columns before drawing the heatmap. It accepts the similar values as \code{subsetRow}. See details above.} \item{txt}{character matrix of the same size as \code{x}, that contains text to display in each cell. \code{NA} values are allowed and are not displayed. See demo for an example.} \item{treeheight}{how much space (in points) should be used to display dendrograms. If specified as a single value, it is used for both dendrograms. A length-2 vector specifies separate values for the row and column dendrogram respectively. Default value: 50 points.} \item{legend}{boolean value that determines if a colour ramp for the heatmap's colour palette should be drawn or not. Default is \code{TRUE}.} \item{annCol}{specifications of column annotation tracks displayed as coloured rows on top of the heatmaps. The annotation tracks are drawn from bottom to top. A single annotation track can be specified as a single vector; multiple tracks are specified as a list, a data frame, or an \emph{ExpressionSet} object, in which case the phenotypic data is used (\code{pData(eset)}). Character or integer vectors are converted and displayed as factors. Unnamed tracks are internally renamed into \code{Xi}, with i being incremented for each unamed track, across both column and row annotation tracks. For each track, if no corresponding colour is specified in argument \code{annColors}, a palette or a ramp is automatically computed and named after the track's name.} \item{annRow}{specifications of row annotation tracks displayed as coloured columns on the left of the heatmaps. The annotation tracks are drawn from left to right. The same conversion, renaming and colouring rules as for argument \code{annCol} apply.} \item{annColors}{list for specifying annotation track colors manually. It is possible to define the colors for only some of the annotations. Check examples for details.} \item{annLegend}{boolean value specifying if the legend for the annotation tracks should be drawn or not. Default is \code{TRUE}.} \item{labRow}{labels for the rows.} \item{labCol}{labels for the columns. See description for argument \code{labRow} for a list of the possible values.} \item{fontsize}{base fontsize for the plot} \item{cexRow}{fontsize for the rownames, specified as a fraction of argument \code{fontsize}.} \item{cexCol}{fontsize for the colnames, specified as a fraction of argument \code{fontsize}.} \item{main}{Main title as a character string or a grob.} \item{sub}{Subtitle as a character string or a grob.} \item{info}{(experimental) Extra information as a character vector or a grob. If \code{info=TRUE}, information about the clustering methods is displayed at the bottom of the plot.} \item{filename}{file path ending where to save the picture. Currently following formats are supported: png, pdf, tiff, bmp, jpeg. Even if the plot does not fit into the plotting window, the file size is calculated so that the plot would fit there, unless specified otherwise.} \item{width}{manual option for determining the output file width in} \item{height}{manual option for determining the output file height in inches.} \item{verbose}{if \code{TRUE} then verbose messages are displayed and the borders of some viewports are highlighted. It is entended for debugging purposes.} \item{gp}{graphical parameters for the text used in plot. Parameters passed to \code{\link{grid.text}}, see \code{\link{gpar}}.} } \description{ The function \code{aheatmap} plots high-quality heatmaps, with a detailed legend and unlimited annotation tracks for both columns and rows. The annotations are coloured differently according to their type (factor or numeric covariate). Although it uses grid graphics, the generated plot is compatible with base layouts such as the ones defined with \code{'mfrow'} or \code{\link{layout}}, enabling the easy drawing of multiple heatmaps on a single a plot -- at last!. } \details{ The development of this function started as a fork of the function \code{pheatmap} from the \pkg{pheatmap} package, and provides several enhancements such as: \itemize{ \item argument names match those used in the base function \code{\link{heatmap}}; \item unlimited number of annotation for \strong{both} columns and rows, with simplified and more flexible interface; \item easy specification of clustering methods and colors; \item return clustering data, as well as grid grob object. } Please read the associated vignette for more information and sample code. } \section{PDF graphic devices}{ if plotting on a PDF graphic device -- started with \code{\link{pdf}}, one may get generate a first blank page, due to internals of standard functions from the \pkg{grid} package that are called by \code{aheatmap}. The \pkg{NMF} package ships a custom patch that fixes this issue. However, in order to comply with CRAN policies, the patch is \strong{not} applied by default and the user must explicitly be enabled it. This can be achieved on runtime by either setting the NMF specific option 'grid.patch' via \code{nmf.options(grid.patch=TRUE)}, or on load time if the environment variable 'R_PACKAGE_NMF_GRID_PATCH' is defined and its value is something that is not equivalent to \code{FALSE} (i.e. not '', 'false' nor 0). } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } ## See the demo 'aheatmap' for more examples: \dontrun{ demo('aheatmap') } # Generate random data n <- 50; p <- 20 x <- abs(rmatrix(n, p, rnorm, mean=4, sd=1)) x[1:10, seq(1, 10, 2)] <- x[1:10, seq(1, 10, 2)] + 3 x[11:20, seq(2, 10, 2)] <- x[11:20, seq(2, 10, 2)] + 2 rownames(x) <- paste("ROW", 1:n) colnames(x) <- paste("COL", 1:p) ## Default heatmap aheatmap(x) ## Distance methods aheatmap(x, Rowv = "correlation") aheatmap(x, Rowv = "man") # partially matched to 'manhattan' aheatmap(x, Rowv = "man", Colv="binary") # Generate column annotations annotation = data.frame(Var1 = factor(1:p \%\% 2 == 0, labels = c("Class1", "Class2")), Var2 = 1:10) aheatmap(x, annCol = annotation) } \author{ Original version of \code{pheatmap}: Raivo Kolde Enhancement into \code{aheatmap}: Renaud Gaujoux } NMF/man/staticVar.Rd0000644000176200001440000000155213620502674013704 0ustar liggesusers\name{staticVar} \alias{staticVar} \title{Get/Set a Static Variable in NMF Algorithms} \usage{ staticVar(name, value, init = FALSE) } \arguments{ \item{name}{Name of the static variable (as a single character string)} \item{value}{New value of the static variable} \item{init}{a logical used when a \code{value} is provided, that specifies if the variable should be set to the new value only if it does not exist yet (\code{init=TRUE}).} } \value{ The value of the static variable } \description{ This function is used in iterative NMF algorithms to manage variables stored in a local workspace, that are accessible to all functions that define the iterative schema described in \code{\linkS4class{NMFStrategyIterative}}. It is specially useful for computing stopping criteria, which often require model data from different iterations. } NMF/man/NMF-class.Rd0000644000176200001440000004112113620502674013463 0ustar liggesusers\docType{class} \name{NMF-class} \alias{.DollarNames,NMF-method} \alias{misc} \alias{NMF-class} \alias{$<-,NMF-method} \alias{$,NMF-method} \title{Generic Interface for Nonnegative Matrix Factorisation Models} \usage{ misc(object, ...) \S4method{$}{NMF}(x, name) \S4method{$}{NMF}(x, name)<-value \S4method{.DollarNames}{NMF}(x, pattern = "") } \arguments{ \item{object}{an object that inherit from class \code{NMF}} \item{...}{extra arguments (not used)} \item{x}{ object from which to extract element(s) or in which to replace element(s). } \item{name}{ A literal character string or a \link{name} (possibly \link{backtick} quoted). For extraction, this is normally (see under \sQuote{Environments}) partially matched to the \code{\link{names}} of the object. } \item{value}{typically an array-like \R object of a similar class as \code{x}.} \item{pattern}{ A regular expression. Only matching names are returned. } } \description{ The class \code{NMF} is a \emph{virtual class} that defines a common interface to handle Nonnegative Matrix Factorization models (NMF models) in a generic way. Provided a minimum set of generic methods is implemented by concrete model classes, these benefit from a whole set of functions and utilities to perform common computations and tasks in the context of Nonnegative Matrix Factorization. The function \code{misc} provides access to miscellaneous data members stored in slot \code{misc} (as a \code{list}), which allow extensions of NMF models to be implemented, without defining a new S4 class. } \details{ Class \code{NMF} makes it easy to develop new models that integrate well into the general framework implemented by the \emph{NMF} package. Following a few simple guidelines, new types of NMF models benefit from all the functionalities available for the built-in NMF models -- that derive themselves from class \code{NMF}. See section \emph{Implementing NMF models} below. See \code{\linkS4class{NMFstd}}, and references and links therein for details on the built-in implementations of the standard NMF model and its extensions. } \section{Slots}{ \describe{ \item{misc}{A list that is used internally to temporarily store algorithm parameters during the computation.} } } \section{Methods}{ \describe{ \item{[}{\code{signature(x = "NMF")}: This method provides a convenient way of sub-setting objects of class \code{NMF}, using a matrix-like syntax. It allows to consistently subset one or both matrix factors in the NMF model, as well as retrieving part of the basis components or part of the mixture coefficients with a reduced amount of code. See \code{\link{[,NMF-method}} for more details. } \item{$}{\code{signature(x = "NMF")}: shortcut for \code{x@misc[[name, exact=TRUE]]} respectively. } \item{$}{\code{signature(x = "NMF")}: shortcut for \code{x@misc[[name, exact=TRUE]]} respectively. } \item{$<-}{\code{signature(x = "NMF")}: shortcut for \code{x@misc[[name]] <- value} } \item{$<-}{\code{signature(x = "NMF")}: shortcut for \code{x@misc[[name]] <- value} } \item{.basis}{\code{signature(object = "NMF")}: Pure virtual method for objects of class \code{\linkS4class{NMF}}, that should be overloaded by sub-classes, and throws an error if called. } \item{.basis<-}{\code{signature(object = "NMF", value = "matrix")}: Pure virtual method for objects of class \code{\linkS4class{NMF}}, that should be overloaded by sub-classes, and throws an error if called. } \item{basis<-}{\code{signature(object = "NMF")}: Default methods that calls \code{.basis<-} and check the validity of the updated object. } \item{basiscor}{\code{signature(x = "NMF", y = "matrix")}: Computes the correlations between the basis vectors of \code{x} and the columns of \code{y}. } \item{basiscor}{\code{signature(x = "NMF", y = "NMF")}: Computes the correlations between the basis vectors of \code{x} and \code{y}. } \item{basiscor}{\code{signature(x = "NMF", y = "missing")}: Computes the correlations between the basis vectors of \code{x}. } \item{basismap}{\code{signature(object = "NMF")}: Plots a heatmap of the basis matrix of the NMF model \code{object}. This method also works for fitted NMF models (i.e. \code{NMFfit} objects). } \item{c}{\code{signature(x = "NMF")}: Binds compatible matrices and NMF models together. } \item{.coef}{\code{signature(object = "NMF")}: Pure virtual method for objects of class \code{\linkS4class{NMF}}, that should be overloaded by sub-classes, and throws an error if called. } \item{.coef<-}{\code{signature(object = "NMF", value = "matrix")}: Pure virtual method for objects of class \code{\linkS4class{NMF}}, that should be overloaded by sub-classes, and throws an error if called. } \item{coef<-}{\code{signature(object = "NMF")}: Default methods that calls \code{.coef<-} and check the validity of the updated object. } \item{coefficients}{\code{signature(object = "NMF")}: Alias to \code{coef,NMF}, therefore also pure virtual. } \item{coefmap}{\code{signature(object = "NMF")}: The default method for NMF objects has special default values for some arguments of \code{\link{aheatmap}} (see argument description). } \item{connectivity}{\code{signature(object = "NMF")}: Computes the connectivity matrix for an NMF model, for which cluster membership is given by the most contributing basis component in each sample. See \code{\link{predict,NMF-method}}. } \item{consensus}{\code{signature(object = "NMF")}: This method is provided for completeness and is identical to \code{\link{connectivity}}, and returns the connectivity matrix, which, in the case of a single NMF model, is also the consensus matrix. } \item{consensushc}{\code{signature(object = "NMF")}: Compute the hierarchical clustering on the connectivity matrix of \code{object}. } \item{consensusmap}{\code{signature(object = "NMF")}: Plots a heatmap of the connectivity matrix of an NMF model. } \item{deviance}{\code{signature(object = "NMF")}: Computes the distance between a matrix and the estimate of an \code{NMF} model. } \item{dim}{\code{signature(x = "NMF")}: method for NMF objects for the base generic \code{\link{dim}}. It returns all dimensions in a length-3 integer vector: the number of row and columns of the estimated target matrix, as well as the factorization rank (i.e. the number of basis components). } \item{dimnames}{\code{signature(x = "NMF")}: Returns the dimension names of the NMF model \code{x}. It returns either NULL if no dimnames are set on the object, or a 3-length list containing the row names of the basis matrix, the column names of the mixture coefficient matrix, and the column names of the basis matrix (i.e. the names of the basis components). } \item{dimnames<-}{\code{signature(x = "NMF")}: sets the dimension names of the NMF model \code{x}. \code{value} can be \code{NULL} which resets all dimension names, or a 1, 2 or 3-length list providing names at least for the rows of the basis matrix. See \code{\link{dimnames}} for more details. } \item{.DollarNames}{\code{signature(x = "NMF")}: Auto-completion for \code{\linkS4class{NMF}} objects } \item{.DollarNames}{\code{signature(x = "NMF")}: Auto-completion for \code{\linkS4class{NMF}} objects } \item{extractFeatures}{\code{signature(object = "NMF")}: Select basis-specific features from an NMF model, by applying the method \code{extractFeatures,matrix} to its basis matrix. } \item{featureScore}{\code{signature(object = "NMF")}: Computes feature scores on the basis matrix of an NMF model. } \item{fitted}{\code{signature(object = "NMF")}: Pure virtual method for objects of class \code{\linkS4class{NMF}}, that should be overloaded by sub-classes, and throws an error if called. } \item{ibterms}{\code{signature(object = "NMF")}: Default pure virtual method that ensure a method is defined for concrete NMF model classes. } \item{icterms}{\code{signature(object = "NMF")}: Default pure virtual method that ensure a method is defined for concrete NMF model classes. } \item{loadings}{\code{signature(x = "NMF")}: Method loadings for NMF Models The method \code{loadings} is identical to \code{basis}, but do not accept any extra argument. See \code{\link{loadings,NMF-method}} for more details. } \item{metaHeatmap}{\code{signature(object = "NMF")}: Deprecated method that is substituted by \code{\link{coefmap}} and \code{\link{basismap}}. } \item{nmf.equal}{\code{signature(x = "NMF", y = "NMF")}: Compares two NMF models. Arguments in \code{...} are used only when \code{identical=FALSE} and are passed to \code{all.equal}. } \item{nmf.equal}{\code{signature(x = "NMF", y = "NMFfit")}: Compares two NMF models when at least one comes from a NMFfit object, i.e. an object returned by a single run of \code{\link{nmf}}. } \item{nmf.equal}{\code{signature(x = "NMF", y = "NMFfitX")}: Compares two NMF models when at least one comes from multiple NMF runs. } \item{nneg}{\code{signature(object = "NMF")}: Apply \code{nneg} to the basis matrix of an \code{\link{NMF}} object (i.e. \code{basis(object)}). All extra arguments in \code{...} are passed to the method \code{nneg,matrix}. } \item{predict}{\code{signature(object = "NMF")}: Default method for NMF models } \item{profcor}{\code{signature(x = "NMF", y = "matrix")}: Computes the correlations between the basis profiles of \code{x} and the rows of \code{y}. } \item{profcor}{\code{signature(x = "NMF", y = "NMF")}: Computes the correlations between the basis profiles of \code{x} and \code{y}. } \item{profcor}{\code{signature(x = "NMF", y = "missing")}: Computes the correlations between the basis profiles of \code{x}. } \item{rmatrix}{\code{signature(x = "NMF")}: Returns the target matrix estimate of the NMF model \code{x}, perturbated by adding a random matrix generated using the default method of \code{rmatrix}: it is a equivalent to \code{fitted(x) + rmatrix(fitted(x), ...)}. This method can be used to generate random target matrices that depart from a known NMF model to a controlled extend. This is useful to test the robustness of NMF algorithms to the presence of certain types of noise in the data. } \item{rnmf}{\code{signature(x = "NMF", target = "numeric")}: Generates a random NMF model of the same class and rank as another NMF model. This is the workhorse method that is eventually called by all other methods. It generates an NMF model of the same class and rank as \code{x}, compatible with the dimensions specified in \code{target}, that can be a single or 2-length numeric vector, to specify a square or rectangular target matrix respectively. See \code{\link{rnmf,NMF,numeric-method}} for more details. } \item{rnmf}{\code{signature(x = "NMF", target = "missing")}: Generates a random NMF model of the same dimension as another NMF model. It is a shortcut for \code{rnmf(x, nrow(x), ncol(x), ...)}, which returns a random NMF model of the same class and dimensions as \code{x}. } \item{rposneg}{\code{signature(object = "NMF")}: Apply \code{rposneg} to the basis matrix of an \code{\link{NMF}} object. } \item{show}{\code{signature(object = "NMF")}: Show method for objects of class \code{NMF} } \item{sparseness}{\code{signature(x = "NMF")}: Compute the sparseness of an object of class \code{NMF}, as the sparseness of the basis and coefficient matrices computed separately. It returns the two values in a numeric vector with names \sQuote{basis} and \sQuote{coef}. } \item{summary}{\code{signature(object = "NMF")}: Computes summary measures for a single NMF model. The following measures are computed: See \code{\link{summary,NMF-method}} for more details. } } } \section{Implementing NMF models}{ The class \code{NMF} only defines a basic data/low-level interface for NMF models, as a collection of generic methods, responsible with data handling, upon which relies a comprehensive set of functions, composing a rich higher-level interface. Actual NMF models are defined as sub-classes that inherits from class \code{NMF}, and implement the management of data storage, providing definitions for the interface's pure virtual methods. The minimum requirement to define a new NMF model that integrates into the framework of the \emph{NMF} package are the followings: \itemize{ \item Define a class that inherits from class \code{NMF} and implements the new model, say class \code{myNMF}. \item Implement the following S4 methods for the new class \code{myNMF}: \describe{ \item{fitted}{\code{signature(object = "myNMF", value = "matrix")}: Must return the estimated target matrix as fitted by the NMF model \code{object}. } \item{basis}{\code{signature(object = "myNMF")}: Must return the basis matrix(e.g. the first matrix factor in the standard NMF model). } \item{basis<-}{\code{signature(object = "myNMF", value = "matrix")}: Must return \code{object} with the basis matrix set to \code{value}. } \item{coef}{\code{signature(object = "myNMF")}: Must return the matrix of mixture coefficients (e.g. the second matrix factor in the standard NMF model). } \item{coef<-}{\code{signature(object = "myNMF", value = "matrix")}: Must return \code{object} with the matrix of mixture coefficients set to \code{value}. } } The \emph{NMF} package provides "pure virtual" definitions of these methods for class \code{NMF} (i.e. with signatures \code{(object='NMF', ...)} and \code{(object='NMF', value='matrix')}) that throw an error if called, so as to force their definition for model classes. \item Optionally, implement method \code{rnmf}(signature(x="myNMF", target="ANY")). This method should call \code{callNextMethod(x=x, target=target, ...)} and fill the returned NMF model with its specific data suitable random values. } For concrete examples of NMF models implementations, see class \code{\linkS4class{NMFstd}} and its extensions (e.g. classes \code{\linkS4class{NMFOffset}} or \code{\linkS4class{NMFns}}). } \section{Creating NMF objects}{ Strictly speaking, because class \code{NMF} is virtual, no object of class \code{NMF} can be instantiated, only objects from its sub-classes. However, those objects are sometimes shortly referred in the documentation and vignettes as "\code{NMF} objects" instead of "objects that inherits from class \code{NMF}". For built-in models or for models that inherit from the standard model class \code{\linkS4class{NMFstd}}, the factory method \code{nmfModel} enables to easily create valid \code{NMF} objects in a variety of common situations. See documentation for the the factory method \code{\link{nmfModel}} for more details. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # show all the NMF models available (i.e. the classes that inherit from class NMF) nmfModels() # show all the built-in NMF models available nmfModels(builtin.only=TRUE) # class NMF is a virtual class so cannot be instantiated: try( new('NMF') ) # To instantiate an NMF model, use the factory method nmfModel. see ?nmfModel nmfModel() nmfModel(3) nmfModel(3, model='NMFns') } \references{ Definition of Nonnegative Matrix Factorization in its modern formulation: \cite{Lee et al. (1999)} Historical first definition and algorithms: \cite{Paatero et al. (1994)} Lee DD and Seung HS (1999). "Learning the parts of objects by non-negative matrix factorization." _Nature_, *401*(6755), pp. 788-91. ISSN 0028-0836, , . Paatero P and Tapper U (1994). "Positive matrix factorization: A non-negative factor model with optimal utilization of error estimates of data values." _Environmetrics_, *5*(2), pp. 111-126. , . } \seealso{ Main interface to perform NMF in \code{\link{nmf-methods}}. Built-in NMF models and factory method in \code{\link{nmfModel}}. Method \code{\link{seed}} to set NMF objects with values suitable to start algorithms with. Other NMF-interface: \code{\link{basis}}, \code{\link{.basis}}, \code{\link{.basis<-}}, \code{\link{basis<-}}, \code{\link{coef}}, \code{\link{.coef}}, \code{\link{.coef<-}}, \code{\link{coef<-}}, \code{\link{coefficients}}, \code{\link{loadings,NMF-method}}, \code{\link{nmfModel}}, \code{\link{nmfModels}}, \code{\link{rnmf}}, \code{\link{scoef}} } \keyword{methods} NMF/man/runtime-commaNMFList-method.Rd0000644000176200001440000000150513620502674017172 0ustar liggesusers\docType{methods} \name{runtime,NMFList-method} \alias{runtime,NMFList-method} \title{Returns the CPU time required to compute all NMF fits in the list. It returns \code{NULL} if the list is empty. If no timing data are available, the sequential time is returned.} \usage{ \S4method{runtime}{NMFList}(object, all = FALSE) } \arguments{ \item{all}{logical that indicates if the CPU time of each fit should be returned (\code{TRUE}) or only the total CPU time used to compute all the fits in \code{object}.} \item{object}{an object computed using some algorithm, or that describes an algorithm itself.} } \description{ Returns the CPU time required to compute all NMF fits in the list. It returns \code{NULL} if the list is empty. If no timing data are available, the sequential time is returned. } \keyword{methods} NMF/man/rmatrix.Rd0000644000176200001440000001075613620502674013440 0ustar liggesusers\docType{methods} \name{rmatrix} \alias{rmatrix} \alias{rmatrix,ANY-method} \alias{rmatrix-methods} \alias{rmatrix,NMF-method} \alias{rmatrix,numeric-method} \title{Generating Random Matrices} \usage{ rmatrix(x, ...) \S4method{rmatrix}{numeric}(x, y = NULL, dist = runif, byrow = FALSE, dimnames = NULL, ...) } \arguments{ \item{x}{object from which to generate a random matrix} \item{y}{optional specification of number of columns} \item{dist}{a random distribution function or a numeric seed (see details of method \code{rmatrix,numeric})} \item{byrow}{a logical passed in the internal call to the function \code{\link{matrix}}} \item{dimnames}{\code{NULL} or a \code{list} passed in the internal call to the function \code{\link{matrix}}} \item{...}{extra arguments passed to the distribution function \code{dist}.} } \description{ The S4 generic \code{rmatrix} generates a random matrix from a given object. Methods are provided to generate matrices with entries drawn from any given random distribution function, e.g. \code{\link{runif}} or \code{\link{rnorm}}. } \section{Methods}{ \describe{ \item{rmatrix}{\code{signature(x = "numeric")}: Generates a random matrix of given dimensions, whose entries are drawn using the distribution function \code{dist}. This is the workhorse method that is eventually called by all other methods. It returns a matrix with: \itemize{ \item \code{x} rows and \code{y} columns if \code{y} is not missing and not \code{NULL}; \item dimension \code{x[1]} x \code{x[2]} if \code{x} has at least two elements; \item dimension \code{x} (i.e. a square matrix) otherwise. } The default is to draw its entries from the standard uniform distribution using the base function \code{\link{runif}}, but any other function that generates random numeric vectors of a given length may be specified in argument \code{dist}. All arguments in \code{...} are passed to the function specified in \code{dist}. The only requirement is that the function in \code{dist} is of the following form: \samp{ function(n, ...){ # return vector of length n ... }} This is the case of all base random draw function such as \code{\link{rnorm}}, \code{\link{rgamma}}, etc\ldots } \item{rmatrix}{\code{signature(x = "ANY")}: Default method which calls \code{rmatrix,vector} on the dimensions of \code{x} that is assumed to be returned by a suitable \code{dim} method: it is equivalent to \code{rmatrix(dim(x), y=NULL, ...)}. } \item{rmatrix}{\code{signature(x = "NMF")}: Returns the target matrix estimate of the NMF model \code{x}, perturbated by adding a random matrix generated using the default method of \code{rmatrix}: it is a equivalent to \code{fitted(x) + rmatrix(fitted(x), ...)}. This method can be used to generate random target matrices that depart from a known NMF model to a controlled extend. This is useful to test the robustness of NMF algorithms to the presence of certain types of noise in the data. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } #---------- # rmatrix,numeric-method #---------- ## Generate a random matrix of a given size rmatrix(5, 3) \dontshow{ stopifnot( identical(dim(rmatrix(5, 3)), c(5L,3L)) ) } ## Generate a random matrix of the same dimension of a template matrix a <- matrix(1, 3, 4) rmatrix(a) \dontshow{ stopifnot( identical(dim(rmatrix(a)), c(3L,4L)) ) } ## Specificy the distribution to use # the default is uniform a <- rmatrix(1000, 50) \dontrun{ hist(a) } # use normal ditribution a <- rmatrix(1000, 50, rnorm) \dontrun{ hist(a) } # extra arguments can be passed to the random variate generation function a <- rmatrix(1000, 50, rnorm, mean=2, sd=0.5) \dontrun{ hist(a) } #---------- # rmatrix,ANY-method #---------- # random matrix of the same dimension as another matrix x <- matrix(3,4) dim(rmatrix(x)) #---------- # rmatrix,NMF-method #---------- # generate noisy fitted target from an NMF model (the true model) gr <- as.numeric(mapply(rep, 1:3, 3)) h <- outer(1:3, gr, '==') + 0 x <- rnmf(10, H=h) y <- rmatrix(x) \dontrun{ # show heatmap of the noisy target matrix: block patterns should be clear aheatmap(y) } \dontshow{ stopifnot( identical(dim(y), dim(x)[1:2]) ) } # test NMF algorithm on noisy data # add some noise to the true model (drawn from uniform [0,1]) res <- nmf(rmatrix(x), 3) summary(res) # add more noise to the true model (drawn from uniform [0,10]) res <- nmf(rmatrix(x, max=10), 3) summary(res) } \keyword{methods} NMF/man/NMF-defunct.Rd0000644000176200001440000000157613620502674014020 0ustar liggesusers\docType{methods} \name{NMF-defunct} \alias{metaHeatmap} \alias{metaHeatmap,matrix-method} \alias{metaHeatmap-methods} \alias{metaHeatmap,NMFfitX-method} \alias{metaHeatmap,NMF-method} \alias{NMF-defunct} \title{Defunct Functions and Classes in the NMF Package} \usage{ metaHeatmap(object, ...) } \arguments{ \item{object}{an R object} \item{...}{other arguments} } \description{ Defunct Functions and Classes in the NMF Package } \section{Methods}{ \describe{ \item{metaHeatmap}{\code{signature(object = "matrix")}: Defunct method substituted by \code{\link{aheatmap}}. } \item{metaHeatmap}{\code{signature(object = "NMF")}: Deprecated method that is substituted by \code{\link{coefmap}} and \code{\link{basismap}}. } \item{metaHeatmap}{\code{signature(object = "NMFfitX")}: Deprecated method subsituted by \code{\link{consensusmap}}. } } } \keyword{methods} NMF/man/SNMF-nmf.Rd0000644000176200001440000000546113620502674013270 0ustar liggesusers\name{nmfAlgorithm.SNMF_R} \alias{nmfAlgorithm.SNMF_L} \alias{nmfAlgorithm.SNMF_R} \alias{SNMF/L-nmf} \alias{SNMF/R-nmf} \title{NMF Algorithm - Sparse NMF via Alternating NNLS} \usage{ nmfAlgorithm.SNMF_R(..., maxIter = 20000L, eta = -1, beta = 0.01, bi_conv = c(0, 10), eps_conv = 1e-04) nmfAlgorithm.SNMF_L(..., maxIter = 20000L, eta = -1, beta = 0.01, bi_conv = c(0, 10), eps_conv = 1e-04) } \arguments{ \item{maxIter}{maximum number of iterations.} \item{eta}{parameter to suppress/bound the L2-norm of \code{W} and in \code{H} in \sQuote{SNMF/R} and \sQuote{SNMF/L} respectively. If \code{eta < 0}, then it is set to the maximum value in the target matrix is used.} \item{beta}{regularisation parameter for sparsity control, which balances the trade-off between the accuracy of the approximation and the sparseness of \code{H} and \code{W} in \sQuote{SNMF/R} and \sQuote{SNMF/L} respectively. Larger beta generates higher sparseness on \code{H} (resp. \code{W}). Too large beta is not recommended.} \item{bi_conv}{parameter of the biclustering convergence test. It must be a size 2 numeric vector \code{bi_conv=c(wminchange, iconv)}, with: \describe{ \item{\code{wminchange}:}{the minimal allowance of change in row-clusters.} \item{\code{iconv}:}{ decide convergence if row-clusters (within the allowance of \code{wminchange}) and column-clusters have not changed for \code{iconv} convergence checks.} } Convergence checks are performed every 5 iterations.} \item{eps_conv}{threshold for the KKT convergence test.} \item{...}{extra argument not used.} } \description{ NMF algorithms proposed by \cite{Kim et al. (2007)} that enforces sparsity constraint on the basis matrix (algorithm \sQuote{SNMF/L}) or the mixture coefficient matrix (algorithm \sQuote{SNMF/R}). } \details{ The algorithm \sQuote{SNMF/R} solves the following NMF optimization problem on a given target matrix \eqn{A} of dimension \eqn{n \times p}{n x p}: \deqn{ \begin{array}{ll} & \min_{W,H} \frac{1}{2} \left(|| A - WH ||_F^2 + \eta ||W||_F^2 + \beta (\sum_{j=1}^p ||H_{.j}||_1^2)\right)\\ s.t. & W\geq 0, H\geq 0 \end{array} }{ min_{W,H} 1/2 (|| A - WH ||_F^2 + eta ||W||_F^2 + beta (sum_j ||H[,j]||_1^2)) s.t. W>=0, H>=0 } The algorithm \sQuote{SNMF/L} solves a similar problem on the transposed target matrix \eqn{A}, where \eqn{H} and \eqn{W} swap roles, i.e. with sparsity constraints applied to \code{W}. } \references{ Kim H and Park H (2007). "Sparse non-negative matrix factorizations via alternating non-negativity-constrained least squares for microarray data analysis." _Bioinformatics (Oxford, England)_, *23*(12), pp. 1495-502. ISSN 1460-2059, , . } NMF/man/deviance.Rd0000644000176200001440000000623613620502674013526 0ustar liggesusers\docType{methods} \name{deviance} \alias{deviance} \alias{deviance-methods} \alias{deviance,NMFfit-method} \alias{deviance,NMFfitX-method} \alias{deviance,NMF-method} \alias{deviance,NMFStrategy-method} \alias{nmfDistance} \title{Distances and Objective Functions} \usage{ deviance(object, ...) \S4method{deviance}{NMF}(object, y, method = c("", "KL", "euclidean"), ...) nmfDistance(method = c("", "KL", "euclidean")) \S4method{deviance}{NMFfit}(object, y, method, ...) \S4method{deviance}{NMFStrategy}(object, x, y, ...) } \arguments{ \item{y}{a matrix compatible with the NMF model \code{object}, i.e. \code{y} must have the same dimension as \code{fitted(object)}.} \item{method}{a character string or a function with signature \code{(x="NMF", y="matrix", ...)} that implements a distance measure between an NMF model \code{x} and a target matrix \code{y}, i.e. an objective function to use to compute the deviance. In \code{deviance}, it is passed to \code{nmfDistance} to get the function that effectively computes the deviance.} \item{...}{extra parameters passed to the objective function.} \item{x}{an NMF model that estimates \code{y}.} \item{object}{an object for which the deviance is desired.} } \value{ \code{deviance} returns a nonnegative numerical value \code{nmfDistance} returns a function with least two arguments: an NMF model and a matrix. } \description{ The NMF package defines methods for the generic \code{deviance} from the package \code{stats}, to compute approximation errors between NMF models and matrices, using a variety of objective functions. \code{nmfDistance} returns a function that computes the distance between an NMF model and a compatible matrix. } \section{Methods}{ \describe{ \item{deviance}{\code{signature(object = "NMF")}: Computes the distance between a matrix and the estimate of an \code{NMF} model. } \item{deviance}{\code{signature(object = "NMFfit")}: Returns the deviance of a fitted NMF model. This method returns the final residual value if the target matrix \code{y} is not supplied, or the approximation error between the fitted NMF model stored in \code{object} and \code{y}. In this case, the computation is performed using the objective function \code{method} if not missing, or the objective of the algorithm that fitted the model (stored in slot \code{'distance'}). If not computed by the NMF algorithm itself, the value is automatically computed at the end of the fitting process by the function \code{\link{nmf}}, using the objective function associated with the NMF algorithm, so that it should always be available. } \item{deviance}{\code{signature(object = "NMFfitX")}: Returns the deviance achieved by the best fit object, i.e. the lowest deviance achieved across all NMF runs. } \item{deviance}{\code{signature(object = "NMFStrategy")}: Computes the value of the objective function between the estimate \code{x} and the target \code{y}. } } } \seealso{ Other stats: \code{\link{deviance,NMF-method}}, \code{\link{hasTrack}}, \code{\link{residuals}}, \code{\link{residuals<-}}, \code{\link{trackError}} } \keyword{methods} NMF/man/foreach.Rd0000644000176200001440000001155613620502674013360 0ustar liggesusers\docType{methods} \name{registerDoBackend} \alias{ForeachBackend} \alias{ForeachBackend,ANY-method} \alias{ForeachBackend,character-method} \alias{ForeachBackend,cluster-method} \alias{ForeachBackend,doMPI_backend-method} \alias{ForeachBackend,doParallel_backend-method} \alias{ForeachBackend,doParallelMC_backend-method} \alias{ForeachBackend,doParallelSNOW_backend-method} \alias{ForeachBackend,doPSOCK_backend-method} \alias{ForeachBackend-methods} \alias{ForeachBackend,missing-method} \alias{ForeachBackend,mpicluster-method} \alias{ForeachBackend,NULL-method} \alias{ForeachBackend,numeric-method} \alias{getDoBackend} \alias{getDoParHosts} \alias{getDoParHosts,ANY-method} \alias{getDoParHosts-methods} \alias{getDoParNHosts} \alias{register} \alias{registerDoBackend} \alias{setDoBackend} \title{Utilities and Extensions for Foreach Loops} \usage{ registerDoBackend(object, ...) getDoBackend() setDoBackend(data, cleanup = FALSE) register(x, ...) ForeachBackend(object, ...) \S4method{ForeachBackend}{doParallel_backend}(object, cl, type = NULL) \S4method{ForeachBackend}{doPSOCK_backend}(object, cl) \S4method{ForeachBackend}{doMPI_backend}(object, cl) getDoParHosts(object, ...) getDoParNHosts(object) } \arguments{ \item{object}{specification of a foreach backend, e.g. \sQuote{SEQ}, \sQuote{PAR} (for doParallel), \sQuote{MPI}, etc\ldots} \item{...}{extra arguments passed to the backend own registration function.} \item{data}{internal data of a foreach \%dopar\% backend.} \item{cleanup}{logical that indicates if the previous backend's cleanup procedure should be run, \strong{before} setting the new backend.} \item{x}{specification of a foreach backend} \item{cl}{cluster specification: a cluster object or a numeric that indicates the number of nodes to use.} \item{type}{type of cluster, See \code{\link[parallel]{makeCluster}}.} } \description{ \code{registerDoBackend} is a unified register function for foreach backends. \code{getDoBackend} returns the internal data of the currently registered foreach \%dopar\% backend. \code{setDoBackend} is identical to \code{\link[foreach]{setDoPar}}, but returns the internal of the previously registered backend. \code{register} is a generic function that register objects. It is used to as a unified interface to register foreach backends. \code{ForeachBackend} is a factory method for foreach backend objects. \code{getDoParHosts} is a generic function that returns the hostname of the worker nodes used by a backend. \code{getDoParNHosts} returns the number of hosts used by a backend. } \section{Methods}{ \describe{ \item{ForeachBackend}{\code{signature(object = "ANY")}: Default method defined to throw an informative error message, when no other method was found. } \item{ForeachBackend}{\code{signature(object = "character")}: Creates a foreach backend object based on its name. } \item{ForeachBackend}{\code{signature(object = "missing")}: Creates a foreach backend object for the currently registered backend. } \item{ForeachBackend}{\code{signature(object = "NULL")}: Dummy method that returns \code{NULL}, defined for correct dispatch. } \item{ForeachBackend}{\code{signature(object = "cluster")}: Creates a doParallel foreach backend that uses the cluster described in \code{object}. } \item{ForeachBackend}{\code{signature(object = "numeric")}: Creates a doParallel foreach backend with \code{object} processes. } \item{ForeachBackend}{\code{signature(object = "doParallel_backend")}: doParallel-specific backend factory } \item{ForeachBackend}{\code{signature(object = "doParallelMC_backend")}: doParallel-specific backend factory for multicore (fork) clusters This method is needed since version 1.0.7 of \pkg{doParallel}, which removed internal function \code{info} and defined separate backend names for mc and snow clusters. } \item{ForeachBackend}{\code{signature(object = "doParallelSNOW_backend")}: doParallel-specific backend factory for SNOW clusters. This method is needed since version 1.0.7 of \pkg{doParallel}, which removed internal function \code{info} and defined separate backend names for mc and snow clusters. } \item{ForeachBackend}{\code{signature(object = "doPSOCK_backend")}: doSNOW-specific backend factory } \item{ForeachBackend}{\code{signature(object = "mpicluster")}: Creates a doMPI foreach backend that uses the MPI cluster described in \code{object}. } \item{ForeachBackend}{\code{signature(object = "doMPI_backend")}: doMPI-specific backend factory } \item{getDoParHosts}{\code{signature(object = "ANY")}: Default method that tries to heuristaically infer the number of hosts and in last resort temporarly register the backend and performs a foreach loop, to retrieve the nodename from each worker. } } } \keyword{internal} \keyword{methods} NMF/man/heatmaps.Rd0000644000176200001440000003661013620502674013551 0ustar liggesusers\docType{methods} \name{heatmap-NMF} \alias{basismap} \alias{basismap-methods} \alias{basismap,NMFfitX-method} \alias{basismap,NMF-method} \alias{coefmap} \alias{coefmap-methods} \alias{coefmap,NMFfitX-method} \alias{coefmap,NMF-method} \alias{consensusmap} \alias{consensusmap,matrix-method} \alias{consensusmap-methods} \alias{consensusmap,NMFfitX-method} \alias{consensusmap,NMF-method} \alias{heatmap-NMF} \title{Heatmaps of NMF Factors} \usage{ basismap(object, ...) \S4method{basismap}{NMF}(object, color = "YlOrRd:50", scale = "r1", Rowv = TRUE, Colv = NA, subsetRow = FALSE, annRow = NA, annCol = NA, tracks = "basis", main = "Basis components", info = FALSE, ...) coefmap(object, ...) \S4method{coefmap}{NMF}(object, color = "YlOrRd:50", scale = "c1", Rowv = NA, Colv = TRUE, annRow = NA, annCol = NA, tracks = "basis", main = "Mixture coefficients", info = FALSE, ...) consensusmap(object, ...) \S4method{consensusmap}{NMFfitX}(object, annRow = NA, annCol = NA, tracks = c("basis:", "consensus:", "silhouette:"), main = "Consensus matrix", info = FALSE, ...) \S4method{consensusmap}{matrix}(object, color = "-RdYlBu", distfun = function(x) as.dist(1 - x), hclustfun = "average", Rowv = TRUE, Colv = "Rowv", main = if (is.null(nr) || nr > 1) "Consensus matrix" else "Connectiviy matrix", info = FALSE, ...) \S4method{coefmap}{NMFfitX}(object, Colv = TRUE, annRow = NA, annCol = NA, tracks = c("basis", "consensus:"), ...) } \arguments{ \item{object}{an object from which is extracted NMF factors or a consensus matrix} \item{...}{extra arguments passed to \code{\link{aheatmap}}.} \item{subsetRow}{Argument that specifies how to filter the rows that will appear in the heatmap. When \code{FALSE} (default), all rows are used. Besides the values supported by argument \code{subsetRow} of \code{\link{aheatmap}}, other possible values are: \itemize{ \item \code{TRUE}: only the rows that are basis-specific are used. The default selection method is from \cite{KimH2007}. This is equivalent to \code{subsetRow='kim'}. \item a single \code{character} string or numeric value that specifies the method to use to select the basis-specific rows, that should appear in the heatmap (cf. argument \code{method} for function \code{\link{extractFeatures}}). Note \code{\link{extractFeatures}} is called with argument \code{nodups=TRUE}, so that features that are selected for multiple components only appear once. }} \item{tracks}{Special additional annotation tracks to highlight associations between basis components and sample clusters: \describe{ \item{basis}{matches each row (resp. column) to the most contributing basis component in \code{basismap} (resp. \code{coefmap}). In \code{basismap} (resp. \code{coefmap}), adding a track \code{':basis'} to \code{annCol} (resp. \code{annRow}) makes the column (resp. row) corresponding to the component being also highlited using the mathcing colours.} }} \item{info}{if \code{TRUE} then the name of the algorithm that fitted the NMF model is displayed at the bottom of the plot, if available. Other wise it is passed as is to \code{aheatmap}.} \item{color}{colour specification for the heatmap. Default to palette '-RdYlBu2:100', i.e. reversed palette 'RdYlBu2' (a slight modification of RColorBrewer's palette 'RdYlBu') with 100 colors. Possible values are: \itemize{ \item a character/integer vector of length greater than 1 that is directly used and assumed to contain valid R color specifications. \item a single color/integer (between 0 and 8)/other numeric value that gives the dominant colors. Numeric values are converted into a pallete by \code{rev(sequential_hcl(2, h = x, l = c(50, 95)))}. Other values are concatenated with the grey colour '#F1F1F1'. \item one of RColorBrewer's palette name (see \code{\link[RColorBrewer]{display.brewer.all}}) , or one of 'RdYlBu2', 'rainbow', 'heat', 'topo', 'terrain', 'cm'. } When the coluor palette is specified with a single value, and is negative or preceded a minus ('-'), the reversed palette is used. The number of breaks can also be specified after a colon (':'). For example, the default colour palette is specified as '-RdYlBu2:100'.} \item{scale}{character indicating how the values should scaled in either the row direction or the column direction. Note that the scaling is performed after row/column clustering, so that it has no effect on the row/column ordering. Possible values are: \itemize{ \item \code{"row"}: center and standardize each row separately to row Z-scores \item \code{"column"}: center and standardize each column separately to column Z-scores \item \code{"r1"}: scale each row to sum up to one \item \code{"c1"}: scale each column to sum up to one \item \code{"none"}: no scaling }} \item{Rowv}{clustering specification(s) for the rows. It allows to specify the distance/clustering/ordering/display parameters to be used for the \emph{rows only}. Possible values are: \itemize{ \item \code{TRUE} or \code{NULL} (to be consistent with \code{\link{heatmap}}): compute a dendrogram from hierarchical clustering using the distance and clustering methods \code{distfun} and \code{hclustfun}. \item \code{NA}: disable any ordering. In this case, and if not otherwise specified with argument \code{revC=FALSE}, the heatmap shows the input matrix with the rows in their original order, with the first row on top to the last row at the bottom. Note that this differ from the behaviour or \code{\link{heatmap}}, but seemed to be a more sensible choice when vizualizing a matrix without reordering. \item an integer vector of length the number of rows of the input matrix (\code{nrow(x)}), that specifies the row order. As in the case \code{Rowv=NA}, the ordered matrix is shown first row on top, last row at the bottom. \item a character vector or a list specifying values to use instead of arguments \code{distfun}, \code{hclustfun} and \code{reorderfun} when clustering the rows (see the respective argument descriptions for a list of accepted values). If \code{Rowv} has no names, then the first element is used for \code{distfun}, the second (if present) is used for \code{hclustfun}, and the third (if present) is used for \code{reorderfun}. \item a numeric vector of weights, of length the number of rows of the input matrix, used to reorder the internally computed dendrogram \code{d} by \code{reorderfun(d, Rowv)}. \item \code{FALSE}: the dendrogram \emph{is} computed using methods \code{distfun}, \code{hclustfun}, and \code{reorderfun} but is not shown. \item a single integer that specifies how many subtrees (i.e. clusters) from the computed dendrogram should have their root faded out. This can be used to better highlight the different clusters. \item a single double that specifies how much space is used by the computed dendrogram. That is that this value is used in place of \code{treeheight}. }} \item{Colv}{clustering specification(s) for the columns. It accepts the same values as argument \code{Rowv} (modulo the expected length for vector specifications), and allow specifying the distance/clustering/ordering/display parameters to be used for the \emph{columns only}. \code{Colv} may also be set to \code{"Rowv"}, in which case the dendrogram or ordering specifications applied to the rows are also applied to the columns. Note that this is allowed only for square input matrices, and that the row ordering is in this case by default reversed (\code{revC=TRUE}) to obtain the diagonal in the standard way (from top-left to bottom-right). See argument \code{Rowv} for other possible values.} \item{annRow}{specifications of row annotation tracks displayed as coloured columns on the left of the heatmaps. The annotation tracks are drawn from left to right. The same conversion, renaming and colouring rules as for argument \code{annCol} apply.} \item{annCol}{specifications of column annotation tracks displayed as coloured rows on top of the heatmaps. The annotation tracks are drawn from bottom to top. A single annotation track can be specified as a single vector; multiple tracks are specified as a list, a data frame, or an \emph{ExpressionSet} object, in which case the phenotypic data is used (\code{pData(eset)}). Character or integer vectors are converted and displayed as factors. Unnamed tracks are internally renamed into \code{Xi}, with i being incremented for each unamed track, across both column and row annotation tracks. For each track, if no corresponding colour is specified in argument \code{annColors}, a palette or a ramp is automatically computed and named after the track's name.} \item{main}{Main title as a character string or a grob.} \item{distfun}{default distance measure used in clustering rows and columns. Possible values are: \itemize{ \item all the distance methods supported by \code{\link{dist}} (e.g. "euclidean" or "maximum"). \item all correlation methods supported by \code{\link{cor}}, such as \code{"pearson"} or \code{"spearman"}. The pairwise distances between rows/columns are then computed as \code{d <- dist(1 - cor(..., method = distfun))}. One may as well use the string "correlation" which is an alias for "pearson". \item an object of class \code{dist} such as returned by \code{\link{dist}} or \code{\link{as.dist}}. }} \item{hclustfun}{default clustering method used to cluster rows and columns. Possible values are: \itemize{ \item a method name (a character string) supported by \code{\link{hclust}} (e.g. \code{'average'}). \item an object of class \code{hclust} such as returned by \code{\link{hclust}} \item a dendrogram }} } \description{ The NMF package ships an advanced heatmap engine implemented by the function \code{\link{aheatmap}}. Some convenience heatmap functions have been implemented for NMF models, which redefine default values for some of the arguments of \code{\link{aheatmap}}, hence tuning the output specifically for NMF models. } \details{ \strong{IMPORTANT:} although they essentially have the same set of arguments, their order sometimes differ between them, as well as from \code{\link{aheatmap}}. We therefore strongly recommend to use fully named arguments when calling these functions. \code{basimap} default values for the following arguments of \code{\link{aheatmap}}: \itemize{ \item the color palette; \item the scaling specification, which by default scales each row separately so that they sum up to one (\code{scale='r1'}); \item the column ordering which is disabled; \item allowing for passing feature extraction methods in argument \code{subsetRow}, that are passed to \code{\link{extractFeatures}}. See argument description here and therein. \item the addition of a default named annotation track, that shows the dominant basis component for each row (i.e. each feature). This track is specified in argument \code{tracks} (see its argument description). By default, a matching column annotation track is also displayed, but may be disabled using \code{tracks=':basis'}. \item a suitable title and extra information like the fitting algorithm, when \code{object} is a fitted NMF model. } \code{coefmap} redefines default values for the following arguments of \code{\link{aheatmap}}: \itemize{ \item the color palette; \item the scaling specification, which by default scales each column separately so that they sum up to one (\code{scale='c1'}); \item the row ordering which is disabled; \item the addition of a default annotation track, that shows the most contributing basis component for each column (i.e. each sample). This track is specified in argument \code{tracks} (see its argument description). By default, a matching row annotation track is also displayed, but can be disabled using \code{tracks='basis:'}. \item a suitable title and extra information like the fitting algorithm, when \code{object} is a fitted NMF model. } \code{consensusmap} redefines default values for the following arguments of \code{\link{aheatmap}}: \itemize{ \item the colour palette; \item the column ordering which is set equal to the row ordering, since a consensus matrix is symmetric; \item the distance and linkage methods used to order the rows (and columns). The default is to use 1 minus the consensus matrix itself as distance, and average linkage. \item the addition of two special named annotation tracks, \code{'basis:'} and \code{'consensus:'}, that show, for each column (i.e. each sample), the dominant basis component in the best fit and the hierarchical clustering of the consensus matrix respectively (using 1-consensus as distance and average linkage). These tracks are specified in argument \code{tracks}, which behaves as in \code{\link{basismap}}. \item a suitable title and extra information like the type of NMF model or the fitting algorithm, when \code{object} is a fitted NMF model. } } \section{Methods}{ \describe{ \item{basismap}{\code{signature(object = "NMF")}: Plots a heatmap of the basis matrix of the NMF model \code{object}. This method also works for fitted NMF models (i.e. \code{NMFfit} objects). } \item{basismap}{\code{signature(object = "NMFfitX")}: Plots a heatmap of the basis matrix of the best fit in \code{object}. } \item{coefmap}{\code{signature(object = "NMF")}: The default method for NMF objects has special default values for some arguments of \code{\link{aheatmap}} (see argument description). } \item{coefmap}{\code{signature(object = "NMFfitX")}: Plots a heatmap of the coefficient matrix of the best fit in \code{object}. This method adds: \itemize{ \item an extra special column annotation track for multi-run NMF fits, \code{'consensus:'}, that shows the consensus cluster associated to each sample. \item a column sorting schema \code{'consensus'} that can be passed to argument \code{Colv} and orders the columns using the hierarchical clustering of the consensus matrix with average linkage, as returned by \code{\link{consensushc}(object)}. This is also the ordering that is used by default for the heatmap of the consensus matrix as ploted by \code{\link{consensusmap}}. } } \item{consensusmap}{\code{signature(object = "NMFfitX")}: Plots a heatmap of the consensus matrix obtained when fitting an NMF model with multiple runs. } \item{consensusmap}{\code{signature(object = "NMF")}: Plots a heatmap of the connectivity matrix of an NMF model. } \item{consensusmap}{\code{signature(object = "matrix")}: Main method that redefines default values for arguments of \code{\link{aheatmap}}. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } #---------- # heatmap-NMF #---------- ## More examples are provided in demo `heatmaps` \dontrun{ demo(heatmaps) } ## # random data with underlying NMF model v <- syntheticNMF(20, 3, 10) # estimate a model x <- nmf(v, 3) #---------- # basismap #---------- # show basis matrix basismap(x) \dontrun{ # without the default annotation tracks basismap(x, tracks=NA) } #---------- # coefmap #---------- # coefficient matrix coefmap(x) \dontrun{ # without the default annotation tracks coefmap(x, tracks=NA) } #---------- # consensusmap #---------- \dontrun{ res <- nmf(x, 3, nrun=3) consensusmap(res) } } \keyword{methods} NMF/man/dot-fcnnls.Rd0000644000176200001440000000407313620502674014014 0ustar liggesusers\name{.fcnnls} \alias{.fcnnls} \title{Internal Routine for Fast Combinatorial Nonnegative Least-Squares} \usage{ .fcnnls(x, y, verbose = FALSE, pseudo = FALSE, eps = 0) } \arguments{ \item{x}{the coefficient matrix} \item{y}{the target matrix to be approximated by \eqn{X K}.} \item{verbose}{logical that indicates if log messages should be shown.} \item{pseudo}{By default (\code{pseudo=FALSE}) the algorithm uses Gaussian elimination to solve the successive internal linear problems, using the \code{\link{solve}} function. If \code{pseudo=TRUE} the algorithm uses Moore-Penrose generalized \code{\link[corpcor]{pseudoinverse}} from the \code{corpcor} package instead of \link{solve}.} \item{eps}{threshold for considering entries as nonnegative. This is an experimental parameter, and it is recommended to leave it at 0.} } \value{ A list with the following elements: \item{coef}{the fitted coefficient matrix.} \item{Pset}{the set of passive constraints, as a logical matrix of the same size as \code{K} that indicates which element is positive.} } \description{ This is the workhorse function for the higher-level function \code{\link{fcnnls}}, which implements the fast nonnegative least-square algorithm for multiple right-hand-sides from \cite{Van Benthem et al. (2004)} to solve the following problem: \deqn{ \begin{array}{l} \min \|Y - X K\|_F\\ \mbox{s.t. } K>=0 \end{array} }{min ||Y - X K||_F, s.t. K>=0} where \eqn{Y} and \eqn{X} are two real matrices of dimension \eqn{n \times p}{n x p} and \eqn{n \times r}{n x r} respectively, and \eqn{\|.\|_F}{|.|_F} is the Frobenius norm. The algorithm is very fast compared to other approaches, as it is optimised for handling multiple right-hand sides. } \references{ Van Benthem M and Keenan MR (2004). "Fast algorithm for the solution of large-scale non-negativity-constrained least squares problems." _Journal of Chemometrics_, *18*(10), pp. 441-450. ISSN 0886-9383, , . } NMF/man/offset-nmf.Rd0000644000176200001440000001221213620502674014003 0ustar liggesusers\name{nmf_update.euclidean_offset.h} \alias{nmfAlgorithm.offset} \alias{nmfAlgorithm.offset_R} \alias{nmf_update.euclidean_offset.h} \alias{nmf_update.euclidean_offset.w} \alias{nmf_update.offset} \alias{nmf_update.offset_R} \alias{offset_R-nmf} \title{NMF Multiplicative Update for NMF with Offset Models} \usage{ nmf_update.euclidean_offset.h(v, w, h, offset, eps = 10^-9, copy = TRUE) nmf_update.euclidean_offset.w(v, w, h, offset, eps = 10^-9, copy = TRUE) nmf_update.offset_R(i, v, x, eps = 10^-9, ...) nmf_update.offset(i, v, x, copy = FALSE, eps = 10^-9, ...) nmfAlgorithm.offset_R(..., .stop = NULL, maxIter = nmf.getOption("maxIter") \%||\% 2000, eps = 10^-9, stopconv = 40, check.interval = 10) nmfAlgorithm.offset(..., .stop = NULL, maxIter = nmf.getOption("maxIter") \%||\% 2000, copy = FALSE, eps = 10^-9, stopconv = 40, check.interval = 10) } \arguments{ \item{offset}{current value of the offset/intercept vector. It must be of length equal to the number of rows in the target matrix.} \item{v}{target matrix.} \item{eps}{small numeric value used to ensure numeric stability, by shifting up entries from zero to this fixed value.} \item{copy}{logical that indicates if the update should be made on the original matrix directly (\code{FALSE}) or on a copy (\code{TRUE} - default). With \code{copy=FALSE} the memory footprint is very small, and some speed-up may be achieved in the case of big matrices. However, greater care should be taken due the side effect. We recommend that only experienced users use \code{copy=TRUE}.} \item{i}{current iteration number.} \item{x}{current NMF model, as an \code{\linkS4class{NMF}} object.} \item{...}{extra arguments. These are generally not used and present only to allow other arguments from the main call to be passed to the initialisation and stopping criterion functions (slots \code{onInit} and \code{Stop} respectively).} \item{.stop}{specification of a stopping criterion, that is used instead of the one associated to the NMF algorithm. It may be specified as: \itemize{ \item the access key of a registered stopping criterion; \item a single integer that specifies the exact number of iterations to perform, which will be honoured unless a lower value is explicitly passed in argument \code{maxIter}. \item a single numeric value that specifies the stationnarity threshold for the objective function, used in with \code{\link{nmf.stop.stationary}}; \item a function with signature \code{(object="NMFStrategy", i="integer", y="matrix", x="NMF", ...)}, where \code{object} is the \code{NMFStrategy} object that describes the algorithm being run, \code{i} is the current iteration, \code{y} is the target matrix and \code{x} is the current value of the NMF model. }} \item{maxIter}{maximum number of iterations to perform.} \item{stopconv}{number of iterations intervals over which the connectivity matrix must not change for stationarity to be achieved.} \item{check.interval}{interval (in number of iterations) on which the stopping criterion is computed.} \item{w}{current basis matrix} \item{h}{current coefficient matrix} } \value{ an \code{\linkS4class{NMFOffset}} model object. } \description{ These update rules proposed by \cite{Badea (2008)} are modified version of the updates from \cite{Lee et al. (2001)}, that include an offset/intercept vector, which models a common baseline for each feature accross all samples: \deqn{V \approx W H + I} \code{nmf_update.euclidean_offset.h} and \code{nmf_update.euclidean_offset.w} compute the updated NMFOffset model, using the optimized \emph{C++} implementations. \code{nmf_update.offset_R} implements a complete single update step, using plain R updates. \code{nmf_update.offset} implements a complete single update step, using C++-optimised updates. Algorithms \sQuote{offset} and \sQuote{.R#offset} provide the complete NMF-with-offset algorithm from \cite{Badea (2008)}, using the C++-optimised and pure R updates \code{\link{nmf_update.offset}} and \code{\link{nmf_update.offset_R}} respectively. } \details{ The associated model is defined as an \code{\linkS4class{NMFOffset}} object. The details of the multiplicative updates can be found in \cite{Badea (2008)}. Note that the updates are the ones defined for a single datasets, not the simultaneous NMF model, which is fit by algorithm \sQuote{siNMF} from formula-based NMF models. } \author{ Original update definition: Liviu Badea Port to R and optimisation in C++: Renaud Gaujoux } \references{ Badea L (2008). "Extracting gene expression profiles common to colon and pancreatic adenocarcinoma using simultaneous nonnegative matrix factorization." _Pacific Symposium on Biocomputing. Pacific Symposium on Biocomputing_, *290*, pp. 267-78. ISSN 1793-5091, . Lee DD and Seung H (2001). "Algorithms for non-negative matrix factorization." _Advances in neural information processing systems_. . } NMF/man/algorithmic.Rd0000644000176200001440000003217513620502674014253 0ustar liggesusers\docType{methods} \name{algorithmic-NMF} \alias{algorithm} \alias{algorithm<-} \alias{algorithmic-NMF} \alias{algorithm<--methods} \alias{algorithm-methods} \alias{algorithm<-,NMFfit,ANY-method} \alias{algorithm,NMFfit-method} \alias{algorithm,NMFfitXn-method} \alias{algorithm<-,NMFSeed,function-method} \alias{algorithm,NMFSeed-method} \alias{algorithm<-,NMFStrategyFunction,function-method} \alias{algorithm,NMFStrategyFunction-method} \alias{compare} \alias{compare-methods} \alias{compare,NMFfitXn-method} \alias{logs} \alias{logs,ANY-method} \alias{logs-methods} \alias{modelname} \alias{modelname,ANY-method} \alias{modelname-methods} \alias{modelname,NMFfit-method} \alias{modelname,NMFfitXn-method} \alias{modelname,NMFStrategy-method} \alias{niter} \alias{niter<-} \alias{niter<--methods} \alias{niter-methods} \alias{niter,NMFfit-method} \alias{niter<-,NMFfit,numeric-method} \alias{nrun} \alias{nrun,ANY-method} \alias{nrun-methods} \alias{nrun,NMFfit-method} \alias{nrun,NMFfitX1-method} \alias{nrun,NMFfitX-method} \alias{nrun,NMFfitXn-method} \alias{objective} \alias{objective<-} \alias{objective<--methods} \alias{objective-methods} \alias{objective<-,NMFfit,ANY-method} \alias{run} \alias{run-methods} \alias{runtime} \alias{runtime.all} \alias{runtime.all-methods} \alias{runtime.all,NMFfit-method} \alias{runtime.all,NMFfitX-method} \alias{runtime-methods} \alias{runtime,NMFfit-method} \alias{seeding} \alias{seeding<-} \alias{seeding<--methods} \alias{seeding-methods} \alias{seeding<-,NMFfit-method} \alias{seeding,NMFfit-method} \alias{seeding,NMFfitXn-method} \alias{seqtime} \alias{seqtime-methods} \alias{seqtime,NMFfitXn-method} \alias{seqtime,NMFList-method} \title{Generic Interface for Algorithms} \usage{ algorithm(object, ...) algorithm(object, ...)<-value seeding(object, ...) seeding(object, ...)<-value niter(object, ...) niter(object, ...)<-value nrun(object, ...) objective(object, ...) objective(object, ...)<-value runtime(object, ...) runtime.all(object, ...) seqtime(object, ...) modelname(object, ...) run(object, y, x, ...) logs(object, ...) compare(object, ...) } \arguments{ \item{object}{an object computed using some algorithm, or that describes an algorithm itself.} \item{value}{replacement value} \item{...}{extra arguments to allow extension} \item{y}{data object, e.g. a target matrix} \item{x}{a model object used as a starting point by the algorithm, e.g. a non-empty NMF model.} } \description{ The functions documented here are S4 generics that define an general interface for -- optimisation -- algorithms. This interface builds upon the broad definition of an algorithm as a workhorse function to which is associated auxiliary objects such as an underlying model or an objective function that measures the adequation of the model with observed data. It aims at complementing the interface provided by the \code{\link{stats}} package. } \details{ \code{algorithm} and \code{algorithm<-} get/set an object that describes the algorithm used to compute another object, or with which it is associated. It may be a simple character string that gives the algorithm's names, or an object that includes the algorithm's definition itself (e.g. an \code{\link{NMFStrategy}} object). \code{seeding} get/set the seeding method used to initialise the computation of an object, i.e. usually the function that sets the starting point of an algorithm. \code{niter} and \code{niter<-} get/set the number of iterations performed to compute an object. The function \code{niter<-} would usually be called just before returning the result of an algorithm, when putting together data about the fit. \code{nrun} returns the number of times the algorithm has been run to compute an object. Usually this will be 1, but may be be more if the algorithm involves multiple starting points. \code{objective} and \code{objective<-} get/set the objective function associated with an object. Some methods for \code{objective} may also compute the objective value with respect to some target/observed data. \code{runtime} returns the CPU time required to compute an object. This would generally be an object of class \code{\link[=proc.time]{proc_time}}. \code{runtime.all} returns the CPU time required to compute a collection of objects, e.g. a sequence of independent fits. \code{seqtime} returns the sequential CPU time -- that would be -- required to compute a collection of objects. It would differ from \code{runtime.all} if the computations were performed in parallel. \code{modelname} returns a the type of model associated with an object. \code{run} calls the workhorse function that actually implements a strategy/algorithm, and run it on some data object. \code{logs} returns the log messages output during the computation of an object. \code{compare} compares objects obtained from running separate algorithms. } \section{Methods}{ \describe{ \item{algorithm}{\code{signature(object = "NMFfit")}: Returns the name of the algorithm that fitted the NMF model \code{object}. } \item{algorithm}{\code{signature(object = "NMFList")}: Returns the method names used to compute the NMF fits in the list. It returns \code{NULL} if the list is empty. See \code{\link{algorithm,NMFList-method}} for more details. } \item{algorithm}{\code{signature(object = "NMFfitXn")}: Returns the name of the common NMF algorithm used to compute all fits stored in \code{object} Since all fits are computed with the same algorithm, this method returns the name of algorithm that computed the first fit. It returns \code{NULL} if the object is empty. } \item{algorithm}{\code{signature(object = "NMFSeed")}: Returns the workhorse function of the seeding method described by \code{object}. } \item{algorithm}{\code{signature(object = "NMFStrategyFunction")}: Returns the single R function that implements the NMF algorithm -- as stored in slot \code{algorithm}. } \item{algorithm<-}{\code{signature(object = "NMFSeed", value = "function")}: Sets the workhorse function of the seeding method described by \code{object}. } \item{algorithm<-}{\code{signature(object = "NMFStrategyFunction", value = "function")}: Sets the function that implements the NMF algorithm, stored in slot \code{algorithm}. } \item{compare}{\code{signature(object = "NMFfitXn")}: Compares the fits obtained by separate runs of NMF, in a single call to \code{\link{nmf}}. } \item{logs}{\code{signature(object = "ANY")}: Default method that returns the value of attribute/slot \code{'logs'} or, if this latter does not exists, the value of element \code{'logs'} if \code{object} is a \code{list}. It returns \code{NULL} if no logging data was found. } \item{modelname}{\code{signature(object = "ANY")}: Default method which returns the class name(s) of \code{object}. This should work for objects representing models on their own. For NMF objects, this is the type of NMF model, that corresponds to the name of the S4 sub-class of \code{\linkS4class{NMF}}, inherited by \code{object}. } \item{modelname}{\code{signature(object = "NMFfit")}: Returns the type of a fitted NMF model. It is a shortcut for \code{modelname(fit(object)}. } \item{modelname}{\code{signature(object = "NMFfitXn")}: Returns the common type NMF model of all fits stored in \code{object} Since all fits are from the same NMF model, this method returns the model type of the first fit. It returns \code{NULL} if the object is empty. } \item{modelname}{\code{signature(object = "NMFStrategy")}: Returns the model(s) that an NMF algorithm can fit. } \item{niter}{\code{signature(object = "NMFfit")}: Returns the number of iteration performed to fit an NMF model, typically with function \code{\link{nmf}}. Currently this data is stored in slot \code{'extra'}, but this might change in the future. } \item{niter<-}{\code{signature(object = "NMFfit", value = "numeric")}: Sets the number of iteration performed to fit an NMF model. This function is used internally by the function \code{\link{nmf}}. It is not meant to be called by the user, except when developing new NMF algorithms implemented as single function, to set the number of iterations performed by the algorithm on the seed, before returning it (see \code{\linkS4class{NMFStrategyFunction}}). } \item{nrun}{\code{signature(object = "ANY")}: Default method that returns the value of attribute \sQuote{nrun}. Such an attribute my be attached to objects to keep track of data about the parent fit object (e.g. by method \code{\link{consensus}}), which can be used by subsequent function calls such as plot functions (e.g. see \code{\link{consensusmap}}). This method returns \code{NULL} if no suitable data was found. } \item{nrun}{\code{signature(object = "NMFfitX")}: Returns the number of NMF runs performed to create \code{object}. It is a pure virtual method defined to ensure \code{nrun} is defined for sub-classes of \code{NMFfitX}, which throws an error if called. Note that because the \code{\link{nmf}} function allows to run the NMF computation keeping only the best fit, \code{nrun} may return a value greater than one, while only the result of the best run is stored in the object (cf. option \code{'k'} in method \code{\link{nmf}}). } \item{nrun}{\code{signature(object = "NMFfit")}: This method always returns 1, since an \code{NMFfit} object is obtained from a single NMF run. } \item{nrun}{\code{signature(object = "NMFfitX1")}: Returns the number of NMF runs performed, amongst which \code{object} was selected as the best fit. } \item{nrun}{\code{signature(object = "NMFfitXn")}: Returns the number of runs performed to compute the fits stored in the list (i.e. the length of the list itself). } \item{objective}{\code{signature(object = "NMFfit")}: Returns the objective function associated with the algorithm that computed the fitted NMF model \code{object}, or the objective value with respect to a given target matrix \code{y} if it is supplied. See \code{\link{objective,NMFfit-method}} for more details. } \item{runtime}{\code{signature(object = "NMFfit")}: Returns the CPU time required to compute a single NMF fit. } \item{runtime}{\code{signature(object = "NMFList")}: Returns the CPU time required to compute all NMF fits in the list. It returns \code{NULL} if the list is empty. If no timing data are available, the sequential time is returned. See \code{\link{runtime,NMFList-method}} for more details. } \item{runtime.all}{\code{signature(object = "NMFfit")}: Identical to \code{runtime}, since their is a single fit. } \item{runtime.all}{\code{signature(object = "NMFfitX")}: Returns the CPU time required to compute all the NMF runs. It returns \code{NULL} if no CPU data is available. } \item{runtime.all}{\code{signature(object = "NMFfitXn")}: If no time data is available from in slot \sQuote{runtime.all} and argument \code{null=TRUE}, then the sequential time as computed by \code{\link{seqtime}} is returned, and a warning is thrown unless \code{warning=FALSE}. See \code{\link{runtime.all,NMFfitXn-method}} for more details. } \item{seeding}{\code{signature(object = "NMFfit")}: Returns the name of the seeding method that generated the starting point for the NMF algorithm that fitted the NMF model \code{object}. } \item{seeding}{\code{signature(object = "NMFfitXn")}: Returns the name of the common seeding method used the computation of all fits stored in \code{object} Since all fits are seeded using the same method, this method returns the name of the seeding method used for the first fit. It returns \code{NULL} if the object is empty. } \item{seqtime}{\code{signature(object = "NMFList")}: Returns the CPU time that would be required to sequentially compute all NMF fits stored in \code{object}. This method calls the function \code{runtime} on each fit and sum up the results. It returns \code{NULL} on an empty object. } \item{seqtime}{\code{signature(object = "NMFfitXn")}: Returns the CPU time that would be required to sequentially compute all NMF fits stored in \code{object}. This method calls the function \code{runtime} on each fit and sum up the results. It returns \code{NULL} on an empty object. } } } \section{Interface fo NMF algorithms}{ This interface is implemented for NMF algorithms by the classes \code{\link{NMFfit}}, \code{\link{NMFfitX}} and \code{\link{NMFStrategy}}, and their respective sub-classes. The examples given in this documentation page are mainly based on this implementation. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } #---------- # modelname,ANY-method #---------- # get the type of an NMF model modelname(nmfModel(3)) modelname(nmfModel(3, model='NMFns')) modelname(nmfModel(3, model='NMFOffset')) #---------- # modelname,NMFStrategy-method #---------- # get the type of model(s) associated with an NMF algorithm modelname( nmfAlgorithm('brunet') ) modelname( nmfAlgorithm('nsNMF') ) modelname( nmfAlgorithm('offset') ) } \keyword{methods} NMF/man/basis-coef-methods.Rd0000644000176200001440000002513113620502674015417 0ustar liggesusers\docType{methods} \name{basis} \alias{basis} \alias{.basis} \alias{.basis<-} \alias{basis<-} \alias{basis,ANY-method} \alias{.basis<--methods} \alias{.basis-methods} \alias{basis<--methods} \alias{basis-methods} \alias{.basis<-,NMFfit,matrix-method} \alias{.basis,NMFfit-method} \alias{basis,NMFfitXn-method} \alias{.basis<-,NMF,matrix-method} \alias{.basis,NMF-method} \alias{basis<-,NMF-method} \alias{basis,NMF-method} \alias{.basis<-,NMFstd,matrix-method} \alias{.basis,NMFstd-method} \alias{coef} \alias{.coef} \alias{.coef<-} \alias{coef<-} \alias{coefficients} \alias{coefficients-methods} \alias{coefficients,NMF-method} \alias{.coef<--methods} \alias{.coef-methods} \alias{coef<--methods} \alias{coef-methods} \alias{.coef<-,NMFfit,matrix-method} \alias{.coef,NMFfit-method} \alias{coef,NMFfitXn-method} \alias{.coef<-,NMF,matrix-method} \alias{.coef,NMF-method} \alias{coef<-,NMF-method} \alias{coef,NMF-method} \alias{.coef<-,NMFstd,matrix-method} \alias{.coef,NMFstd-method} \alias{loadings,NMF-method} \alias{scoef} \alias{scoef,matrix-method} \alias{scoef-methods} \alias{scoef,NMF-method} \title{Accessing NMF Factors} \usage{ basis(object, ...) \S4method{basis}{NMF}(object, all = TRUE, ...) .basis(object, ...) basis(object, ...)<-value \S4method{basis}{NMF}(object, use.dimnames = TRUE, ...)<-value .basis(object)<-value \S4method{loadings}{NMF}(x) coef(object, ...) \S4method{coef}{NMF}(object, all = TRUE, ...) .coef(object, ...) coef(object, ...)<-value \S4method{coef}{NMF}(object, use.dimnames = TRUE, ...)<-value .coef(object)<-value coefficients(object, ...) \S4method{coefficients}{NMF}(object, all = TRUE, ...) scoef(object, ...) \S4method{scoef}{NMF}(object, scale = 1) \S4method{scoef}{matrix}(object, scale = 1) } \arguments{ \item{object}{an object from which to extract the factor matrices, typically an object of class \code{\linkS4class{NMF}}.} \item{...}{extra arguments to allow extension and passed to the low-level access functions \code{.coef} and \code{.basis}. Note that these throw an error if used in replacement functions \code{}.} \item{all}{a logical that indicates whether the complete matrix factor should be returned (\code{TRUE}) or only the non-fixed part. This is relevant only for formula-based NMF models that include fixed basis or coefficient terms.} \item{use.dimnames}{logical that indicates if the object's dim names should be set using those from the new value, or left unchanged -- after truncating them to fit new dimensions if necessary. This is useful to only set the entries of a factor.} \item{value}{replacement value} \item{scale}{scaling factor, which indicates to the value the columns of the coefficient matrix should sum up to.} \item{x}{an object of class \code{"\link{factanal}"} or \code{"\link{princomp}"} or the \code{loadings} component of such an object.} } \description{ \code{basis} and \code{basis<-} are S4 generic functions which respectively extract and set the matrix of basis components of an NMF model (i.e. the first matrix factor). The methods \code{.basis}, \code{.coef} and their replacement versions are implemented as pure virtual methods for the interface class \code{NMF}, meaning that concrete NMF models must provide a definition for their corresponding class (i.e. sub-classes of class \code{NMF}). See \code{\linkS4class{NMF}} for more details. \code{coef} and \code{coef<-} respectively extract and set the coefficient matrix of an NMF model (i.e. the second matrix factor). For example, in the case of the standard NMF model \eqn{V \equiv WH}{V ~ W H}, the method \code{coef} will return the matrix \eqn{H}. \code{.coef} and \code{.coef<-} are low-level S4 generics that simply return/set coefficient data in an object, leaving some common processing to be performed in \code{coef} and \code{coef<-}. Methods \code{coefficients} and \code{coefficients<-} are simple aliases for methods \code{coef} and \code{coef<-} respectively. \code{scoef} is similar to \code{coef}, but returns the mixture coefficient matrix of an NMF model, with the columns scaled so that they sum up to a given value (1 by default). } \details{ For example, in the case of the standard NMF model \eqn{V \equiv W H}{V ~ W H}, the method \code{basis} will return the matrix \eqn{W}. \code{basis} and \code{basis<-} are defined for the top virtual class \code{\linkS4class{NMF}} only, and rely internally on the low-level S4 generics \code{.basis} and \code{.basis<-} respectively that effectively extract/set the coefficient data. These data are post/pre-processed, e.g., to extract/set only their non-fixed terms or check dimension compatibility. \code{coef} and \code{coef<-} are S4 methods defined for the corresponding generic functions from package \code{stats} (See \link[stats]{coef}). Similarly to \code{basis} and \code{basis<-}, they are defined for the top virtual class \code{\linkS4class{NMF}} only, and rely internally on the S4 generics \code{.coef} and \code{.coef<-} respectively that effectively extract/set the coefficient data. These data are post/pre-processed, e.g., to extract/set only their non-fixed terms or check dimension compatibility. } \section{Methods}{ \describe{ \item{basis}{\code{signature(object = "ANY")}: Default method returns the value of S3 slot or attribute \code{'basis'}. It returns \code{NULL} if none of these are set. Arguments \code{...} are not used by this method. } \item{basis}{\code{signature(object = "NMFfitXn")}: Returns the basis matrix of the best fit amongst all the fits stored in \code{object}. It is a shortcut for \code{basis(fit(object))}. } \item{.basis}{\code{signature(object = "NMF")}: Pure virtual method for objects of class \code{\linkS4class{NMF}}, that should be overloaded by sub-classes, and throws an error if called. } \item{.basis}{\code{signature(object = "NMFstd")}: Get the basis matrix in standard NMF models This function returns slot \code{W} of \code{object}. } \item{.basis}{\code{signature(object = "NMFfit")}: Returns the basis matrix from an NMF model fitted with function \code{\link{nmf}}. It is a shortcut for \code{.basis(fit(object), ...)}, dispatching the call to the \code{.basis} method of the actual NMF model. } \item{.basis<-}{\code{signature(object = "NMF", value = "matrix")}: Pure virtual method for objects of class \code{\linkS4class{NMF}}, that should be overloaded by sub-classes, and throws an error if called. } \item{.basis<-}{\code{signature(object = "NMFstd", value = "matrix")}: Set the basis matrix in standard NMF models This function sets slot \code{W} of \code{object}. } \item{.basis<-}{\code{signature(object = "NMFfit", value = "matrix")}: Sets the the basis matrix of an NMF model fitted with function \code{\link{nmf}}. It is a shortcut for \code{.basis(fit(object)) <- value}, dispatching the call to the \code{.basis<-} method of the actual NMF model. It is not meant to be used by the user, except when developing NMF algorithms, to update the basis matrix of the seed object before returning it. } \item{basis<-}{\code{signature(object = "NMF")}: Default methods that calls \code{.basis<-} and check the validity of the updated object. } \item{coef}{\code{signature(object = "NMFfitXn")}: Returns the coefficient matrix of the best fit amongst all the fits stored in \code{object}. It is a shortcut for \code{coef(fit(object))}. } \item{.coef}{\code{signature(object = "NMF")}: Pure virtual method for objects of class \code{\linkS4class{NMF}}, that should be overloaded by sub-classes, and throws an error if called. } \item{.coef}{\code{signature(object = "NMFstd")}: Get the mixture coefficient matrix in standard NMF models This function returns slot \code{H} of \code{object}. } \item{.coef}{\code{signature(object = "NMFfit")}: Returns the the coefficient matrix from an NMF model fitted with function \code{\link{nmf}}. It is a shortcut for \code{.coef(fit(object), ...)}, dispatching the call to the \code{.coef} method of the actual NMF model. } \item{.coef<-}{\code{signature(object = "NMF", value = "matrix")}: Pure virtual method for objects of class \code{\linkS4class{NMF}}, that should be overloaded by sub-classes, and throws an error if called. } \item{.coef<-}{\code{signature(object = "NMFstd", value = "matrix")}: Set the mixture coefficient matrix in standard NMF models This function sets slot \code{H} of \code{object}. } \item{.coef<-}{\code{signature(object = "NMFfit", value = "matrix")}: Sets the the coefficient matrix of an NMF model fitted with function \code{\link{nmf}}. It is a shortcut for \code{.coef(fit(object)) <- value}, dispatching the call to the \code{.coef<-} method of the actual NMF model. It is not meant to be used by the user, except when developing NMF algorithms, to update the coefficient matrix in the seed object before returning it. } \item{coef<-}{\code{signature(object = "NMF")}: Default methods that calls \code{.coef<-} and check the validity of the updated object. } \item{coefficients}{\code{signature(object = "NMF")}: Alias to \code{coef,NMF}, therefore also pure virtual. } \item{loadings}{\code{signature(x = "NMF")}: Method loadings for NMF Models The method \code{loadings} is identical to \code{basis}, but do not accept any extra argument. The method \code{loadings} is provided to standardise the NMF interface against the one defined in the \code{\link{stats}} package, and emphasises the similarities between NMF and PCA or factorial analysis (see \code{\link{loadings}}). } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } #---------- # scoef #---------- # Scaled coefficient matrix x <- rnmf(3, 10, 5) scoef(x) scoef(x, 100) #---------- # .basis,NMFstd-method #---------- # random standard NMF model x <- rnmf(3, 10, 5) basis(x) coef(x) # set matrix factors basis(x) <- matrix(1, nrow(x), nbasis(x)) coef(x) <- matrix(1, nbasis(x), ncol(x)) # set random factors basis(x) <- rmatrix(basis(x)) coef(x) <- rmatrix(coef(x)) # incompatible matrices generate an error: try( coef(x) <- matrix(1, nbasis(x)-1, nrow(x)) ) # but the low-level method allow it .coef(x) <- matrix(1, nbasis(x)-1, nrow(x)) try( validObject(x) ) } \seealso{ Other NMF-interface: \code{\link{.DollarNames,NMF-method}}, \code{\link{misc}}, \code{\link{NMF-class}}, \code{\link{$<-,NMF-method}}, \code{\link{$,NMF-method}}, \code{\link{nmfModel}}, \code{\link{nmfModels}}, \code{\link{rnmf}} } \keyword{methods} NMF/man/nmfAlgorithm.Rd0000644000176200001440000000430513620502674014372 0ustar liggesusers\name{nmfAlgorithm} \alias{nmfAlgorithm} \title{Listing and Retrieving NMF Algorithms} \usage{ nmfAlgorithm(name = NULL, version = NULL, all = FALSE, ...) } \arguments{ \item{name}{Access key. If not missing, it must be a single character string that is partially matched against the available algorithms in the registry. In this case, if \code{all=FALSE} (default), then the algorithm is returned as an \code{NMFStrategy} object that can be directly passed to \code{\link{nmf}}. An error is thrown if no matching algorithm is found. If missing or \code{NULL}, then access keys of algorithms -- that match the criteria \code{version}, are returned. This argument is assumed to be regular expression if \code{all=TRUE} or \code{version} is not \code{NULL}.} \item{version}{version of the algorithm(s) to retrieve. Currently only value \code{'R'} is supported, which searched for plain R implementations.} \item{all}{a logical that indicates if all algorithm keys should be returned, including the ones from alternative algorithm versions (e.g. plain R implementations of algorithms, for which a version based on optimised C updates is used by default).} \item{...}{extra arguments passed to \code{\link{getNMFMethod}} when \code{name} is not \code{NULL} and \code{all=FALSE}. It is not used otherwise.} } \value{ an \code{\linkS4class{NMFStrategy}} object if \code{name} is not \code{NULL} and \code{all=FALSE}, or a named character vector that contains the access keys of the matching algorithms. The names correspond to the access key of the primary algorithm: e.g. algorithm \sQuote{lee} has two registered versions, one plain R (\sQuote{.R#lee}) and the other uses optimised C updates (\sQuote{lee}), which will all get named \sQuote{lee}. } \description{ \code{nmfAlgorithm} lists access keys or retrieves NMF algorithms that are stored in registry. It allows to list } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # list all main algorithms nmfAlgorithm() # list all versions of algorithms nmfAlgorithm(all=TRUE) # list all plain R versions nmfAlgorithm(version='R') } \seealso{ Other regalgo: \code{\link{canFit}} } NMF/man/runtime.all-commaNMFfitXn-method.Rd0000644000176200001440000000164413620502674020122 0ustar liggesusers\docType{methods} \name{runtime.all,NMFfitXn-method} \alias{runtime.all,NMFfitXn-method} \title{Returns the CPU time used to perform all the NMF fits stored in \code{object}.} \usage{ \S4method{runtime.all}{NMFfitXn}(object, null = FALSE, warning = TRUE) } \arguments{ \item{null}{a logical that indicates if the sequential time should be returned if no time data is available in slot \sQuote{runtime.all}.} \item{warning}{a logical that indicates if a warning should be thrown if the sequential time is returned instead of the real CPU time.} \item{object}{an object computed using some algorithm, or that describes an algorithm itself.} } \description{ If no time data is available from in slot \sQuote{runtime.all} and argument \code{null=TRUE}, then the sequential time as computed by \code{\link{seqtime}} is returned, and a warning is thrown unless \code{warning=FALSE}. } \keyword{methods} NMF/man/ccPalette.Rd0000644000176200001440000000036313620502674013647 0ustar liggesusers\name{ccPalette} \alias{ccPalette} \title{Builds a Color Palette from Compact Color Specification} \usage{ ccPalette(x, n = NA, verbose = FALSE) } \description{ Builds a Color Palette from Compact Color Specification } \keyword{internal} NMF/man/Strategy-class.Rd0000644000176200001440000000327013620502674014650 0ustar liggesusers\docType{class} \name{Strategy-class} \alias{name} \alias{name<-} \alias{name<--methods} \alias{name-methods} \alias{name<-,Strategy,character-method} \alias{name,Strategy-method} \alias{Strategy-class} \title{Generic Strategy Class} \usage{ name(object, ...) \S4method{name}{Strategy}(object, all = FALSE) name(object, ...)<-value } \arguments{ \item{object}{an R object with a defined \code{name} method} \item{...}{extra arguments to allow extension} \item{value}{replacement value} \item{all}{a logical that indicates if all the names associated with a strategy should be returned (\code{TRUE}), or only the first (primary) one (\code{FALSE}).} } \description{ This class defines a common interface for generic algorithm strategies (e.g., \code{\linkS4class{NMFStrategy}}). \code{name} and \code{name<-} gets and sets the name associated with an object. In the case of \code{Strategy} objects it is the the name of the algorithm. } \section{Slots}{ \describe{ \item{name}{character string giving the name of the algorithm} \item{package}{name of the package that defined the strategy.} \item{defaults}{default values for some of the algorithm's arguments.} } } \section{Methods}{ \describe{ \item{name}{\code{signature(object = "Strategy")}: Returns the name of an algorithm } \item{name}{\code{signature(object = "Strategy")}: Returns the name of an algorithm } \item{name<-}{\code{signature(object = "Strategy", value = "character")}: Sets the name(s) of an NMF algorithm } \item{name<-}{\code{signature(object = "Strategy", value = "character")}: Sets the name(s) of an NMF algorithm } } } \keyword{internal} \keyword{methods} NMF/man/scale.NMF.Rd0000644000176200001440000000373513620502674013457 0ustar liggesusers\name{scale.NMF} \alias{scale.NMF} \title{Rescaling NMF Models} \usage{ \method{scale}{NMF} (x, center = c("basis", "coef"), scale = 1) } \arguments{ \item{x}{an NMF object} \item{center}{either a numeric normalising vector \eqn{\delta}{delta}, or either \code{'basis'} or \code{'coef'}, which respectively correspond to using the column sums of the basis matrix or the inverse of the row sums of the coefficient matrix as a normalising vector. If numeric, \code{center} should be a single value or a vector of length the rank of the NMF model, i.e. the number of columns in the basis matrix.} \item{scale}{scaling coefficient applied to \eqn{D}, i.e. the value of \eqn{\alpha}{alpha}, or, if \code{center='coef'}, the value of \eqn{1/\alpha}{1/alpha} (see section \emph{Details}).} } \value{ an NMF object } \description{ Rescales an NMF model keeping the fitted target matrix identical. } \details{ Standard NMF models are identifiable modulo a scaling factor, meaning that the basis components and basis profiles can be rescaled without changing the fitted values: \deqn{X = W_1 H_1 = (W_1 D) (D^{-1} H_1) = W_2 H_2}{X = W H = (W D) (D^-1 H)} with \eqn{D= \alpha diag(1/\delta_1, \ldots, 1\delta_r)}{D= alpha * diag(1/delta_1, ..., 1/delta_r)} The default call \code{scale(object)} rescales the basis NMF object so that each column of the basis matrix sums up to one. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # random 3-rank 10x5 NMF model x <- rnmf(3, 10, 5) # rescale based on basis colSums(basis(x)) colSums(basis(scale(x))) rx <- scale(x, 'basis', 10) colSums(basis(rx)) rowSums(coef(rx)) # rescale based on coef rowSums(coef(x)) rowSums(coef(scale(x, 'coef'))) rx <- scale(x, 'coef', 10) rowSums(coef(rx)) colSums(basis(rx)) # fitted target matrix is identical but the factors have been rescaled rx <- scale(x, 'basis') all.equal(fitted(x), fitted(rx)) all.equal(basis(x), basis(rx)) } NMF/man/sparseness.Rd0000644000176200001440000000465113620502674014135 0ustar liggesusers\docType{methods} \name{sparseness} \alias{sparseness} \alias{sparseness,matrix-method} \alias{sparseness-methods} \alias{sparseness,NMF-method} \alias{sparseness,numeric-method} \title{Sparseness} \usage{ sparseness(x, ...) } \arguments{ \item{x}{an object whose sparseness is computed.} \item{...}{extra arguments to allow extension} } \value{ usually a single numeric value -- in [0,1], or a numeric vector. See each method for more details. } \description{ Generic function that computes the \emph{sparseness} of an object, as defined by \cite{Hoyer (2004)}. The sparseness quantifies how much energy of a vector is packed into only few components. } \details{ In \cite{Hoyer (2004)}, the sparseness is defined for a real vector \eqn{x} as: \deqn{Sparseness(x) = \frac{\sqrt{n} - \frac{\sum |x_i|}{\sqrt{\sum x_i^2}}}{\sqrt{n}-1}}{ (srqt(n) - ||x||_1 / ||x||_2) / (sqrt(n) - 1)} , where \eqn{n} is the length of \eqn{x}. The sparseness is a real number in \eqn{[0,1]}. It is equal to 1 if and only if \code{x} contains a single nonzero component, and is equal to 0 if and only if all components of \code{x} are equal. It interpolates smoothly between these two extreme values. The closer to 1 is the sparseness the sparser is the vector. The basic definition is for a \code{numeric} vector, and is extended for matrices as the mean sparseness of its column vectors. } \section{Methods}{ \describe{ \item{sparseness}{\code{signature(x = "numeric")}: Base method that computes the sparseness of a numeric vector. It returns a single numeric value, computed following the definition given in section \emph{Description}. } \item{sparseness}{\code{signature(x = "matrix")}: Computes the sparseness of a matrix as the mean sparseness of its column vectors. It returns a single numeric value. } \item{sparseness}{\code{signature(x = "NMF")}: Compute the sparseness of an object of class \code{NMF}, as the sparseness of the basis and coefficient matrices computed separately. It returns the two values in a numeric vector with names \sQuote{basis} and \sQuote{coef}. } } } \references{ Hoyer P (2004). "Non-negative matrix factorization with sparseness constraints." _The Journal of Machine Learning Research_, *5*, pp. 1457-1469. . } \seealso{ Other assess: \code{\link{entropy}}, \code{\link{purity}} } \keyword{methods} NMF/man/setNMFSeed.Rd0000644000176200001440000000411013620502674013672 0ustar liggesusers\docType{methods} \name{NMFSeed} \alias{NMFSeed} \alias{NMFSeed,character-method} \alias{NMFSeed-methods} \alias{NMFSeed,NMFSeed-method} \alias{removeNMFSeed} \alias{setNMFSeed} \title{\code{NMFSeed} is a constructor method that instantiate \code{\linkS4class{NMFSeed}} objects.} \usage{ NMFSeed(key, method, ...) setNMFSeed(..., overwrite = isLoadingNamespace(), verbose = TRUE) removeNMFSeed(name, ...) } \arguments{ \item{key}{access key as a single character string} \item{method}{specification of the seeding method, as a function that takes at least the following arguments: \describe{ \item{object}{uninitialised/empty NMF model, i.e. that it has 0 rows and columns, but has already the rank requested in the call to \code{\link{nmf}} or \code{\link{seed}}.} \item{x}{target matrix} \item{...}{extra arguments} }} \item{...}{arguments passed to \code{NMFSeed} and used to initialise slots in the \code{\linkS4class{NMFSeed}} object, or to \code{\link[pkgmaker]{pkgreg_remove}}.} \item{name}{name of the seeding method.} \item{overwrite}{logical that indicates if any existing NMF method with the same name should be overwritten (\code{TRUE}) or not (\code{FALSE}), in which case an error is thrown.} \item{verbose}{a logical that indicates if information about the registration should be printed (\code{TRUE}) or not (\code{FALSE}).} } \description{ \code{NMFSeed} is a constructor method that instantiate \code{\linkS4class{NMFSeed}} objects. NMF seeding methods are registered via the function \code{setNMFSeed}, which stores them as \code{\linkS4class{NMFSeed}} objects in a dedicated registry. \code{removeNMFSeed} removes an NMF seeding method from the registry. } \section{Methods}{ \describe{ \item{NMFSeed}{\code{signature(key = "character")}: Default method simply calls \code{\link{new}} with the same arguments. } \item{NMFSeed}{\code{signature(key = "NMFSeed")}: Creates an \code{NMFSeed} based on a template object (Constructor-Copy), in particular it uses the \strong{same} name. } } } \keyword{methods} NMF/man/dimnames.Rd0000644000176200001440000001154413620502674013543 0ustar liggesusers\docType{methods} \name{basisnames} \alias{basisnames} \alias{basisnames<-} \alias{basisnames<-,ANY-method} \alias{basisnames,ANY-method} \alias{basisnames<--methods} \alias{basisnames-methods} \alias{dimnames-NMF} \alias{dimnames<-,NMF-method} \alias{dimnames,NMF-method} \title{Dimension names for NMF objects} \usage{ basisnames(x, ...) basisnames(x, ...)<-value \S4method{dimnames}{NMF}(x) \S4method{dimnames}{NMF}(x)<-value } \arguments{ \item{x}{an object with suitable \code{basis} and \code{coef} methods, such as an object that inherit from \code{\linkS4class{NMF}}.} \item{...}{extra argument to allow extension.} \item{value}{a character vector, or \code{NULL} or, in the case of \code{dimnames<-}, a list 2 or 3-length list of character vectors.} } \description{ The methods \code{dimnames}, \code{rownames}, \code{colnames} and \code{basisnames} and their respective replacement form allow to get and set the dimension names of the matrix factors in a NMF model. \code{dimnames} returns all the dimension names in a single list. Its replacement form \code{dimnames<-} allows to set all dimension names at once. \code{rownames}, \code{colnames} and \code{basisnames} provide separate access to each of these dimension names respectively. Their respective replacement form allow to set each dimension names separately. } \details{ The function \code{basisnames} is a new S4 generic defined in the package NMF, that returns the names of the basis components of an object. Its default method should work for any object, that has a suitable \code{basis} method defined for its class. The method \code{dimnames} is implemented for the base generic \code{\link{dimnames}}, which make the base function \code{\link{rownames}} and \code{\link{colnames}} work directly. Overall, these methods behave as their equivalent on \code{matrix} objects. The function \code{basisnames<-} ensures that the dimension names are handled in a consistent way on both factors, enforcing the names on both matrix factors simultaneously. The function \code{basisnames<-} is a new S4 generic defined in the package NMF, that sets the names of the basis components of an object. Its default method should work for any object, that has suitable \code{basis<-} and \code{coef<-} methods method defined for its class. } \section{Methods}{ \describe{ \item{basisnames}{\code{signature(x = "ANY")}: Default method which returns the column names of the basis matrix extracted from \code{x}, using the \code{basis} method. For NMF objects these also correspond to the row names of the coefficient matrix. } \item{basisnames<-}{\code{signature(x = "ANY")}: Default method which sets, respectively, the row and the column names of the basis matrix and coefficient matrix of \code{x} to \code{value}. } \item{dimnames}{\code{signature(x = "NMF")}: Returns the dimension names of the NMF model \code{x}. It returns either NULL if no dimnames are set on the object, or a 3-length list containing the row names of the basis matrix, the column names of the mixture coefficient matrix, and the column names of the basis matrix (i.e. the names of the basis components). } \item{dimnames<-}{\code{signature(x = "NMF")}: sets the dimension names of the NMF model \code{x}. \code{value} can be \code{NULL} which resets all dimension names, or a 1, 2 or 3-length list providing names at least for the rows of the basis matrix. The optional second element of \code{value} (NULL if absent) is used to set the column names of the coefficient matrix. The optional third element of \code{value} (NULL if absent) is used to set both the column names of the basis matrix and the row names of the coefficient matrix. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # create a random NMF object a <- rnmf(2, 5, 3) # set dimensions dims <- list( features=paste('f', 1:nrow(a), sep='') , samples=paste('s', 1:ncol(a), sep='') , basis=paste('b', 1:nbasis(a), sep='') ) dimnames(a) <- dims dimnames(a) basis(a) coef(a) # access the dimensions separately rownames(a) colnames(a) basisnames(a) # set only the first dimension (rows of basis): the other two dimnames are set to NULL dimnames(a) <- dims[1] dimnames(a) basis(a) coef(a) # set only the two first dimensions (rows and columns of basis and coef respectively): # the basisnames are set to NULL dimnames(a) <- dims[1:2] dimnames(a) basis(a) # reset the dimensions dimnames(a) <- NULL dimnames(a) basis(a) coef(a) # set each dimensions separately rownames(a) <- paste('X', 1:nrow(a), sep='') # only affect rows of basis basis(a) colnames(a) <- paste('Y', 1:ncol(a), sep='') # only affect columns of coef coef(a) basisnames(a) <- paste('Z', 1:nbasis(a), sep='') # affect both basis and coef matrices basis(a) coef(a) } \keyword{methods} NMF/man/profplot.Rd0000644000176200001440000001001613710766107013607 0ustar liggesusers\name{profplot} \alias{profplot} \alias{profplot.default} \title{Plotting Expression Profiles} \usage{ profplot(x, ...) \method{profplot}{default} (x, y, scale = c("none", "max", "c1"), match.names = TRUE, legend = TRUE, confint = TRUE, Colv, labels, annotation, ..., add = FALSE) } \arguments{ \item{x}{a matrix or an NMF object from which is extracted the mixture coefficient matrix. It is extracted from the best fit if \code{x} is the results from multiple NMF runs.} \item{y}{a matrix or an NMF object from which is extracted the mixture coefficient matrix. It is extracted from the best fit if \code{y} is the results from multiple NMF runs.} \item{scale}{specifies how the data should be scaled before plotting. If \code{'none'} or \code{NA}, then no scaling is applied and the "raw" data is plotted. If \code{TRUE} or \code{'max'} then each row of both matrices are normalised with their respective maximum values. If \code{'c1'}, then each column of both matrix is scaled into proportions (i.e. to sum up to one). Default is \code{'none'}.} \item{match.names}{a logical that indicates if the profiles in \code{y} should be subset and/or re-ordered to match the profile names in \code{x} (i.e. the rownames). This is attempted only when both \code{x} and \code{y} have names.} \item{legend}{a logical that specifies whether drawing the legend or not, or coordinates specifications passed to argument \code{x} of \code{\link{legend}}, that specifies the position of the legend.} \item{confint}{logical that indicates if confidence intervals for the R-squared should be shown in legend.} \item{Colv}{specifies the way the columns of \code{x} are ordered before plotting. It is used only when \code{y} is missing. It can be: \itemize{ \item a single numeric value, specifying the index of a row of \code{x}, that is used to order the columns by \code{x[, order(x[abs(Colv),])]}. Decreasing order is specified with a negative index. \item an integer vector directly specifying the order itself, in which case the columns are ordered by \code{x[, Colv]} \item a factor used to order the columns by \code{x[, order(Colv)]} and as argument \code{annotation} if this latter is missing or not \code{NA}. \item any other object with a suitable \code{order} method. The columns are by \code{x[, order(Colv)]} }} \item{labels}{a character vector containing labels for each sample (i.e. each column of \code{x}). These are used for labelling the x-axis.} \item{annotation}{a factor annotating each sample (i.e. each column of \code{x}). If not missing, a coloured raw is plotted under the x-axis and annotates each sample accordingly. If argument \code{Colv} is a factor, then it is used to annotate the plot, unless \code{annotation=NA}.} \item{...}{graphical parameters passed to \code{\link{matplot}} or \code{\link{matpoints}}.} \item{add}{logical that indicates if the plot should be added as points to a previous plot} } \description{ Plotting Expression Profiles When using NMF for clustering in particular, one looks for strong associations between the basis and a priori known groups of samples. Plotting the profiles may highlight such patterns. } \details{ The function can also be used to compare the profiles from two NMF models or mixture coefficient matrices. In this case, it draws a scatter plot of the paired profiles. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # create a random target matrix v <- rmatrix(40, 10) # fit a single NMF model res <- nmf(v, 3) profplot(res) # fit a multi-run NMF model res2 <- nmf(v, 3, nrun=2) # ordering according to first profile profplot(res2, Colv=1) # increasing # draw a profile correlation plot: this show how the basis components are # returned in an unpredictable order profplot(res, res2) # looking at all the correlations allow to order the components in a "common" order profcor(res, res2) } \seealso{ \code{\link{profcor}} } \keyword{aplot} NMF/man/NMF-deprecated.Rd0000644000176200001440000000034213620502674014456 0ustar liggesusers\name{NMF-deprecated} \alias{NMF-deprecated} \title{Deprecated Functions in the Package NMF} \arguments{ \item{object}{an R object} \item{...}{extra arguments} } \description{ Deprecated Functions in the Package NMF } NMF/man/lsNMF-nmf.Rd0000644000176200001440000000566613620502674013513 0ustar liggesusers\name{nmf_update.lsnmf} \alias{lsNMF-nmf} \alias{nmfAlgorithm.lsNMF} \alias{nmf_update.lsnmf} \alias{wrss} \title{Multiplicative Updates for LS-NMF} \usage{ nmf_update.lsnmf(i, X, object, weight, eps = 10^-9, ...) wrss(object, X, weight) nmfAlgorithm.lsNMF(..., .stop = NULL, maxIter = nmf.getOption("maxIter") \%||\% 2000, weight, eps = 10^-9, stationary.th = .Machine$double.eps, check.interval = 5 * check.niter, check.niter = 10L) } \arguments{ \item{i}{current iteration} \item{X}{target matrix} \item{object}{current NMF model} \item{weight}{value for \eqn{\Sigma}{S}, i.e. the weights that are applied to each entry in \code{X} by \code{X * weight} (= entry wise product). Weights are usually specified as a matrix of the same dimension as \code{X} (e.g. uncertainty estimates for each measurement), but may also be passed as a vector, in which case the standard rules for entry wise product between matrices and vectors apply (e.g. recylcing elements).} \item{eps}{small number passed to the standard euclidean-based NMF updates (see \code{\link{nmf_update.euclidean}}).} \item{...}{extra arguments (not used)} \item{.stop}{specification of a stopping criterion, that is used instead of the one associated to the NMF algorithm. It may be specified as: \itemize{ \item the access key of a registered stopping criterion; \item a single integer that specifies the exact number of iterations to perform, which will be honoured unless a lower value is explicitly passed in argument \code{maxIter}. \item a single numeric value that specifies the stationnarity threshold for the objective function, used in with \code{\link{nmf.stop.stationary}}; \item a function with signature \code{(object="NMFStrategy", i="integer", y="matrix", x="NMF", ...)}, where \code{object} is the \code{NMFStrategy} object that describes the algorithm being run, \code{i} is the current iteration, \code{y} is the target matrix and \code{x} is the current value of the NMF model. }} \item{maxIter}{maximum number of iterations to perform.} \item{stationary.th}{maximum absolute value of the gradient, for the objective function to be considered stationary.} \item{check.interval}{interval (in number of iterations) on which the stopping criterion is computed.} \item{check.niter}{number of successive iteration used to compute the stationnary criterion.} } \value{ updated object \code{object} } \description{ Implementation of the updates for the LS-NMF algorithm from \cite{Wang et al. (2006)}. \code{wrss} implements the objective function used by the LS-NMF algorithm. } \references{ Wang G, Kossenkov AV and Ochs MF (2006). "LS-NMF: a modified non-negative matrix factorization algorithm utilizing uncertainty estimates." _BMC bioinformatics_, *7*, pp. 175. ISSN 1471-2105, , . } NMF/man/esGolub.Rd0000644000176200001440000000530713620502674013346 0ustar liggesusers\docType{data} \name{esGolub} \alias{esGolub} \title{Golub ExpressionSet} \format{There are 3 covariates listed. \itemize{ \item Samples: The original sample labels. \item ALL.AML: Whether the patient had AML or ALL. It is a \code{\link{factor}} with levels \code{c('ALL', 'AML')}. \item Cell: ALL arises from two different types of lymphocytes (T-cell and B-cell). This specifies which for the ALL patients; There is no such information for the AML samples. It is a \code{\link{factor}} with levels \code{c('T-cell', 'B-cell', NA)}. }} \source{ Web page for \cite{Brunet2004}:\cr \url{http://www.broadinstitute.org/publications/broad872} Original data from Golub et al.:\cr \code{http://www-genome.wi.mit.edu/mpr/data_set_ALL_AML.html} } \description{ This data comes originally from the gene expression data from \cite{Golub et al. (1999)}. The version included in the package is the one used and referenced in \cite{Brunet et al. (2004)}. The samples are from 27 patients with acute lymphoblastic leukemia (ALL) and 11 patients with acute myeloid leukemia (AML). } \details{ The samples were assayed using Affymetrix Hgu6800 chips and the original data on the expression of 7129 genes (Affymetrix probes) are available on the Broad Institute web site (see references below). The data in \code{esGolub} were obtained from the web page related to the paper from \cite{Brunet et al. (2004)}, which describes an application of Nonnegative Matrix Factorization to gene expression clustering. (see link in section \emph{Source}). They contain the 5,000 most highly varying genes according to their coefficient of variation, and were installed in an object of class \emph{ExpressionSet}. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # requires package Biobase to be installed if(requireNamespace("Biobase", quietly=TRUE)){ data(esGolub) esGolub \dontrun{pData(esGolub)} } } \references{ Golub TR, Slonim DK, Tamayo P, Huard C, Gaasenbeek M, Mesirov JP, Coller H, Loh ML, Downing JR, Caligiuri Ma, Bloomfield CD and Lander ES (1999). "Molecular classification of cancer: class discovery and class prediction by gene expression monitoring." _Science (New York, N.Y.)_, *286*(5439), pp. 531-7. ISSN 0036-8075, . Brunet J, Tamayo P, Golub TR and Mesirov JP (2004). "Metagenes and molecular pattern discovery using matrix factorization." _Proceedings of the National Academy of Sciences of the United States of America_, *101*(12), pp. 4164-9. ISSN 0027-8424, , . } \keyword{datasets} NMF/man/parse_formula.Rd0000644000176200001440000000075613620502674014610 0ustar liggesusers\name{parse_formula} \alias{parse_formula} \title{Simple Parsing of Formula} \usage{ parse_formula(x) } \arguments{ \item{x}{formula to parse} } \value{ a list with the following elements: \item{response}{ logical that indicates if the formula has a response term.} \item{y}{ name of the response variable.} \item{x}{ list of regressor variable names.} \item{n}{ number of regressor variables.} } \description{ Formula parser for formula-based NMF models. } \keyword{internal} NMF/man/bioc.Rd0000644000176200001440000000247113620502674012661 0ustar liggesusers\name{bioc-NMF} \alias{.atrack,ExpressionSet-method} \alias{bioc-NMF} \alias{featureNames,NMFfitX-method} \alias{featureNames<-,NMF-method} \alias{featureNames,NMF-method} \alias{metagenes} \alias{metagenes<-} \alias{metaprofiles} \alias{metaprofiles<-} \alias{nmeta} \alias{nmf,ExpressionSet,ANY,ANY-method} \alias{nmf,matrix,ExpressionSet,ANY-method} \alias{nmfModel,ANY,ExpressionSet-method} \alias{nmfModel,ExpressionSet,ANY-method} \alias{nneg,ExpressionSet-method} \alias{rnmf,ANY,ExpressionSet-method} \alias{rposneg,ExpressionSet-method} \alias{run,NMFStrategy,ExpressionSet,ANY-method} \alias{sampleNames<-,NMF,ANY-method} \alias{sampleNames,NMFfitX-method} \alias{sampleNames,NMF-method} \alias{seed,ExpressionSet,ANY,ANY-method} \title{Specific NMF Layer for Bioconductor} \description{ The package NMF provides an optional layer for working with common objects and functions defined in the Bioconductor platform. } \details{ It provides: \itemize{ \item computation functions that support \code{ExpressionSet} objects as inputs. \item aliases and methods for generic functions defined and widely used by Bioconductor base packages. \item specialised visualisation methods that adapt the titles and legend using bioinformatics terminology. \item functions to link the results with annotations, etc... } } NMF/man/NMFfitXn-class.Rd0000644000176200001440000001376013710764143014505 0ustar liggesusers\docType{class} \name{NMFfitXn-class} \alias{NMFfitXn-class} \title{Structure for Storing All Fits from Multiple NMF Runs} \description{ This class is used to return the result from a multiple run of a single NMF algorithm performed with function \code{nmf} with option \code{keep.all=TRUE} (cf. \code{\link{nmf}}). } \details{ It extends both classes \code{\linkS4class{NMFfitX}} and \code{list}, and stores the result of each run (i.e. a \code{NMFfit} object) in its \code{list} structure. IMPORTANT NOTE: This class is designed to be \strong{read-only}, even though all the \code{list}-methods can be used on its instances. Adding or removing elements would most probably lead to incorrect results in subsequent calls. Capability for concatenating and merging NMF results is for the moment only used internally, and should be included and supported in the next release of the package. } \section{Slots}{ \describe{ \item{.Data}{standard slot that contains the S3 \code{list} object data. See R documentation on S3/S4 classes for more details (e.g., \code{\link{setOldClass}}).} } } \section{Methods}{ \describe{ \item{algorithm}{\code{signature(object = "NMFfitXn")}: Returns the name of the common NMF algorithm used to compute all fits stored in \code{object} Since all fits are computed with the same algorithm, this method returns the name of algorithm that computed the first fit. It returns \code{NULL} if the object is empty. } \item{basis}{\code{signature(object = "NMFfitXn")}: Returns the basis matrix of the best fit amongst all the fits stored in \code{object}. It is a shortcut for \code{basis(fit(object))}. } \item{coef}{\code{signature(object = "NMFfitXn")}: Returns the coefficient matrix of the best fit amongst all the fits stored in \code{object}. It is a shortcut for \code{coef(fit(object))}. } \item{compare}{\code{signature(object = "NMFfitXn")}: Compares the fits obtained by separate runs of NMF, in a single call to \code{\link{nmf}}. } \item{consensus}{\code{signature(object = "NMFfitXn")}: This method returns \code{NULL} on an empty object. The result is a matrix with several attributes attached, that are used by plotting functions such as \code{\link{consensusmap}} to annotate the plots. } \item{dim}{\code{signature(x = "NMFfitXn")}: Returns the dimension common to all fits. Since all fits have the same dimensions, it returns the dimension of the first fit. This method returns \code{NULL} if the object is empty. } \item{entropy}{\code{signature(x = "NMFfitXn", y = "ANY")}: Computes the best or mean entropy across all NMF fits stored in \code{x}. } \item{fit}{\code{signature(object = "NMFfitXn")}: Returns the best NMF fit object amongst all the fits stored in \code{object}, i.e. the fit that achieves the lowest estimation residuals. } \item{.getRNG}{\code{signature(object = "NMFfitXn")}: Returns the RNG settings used for the best fit. This method throws an error if the object is empty. } \item{getRNG1}{\code{signature(object = "NMFfitXn")}: Returns the RNG settings used for the first run. This method throws an error if the object is empty. } \item{minfit}{\code{signature(object = "NMFfitXn")}: Returns the best NMF model in the list, i.e. the run that achieved the lower estimation residuals. The model is selected based on its \code{deviance} value. } \item{modelname}{\code{signature(object = "NMFfitXn")}: Returns the common type NMF model of all fits stored in \code{object} Since all fits are from the same NMF model, this method returns the model type of the first fit. It returns \code{NULL} if the object is empty. } \item{nbasis}{\code{signature(x = "NMFfitXn")}: Returns the number of basis components common to all fits. Since all fits have been computed using the same rank, it returns the factorization rank of the first fit. This method returns \code{NULL} if the object is empty. } \item{nrun}{\code{signature(object = "NMFfitXn")}: Returns the number of runs performed to compute the fits stored in the list (i.e. the length of the list itself). } \item{purity}{\code{signature(x = "NMFfitXn", y = "ANY")}: Computes the best or mean purity across all NMF fits stored in \code{x}. } \item{runtime.all}{\code{signature(object = "NMFfitXn")}: If no time data is available from in slot \sQuote{runtime.all} and argument \code{null=TRUE}, then the sequential time as computed by \code{\link{seqtime}} is returned, and a warning is thrown unless \code{warning=FALSE}. } \item{seeding}{\code{signature(object = "NMFfitXn")}: Returns the name of the common seeding method used the computation of all fits stored in \code{object} Since all fits are seeded using the same method, this method returns the name of the seeding method used for the first fit. It returns \code{NULL} if the object is empty. } \item{seqtime}{\code{signature(object = "NMFfitXn")}: Returns the CPU time that would be required to sequentially compute all NMF fits stored in \code{object}. This method calls the function \code{runtime} on each fit and sum up the results. It returns \code{NULL} on an empty object. } \item{show}{\code{signature(object = "NMFfitXn")}: Show method for objects of class \code{NMFfitXn} } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # generate a synthetic dataset with known classes n <- 15; counts <- c(5, 2, 3); V <- syntheticNMF(n, counts) # get the class factor groups <- V$pData$Group # perform multiple runs of one algorithm, keeping all the fits res <- nmf(V, 3, nrun=2, .options='k') # .options=list(keep.all=TRUE) also works res summary(res) # get more info summary(res, target=V, class=groups) # compute/show computational times runtime.all(res) seqtime(res) # plot the consensus matrix, computed on the fly \dontrun{ consensusmap(res, annCol=groups) } } \seealso{ Other multipleNMF: \code{\link{NMFfitX1-class}}, \code{\link{NMFfitX-class}} } NMF/man/nsNMF-nmf.Rd0000644000176200001440000001062513620502674013504 0ustar liggesusers\name{nmf_update.ns} \alias{nmfAlgorithm.nsNMF} \alias{nmfAlgorithm.nsNMF_R} \alias{nmf_update.ns} \alias{nmf_update.ns_R} \alias{nsNMF_R-nmf} \title{NMF Multiplicative Update for Nonsmooth Nonnegative Matrix Factorization (nsNMF).} \usage{ nmf_update.ns(i, v, x, copy = FALSE, ...) nmf_update.ns_R(i, v, x, ...) nmfAlgorithm.nsNMF_R(..., .stop = NULL, maxIter = nmf.getOption("maxIter") \%||\% 2000, stopconv = 40, check.interval = 10) nmfAlgorithm.nsNMF(..., .stop = NULL, maxIter = nmf.getOption("maxIter") \%||\% 2000, copy = FALSE, stopconv = 40, check.interval = 10) } \arguments{ \item{i}{current iteration number.} \item{v}{target matrix.} \item{x}{current NMF model, as an \code{\linkS4class{NMF}} object.} \item{copy}{logical that indicates if the update should be made on the original matrix directly (\code{FALSE}) or on a copy (\code{TRUE} - default). With \code{copy=FALSE} the memory footprint is very small, and some speed-up may be achieved in the case of big matrices. However, greater care should be taken due the side effect. We recommend that only experienced users use \code{copy=TRUE}.} \item{...}{extra arguments. These are generally not used and present only to allow other arguments from the main call to be passed to the initialisation and stopping criterion functions (slots \code{onInit} and \code{Stop} respectively).} \item{.stop}{specification of a stopping criterion, that is used instead of the one associated to the NMF algorithm. It may be specified as: \itemize{ \item the access key of a registered stopping criterion; \item a single integer that specifies the exact number of iterations to perform, which will be honoured unless a lower value is explicitly passed in argument \code{maxIter}. \item a single numeric value that specifies the stationnarity threshold for the objective function, used in with \code{\link{nmf.stop.stationary}}; \item a function with signature \code{(object="NMFStrategy", i="integer", y="matrix", x="NMF", ...)}, where \code{object} is the \code{NMFStrategy} object that describes the algorithm being run, \code{i} is the current iteration, \code{y} is the target matrix and \code{x} is the current value of the NMF model. }} \item{maxIter}{maximum number of iterations to perform.} \item{stopconv}{number of iterations intervals over which the connectivity matrix must not change for stationarity to be achieved.} \item{check.interval}{interval (in number of iterations) on which the stopping criterion is computed.} } \value{ an \code{\linkS4class{NMFns}} model object. } \description{ These update rules, defined for the \code{\linkS4class{NMFns}} model \eqn{V \approx W S H} from \cite{Pascual-Montano et al. (2006)}, that introduces an intermediate smoothing matrix to enhance sparsity of the factors. \code{nmf_update.ns} computes the updated nsNMF model. It uses the optimized \emph{C++} implementations \code{\link{nmf_update.KL.w}} and \code{\link{nmf_update.KL.h}} to update \eqn{W} and \eqn{H} respectively. \code{nmf_update.ns_R} implements the same updates in \emph{plain R}. Algorithms \sQuote{nsNMF} and \sQuote{.R#nsNMF} provide the complete NMF algorithm from \cite{Pascual-Montano et al. (2006)}, using the C++-optimised and plain R updates \code{\link{nmf_update.brunet}} and \code{\link{nmf_update.brunet_R}} respectively. The stopping criterion is based on the stationarity of the connectivity matrix. } \details{ The multiplicative updates are based on the updates proposed by \cite{Brunet et al. (2004)}, except that the NMF estimate \eqn{W H} is replaced by \eqn{W S H} and \eqn{W} (resp. \eqn{H}) is replaced by \eqn{W S} (resp. \eqn{S H}) in the update of \eqn{H} (resp. \eqn{W}). See \code{\link{nmf_update.KL}} for more details on the update formula. } \references{ Pascual-Montano A, Carazo JM, Kochi K, Lehmann D and Pascual-marqui RD (2006). "Nonsmooth nonnegative matrix factorization (nsNMF)." _IEEE Trans. Pattern Anal. Mach. Intell_, *28*, pp. 403-415. Brunet J, Tamayo P, Golub TR and Mesirov JP (2004). "Metagenes and molecular pattern discovery using matrix factorization." _Proceedings of the National Academy of Sciences of the United States of America_, *101*(12), pp. 4164-9. ISSN 0027-8424, , . } NMF/man/fit.Rd0000644000176200001440000000746213620502674012534 0ustar liggesusers\docType{methods} \name{fit} \alias{fit} \alias{fit<-} \alias{fit<--methods} \alias{fit-methods} \alias{fit,NMFfit-method} \alias{fit<-,NMFfit,NMF-method} \alias{fit,NMFfitX1-method} \alias{fit,NMFfitX-method} \alias{fit,NMFfitXn-method} \alias{minfit} \alias{minfit-methods} \alias{minfit,NMFfit-method} \alias{minfit,NMFfitX1-method} \alias{minfit,NMFfitX-method} \alias{minfit,NMFfitXn-method} \title{Extracting Fitted Models} \usage{ fit(object, ...) fit(object)<-value minfit(object, ...) } \arguments{ \item{object}{an object fitted by some algorithm, e.g. as returned by the function \code{\link{nmf}}.} \item{value}{replacement value} \item{...}{extra arguments to allow extension} } \description{ The functions \code{fit} and \code{minfit} are S4 genetics that extract the best model object and the best fit object respectively, from a collection of models or from a wrapper object. \code{fit<-} sets the fitted model in a fit object. It is meant to be called only when developing new NMF algorithms, e.g. to update the value of the model stored in the starting point. } \details{ A fit object differs from a model object in that it contains data about the fit, such as the initial RNG settings, the CPU time used, etc\ldots, while a model object only contains the actual modelling data such as regression coefficients, loadings, etc\ldots That best model is generally defined as the one that achieves the maximum/minimum some quantitative measure, amongst all models in a collection. In the case of NMF models, the best model is the one that achieves the best approximation error, according to the objective function associated with the algorithm that performed the fit(s). } \section{Methods}{ \describe{ \item{fit}{\code{signature(object = "NMFfit")}: Returns the NMF model object stored in slot \code{'fit'}. } \item{fit}{\code{signature(object = "NMFfitX")}: Returns the model object that achieves the lowest residual approximation error across all the runs. It is a pure virtual method defined to ensure \code{fit} is defined for sub-classes of \code{NMFfitX}, which throws an error if called. } \item{fit}{\code{signature(object = "NMFfitX1")}: Returns the model object associated with the best fit, amongst all the runs performed when fitting \code{object}. Since \code{NMFfitX1} objects only hold the best fit, this method simply returns the NMF model fitted by \code{object} -- that is stored in slot \sQuote{fit}. } \item{fit}{\code{signature(object = "NMFfitXn")}: Returns the best NMF fit object amongst all the fits stored in \code{object}, i.e. the fit that achieves the lowest estimation residuals. } \item{fit<-}{\code{signature(object = "NMFfit", value = "NMF")}: Updates the NMF model object stored in slot \code{'fit'} with a new value. } \item{minfit}{\code{signature(object = "NMFfit")}: Returns the object its self, since there it is the result of a single NMF run. } \item{minfit}{\code{signature(object = "NMFfitX")}: Returns the fit object that achieves the lowest residual approximation error across all the runs. It is a pure virtual method defined to ensure \code{minfit} is defined for sub-classes of \code{NMFfitX}, which throws an error if called. } \item{minfit}{\code{signature(object = "NMFfitX1")}: Returns the fit object associated with the best fit, amongst all the runs performed when fitting \code{object}. Since \code{NMFfitX1} objects only hold the best fit, this method simply returns \code{object} coerced into an \code{NMFfit} object. } \item{minfit}{\code{signature(object = "NMFfitXn")}: Returns the best NMF model in the list, i.e. the run that achieved the lower estimation residuals. The model is selected based on its \code{deviance} value. } } } \keyword{methods} NMF/man/c-commaNMF-method.Rd0000644000176200001440000000161613620502674015100 0ustar liggesusers\docType{methods} \name{c,NMF-method} \alias{c,NMF-method} \title{Concatenating NMF Models} \usage{ \S4method{c}{NMF}(x, ..., margin = 3, recursive = FALSE) } \arguments{ \item{x}{an NMF model} \item{...}{other objects to concatenate. Currently only two objects at a time can be concatenated (i.e. \code{x} and \code{..1}).} \item{margin}{integer that indicates the margin along which to concatenate (only used when \code{..1} is a matrix): \describe{ \item{1L}{} \item{2L}{} \item{3L}{} \item{4L}{} } If missing the margin is heuristically determined by looking at common dimensions between the objects.} \item{recursive}{logical. If \code{recursive = TRUE}, the function recursively descends through lists (and pairlists) combining all their elements into a vector.} } \description{ Binds compatible matrices and NMF models together. } \keyword{internal} \keyword{methods} NMF/man/seed.Rd0000644000176200001440000000705113620502674012664 0ustar liggesusers\docType{methods} \name{seed} \alias{seed} \alias{seed,ANY,ANY,character-method} \alias{seed,ANY,ANY,function-method} \alias{seed,ANY,ANY,missing-method} \alias{seed,ANY,ANY,NULL-method} \alias{seed,ANY,ANY,numeric-method} \alias{seed,ANY,list,NMFSeed-method} \alias{seed,ANY,numeric,NMFSeed-method} \alias{seed,matrix,NMF,NMFSeed-method} \alias{seed-methods} \title{Interface for NMF Seeding Methods} \usage{ seed(x, model, method, ...) \S4method{seed}{matrix,NMF,NMFSeed}(x, model, method, rng, ...) \S4method{seed}{ANY,ANY,function}(x, model, method, name, ...) } \arguments{ \item{x}{target matrix one wants to approximate with NMF} \item{model}{specification of the NMF model, e.g., the factorization rank.} \item{method}{specification of a seeding method. See each method for details on the supported formats.} \item{...}{extra to allow extensions and passed down to the actual seeding method.} \item{rng}{rng setting to use. If not missing the RNG settings are set and restored on exit using \code{\link{setRNG}}. All arguments in \code{...} are passed to teh seeding strategy.} \item{name}{optional name of the seeding method for custom seeding strategies.} } \value{ an \code{\linkS4class{NMFfit}} object. } \description{ The function \code{seed} provides a single interface for calling all seeding methods used to initialise NMF computations. These methods at least set the basis and coefficient matrices of the initial \code{object} to valid nonnegative matrices. They will be used as a starting point by any NMF algorithm that accept initialisation. IMPORTANT: this interface is still considered experimental and is subject to changes in future release. } \section{Methods}{ \describe{ \item{seed}{\code{signature(x = "matrix", model = "NMF", method = "NMFSeed")}: This is the workhorse method that seeds an NMF model object using a given seeding strategy defined by an \code{NMFSeed} object, to fit a given target matrix. } \item{seed}{\code{signature(x = "ANY", model = "ANY", method = "function")}: Seeds an NMF model using a custom seeding strategy, defined by a function. \code{method} must have signature \code{(x='NMFfit', y='matrix', ...)}, where \code{x} is the unseeded NMF model and \code{y} is the target matrix to fit. It must return an \code{\linkS4class{NMF}} object, that contains the seeded NMF model. } \item{seed}{\code{signature(x = "ANY", model = "ANY", method = "missing")}: Seeds the model with the default seeding method given by \code{nmf.getOption('default.seed')} } \item{seed}{\code{signature(x = "ANY", model = "ANY", method = "NULL")}: Use NMF method \code{'none'}. } \item{seed}{\code{signature(x = "ANY", model = "ANY", method = "numeric")}: Use \code{method} to set the RNG with \code{\link{setRNG}} and use method \dQuote{random} to seed the NMF model. Note that in this case the RNG settings are not restored. This is due to some internal technical reasons, and might change in future releases. } \item{seed}{\code{signature(x = "ANY", model = "ANY", method = "character")}: Use the registered seeding method whose access key is \code{method}. } \item{seed}{\code{signature(x = "ANY", model = "list", method = "NMFSeed")}: Seed a model using the elements in \code{model} to instantiate it with \code{\link{nmfModel}}. } \item{seed}{\code{signature(x = "ANY", model = "numeric", method = "NMFSeed")}: Seeds a standard NMF model (i.e. of class \code{\linkS4class{NMFstd}}) of rank \code{model}. } } } \keyword{methods} NMF/man/nmfFormals.Rd0000644000176200001440000000122713620502674014047 0ustar liggesusers\name{nmfFormals} \alias{nmfArgs} \alias{nmfFormals} \title{Showing Arguments of NMF Algorithms} \usage{ nmfFormals(x, ...) nmfArgs(x) } \arguments{ \item{x}{algorithm specification} \item{...}{extra argument to allow extension} } \description{ This function returns the extra arguments that can be passed to a given NMF algorithm in call to \code{\link{nmf}}. \code{nmfArgs} is a shortcut for \code{args(nmfWrapper(x))}, to display the arguments of a given NMF algorithm. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # show arguments of an NMF algorithm nmfArgs('brunet') nmfArgs('snmf/r') } NMF/man/rnmf.Rd0000644000176200001440000003231113620502674012703 0ustar liggesusers\docType{methods} \name{rnmf} \alias{rnmf} \alias{rnmf,ANY,data.frame-method} \alias{rnmf,ANY,matrix-method} \alias{rnmf,formula,ANY-method} \alias{rnmf-methods} \alias{rnmf,missing,missing-method} \alias{rnmf,NMF,missing-method} \alias{rnmf,NMF,numeric-method} \alias{rnmf,NMFOffset,numeric-method} \alias{rnmf,numeric,missing-method} \alias{rnmf,numeric,numeric-method} \title{Generating Random NMF Models} \usage{ rnmf(x, target, ...) \S4method{rnmf}{NMF,numeric}(x, target, ncol = NULL, keep.names = TRUE, dist = runif) \S4method{rnmf}{ANY,matrix}(x, target, ..., dist = list(max = max(max(target, na.rm = TRUE), 1)), use.dimnames = TRUE) \S4method{rnmf}{numeric,missing}(x, target, ..., W, H, dist = runif) \S4method{rnmf}{missing,missing}(x, target, ..., W, H) \S4method{rnmf}{numeric,numeric}(x, target, ncol = NULL, ..., dist = runif) \S4method{rnmf}{formula,ANY}(x, target, ..., dist = runif) } \arguments{ \item{x}{an object that determines the rank, dimension and/or class of the generated NMF model, e.g. a numeric value or an object that inherits from class \code{\linkS4class{NMF}}. See the description of the specific methods for more details on the supported types.} \item{target}{optional specification of target dimensions. See section \emph{Methods} for how this parameter is used by the different methods.} \item{...}{extra arguments to allow extensions and passed to the next method eventually down to \code{\link{nmfModel}}, where they are used to initialise slots that are specific to the instantiating NMF model.} \item{ncol}{single numeric value that specifies the number of columns of the coefficient matrix. Only used when \code{target} is a single numeric value.} \item{keep.names}{a logical that indicates if the dimension names of the original NMF object \code{x} should be conserved (\code{TRUE}) or discarded (\code{FALSE}).} \item{dist}{specification of the random distribution to use to draw the entries of the basis and coefficient matrices. It may be specified as: \itemize{ \item a \code{function} which must be a distribution function such as e.g. \code{\link{runif}} that is used to draw the entries of both the basis and coefficient matrices. It is passed in the \code{dist} argument of \code{\link{rmatrix}}. \item a \code{list} of arguments that are passed internally to \code{\link{rmatrix}}, via \code{do.call('rmatrix', dist)}. \item a \code{character} string that is partially matched to \sQuote{basis} or \sQuote{coef}, that specifies which matrix in should be drawn randomly, the other remaining as in \code{x} -- unchanged. \item a \code{list} with elements \sQuote{basis} and/or \sQuote{coef}, which specify the \code{dist} argument separately for the basis and coefficient matrix respectively. These elements may be either a distribution function, or a list of arguments that are passed internally to \code{\link{rmatrix}}, via \code{do.call('rmatrix', dist$basis)} or \code{do.call('rmatrix', dist$coef)}. }} \item{use.dimnames}{a logical that indicates whether the dimnames of the target matrix should be set on the returned NMF model.} \item{W}{value for the basis matrix. \code{data.frame} objects are converted into matrices with \code{\link{as.matrix}}.} \item{H}{value for the mixture coefficient matrix \code{data.frame} objects are converted into matrices with \code{\link{as.matrix}}.} } \value{ An NMF model, i.e. an object that inherits from class \code{\linkS4class{NMF}}. } \description{ Generates NMF models with random values drawn from a uniform distribution. It returns an NMF model with basis and mixture coefficient matrices filled with random values. The main purpose of the function \code{rnmf} is to provide a common interface to generate random seeds used by the \code{\link{nmf}} function. } \details{ If necessary, extensions of the standard NMF model or custom models must define a method "rnmf,,numeric" for initialising their specific slots other than the basis and mixture coefficient matrices. In order to benefit from the complete built-in interface, the overloading methods should call the generic version using function \code{\link{callNextMethod}}, prior to set the values of the specific slots. See for example the method \code{\link[=rnmf,NMFOffset,numeric-method]{rnmf}} defined for \code{\linkS4class{NMFOffset}} models: \code{showMethods(rnmf, class='NMFOffset', include=TRUE))}. For convenience, shortcut methods for working on \code{data.frame} objects directly are implemented. However, note that conversion of a \code{data.frame} into a \code{matrix} object may take some non-negligible time, for large datasets. If using this method or other NMF-related methods several times, consider converting your data \code{data.frame} object into a matrix once for good, when first loaded. } \section{Methods}{ \describe{ \item{rnmf}{\code{signature(x = "NMFOffset", target = "numeric")}: Generates a random NMF model with offset, from class \code{NMFOffset}. The offset values are drawn from a uniform distribution between 0 and the maximum entry of the basis and coefficient matrices, which are drawn by the next suitable \code{\link{rnmf}} method, which is the workhorse method \code{rnmf,NMF,numeric}. } \item{rnmf}{\code{signature(x = "NMF", target = "numeric")}: Generates a random NMF model of the same class and rank as another NMF model. This is the workhorse method that is eventually called by all other methods. It generates an NMF model of the same class and rank as \code{x}, compatible with the dimensions specified in \code{target}, that can be a single or 2-length numeric vector, to specify a square or rectangular target matrix respectively. The second dimension can also be passed via argument \code{ncol}, so that calling \code{rnmf(x, 20, 10, ...)} is equivalent to \code{rnmf(x, c(20, 10), ...)}, but easier to write. The entries are uniformly drawn between \code{0} and \code{max} (optionally specified in \code{...}) that defaults to 1. By default the dimnames of \code{x} are set on the returned NMF model. This behaviour is disabled with argument \code{keep.names=FALSE}. See \code{\link{nmfModel}}. } \item{rnmf}{\code{signature(x = "ANY", target = "matrix")}: Generates a random NMF model compatible and consistent with a target matrix. The entries are uniformly drawn between \code{0} and \code{max(target)}. It is more or less a shortcut for: \samp{ rnmf(x, dim(target), max=max(target), ...)} It returns an NMF model of the same class as \code{x}. } \item{rnmf}{\code{signature(x = "ANY", target = "data.frame")}: Shortcut for \code{rnmf(x, as.matrix(target))}. } \item{rnmf}{\code{signature(x = "NMF", target = "missing")}: Generates a random NMF model of the same dimension as another NMF model. It is a shortcut for \code{rnmf(x, nrow(x), ncol(x), ...)}, which returns a random NMF model of the same class and dimensions as \code{x}. } \item{rnmf}{\code{signature(x = "numeric", target = "missing")}: Generates a random NMF model of a given rank, with known basis and/or coefficient matrices. This methods allow to easily generate partially random NMF model, where one or both factors are known. Although the later case might seems strange, it makes sense for NMF models that have fit extra data, other than the basis and coefficient matrices, that are drawn by an \code{rnmf} method defined for their own class, which should internally call \code{rnmf,NMF,numeric} and let it draw the basis and coefficient matrices. (e.g. see \code{\linkS4class{NMFOffset}} and \code{\link{rnmf,NMFOffset,numeric-method}}). Depending on whether arguments \code{W} and/or \code{H} are missing, this method interprets \code{x} differently: \itemize{ \item \code{W} provided, \code{H} missing: \code{x} is taken as the number of columns that must be drawn to build a random coefficient matrix (i.e. the number of columns in the target matrix). \item \code{W} is missing, \code{H} is provided: \code{x} is taken as the number of rows that must be drawn to build a random basis matrix (i.e. the number of rows in the target matrix). \item both \code{W} and \code{H} are provided: \code{x} is taken as the target rank of the model to generate. \item Having both \code{W} and \code{H} missing produces an error, as the dimension of the model cannot be determined in this case. } The matrices \code{W} and \code{H} are reduced if necessary and possible to be consistent with this value of the rank, by the internal call to \code{\link{nmfModel}}. All arguments in \code{...} are passed to the function \code{\link{nmfModel}} which is used to build an initial NMF model, that is in turn passed to \code{rnmf,NMF,numeric} with \code{dist=list(coef=dist)} or \code{dist=list(basis=dist)} when suitable. The type of NMF model to generate can therefore be specified in argument \code{model} (see \code{\link{nmfModel}} for other possible arguments). The returned NMF model, has a basis matrix equal to \code{W} (if not missing) and a coefficient matrix equal to \code{H} (if not missing), or drawn according to the specification provided in argument \code{dist} (see method \code{rnmf,NMF,numeric} for details on the supported values for \code{dist}). } \item{rnmf}{\code{signature(x = "missing", target = "missing")}: Generates a random NMF model with known basis and coefficient matrices. This method is a shortcut for calling \code{rnmf,numeric,missing} with a suitable value for \code{x} (the rank), when both factors are known: code{rnmf(min(ncol(W), nrow(H)), ..., W=W, H=H)}. Arguments \code{W} and \code{H} are required. Note that calling this method only makes sense for NMF models that contains data to fit other than the basis and coefficient matrices, e.g. \code{\linkS4class{NMFOffset}}. } \item{rnmf}{\code{signature(x = "numeric", target = "numeric")}: Generates a random standard NMF model of given dimensions. This is a shortcut for \code{rnmf(nmfModel(x, target, ncol, ...)), dist=dist)}. It generates a standard NMF model compatible with the dimensions passed in \code{target}, that can be a single or 2-length numeric vector, to specify a square or rectangular target matrix respectively. See \code{\link{nmfModel}}. } \item{rnmf}{\code{signature(x = "formula", target = "ANY")}: Generate a random formula-based NMF model, using the method \code{\link{nmfModel,formula,ANY-method}}. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } #---------- # rnmf,NMFOffset,numeric-method #---------- # random NMF model with offset x <- rnmf(2, 3, model='NMFOffset') x offset(x) # from a matrix x <- rnmf(2, rmatrix(5,3, max=10), model='NMFOffset') offset(x) #---------- # rnmf,NMF,numeric-method #---------- ## random NMF of same class and rank as another model x <- nmfModel(3, 10, 5) x rnmf(x, 20) # square rnmf(x, 20, 13) rnmf(x, c(20, 13)) # using another distribution rnmf(x, 20, dist=rnorm) # other than standard model y <- rnmf(3, 50, 10, model='NMFns') y \dontshow{ stopifnot( identical(dim(y), c(50L,10L,3L)) ) } \dontshow{ stopifnot( is(y, 'NMFns') ) } #---------- # rnmf,ANY,matrix-method #---------- # random NMF compatible with a target matrix x <- nmfModel(3, 10, 5) y <- rmatrix(20, 13) rnmf(x, y) # rank of x rnmf(2, y) # rank 2 #---------- # rnmf,NMF,missing-method #---------- ## random NMF from another model a <- nmfModel(3, 100, 20) b <- rnmf(a) \dontshow{ stopifnot( !nmf.equal(a,b) ) } #---------- # rnmf,numeric,missing-method #---------- # random NMF model with known basis matrix x <- rnmf(5, W=matrix(1:18, 6)) # 6 x 5 model with rank=3 basis(x) # fixed coef(x) # random # random NMF model with known coefficient matrix x <- rnmf(5, H=matrix(1:18, 3)) # 5 x 6 model with rank=3 basis(x) # random coef(x) # fixed # random model other than standard NMF x <- rnmf(5, H=matrix(1:18, 3), model='NMFOffset') basis(x) # random coef(x) # fixed offset(x) # random #---------- # rnmf,missing,missing-method #---------- # random model other than standard NMF x <- rnmf(W=matrix(1:18, 6), H=matrix(21:38, 3), model='NMFOffset') basis(x) # fixed coef(x) # fixed offset(x) # random #---------- # rnmf,numeric,numeric-method #---------- ## random standard NMF of given dimensions # generate a random NMF model with rank 3 that fits a 100x20 matrix rnmf(3, 100, 20) \dontshow{ stopifnot( identical(dim(rnmf(3, 100, 20)), c(100L,20L,3L)) ) } # generate a random NMF model with rank 3 that fits a 100x100 matrix rnmf(3, 100) \dontshow{ stopifnot( identical(dim(rnmf(3, 100)), c(100L,100L,3L)) ) } } \seealso{ \code{\link{rmatrix}} Other NMF-interface: \code{\link{basis}}, \code{\link{.basis}}, \code{\link{.basis<-}}, \code{\link{basis<-}}, \code{\link{coef}}, \code{\link{.coef}}, \code{\link{.coef<-}}, \code{\link{coef<-}}, \code{\link{coefficients}}, \code{\link{.DollarNames,NMF-method}}, \code{\link{loadings,NMF-method}}, \code{\link{misc}}, \code{\link{NMF-class}}, \code{\link{$<-,NMF-method}}, \code{\link{$,NMF-method}}, \code{\link{nmfModel}}, \code{\link{nmfModels}}, \code{\link{scoef}} } \keyword{methods} NMF/man/NMFfitX1-class.Rd0000644000176200001440000000676513710557003014412 0ustar liggesusers\docType{class} \name{NMFfitX1-class} \alias{NMFfitX1-class} \title{Structure for Storing the Best Fit Amongst Multiple NMF Runs} \description{ This class is used to return the result from a multiple run of a single NMF algorithm performed with function \code{nmf} with the -- default -- option \code{keep.all=FALSE} (cf. \code{\link{nmf}}). } \details{ It extends both classes \code{\linkS4class{NMFfitX}} and \code{\linkS4class{NMFfit}}, and stores a the result of the best fit in its \code{NMFfit} structure. Beside the best fit, this class allows to hold data about the computation of the multiple runs, such as the number of runs, the CPU time used to perform all the runs, as well as the consensus matrix. Due to the inheritance from class \code{NMFfit}, objects of class \code{NMFfitX1} can be handled exactly as the results of single NMF run -- as if only the best run had been performed. } \section{Slots}{ \describe{ \item{consensus}{object of class \code{matrix} used to store the consensus matrix based on all the runs.} \item{nrun}{an \code{integer} that contains the number of runs performed to compute the object.} \item{rng1}{an object that contains RNG settings used for the first run. See \code{\link{getRNG1}}.} } } \section{Methods}{ \describe{ \item{consensus}{\code{signature(object = "NMFfitX1")}: The result is the matrix stored in slot \sQuote{consensus}. This method returns \code{NULL} if the consensus matrix is empty. } \item{fit}{\code{signature(object = "NMFfitX1")}: Returns the model object associated with the best fit, amongst all the runs performed when fitting \code{object}. Since \code{NMFfitX1} objects only hold the best fit, this method simply returns the NMF model fitted by \code{object} -- that is stored in slot \sQuote{fit}. } \item{getRNG1}{\code{signature(object = "NMFfitX1")}: Returns the RNG settings used to compute the first of all NMF runs, amongst which \code{object} was selected as the best fit. } \item{minfit}{\code{signature(object = "NMFfitX1")}: Returns the fit object associated with the best fit, amongst all the runs performed when fitting \code{object}. Since \code{NMFfitX1} objects only hold the best fit, this method simply returns \code{object} coerced into an \code{NMFfit} object. } \item{nmf.equal}{\code{signature(x = "NMFfitX1", y = "NMFfitX1")}: Compares the NMF models fitted by multiple runs, that only kept the best fits. } \item{nrun}{\code{signature(object = "NMFfitX1")}: Returns the number of NMF runs performed, amongst which \code{object} was selected as the best fit. } \item{show}{\code{signature(object = "NMFfitX1")}: Show method for objects of class \code{NMFfitX1} } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # generate a synthetic dataset with known classes n <- 15; counts <- c(5, 2, 3); V <- syntheticNMF(n, counts) # get the class factor groups <- V$pData$Group # perform multiple runs of one algorithm, keeping only the best fit (default) #i.e.: the implicit nmf options are .options=list(keep.all=FALSE) or .options='-k' res <- nmf(V, 3, nrun=2) res # compute summary measures summary(res) # get more info summary(res, target=V, class=groups) # show computational time runtime.all(res) # plot the consensus matrix, as stored (pre-computed) in the object \dontrun{ consensusmap(res, annCol=groups) } } \seealso{ Other multipleNMF: \code{\link{NMFfitX-class}}, \code{\link{NMFfitXn-class}} } NMF/man/purity.Rd0000644000176200001440000001274213620502674013303 0ustar liggesusers\docType{methods} \name{purity} \alias{entropy} \alias{entropy,ANY,ANY-method} \alias{entropy,factor,ANY-method} \alias{entropy-methods} \alias{entropy,NMFfitXn,ANY-method} \alias{entropy,table,missing-method} \alias{purity} \alias{purity,ANY,ANY-method} \alias{purity,factor,ANY-method} \alias{purity-methods} \alias{purity,NMFfitXn,ANY-method} \alias{purity,table,missing-method} \title{Purity and Entropy of a Clustering} \usage{ purity(x, y, ...) entropy(x, y, ...) \S4method{purity}{NMFfitXn,ANY}(x, y, method = "best", ...) \S4method{entropy}{NMFfitXn,ANY}(x, y, method = "best", ...) } \arguments{ \item{x}{an object that can be interpreted as a factor or can generate such an object, e.g. via a suitable method \code{\link{predict}}, which gives the cluster membership for each sample.} \item{y}{a factor or an object coerced into a factor that gives the true class labels for each sample. It may be missing if \code{x} is a contingency table.} \item{...}{extra arguments to allow extension, and usually passed to the next method.} \item{method}{a character string that specifies how the value is computed. It may be either \code{'best'} or \code{'mean'} to compute the best or mean purity respectively.} } \value{ a single numeric value the entropy (i.e. a single numeric value) } \description{ The functions \code{purity} and \code{entropy} respectively compute the purity and the entropy of a clustering given \emph{a priori} known classes. The purity and entropy measure the ability of a clustering method, to recover known classes (e.g. one knows the true class labels of each sample), that are applicable even when the number of cluster is different from the number of known classes. \cite{Kim et al. (2007)} used these measures to evaluate the performance of their alternate least-squares NMF algorithm. } \details{ Suppose we are given \eqn{l} categories, while the clustering method generates \eqn{k} clusters. The purity of the clustering with respect to the known categories is given by: \deqn{Purity = \frac{1}{n} \sum_{q=1}^k \max_{1 \leq j \leq l} n_q^j} , where: \itemize{ \item \eqn{n} is the total number of samples; \item \eqn{n_q^j} is the number of samples in cluster \eqn{q} that belongs to original class \eqn{j} (\eqn{1 \leq j \leq l}). } The purity is therefore a real number in \eqn{[0,1]}. The larger the purity, the better the clustering performance. The entropy of the clustering with respect to the known categories is given by: \deqn{Entropy = - \frac{1}{n \log_2 l} \sum_{q=1}^k \sum_{j=1}^l n_q^j \log_2 \frac{n_q^j}{n_q}}{ - 1/(n log2(l) ) sum_q sum_j n(q,j) log2( n(q,j) / n_q )}, where: \itemize{ \item \eqn{n} is the total number of samples; \item \eqn{n}{n_q} is the total number of samples in cluster \eqn{q} (\eqn{1 \leq q \leq k}); \item \eqn{n_q^j}{n(q,j)} is the number of samples in cluster \eqn{q} that belongs to original class \eqn{j} (\eqn{1 \leq j \leq l}). } The smaller the entropy, the better the clustering performance. } \section{Methods}{ \describe{ \item{entropy}{\code{signature(x = "table", y = "missing")}: Computes the purity directly from the contingency table \code{x}. This is the workhorse method that is eventually called by all other methods. } \item{entropy}{\code{signature(x = "factor", y = "ANY")}: Computes the purity on the contingency table of \code{x} and \code{y}, that is coerced into a factor if necessary. } \item{entropy}{\code{signature(x = "ANY", y = "ANY")}: Default method that should work for results of clustering algorithms, that have a suitable \code{predict} method that returns the cluster membership vector: the purity is computed between \code{x} and \code{predict{y}} } \item{entropy}{\code{signature(x = "NMFfitXn", y = "ANY")}: Computes the best or mean entropy across all NMF fits stored in \code{x}. } \item{purity}{\code{signature(x = "table", y = "missing")}: Computes the purity directly from the contingency table \code{x} } \item{purity}{\code{signature(x = "factor", y = "ANY")}: Computes the purity on the contingency table of \code{x} and \code{y}, that is coerced into a factor if necessary. } \item{purity}{\code{signature(x = "ANY", y = "ANY")}: Default method that should work for results of clustering algorithms, that have a suitable \code{predict} method that returns the cluster membership vector: the purity is computed between \code{x} and \code{predict{y}} } \item{purity}{\code{signature(x = "NMFfitXn", y = "ANY")}: Computes the best or mean purity across all NMF fits stored in \code{x}. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # generate a synthetic dataset with known classes: 50 features, 18 samples (5+5+8) n <- 50; counts <- c(5, 5, 8); V <- syntheticNMF(n, counts) cl <- unlist(mapply(rep, 1:3, counts)) # perform default NMF with rank=2 x2 <- nmf(V, 2) purity(x2, cl) entropy(x2, cl) # perform default NMF with rank=2 x3 <- nmf(V, 3) purity(x3, cl) entropy(x3, cl) } \references{ Kim H and Park H (2007). "Sparse non-negative matrix factorizations via alternating non-negativity-constrained least squares for microarray data analysis." _Bioinformatics (Oxford, England)_, *23*(12), pp. 1495-502. ISSN 1460-2059, , . } \seealso{ Other assess: \code{\link{sparseness}} } \keyword{methods} NMF/man/advanced.Rd0000644000176200001440000000125713620502674013513 0ustar liggesusers\name{advanced-NMF} \alias{advanced-NMF} \alias{which.best} \title{Advanced Usage of the Package NMF} \usage{ which.best(object, FUN = deviance, ...) } \arguments{ \item{object}{an NMF model fitted by multiple runs.} \item{FUN}{the function that computes the quantitative measure.} \item{...}{extra arguments passed to \code{FUN}.} } \description{ The functions documented here provide advanced functionalities useful when developing within the framework implemented in the NMF package. \code{which.best} returns the index of the best fit in a list of NMF fit, according to some quantitative measure. The index of the fit with the lowest measure is returned. } NMF/man/ccBreaks.Rd0000644000176200001440000000036013620502674013455 0ustar liggesusers\name{ccBreaks} \alias{ccBreaks} \title{Generate Break Intervals from Numeric Variables} \usage{ ccBreaks(x, breaks) } \description{ Implementation is borrowed from the R core function \code{\link{cut.default}}. } \keyword{internal} NMF/man/show-commaNMFfitXn-method.Rd0000644000176200001440000000046413620502674016647 0ustar liggesusers\docType{methods} \name{show,NMFfitXn-method} \alias{show,NMFfitXn-method} \title{Show method for objects of class \code{NMFfitXn}} \usage{ \S4method{show}{NMFfitXn}(object) } \arguments{ \item{object}{Any R object} } \description{ Show method for objects of class \code{NMFfitXn} } \keyword{methods} NMF/man/assess.Rd0000644000176200001440000001062013620502674013241 0ustar liggesusers\docType{methods} \name{summary} \alias{summary} \alias{summary-methods} \alias{summary-NMF} \alias{summary,NMFfit-method} \alias{summary,NMFfitX-method} \alias{summary,NMF-method} \title{Assessing and Comparing NMF Models} \usage{ summary(object, ...) \S4method{summary}{NMF}(object, class, target) } \arguments{ \item{object}{an NMF object. See available methods in section \emph{Methods}.} \item{...}{extra arguments passed to the next \code{summary} method.} \item{class}{known classes/cluster of samples specified in one of the formats that is supported by the functions \code{\link{entropy}} and \code{\link{purity}}.} \item{target}{target matrix specified in one of the formats supported by the functions \code{\link{rss}} and \code{\link{evar}}} } \description{ The NMF package defines \code{summary} methods for different classes of objects, which helps assessing and comparing the quality of NMF models by computing a set of quantitative measures, e.g. with respect to their ability to recover known classes and/or the original target matrix. The most useful methods are for classes \code{\linkS4class{NMF}}, \code{\linkS4class{NMFfit}}, \code{\linkS4class{NMFfitX}} and \code{\linkS4class{NMFList}}, which compute summary measures for, respectively, a single NMF model, a single fit, a multiple-run fit and a list of heterogenous fits performed with the function \code{\link{nmf}}. } \details{ Due to the somehow hierarchical structure of the classes mentionned in \emph{Description}, their respective \code{summary} methods call each other in chain, each super-class adding some extra measures, only relevant for objects of a specific class. } \section{Methods}{ \describe{ \item{summary}{\code{signature(object = "NMF")}: Computes summary measures for a single NMF model. The following measures are computed: \describe{ \item{sparseness}{Sparseness of the factorization computed by the function \code{\link{sparseness}}.} \item{entropy}{Purity of the clustering, with respect to known classes, computed by the function \code{\link{purity}}.} \item{entropy}{Entropy of the clustering, with respect to known classes, computed by the function \code{\link{entropy}}.} \item{RSS}{Residual Sum of Squares computed by the function \code{\link{rss}}.} \item{evar}{Explained variance computed by the function \code{\link{evar}}.} } } \item{summary}{\code{signature(object = "NMFfit")}: Computes summary measures for a single fit from \code{\link{nmf}}. This method adds the following measures to the measures computed by the method \code{summary,NMF}: \describe{ \item{residuals}{Residual error as measured by the objective function associated to the algorithm used to fit the model.} \item{niter}{Number of iterations performed to achieve convergence of the algorithm.} \item{cpu}{Total CPU time required for the fit.} \item{cpu.all}{Total CPU time required for the fit. For \code{NMFfit} objects, this element is always equal to the value in \dQuote{cpu}, but will be different for multiple-run fits.} \item{nrun}{Number of runs performed to fit the model. This is always equal to 1 for \code{NMFfit} objects, but will vary for multiple-run fits.} } } \item{summary}{\code{signature(object = "NMFfitX")}: Computes a set of measures to help evaluate the quality of the \emph{best fit} of the set. The result is similar to the result from the \code{summary} method of \code{NMFfit} objects. See \code{\linkS4class{NMF}} for details on the computed measures. In addition, the cophenetic correlation (\code{\link{cophcor}}) and \code{\link{dispersion}} coefficients of the consensus matrix are returned, as well as the total CPU time (\code{\link{runtime.all}}). } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } #---------- # summary,NMF-method #---------- # random NMF model x <- rnmf(3, 20, 12) summary(x) summary(x, gl(3, 4)) summary(x, target=rmatrix(x)) summary(x, gl(3,4), target=rmatrix(x)) #---------- # summary,NMFfit-method #---------- # generate a synthetic dataset with known classes: 50 features, 18 samples (5+5+8) n <- 50; counts <- c(5, 5, 8); V <- syntheticNMF(n, counts) cl <- unlist(mapply(rep, 1:3, counts)) # perform default NMF with rank=2 x2 <- nmf(V, 2) summary(x2, cl, V) # perform default NMF with rank=2 x3 <- nmf(V, 3) summary(x2, cl, V) } \keyword{methods} NMF/man/revPalette.Rd0000644000176200001440000000032113620502674014050 0ustar liggesusers\name{revPalette} \alias{revPalette} \title{Flags a Color Palette Specification for Reversion} \usage{ revPalette(x) } \description{ Flags a Color Palette Specification for Reversion } \keyword{internal} NMF/man/lverbose.Rd0000644000176200001440000000040213620502674013556 0ustar liggesusers\name{lverbose} \alias{lverbose} \title{Internal verbosity option} \usage{ lverbose(val) } \arguments{ \item{val}{logical that sets the verbosity level.} } \value{ the old verbose level } \description{ Internal verbosity option } \keyword{internal} NMF/man/checkErrors.Rd0000644000176200001440000000050513620502674014213 0ustar liggesusers\name{checkErrors} \alias{checkErrors} \title{Error Checks in NMF Runs} \usage{ checkErrors(object, element = NULL) } \arguments{ \item{object}{a list of lists} \item{element}{name of an element of the inner lists} } \description{ Auxiliary function for internal error checks in nmf results. } \keyword{internal} NMF/man/atrack.Rd0000644000176200001440000001013413620502674013205 0ustar liggesusers\docType{methods} \name{.atrack} \alias{adata} \alias{alength} \alias{amargin} \alias{anames} \alias{annotationTrack} \alias{atrack} \alias{.atrack} \alias{.atrack,ANY-method} \alias{.atrack,character-method} \alias{.atrack,data.frame-method} \alias{.atrack,matrix-method} \alias{.atrack-methods} \alias{is.atrack} \title{Annotation Tracks} \usage{ .atrack(object, ...) is.atrack(x) adata(x, value, ...) amargin(x, value) anames(x, default.margin) alength(x, default.margin) \S4method{.atrack}{ANY}(object, data = NULL, ...) atrack(..., order = NULL, enforceNames = FALSE, .SPECIAL = NA, .DATA = NULL, .CACHE = NULL) annotationTrack(x = list()) } \arguments{ \item{object}{an object from which is extracted annotation tracks} \item{...}{extra arguments to allow extensions and passed to the next method call. For \code{atrack}, arguments in \code{...} are concatenated into a single \code{annotationTrack} object.} \item{x}{an R object} \item{value}{replacement value for the complete annotation data list} \item{default.margin}{margin to use if no margin data is stored in the \code{x}.} \item{data}{object used to extend the annotation track within a given data context. It is typically a matrix-like object, against which annotation specifications are matched using \code{\link{match_atrack}}.} \item{order}{an integer vector that indicates the order of the annotation tracks in the result list} \item{enforceNames}{logical that indicates if missing track names should be generated as \code{X}} \item{.SPECIAL}{an optional list of functions (with no arguments) that are called to generate special annotation tracks defined by codes of the form \code{':NAME'}. e.g., the function \code{link{consensusmap}} defines special tracks \code{':basis'} and \code{':consensus'}. If \code{.SPECIAL=FALSE}, then any special tracks is discarded and a warning is thrown.} \item{.DATA}{data used to match and extend annotation specifications. It is passed to argument \code{data} of the \code{.atrack} methods, which in turn use pass it to \code{\link{match_atrack}}.} \item{.CACHE}{an \code{annotationTrack} object with which the generated annotation track should be consistent. This argument is more for internal/advanced usage and should not be used by the end-user.} } \value{ \code{atrack} returns a list, decorated with class \code{'annotationTrack'}, where each element contains the description of an annotation track. } \description{ \code{.atrack} is an S4 generic method that converts an object into an annotation track object. It provides a general and flexible annotation framework that is used by \code{\link{aheatmap}} to annotates heatmap rows and columns. \code{is.atrack} tests if an object is an \code{annotationTrack} object. \code{adata} get/sets the annotation parameters on an object \code{amargin} get/sets the annotation margin, i.e. along which dimension of the data the annotations are to be considered. \code{anames} returns the reference margin names for annotation tracks, from their embedded annotation data object. \code{alength} returns the reference length for annotation tracks, from their embedded annotation data object \code{atrack} creates/concatenates \code{annotationTrack} objects \code{annotationTrack} is constructor function for \code{annotationTrack} object } \details{ Methods for \code{.atrack} exist for common type of objects, which should provide enough options for new methods to define how annotation track are extracted from more complex objects, by coercing/filtering them into a supported type. } \section{Methods}{ \describe{ \item{.atrack}{\code{signature(object = "ANY")}: The default method converts character or integer vectors into factors. Numeric vectors, factors, a single NA or \code{annotationTrack} objects are returned unchanged (except from reordering by argument \code{order}). Data frames are not changed either, but class 'annotationTrack' is appended to their original class set. } } } \keyword{internal} \keyword{methods} NMF/man/NMFSeed-class.Rd0000644000176200001440000000247413620502674014274 0ustar liggesusers\docType{class} \name{NMFSeed-class} \alias{NMFSeed-class} \title{Base class that defines the interface for NMF seeding methods.} \description{ This class implements a simple wrapper strategy object that defines a unified interface to seeding methods, that are used to initialise NMF models before fitting them with any NMF algorithm. } \section{Slots}{ \describe{ \item{name}{character string giving the name of the seeding strategy} \item{method}{workhorse function that implements the seeding strategy. It must have signature \code{(object="NMF", x="matrix", ...)} and initialise the NMF model \code{object} with suitable values for fitting the target matrix \code{x}.} } } \section{Methods}{ \describe{ \item{algorithm}{\code{signature(object = "NMFSeed")}: Returns the workhorse function of the seeding method described by \code{object}. } \item{algorithm<-}{\code{signature(object = "NMFSeed", value = "function")}: Sets the workhorse function of the seeding method described by \code{object}. } \item{NMFSeed}{\code{signature(key = "NMFSeed")}: Creates an \code{NMFSeed} based on a template object (Constructor-Copy), in particular it uses the \strong{same} name. } \item{show}{\code{signature(object = "NMFSeed")}: Show method for objects of class \code{NMFSeed} } } } NMF/man/residuals.Rd0000644000176200001440000000633313620502674013741 0ustar liggesusers\docType{methods} \name{residuals} \alias{hasTrack} \alias{residuals} \alias{residuals<-} \alias{residuals<--methods} \alias{residuals-methods} \alias{residuals<-,NMFfit-method} \alias{residuals,NMFfit-method} \alias{residuals,NMFfitX-method} \alias{trackError} \title{Residuals in NMF Models} \usage{ residuals(object, ...) \S4method{residuals}{NMFfit}(object, track = FALSE, niter = NULL, ...) residuals(object, ...)<-value \S4method{residuals}{NMFfit}(object, ..., niter = NULL, track = FALSE)<-value hasTrack(object, niter = NULL) trackError(object, value, niter, force = FALSE) } \arguments{ \item{object}{an \code{NMFfit} object as fitted by function \code{\link{nmf}}, in single run mode.} \item{...}{extra parameters (not used)} \item{track}{a logical that indicates if the complete track of residuals should be returned (if it has been computed during the fit), or only the last value.} \item{niter}{specifies the iteration number for which one wants to get/set/test a residual value. This argument is used only if not \code{NULL}} \item{value}{residual value} \item{force}{logical that indicates if the value should be added to the track even if there already is a value for this iteration number or if the iteration does not conform to the tracking interval \code{nmf.getOption('track.interval')}.} } \value{ \code{residuals} returns a single numeric value if \code{track=FALSE} or a numeric vector containing the residual values at some iterations. The names correspond to the iterations at which the residuals were computed. } \description{ The package NMF defines methods for the function \code{\link[stats]{residuals}} that returns the final residuals of an NMF fit or the track of the residuals along the fit process, computed according to the objective function associated with the algorithm that fitted the model. \code{residuals<-} sets the value of the last residuals, or, optionally, of the complete residual track. Tells if an \code{NMFfit} object contains a recorded residual track. \code{trackError} adds a residual value to the track of residuals. } \details{ When called with \code{track=TRUE}, the whole residuals track is returned, if available. Note that method \code{\link{nmf}} does not compute the residuals track, unless explicitly required. It is a S4 methods defined for the associated generic functions from package \code{stats} (See \link[stats]{residuals}). } \note{ Stricly speaking, the method \code{residuals,NMFfit} does not fulfill its contract as defined by the package \code{stats}, but rather acts as function \code{deviance}. The might be changed in a later release to make it behave as it should. } \section{Methods}{ \describe{ \item{residuals}{\code{signature(object = "NMFfit")}: Returns the residuals -- track -- between the target matrix and the NMF fit \code{object}. } \item{residuals}{\code{signature(object = "NMFfitX")}: Returns the residuals achieved by the best fit object, i.e. the lowest residual approximation error achieved across all NMF runs. } } } \seealso{ Other stats: \code{\link{deviance}}, \code{\link{deviance,NMF-method}}, \code{\link{nmfDistance}} } \keyword{methods} NMF/man/rss.Rd0000644000176200001440000001015113620502674012546 0ustar liggesusers\docType{methods} \name{rss} \alias{evar} \alias{evar,ANY-method} \alias{evar-methods} \alias{rss} \alias{rss,ANY-method} \alias{rss,matrix-method} \alias{rss-methods} \title{Residual Sum of Squares and Explained Variance} \usage{ rss(object, ...) \S4method{rss}{matrix}(object, target) evar(object, ...) \S4method{evar}{ANY}(object, target, ...) } \arguments{ \item{object}{an R object with a suitable \code{\link{fitted}}, \code{rss} or \code{evar} method.} \item{...}{extra arguments to allow extension, e.g. passed to \code{rss} in \code{evar} calls.} \item{target}{target matrix} } \value{ a single numeric value } \description{ \code{rss} and \code{evar} are S4 generic functions that respectively computes the Residual Sum of Squares (RSS) and explained variance achieved by a model. The explained variance for a target \eqn{V} is computed as: \deqn{evar = 1 - \frac{RSS}{\sum_{i,j} v_{ij}^2} }{evar = 1 - RSS/sum v_{ij}^2}, } \details{ where RSS is the residual sum of squares. The explained variance is usefull to compare the performance of different models and their ability to accurately reproduce the original target matrix. Note, however, that a possible caveat is that some models explicitly aim at minimizing the RSS (i.e. maximizing the explained variance), while others do not. } \section{Methods}{ \describe{ \item{evar}{\code{signature(object = "ANY")}: Default method for \code{evar}. It requires a suitable \code{rss} method to be defined for \code{object}, as it internally calls \code{rss(object, target, ...)}. } \item{rss}{\code{signature(object = "matrix")}: Computes the RSS between a target matrix and its estimate \code{object}, which must be a matrix of the same dimensions as \code{target}. The RSS between a target matrix \eqn{V} and its estimate \eqn{v} is computed as: \deqn{RSS = \sum_{i,j} (v_{ij} - V_{ij})^2} Internally, the computation is performed using an optimised C++ implementation, that is light in memory usage. } \item{rss}{\code{signature(object = "ANY")}: Residual sum of square between a given target matrix and a model that has a suitable \code{\link{fitted}} method. It is equivalent to \code{rss(fitted(object), ...)} In the context of NMF, \cite{Hutchins et al. (2008)} used the variation of the RSS in combination with the algorithm from \cite{Lee et al. (1999)} to estimate the correct number of basis vectors. The optimal rank is chosen where the graph of the RSS first shows an inflexion point, i.e. using a screeplot-type criterium. See section \emph{Rank estimation} in \code{\link{nmf}}. Note that this way of estimation may not be suitable for all models. Indeed, if the NMF optimisation problem is not based on the Frobenius norm, the RSS is not directly linked to the quality of approximation of the NMF model. However, it is often the case that it still decreases with the rank. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } #---------- # rss,matrix-method #---------- # RSS bewteeen random matrices x <- rmatrix(20,10, max=50) y <- rmatrix(20,10, max=50) rss(x, y) rss(x, x + rmatrix(x, max=0.1)) #---------- # rss,ANY-method #---------- # RSS between an NMF model and a target matrix x <- rmatrix(20, 10) y <- rnmf(3, x) # random compatible model rss(y, x) # fit a model with nmf(): one should do better y2 <- nmf(x, 3) # default minimizes the KL-divergence rss(y2, x) y2 <- nmf(x, 3, 'lee') # 'lee' minimizes the RSS rss(y2, x) } \references{ Hutchins LN, Murphy SM, Singh P and Graber JH (2008). "Position-dependent motif characterization using non-negative matrix factorization." _Bioinformatics (Oxford, England)_, *24*(23), pp. 2684-90. ISSN 1367-4811, , . Lee DD and Seung HS (1999). "Learning the parts of objects by non-negative matrix factorization." _Nature_, *401*(6755), pp. 788-91. ISSN 0028-0836, , . } \keyword{methods} NMF/man/NMFfit-class.Rd0000644000176200001440000002651313620502674014176 0ustar liggesusers\docType{class} \name{NMFfit-class} \alias{NMFfit} \alias{NMFfit-class} \title{Base Class for to store Nonnegative Matrix Factorisation results} \usage{ NMFfit(fit = nmfModel(), ..., rng = NULL) } \arguments{ \item{fit}{an NMF model} \item{...}{extra argument used to initialise slots in the instantiating \code{NMFfit} object.} \item{rng}{RNG settings specification (typically a suitable value for \code{\link{.Random.seed}}).} } \description{ Base class to handle the results of general \strong{Nonnegative Matrix Factorisation} algorithms (NMF). The function \code{NMFfit} is a factory method for NMFfit objects, that should not need to be called by the user. It is used internally by the functions \code{\link{nmf}} and \code{seed} to instantiate the starting point of NMF algorithms. } \details{ It provides a general structure and generic functions to manage the results of NMF algorithms. It contains a slot with the fitted NMF model (see slot \code{fit}) as well as data about the methods and parameters used to compute the factorization. The purpose of this class is to handle in a generic way the results of NMF algorithms. Its slot \code{fit} contains the fitted NMF model as an object of class \code{\linkS4class{NMF}}. Other slots contains data about how the factorization has been computed, such as the algorithm and seeding method, the computation time, the final residuals, etc\dots{} Class \code{NMFfit} acts as a wrapper class for its slot \code{fit}. It inherits from interface class \code{\linkS4class{NMF}} defined for generic NMF models. Therefore, all the methods defined by this interface can be called directly on objects of class \code{NMFfit}. The calls are simply dispatched on slot \code{fit}, i.e. the results are the same as if calling the methods directly on slot \code{fit}. } \section{Slots}{ \describe{ \item{fit}{An object that inherits from class \code{\linkS4class{NMF}}, and contains the fitted NMF model. NB: class \code{NMF} is a virtual class. The default class for this slot is \code{NMFstd}, that implements the standard NMF model.} \item{residuals}{A \code{numeric} vector that contains the final residuals or the residuals track between the target matrix and its NMF estimate(s). Default value is \code{numeric()}. See method \code{\link{residuals}} for details on accessor methods and main interface \code{\link{nmf}} for details on how to compute NMF with residuals tracking.} \item{method}{a single \code{character} string that contains the name of the algorithm used to fit the model. Default value is \code{''}.} \item{seed}{a single \code{character} string that contains the name of the seeding method used to seed the algorithm that fitted the NMF model. Default value is \code{''}. See \code{\link{nmf}} for more details.} \item{rng}{an object that contains the RNG settings used for the fit. Currently the settings are stored as an integer vector, the value of \code{\link{.Random.seed}} at the time the object is created. It is initialized by the \code{initialized} method. See \code{\link{getRNG}} for more details.} \item{distance}{either a single \code{"character"} string that contains the name of the built-in objective function, or a \code{function} that measures the residuals between the target matrix and its NMF estimate. See \code{\link{objective}} and \code{\link{deviance,NMF-method}}.} \item{parameters}{a \code{list} that contains the extra parameters -- usually specific to the algorithm -- that were used to fit the model.} \item{runtime}{object of class \code{"proc_time"} that contains various measures of the time spent to fit the model. See \code{\link[base]{system.time}}} \item{options}{a \code{list} that contains the options used to compute the object.} \item{extra}{a \code{list} that contains extra miscellaneous data for internal usage only. For example it can be used to store extra parameters or temporary data, without the need to explicitly extend the \code{NMFfit} class. Currently built-in algorithms only use this slot to store the number of iterations performed to fit the object. Data that need to be easily accessible by the end-user should rather be set using the methods \code{$<-} that sets elements in the \code{list} slot \code{misc} -- that is inherited from class \code{\linkS4class{NMF}}.} \item{call}{stored call to the last \code{nmf} method that generated the object.} } } \section{Methods}{ \describe{ \item{algorithm}{\code{signature(object = "NMFfit")}: Returns the name of the algorithm that fitted the NMF model \code{object}. } \item{.basis}{\code{signature(object = "NMFfit")}: Returns the basis matrix from an NMF model fitted with function \code{\link{nmf}}. It is a shortcut for \code{.basis(fit(object), ...)}, dispatching the call to the \code{.basis} method of the actual NMF model. } \item{.basis<-}{\code{signature(object = "NMFfit", value = "matrix")}: Sets the the basis matrix of an NMF model fitted with function \code{\link{nmf}}. It is a shortcut for \code{.basis(fit(object)) <- value}, dispatching the call to the \code{.basis<-} method of the actual NMF model. It is not meant to be used by the user, except when developing NMF algorithms, to update the basis matrix of the seed object before returning it. } \item{.coef}{\code{signature(object = "NMFfit")}: Returns the the coefficient matrix from an NMF model fitted with function \code{\link{nmf}}. It is a shortcut for \code{.coef(fit(object), ...)}, dispatching the call to the \code{.coef} method of the actual NMF model. } \item{.coef<-}{\code{signature(object = "NMFfit", value = "matrix")}: Sets the the coefficient matrix of an NMF model fitted with function \code{\link{nmf}}. It is a shortcut for \code{.coef(fit(object)) <- value}, dispatching the call to the \code{.coef<-} method of the actual NMF model. It is not meant to be used by the user, except when developing NMF algorithms, to update the coefficient matrix in the seed object before returning it. } \item{compare}{\code{signature(object = "NMFfit")}: Compare multiple NMF fits passed as arguments. } \item{deviance}{\code{signature(object = "NMFfit")}: Returns the deviance of a fitted NMF model. This method returns the final residual value if the target matrix \code{y} is not supplied, or the approximation error between the fitted NMF model stored in \code{object} and \code{y}. In this case, the computation is performed using the objective function \code{method} if not missing, or the objective of the algorithm that fitted the model (stored in slot \code{'distance'}). See \code{\link{deviance,NMFfit-method}} for more details. } \item{fit}{\code{signature(object = "NMFfit")}: Returns the NMF model object stored in slot \code{'fit'}. } \item{fit<-}{\code{signature(object = "NMFfit", value = "NMF")}: Updates the NMF model object stored in slot \code{'fit'} with a new value. } \item{fitted}{\code{signature(object = "NMFfit")}: Computes and return the estimated target matrix from an NMF model fitted with function \code{\link{nmf}}. It is a shortcut for \code{fitted(fit(object), ...)}, dispatching the call to the \code{fitted} method of the actual NMF model. } \item{ibterms}{\code{signature(object = "NMFfit")}: Method for single NMF fit objects, which returns the indexes of fixed basis terms from the fitted model. } \item{icterms}{\code{signature(object = "NMFfit")}: Method for single NMF fit objects, which returns the indexes of fixed coefficient terms from the fitted model. } \item{icterms}{\code{signature(object = "NMFfit")}: Method for multiple NMF fit objects, which returns the indexes of fixed coefficient terms from the best fitted model. } \item{minfit}{\code{signature(object = "NMFfit")}: Returns the object its self, since there it is the result of a single NMF run. } \item{modelname}{\code{signature(object = "NMFfit")}: Returns the type of a fitted NMF model. It is a shortcut for \code{modelname(fit(object)}. } \item{niter}{\code{signature(object = "NMFfit")}: Returns the number of iteration performed to fit an NMF model, typically with function \code{\link{nmf}}. Currently this data is stored in slot \code{'extra'}, but this might change in the future. } \item{niter<-}{\code{signature(object = "NMFfit", value = "numeric")}: Sets the number of iteration performed to fit an NMF model. This function is used internally by the function \code{\link{nmf}}. It is not meant to be called by the user, except when developing new NMF algorithms implemented as single function, to set the number of iterations performed by the algorithm on the seed, before returning it (see \code{\linkS4class{NMFStrategyFunction}}). } \item{nmf.equal}{\code{signature(x = "NMFfit", y = "NMF")}: Compares two NMF models when at least one comes from a NMFfit object, i.e. an object returned by a single run of \code{\link{nmf}}. } \item{nmf.equal}{\code{signature(x = "NMFfit", y = "NMFfit")}: Compares two fitted NMF models, i.e. objects returned by single runs of \code{\link{nmf}}. } \item{NMFfitX}{\code{signature(object = "NMFfit")}: Creates an \code{NMFfitX1} object from a single fit. This is used in \code{\link{nmf}} when only the best fit is kept in memory or on disk. } \item{nrun}{\code{signature(object = "NMFfit")}: This method always returns 1, since an \code{NMFfit} object is obtained from a single NMF run. } \item{objective}{\code{signature(object = "NMFfit")}: Returns the objective function associated with the algorithm that computed the fitted NMF model \code{object}, or the objective value with respect to a given target matrix \code{y} if it is supplied. } \item{offset}{\code{signature(object = "NMFfit")}: Returns the offset from the fitted model. } \item{plot}{\code{signature(x = "NMFfit", y = "missing")}: Plots the residual track computed at regular interval during the fit of the NMF model \code{x}. } \item{residuals}{\code{signature(object = "NMFfit")}: Returns the residuals -- track -- between the target matrix and the NMF fit \code{object}. } \item{runtime}{\code{signature(object = "NMFfit")}: Returns the CPU time required to compute a single NMF fit. } \item{runtime.all}{\code{signature(object = "NMFfit")}: Identical to \code{runtime}, since their is a single fit. } \item{seeding}{\code{signature(object = "NMFfit")}: Returns the name of the seeding method that generated the starting point for the NMF algorithm that fitted the NMF model \code{object}. } \item{show}{\code{signature(object = "NMFfit")}: Show method for objects of class \code{NMFfit} } \item{summary}{\code{signature(object = "NMFfit")}: Computes summary measures for a single fit from \code{\link{nmf}}. This method adds the following measures to the measures computed by the method \code{summary,NMF}: See \code{\link{summary,NMFfit-method}} for more details. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # run default NMF algorithm on a random matrix n <- 50; r <- 3; p <- 20 V <- rmatrix(n, p) res <- nmf(V, r) # result class is NMFfit class(res) isNMFfit(res) # show result res # compute summary measures summary(res, target=V) } NMF/man/utils.Rd0000644000176200001440000000105413620502674013101 0ustar liggesusers\name{utils-NMF} \alias{str_args} \alias{utils-NMF} \title{Utility Function in the NMF Package} \usage{ str_args(x, exdent = 10L) } \arguments{ \item{x}{a function} \item{exdent}{indentation for extra lines if the output takes more than one line.} } \description{ Utility Function in the NMF Package \code{str_args} formats the arguments of a function using \code{\link{args}}, but returns the output as a string. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } args(library) str_args(library) } NMF/man/show-commaNMF-method.Rd0000644000176200001440000000043313620502674015632 0ustar liggesusers\docType{methods} \name{show,NMF-method} \alias{show,NMF-method} \title{Show method for objects of class \code{NMF}} \usage{ \S4method{show}{NMF}(object) } \arguments{ \item{object}{Any R object} } \description{ Show method for objects of class \code{NMF} } \keyword{methods} NMF/man/nmf.equal.Rd0000644000176200001440000000774313620502674013642 0ustar liggesusers\docType{methods} \name{nmf.equal} \alias{nmf.equal} \alias{nmf.equal,list,list-method} \alias{nmf.equal,list,missing-method} \alias{nmf.equal-methods} \alias{nmf.equal,NMFfit,NMFfit-method} \alias{nmf.equal,NMFfit,NMF-method} \alias{nmf.equal,NMFfitX1,NMFfitX1-method} \alias{nmf.equal,NMFfitX,NMF-method} \alias{nmf.equal,NMF,NMFfit-method} \alias{nmf.equal,NMF,NMFfitX-method} \alias{nmf.equal,NMF,NMF-method} \title{Testing Equality of NMF Models} \usage{ nmf.equal(x, y, ...) \S4method{nmf.equal}{NMF,NMF}(x, y, identical = TRUE, ...) \S4method{nmf.equal}{list,list}(x, y, ..., all = FALSE, vector = FALSE) } \arguments{ \item{x}{an NMF model or an object that is associated with an NMF model, e.g. the result from a fit with \code{\link{nmf}}.} \item{y}{an NMF model or an object that is associated with an NMF model, e.g. the result from a fit with \code{\link{nmf}}.} \item{identical}{a logical that indicates if the comparison should be made using the function \code{\link{identical}} (\code{TRUE}) or \code{\link{all.equal}} (\code{FALSE}). See description for method \code{nmf.equal,NMF,NMF}.} \item{...}{extra arguments to allow extension, and passed to subsequent calls} \item{all}{a logical that indicates if all fits should be compared separately or only the best fits} \item{vector}{a logical, only used when \code{all=TRUE}, that indicates if all fits must be equal for \code{x} and \code{y} to be declared equal, or if one wants to return the result of each comparison in a vector.} } \description{ The function \code{nmf.equal} tests if two NMF models are the same, i.e. they contain -- almost -- identical data: same basis and coefficient matrices, as well as same extra parameters. } \details{ \code{nmf.equal} compares two NMF models, and return \code{TRUE} iff they are identical acording to the function \code{\link{identical}} when \code{identical=TRUE}, or equal up to some tolerance acording to the function \code{\link{all.equal}}. This means that all data contained in the objects are compared, which includes at least the basis and coefficient matrices, as well as the extra parameters stored in slot \sQuote{misc}. If extra arguments are specified in \code{...}, then the comparison is performed using \code{\link{all.equal}}, irrespective of the value of argument \code{identical}. } \section{Methods}{ \describe{ \item{nmf.equal}{\code{signature(x = "NMF", y = "NMF")}: Compares two NMF models. Arguments in \code{...} are used only when \code{identical=FALSE} and are passed to \code{all.equal}. } \item{nmf.equal}{\code{signature(x = "NMFfit", y = "NMF")}: Compares two NMF models when at least one comes from a NMFfit object, i.e. an object returned by a single run of \code{\link{nmf}}. } \item{nmf.equal}{\code{signature(x = "NMF", y = "NMFfit")}: Compares two NMF models when at least one comes from a NMFfit object, i.e. an object returned by a single run of \code{\link{nmf}}. } \item{nmf.equal}{\code{signature(x = "NMFfit", y = "NMFfit")}: Compares two fitted NMF models, i.e. objects returned by single runs of \code{\link{nmf}}. } \item{nmf.equal}{\code{signature(x = "NMFfitX", y = "NMF")}: Compares two NMF models when at least one comes from multiple NMF runs. } \item{nmf.equal}{\code{signature(x = "NMF", y = "NMFfitX")}: Compares two NMF models when at least one comes from multiple NMF runs. } \item{nmf.equal}{\code{signature(x = "NMFfitX1", y = "NMFfitX1")}: Compares the NMF models fitted by multiple runs, that only kept the best fits. } \item{nmf.equal}{\code{signature(x = "list", y = "list")}: Compares the results of multiple NMF runs. This method either compare the two best fit, or all fits separately. All extra arguments in \code{...} are passed to each internal call to \code{nmf.equal}. } \item{nmf.equal}{\code{signature(x = "list", y = "missing")}: Compare all elements in \code{x} to \code{x[[1]]}. } } } \keyword{methods} NMF/man/KL-nmf.Rd0000644000176200001440000001346313620503124013023 0ustar liggesusers\name{nmf_update.brunet_R} \alias{brunet-nmf} \alias{brunet_R-nmf} \alias{KL-nmf} \alias{nmfAlgorithm.brunet} \alias{nmfAlgorithm.brunet_R} \alias{nmfAlgorithm.KL} \alias{nmf_update.brunet} \alias{nmf_update.brunet_R} \title{NMF Algorithm/Updates for Kullback-Leibler Divergence} \source{ Original MATLAB files and references can be found at: \url{http://www.broadinstitute.org/publications/broad872} Original license terms: This software and its documentation are copyright 2004 by the Broad Institute/Massachusetts Institute of Technology. All rights are reserved. This software is supplied without any warranty or guaranteed support whatsoever. Neither the Broad Institute nor MIT can not be responsible for its use, misuse, or functionality. } \usage{ nmf_update.brunet_R(i, v, x, eps = .Machine$double.eps, ...) nmf_update.brunet(i, v, x, copy = FALSE, eps = .Machine$double.eps, ...) nmfAlgorithm.brunet_R(..., .stop = NULL, maxIter = nmf.getOption("maxIter") \%||\% 2000, eps = .Machine$double.eps, stopconv = 40, check.interval = 10) nmfAlgorithm.brunet(..., .stop = NULL, maxIter = nmf.getOption("maxIter") \%||\% 2000, copy = FALSE, eps = .Machine$double.eps, stopconv = 40, check.interval = 10) nmfAlgorithm.KL(..., .stop = NULL, maxIter = nmf.getOption("maxIter") \%||\% 2000, copy = FALSE, eps = .Machine$double.eps, stationary.th = .Machine$double.eps, check.interval = 5 * check.niter, check.niter = 10L) } \arguments{ \item{i}{current iteration number.} \item{v}{target matrix.} \item{x}{current NMF model, as an \code{\linkS4class{NMF}} object.} \item{eps}{small numeric value used to ensure numeric stability, by shifting up entries from zero to this fixed value.} \item{...}{extra arguments. These are generally not used and present only to allow other arguments from the main call to be passed to the initialisation and stopping criterion functions (slots \code{onInit} and \code{Stop} respectively).} \item{copy}{logical that indicates if the update should be made on the original matrix directly (\code{FALSE}) or on a copy (\code{TRUE} - default). With \code{copy=FALSE} the memory footprint is very small, and some speed-up may be achieved in the case of big matrices. However, greater care should be taken due the side effect. We recommend that only experienced users use \code{copy=TRUE}.} \item{.stop}{specification of a stopping criterion, that is used instead of the one associated to the NMF algorithm. It may be specified as: \itemize{ \item the access key of a registered stopping criterion; \item a single integer that specifies the exact number of iterations to perform, which will be honoured unless a lower value is explicitly passed in argument \code{maxIter}. \item a single numeric value that specifies the stationnarity threshold for the objective function, used in with \code{\link{nmf.stop.stationary}}; \item a function with signature \code{(object="NMFStrategy", i="integer", y="matrix", x="NMF", ...)}, where \code{object} is the \code{NMFStrategy} object that describes the algorithm being run, \code{i} is the current iteration, \code{y} is the target matrix and \code{x} is the current value of the NMF model. }} \item{maxIter}{maximum number of iterations to perform.} \item{stopconv}{number of iterations intervals over which the connectivity matrix must not change for stationarity to be achieved.} \item{check.interval}{interval (in number of iterations) on which the stopping criterion is computed.} \item{stationary.th}{maximum absolute value of the gradient, for the objective function to be considered stationary.} \item{check.niter}{number of successive iteration used to compute the stationnary criterion.} } \description{ The built-in NMF algorithms described here minimise the Kullback-Leibler divergence (KL) between an NMF model and a target matrix. They use the updates for the basis and coefficient matrices (\eqn{W} and \eqn{H}) defined by \cite{Brunet et al. (2004)}, which are essentially those from \cite{Lee et al. (2001)}, with an stabilisation step that shift up all entries from zero every 10 iterations, to a very small positive value. \code{nmf_update.brunet} implements in C++ an optimised version of the single update step. Algorithms \sQuote{brunet} and \sQuote{.R#brunet} provide the complete NMF algorithm from \cite{Brunet et al. (2004)}, using the C++-optimised and pure R updates \code{\link{nmf_update.brunet}} and \code{\link{nmf_update.brunet_R}} respectively. Algorithm \sQuote{KL} provides an NMF algorithm based on the C++-optimised version of the updates from \cite{Brunet et al. (2004)}, which uses the stationarity of the objective value as a stopping criterion \code{\link{nmf.stop.stationary}}, instead of the stationarity of the connectivity matrix \code{\link{nmf.stop.connectivity}} as used by \sQuote{brunet}. } \details{ \code{nmf_update.brunet_R} implements in pure R a single update step, i.e. it updates both matrices. } \author{ Original implementation in MATLAB: Jean-Philippe Brunet \email{brunet@broad.mit.edu} Port to R and optimisation in C++: Renaud Gaujoux } \references{ Brunet J, Tamayo P, Golub TR and Mesirov JP (2004). "Metagenes and molecular pattern discovery using matrix factorization." _Proceedings of the National Academy of Sciences of the United States of America_, *101*(12), pp. 4164-9. ISSN 0027-8424, , . Lee DD and Seung H (2001). "Algorithms for non-negative matrix factorization." _Advances in neural information processing systems_. . } NMF/man/nmf.Rd0000644000176200001440000007437713620502674012543 0ustar liggesusers\docType{methods} \name{nmf} \alias{nmf} \alias{nmf,data.frame,ANY,ANY-method} \alias{nmf,formula,ANY,ANY-method} \alias{nmf,matrix,data.frame,ANY-method} \alias{nmf,matrix,matrix,ANY-method} \alias{nmf,matrix,missing,ANY-method} \alias{nmf,matrix,NMF,ANY-method} \alias{nmf,matrix,NULL,ANY-method} \alias{nmf,matrix,numeric,character-method} \alias{nmf,matrix,numeric,function-method} \alias{nmf,matrix,numeric,list-method} \alias{nmf,matrix,numeric,missing-method} \alias{nmf,matrix,numeric,NMFStrategy-method} \alias{nmf,matrix,numeric,NULL-method} \alias{nmf-methods} \title{Running NMF algorithms} \usage{ nmf(x, rank, method, ...) \S4method{nmf}{matrix,numeric,NULL}(x, rank, method, seed = NULL, model = NULL, ...) \S4method{nmf}{matrix,numeric,list}(x, rank, method, ..., .parameters = list()) \S4method{nmf}{matrix,numeric,function}(x, rank, method, seed, model = "NMFstd", ..., name, objective = "euclidean", mixed = FALSE) \S4method{nmf}{matrix,NMF,ANY}(x, rank, method, seed, ...) \S4method{nmf}{matrix,NULL,ANY}(x, rank, method, seed, ...) \S4method{nmf}{matrix,matrix,ANY}(x, rank, method, seed, model = list(), ...) \S4method{nmf}{formula,ANY,ANY}(x, rank, method, ..., model = NULL) \S4method{nmf}{matrix,numeric,NMFStrategy}(x, rank, method, seed = nmf.getOption("default.seed"), rng = NULL, nrun = if (length(rank) > 1) 30 else 1, model = NULL, .options = list(), .pbackend = nmf.getOption("pbackend"), .callback = NULL, ...) } \arguments{ \item{x}{target data to fit, i.e. a matrix-like object} \item{rank}{specification of the factorization rank. It is usually a single numeric value, but other type of values are possible (e.g. matrix), for which specific methods are implemented. See for example methods \code{nmf,matrix,matrix,ANY}. If \code{rank} is a numeric vector with more than one element, e.g. a range of ranks, then \code{\link{nmf}} performs the estimation procedure described in \code{\link{nmfEstimateRank}}.} \item{method}{specification of the NMF algorithm. The most common way of specifying the algorithm is to pass the access key (i.e. a character string) of an algorithm stored in the package's dedicated registry, but methods exists that handle other types of values, such as \code{function} or \code{list} object. See their descriptions in section \emph{Methods}. If \code{method} is missing the algorithm to use is obtained from the option \code{nmf.getOption('default.algorithm')}, unless it can be infer from the type of NMF model to fit, if this later is available from other arguments. Factory fresh default value is \sQuote{brunet}, which corresponds to the standard NMF algorithm from \cite{Brunet2004} (see section \emph{Algorithms}). Cases where the algorithm is inferred from the call are when an NMF model is passed in arguments \code{rank} or \code{seed} (see description for \code{nmf,matrix,numeric,NULL} in section \emph{Methods}).} \item{...}{extra arguments to allow extension of the generic. Arguments that are not used in the chain of internal calls to \code{nmf} methods are passed to the function that effectively implements the algorithm that fits an NMF model on \code{x}.} \item{.parameters}{list of method-specific parameters. Its elements must have names matching a single method listed in \code{method}, and be lists of named values that are passed to the corresponding method.} \item{name}{name associated with the NMF algorithm implemented by the function \code{method} [only used when \code{method} is a function].} \item{objective}{specification of the objective function associated with the algorithm implemented by the function \code{method} [only used when \code{method} is a function]. It may be either \code{'euclidean'} or \code{'KL'} for specifying the euclidean distance (Frobenius norm) or the Kullback-Leibler divergence respectively, or a function with signature \code{(x="NMF", y="matrix", ...)} that computes the objective value for an NMF model \code{x} on a target matrix \code{y}, i.e. the residuals between the target matrix and its NMF estimate. Any extra argument may be specified, e.g. \code{function(x, y, alpha, beta=2, ...)}.} \item{mixed}{a logical that indicates if the algorithm implemented by the function \code{method} support mixed-sign target matrices, i.e. that may contain negative values [only used when \code{method} is a function].} \item{seed}{specification of the starting point or seeding method, which will compute a starting point, usually using data from the target matrix in order to provide a good guess. The seeding method may be specified in the following way: \describe{ \item{a \code{character} string:}{ giving the name of a \emph{registered} seeding method. The corresponding method will be called to compute the starting point. Available methods can be listed via \code{nmfSeed()}. See its dedicated documentation for details on each available registered methods (\code{\link{nmfSeed}}). } \item{a \code{list}:}{ giving the name of a \emph{registered} seeding method and, optionally, extra parameters to pass to it.} \item{a single \code{numeric}:}{ that is used to seed the random number generator, before generating a random starting point. Note that when performing multiple runs, the L'Ecuyer's RNG is used in order to produce a sequence of random streams, that is used in way that ensures that parallel computation are fully reproducible. } \item{an object that inherits from \code{\linkS4class{NMF}}:}{ it should contain the data of an initialised NMF model, i.e. it must contain valid basis and mixture coefficient matrices, directly usable by the algorithm's workhorse function.} \item{a \code{function}:}{ that computes the starting point. It must have signature \code{(object="NMF", target="matrix", ...)} and return an object that inherits from class \code{NMF}. It is recommended to use argument \code{object} as a template for the returned object, by only updating the basis and coefficient matrices, using \code{\link{basis<-}} and \code{\link{coef<-}} respectively. } }} \item{rng}{rng specification for the run(s). This argument should be used to set the the RNG seed, while still specifying the seeding method argument \var{seed}.} \item{model}{specification of the type of NMF model to use. It is used to instantiate the object that inherits from class \code{\linkS4class{NMF}}, that will be passed to the seeding method. The following values are supported: \itemize{ \item \code{NULL}, the default model associated to the NMF algorithm is instantiated and \code{...} is looked-up for arguments with names that correspond to slots in the model class, which are passed to the function \code{\link{nmfModel}} to instantiate the model. Arguments in \code{...} that do not correspond to slots are passed to the algorithm. \item a single \code{character} string, that is the name of the NMF model class to be instantiate. In this case, arguments in \code{...} are handled in the same way as when \code{model} is \code{NULL}. \item a \code{list} that contains named values that are passed to the function \code{\link{nmfModel}} to instantiate the model. In this case, \code{...} is not looked-up at all, and passed entirely to the algorithm. This means that all necessary model parameters must be specified in \code{model}. } \strong{Argument/slot conflicts:} In the case a parameter of the algorithm has the same name as a model slot, then \code{model} MUST be a list -- possibly empty --, if one wants this parameter to be effectively passed to the algorithm. If a variable appears in both arguments \code{model} and \code{\dots}, the former will be used to initialise the NMF model, the latter will be passed to the NMF algorithm. See code examples for an illustration of this situation.} \item{nrun}{number of runs to perform. It specifies the number of runs to perform. By default only one run is performed, except if \code{rank} is a numeric vector with more than one element, in which case a default of 30 runs per value of the rank are performed, allowing the computation of a consensus matrix that is used in selecting the appropriate rank (see \code{\link{consensus}}). When using a random seeding method, multiple runs are generally required to achieve stability and avoid \emph{bad} local minima.} \item{.options}{this argument is used to set runtime options. It can be a \code{list} containing named options with their values, or, in the case only boolean/integer options need to be set, a character string that specifies which options are turned on/off or their value, in a unix-like command line argument way. The string must be composed of characters that correspond to a given option (see mapping below), and modifiers '+' and '-' that toggle options on and off respectively. E.g. \code{.options='tv'} will toggle on options \code{track} and \code{verbose}, while \code{.options='t-v'} will toggle on option \code{track} and toggle off option \code{verbose}. Modifiers '+' and '-' apply to all option character found after them: \code{t-vp+k} means \code{track=TRUE}, \code{verbose=parallel=FALSE}, and \code{keep.all=TRUE}. The default behaviour is to assume that \code{.options} starts with a '+'. for options that accept integer values, the value may be appended to the option's character e.g. \code{'p4'} for asking for 4 processors or \code{'v3'} for showing verbosity message up to level 3. The following options are available (the characters after \dQuote{-} are those to use to encode \code{.options} as a string): \describe{ \item{debug - d}{ Toggle debug mode (default: \code{FALSE}). Like option \code{verbose} but with more information displayed.} \item{keep.all - k}{ used when performing multiple runs (\code{nrun}>1): if \code{TRUE}, all factorizations are saved and returned (default: \code{FALSE}). Otherwise only the factorization achieving the minimum residuals is returned.} \item{parallel - p}{ this option is useful on multicore *nix or Mac machine only, when performing multiple runs (\code{nrun} > 1) (default: \code{TRUE}). If \code{TRUE}, the runs are performed using the parallel foreach backend defined in argument \code{.pbackend}. If this is set to \code{'mc'} or \code{'par'} then \code{nmf} tries to perform the runs using multiple cores with package \code{link[doParallel]{doParallel}} -- which therefore needs to be installed. If equal to an integer, then \code{nmf} tries to perform the computation on the specified number of processors. When passing options as a string the number is appended to the option's character e.g. \code{'p4'} for asking for 4 processors. If \code{FALSE}, then the computation is performed sequentially using the base function \code{\link{sapply}}. Unlike option 'P' (capital 'P'), if the computation cannot be performed in parallel, then it will still be carried on sequentially. \strong{IMPORTANT NOTE FOR MAC OS X USERS:} The parallel computation is based on the \code{doMC} and \code{multicore} packages, so the same care should be taken as stated in the vignette of \code{doMC}: \emph{\dQuote{it is not safe to use doMC from R.app on Mac OS X. Instead, you should use doMC from a terminal session, starting R from the command line.}} } \item{parallel.required - P}{ Same as \code{p}, but an error is thrown if the computation cannot be performed in parallel or with the specified number of processors.} \item{shared.memory - m}{ toggle usage of shared memory (requires the package \emph{synchronicity}). Default is as defined by \code{nmf.getOption('shared.memory')}.} \item{restore.seed - r}{ deprecated option since version 0.5.99. Will throw a warning if used.} \item{simplifyCB - S}{ toggle simplification of the callback results. Default is \code{TRUE}} \item{track - t}{ enables error tracking (default: FALSE). If \code{TRUE}, the returned object's slot \code{residuals} contains the trajectory of the objective values, which can be retrieved via \code{residuals(res, track=TRUE)} This tracking functionality is available for all built-in algorithms. } \item{verbose - v}{ Toggle verbosity (default: \code{FALSE}). If \code{TRUE}, messages about the configuration and the state of the current run(s) are displayed. The level of verbosity may be specified with an integer value, the greater the level the more messages are displayed. Value \code{FALSE} means no messages are displayed, while value \code{TRUE} is equivalent to verbosity level 1. } }} \item{.pbackend}{specification of the \code{\link{foreach}} parallel backend to register and/or use when running in parallel mode. See options \code{p} and \code{P} in argument \code{.options} for how to enable this mode. Note that any backend that is internally registered is cleaned-up on exit, so that the calling foreach environment should not be affected by a call to \code{nmf} -- except when \code{.pbackend=NULL}. Currently it accepts the following values: \describe{ \item{\sQuote{par}}{ use the backend(s) defined by the package \code{\link{doParallel}};} \item{a numeric value}{ use the specified number of cores with \code{doParallel} backend;} \item{\sQuote{seq}}{ use the foreach sequential backend \code{doSEQ};} \item{\code{NULL}}{ use currently registered backend;} \item{\code{NA}}{ do not compute using a foreach loop -- and therefore not in parallel -- but rather use a call to standard \code{\link{sapply}}. This is useful for when developing/debugging NMF algorithms, as foreach loop handling may sometime get in the way. Note that this is equivalent to using \code{.options='-p'} or \code{.options='p0'}, but takes precedence over any option specified in \code{.options}: e.g. \code{nmf(..., .options='P10', .pbackend=NA)} performs all runs sequentially using \code{sapply}. Use \code{nmf.options(pbackend=NA)} to completely disable foreach/parallel computations for all subsequent \code{nmf} calls.} \item{\sQuote{mc}}{ identical to \sQuote{par} and defined to ensure backward compatibility.} }} \item{.callback}{Used when option \code{keep.all=FALSE} (default). It allows to pass a callback function that is called after each run when performing multiple runs (i.e. with \code{nrun>1}). This is useful for example if one is also interested in saving summary measures or process the result of each NMF fit before it gets discarded. After each run, the callback function is called with two arguments, the \code{\linkS4class{NMFfit}} object that as just been fitted and the run number: \code{.callback(res, i)}. For convenience, a function that takes only one argument or has signature \code{(x, ...)} can still be passed in \code{.callback}. It is wrapped internally into a dummy function with two arguments, only the first of which is passed to the actual callback function (see example with \code{summary}). The call is wrapped into a tryCatch so that callback errors do not stop the whole computation (see below). The results of the different calls to the callback function are stored in a miscellaneous slot accessible using the method \code{$} for \code{NMFfit} objects: \code{res$.callback}. By default \code{nmf} tries to simplify the list of callback result using \code{sapply}, unless option \code{'simplifyCB'} is \code{FASE}. If no error occurs \code{res$.callback} contains the list of values that resulted from the calling the callback function --, ordered as the fits. If any error occurs in one of the callback calls, then the whole computation is \strong{not} stopped, but the error message is stored in \code{res$.callback}, in place of the result. See the examples for sample code.} } \value{ The returned value depends on the run mode: \item{Single run:}{An object of class \code{\linkS4class{NMFfit}}.} \item{Multiple runs, single method:}{When \code{nrun > 1} and \code{method} is not \code{list}, this method returns an object of class \code{\linkS4class{NMFfitX}}.} \item{Multiple runs, multiple methods:}{When \code{nrun > 1} and \code{method} is a \code{list}, this method returns an object of class \code{\linkS4class{NMFList}}.} } \description{ The function \code{nmf} is a S4 generic defines the main interface to run NMF algorithms within the framework defined in package \code{NMF}. It has many methods that facilitates applying, developing and testing NMF algorithms. The package vignette \code{vignette('NMF')} contains an introduction to the interface, through a sample data analysis. } \details{ The \code{nmf} function has multiple methods that compose a very flexible interface allowing to: \itemize{ \item combine NMF algorithms with seeding methods and/or stopping/convergence criterion at runtime; \item perform multiple NMF runs, which are computed in parallel whenever the host machine allows it; \item run multiple algorithms with a common set of parameters, ensuring a consistent environment (notably the RNG settings). } The workhorse method is \code{nmf,matrix,numeric,NMFStrategy}, which is eventually called by all other methods. The other methods provides convenient ways of specifying the NMF algorithm(s), the factorization rank, or the seed to be used. Some allow to directly run NMF algorithms on different types of objects, such as \code{data.frame} or \emph{ExpressionSet} objects. } \section{Methods}{ \describe{ \item{nmf}{\code{signature(x = "data.frame", rank = "ANY", method = "ANY")}: Fits an NMF model on a \code{data.frame}. The target \code{data.frame} is coerced into a matrix with \code{\link{as.matrix}}. } \item{nmf}{\code{signature(x = "matrix", rank = "numeric", method = "NULL")}: Fits an NMF model using an appropriate algorithm when \code{method} is not supplied. This method tries to select an appropriate algorithm amongst the NMF algorithms stored in the internal algorithm registry, which contains the type of NMF models each algorithm can fit. This is possible when the type of NMF model to fit is available from argument \code{seed}, i.e. if it is an NMF model itself. Otherwise the algorithm to use is obtained from \code{nmf.getOption('default.algorithm')}. This method is provided for internal usage, when called from other \code{nmf} methods with argument \code{method} missing in the top call (e.g. \code{nmf,matrix,numeric,missing}). } \item{nmf}{\code{signature(x = "matrix", rank = "numeric", method = "list")}: Fits multiple NMF models on a common matrix using a list of algorithms. The models are fitted sequentially with \code{nmf} using the same options and parameters for all algorithms. In particular, irrespective of the way the computation is seeded, this method ensures that all fits are performed using the same initial RNG settings. This method returns an object of class \code{\linkS4class{NMFList}}, that is essentially a list containing each fit. } \item{nmf}{\code{signature(x = "matrix", rank = "numeric", method = "character")}: Fits an NMF model on \code{x} using an algorithm registered with access key \code{method}. Argument \code{method} is partially match against the access keys of all registered algorithms (case insensitive). Available algorithms are listed in section \emph{Algorithms} below or the introduction vignette. A vector of their names may be retrieved via \code{nmfAlgorithm()}. } \item{nmf}{\code{signature(x = "matrix", rank = "numeric", method = "function")}: Fits an NMF model on \code{x} using a custom algorithm defined the function \code{method}. The supplied function must have signature \code{(x=matrix, start=NMF, ...)} and return an object that inherits from class \code{\linkS4class{NMF}}. It will be called internally by the workhorse \code{nmf} method, with an NMF model to be used as a starting point passed in its argument \code{start}. Extra arguments in \code{...} are passed to \code{method} from the top \code{nmf} call. Extra arguments that have no default value in the definition of the function \code{method} are required to run the algorithm (e.g. see argument \code{alpha} of \code{myfun} in the examples). If the algorithm requires a specific type of NMF model, this can be specified in argument \code{model} that is handled as in the workhorse \code{nmf} method (see description for this argument). } \item{nmf}{\code{signature(x = "matrix", rank = "NMF", method = "ANY")}: Fits an NMF model using the NMF model \code{rank} to seed the computation, i.e. as a starting point. This method is provided for convenience as a shortcut for \code{nmf(x, nbasis(object), method, seed=object, ...)} It discards any value passed in argument \code{seed} and uses the NMF model passed in \code{rank} instead. It throws a warning if argument \code{seed} not missing. If \code{method} is missing, this method will call the method \code{nmf,matrix,numeric,NULL}, which will infer an algorithm suitable for fitting an NMF model of the class of \code{rank}. } \item{nmf}{\code{signature(x = "matrix", rank = "NULL", method = "ANY")}: Fits an NMF model using the NMF model supplied in \code{seed}, to seed the computation, i.e. as a starting point. This method is provided for completeness and is equivalent to \code{nmf(x, seed, method, ...)}. } \item{nmf}{\code{signature(x = "matrix", rank = "missing", method = "ANY")}: Method defined to ensure the correct dispatch to workhorse methods in case of argument \code{rank} is missing. } \item{nmf}{\code{signature(x = "matrix", rank = "numeric", method = "missing")}: Method defined to ensure the correct dispatch to workhorse methods in case of argument \code{method} is missing. } \item{nmf}{\code{signature(x = "matrix", rank = "matrix", method = "ANY")}: Fits an NMF model partially seeding the computation with a given matrix passed in \code{rank}. The matrix \code{rank} is used either as initial value for the basis or mixture coefficient matrix, depending on its dimension. Currently, such partial NMF model is directly used as a seed, meaning that the remaining part is left uninitialised, which is not accepted by all NMF algorithm. This should change in the future, where the missing part of the model will be drawn from some random distribution. Amongst built-in algorithms, only \sQuote{snmf/l} and \sQuote{snmf/r} support partial seeds, with only the coefficient or basis matrix initialised respectively. } \item{nmf}{\code{signature(x = "matrix", rank = "data.frame", method = "ANY")}: Shortcut for \code{nmf(x, as.matrix(rank), method, ...)}. } \item{nmf}{\code{signature(x = "formula", rank = "ANY", method = "ANY")}: This method implements the interface for fitting formula-based NMF models. See \code{\link{nmfModel}}. Argument \code{rank} target matrix or formula environment. If not missing, \code{model} must be a \code{list}, a \code{data.frame} or an \code{environment} in which formula variables are searched for. } } } \section{Optimized C++ vs. plain R}{ Lee and Seung's multiplicative updates are used by several NMF algorithms. To improve speed and memory usage, a C++ implementation of the specific matrix products is used whenever possible. It directly computes the updates for each entry in the updated matrix, instead of using multiple standard matrix multiplication. The algorithms that benefit from this optimization are: 'brunet', 'lee', 'nsNMF' and 'offset'. % and 'lnmf' However there still exists plain R versions for these methods, which implement the updates as standard matrix products. These are accessible by adding the prefix '.R#' to their name: '.R#brunet', '.R#lee', '.R#nsNMF' and '.R#offset'. } \section{Algorithms}{ All algorithms are accessible by their respective access key as listed below. The following algorithms are available: \describe{ \item{\sQuote{brunet}}{ Standard NMF, based on the Kullback-Leibler divergence, from \cite{Brunet et al. (2004)}. It uses simple multiplicative updates from \cite{Lee et al. (2001)}, enhanced to avoid numerical underflow. Default stopping criterion: invariance of the connectivity matrix (see \code{\link{nmf.stop.connectivity}}). } \item{\sQuote{lee}}{ Standard NMF based on the Euclidean distance from \cite{Lee et al. (2001)}. It uses simple multiplicative updates. Default stopping criterion: invariance of the connectivity matrix (see \code{\link{nmf.stop.connectivity}}). } \item{ls-nmf}{ Least-Square NMF from \cite{Wang et al. (2006)}. It uses modified versions of Lee and Seung's multiplicative updates for the Euclidean distance, which incorporates weights on each entry of the target matrix, e.g. to reflect measurement uncertainty. Default stopping criterion: stationarity of the objective function (see \code{\link{nmf.stop.stationary}}). } \item{\sQuote{nsNMF}}{ Nonsmooth NMF from \cite{Pascual-Montano et al. (2006)}. It uses a modified version of Lee and Seung's multiplicative updates for the Kullback-Leibler divergence \cite{Lee et al. (2001)}, to fit a extension of the standard NMF model, that includes an intermediate smoothing matrix, meant meant to produce sparser factors. Default stopping criterion: invariance of the connectivity matrix (see \code{\link{nmf.stop.connectivity}}). } \item{\sQuote{offset}}{ NMF with offset from \cite{Badea (2008)}. It uses a modified version of Lee and Seung's multiplicative updates for Euclidean distance \cite{Lee et al. (2001)}, to fit an NMF model that includes an intercept, meant to capture a common baseline and shared patterns, in order to produce cleaner basis components. Default stopping criterion: invariance of the connectivity matrix (see \code{\link{nmf.stop.connectivity}}). } \item{\sQuote{pe-nmf}}{ Pattern-Expression NMF from \emph{Zhang2008}. It uses multiplicative updates to minimize an objective function based on the Euclidean distance, that is regularized for effective expression of patterns with basis vectors. Default stopping criterion: stationarity of the objective function (see \code{\link{nmf.stop.stationary}}). } \item{\sQuote{snmf/r}, \sQuote{snmf/l}}{ Alternating Least Square (ALS) approach from \cite{Kim et al. (2007)}. It applies the nonnegative least-squares algorithm from \cite{Van Benthem et al. (2004)} (i.e. fast combinatorial nonnegative least-squares for multiple right-hand), to estimate the basis and coefficient matrices alternatively (see \code{\link{fcnnls}}). It minimises an Euclidean-based objective function, that is regularized to favour sparse basis matrices (for \sQuote{snmf/l}) or sparse coefficient matrices (for \sQuote{snmf/r}). Stopping criterion: built-in within the internal workhorse function \code{nmf_snmf}, based on the KKT optimality conditions. } } } \section{Seeding methods}{ The purpose of seeding methods is to compute initial values for the factor matrices in a given NMF model. This initial guess will be used as a starting point by the chosen NMF algorithm. The seeding method to use in combination with the algorithm can be passed to interface \code{nmf} through argument \code{seed}. The seeding seeding methods available in registry are listed by the function \code{\link{nmfSeed}} (see list therein). Detailed examples of how to specify the seeding method and its parameters can be found in the \emph{Examples} section of this man page and in the package's vignette. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # Only basic calls are presented in this manpage. # Many more examples are provided in the demo file nmf.R \dontrun{ demo('nmf') } # random data x <- rmatrix(20,10) # run default algorithm with rank 2 res <- nmf(x, 2) # specify the algorithm res <- nmf(x, 2, 'lee') # get verbose message on what is going on res <- nmf(x, 2, .options='v') \dontrun{ # more messages res <- nmf(x, 2, .options='v2') # even more res <- nmf(x, 2, .options='v3') # and so on ... } } \references{ Brunet J, Tamayo P, Golub TR and Mesirov JP (2004). "Metagenes and molecular pattern discovery using matrix factorization." _Proceedings of the National Academy of Sciences of the United States of America_, *101*(12), pp. 4164-9. ISSN 0027-8424, , . Lee DD and Seung H (2001). "Algorithms for non-negative matrix factorization." _Advances in neural information processing systems_. . Wang G, Kossenkov AV and Ochs MF (2006). "LS-NMF: a modified non-negative matrix factorization algorithm utilizing uncertainty estimates." _BMC bioinformatics_, *7*, pp. 175. ISSN 1471-2105, , . Pascual-Montano A, Carazo JM, Kochi K, Lehmann D and Pascual-marqui RD (2006). "Nonsmooth nonnegative matrix factorization (nsNMF)." _IEEE Trans. Pattern Anal. Mach. Intell_, *28*, pp. 403-415. Badea L (2008). "Extracting gene expression profiles common to colon and pancreatic adenocarcinoma using simultaneous nonnegative matrix factorization." _Pacific Symposium on Biocomputing. Pacific Symposium on Biocomputing_, *290*, pp. 267-78. ISSN 1793-5091, . Kim H and Park H (2007). "Sparse non-negative matrix factorizations via alternating non-negativity-constrained least squares for microarray data analysis." _Bioinformatics (Oxford, England)_, *23*(12), pp. 1495-502. ISSN 1460-2059, , . Van Benthem M and Keenan MR (2004). "Fast algorithm for the solution of large-scale non-negativity-constrained least squares problems." _Journal of Chemometrics_, *18*(10), pp. 441-450. ISSN 0886-9383, , . } \seealso{ \code{\link{nmfAlgorithm}} } \keyword{methods} NMF/man/Frobenius-nmf.Rd0000644000176200001440000001214313620502674014454 0ustar liggesusers\name{nmf_update.lee_R} \alias{Frobenius-nmf} \alias{lee-nmf} \alias{lee_R-nmf} \alias{nmfAlgorithm.Frobenius} \alias{nmfAlgorithm.lee} \alias{nmfAlgorithm.lee_R} \alias{nmf_update.lee} \alias{nmf_update.lee_R} \title{NMF Algorithm/Updates for Frobenius Norm} \usage{ nmf_update.lee_R(i, v, x, rescale = TRUE, eps = 10^-9, ...) nmf_update.lee(i, v, x, rescale = TRUE, copy = FALSE, eps = 10^-9, weight = NULL, ...) nmfAlgorithm.lee_R(..., .stop = NULL, maxIter = nmf.getOption("maxIter") \%||\% 2000, rescale = TRUE, eps = 10^-9, stopconv = 40, check.interval = 10) nmfAlgorithm.lee(..., .stop = NULL, maxIter = nmf.getOption("maxIter") \%||\% 2000, rescale = TRUE, copy = FALSE, eps = 10^-9, weight = NULL, stopconv = 40, check.interval = 10) nmfAlgorithm.Frobenius(..., .stop = NULL, maxIter = nmf.getOption("maxIter") \%||\% 2000, rescale = TRUE, copy = FALSE, eps = 10^-9, weight = NULL, stationary.th = .Machine$double.eps, check.interval = 5 * check.niter, check.niter = 10L) } \arguments{ \item{rescale}{logical that indicates if the basis matrix \eqn{W} should be rescaled so that its columns sum up to one.} \item{i}{current iteration number.} \item{v}{target matrix.} \item{x}{current NMF model, as an \code{\linkS4class{NMF}} object.} \item{eps}{small numeric value used to ensure numeric stability, by shifting up entries from zero to this fixed value.} \item{...}{extra arguments. These are generally not used and present only to allow other arguments from the main call to be passed to the initialisation and stopping criterion functions (slots \code{onInit} and \code{Stop} respectively).} \item{copy}{logical that indicates if the update should be made on the original matrix directly (\code{FALSE}) or on a copy (\code{TRUE} - default). With \code{copy=FALSE} the memory footprint is very small, and some speed-up may be achieved in the case of big matrices. However, greater care should be taken due the side effect. We recommend that only experienced users use \code{copy=TRUE}.} \item{.stop}{specification of a stopping criterion, that is used instead of the one associated to the NMF algorithm. It may be specified as: \itemize{ \item the access key of a registered stopping criterion; \item a single integer that specifies the exact number of iterations to perform, which will be honoured unless a lower value is explicitly passed in argument \code{maxIter}. \item a single numeric value that specifies the stationnarity threshold for the objective function, used in with \code{\link{nmf.stop.stationary}}; \item a function with signature \code{(object="NMFStrategy", i="integer", y="matrix", x="NMF", ...)}, where \code{object} is the \code{NMFStrategy} object that describes the algorithm being run, \code{i} is the current iteration, \code{y} is the target matrix and \code{x} is the current value of the NMF model. }} \item{maxIter}{maximum number of iterations to perform.} \item{stopconv}{number of iterations intervals over which the connectivity matrix must not change for stationarity to be achieved.} \item{check.interval}{interval (in number of iterations) on which the stopping criterion is computed.} \item{stationary.th}{maximum absolute value of the gradient, for the objective function to be considered stationary.} \item{check.niter}{number of successive iteration used to compute the stationnary criterion.} \item{weight}{numeric vector of sample weights, e.g., used to normalise samples coming from multiple datasets. It must be of the same length as the number of samples/columns in \code{v} -- and \code{h}.} } \description{ The built-in NMF algorithms described here minimise the Frobenius norm (Euclidean distance) between an NMF model and a target matrix. They use the updates for the basis and coefficient matrices (\eqn{W} and \eqn{H}) defined by \cite{Lee et al. (2001)}. \code{nmf_update.lee} implements in C++ an optimised version of the single update step. Algorithms \sQuote{lee} and \sQuote{.R#lee} provide the complete NMF algorithm from \cite{Lee et al. (2001)}, using the C++-optimised and pure R updates \code{\link{nmf_update.lee}} and \code{\link{nmf_update.lee_R}} respectively. Algorithm \sQuote{Frobenius} provides an NMF algorithm based on the C++-optimised version of the updates from \cite{Lee et al. (2001)}, which uses the stationarity of the objective value as a stopping criterion \code{\link{nmf.stop.stationary}}, instead of the stationarity of the connectivity matrix \code{\link{nmf.stop.connectivity}} as used by \sQuote{lee}. } \details{ \code{nmf_update.lee_R} implements in pure R a single update step, i.e. it updates both matrices. } \author{ Original update definition: D D Lee and HS Seung Port to R and optimisation in C++: Renaud Gaujoux } \references{ Lee DD and Seung H (2001). "Algorithms for non-negative matrix factorization." _Advances in neural information processing systems_. . } NMF/man/NMFns-class.Rd0000644000176200001440000001062213620502674014026 0ustar liggesusers\docType{class} \name{NMFns-class} \alias{NMFns-class} \title{NMF Model - Nonsmooth Nonnegative Matrix Factorization} \description{ This class implements the \emph{Nonsmooth Nonnegative Matrix Factorization} (nsNMF) model, required by the Nonsmooth NMF algorithm. The Nonsmooth NMF algorithm is defined by \cite{Pascual-Montano et al. (2006)} as a modification of the standard divergence based NMF algorithm (see section Details and references below). It aims at obtaining sparser factor matrices, by the introduction of a smoothing matrix. } \details{ The Nonsmooth NMF algorithm is a modification of the standard divergence based NMF algorithm (see \code{\linkS4class{NMF}}). Given a non-negative \eqn{n \times p}{n x p} matrix \eqn{V} and a factorization rank \eqn{r}, it fits the following model: \deqn{V \equiv W S(\theta) H,}{V ~ W S(theta) H,} where: \itemize{ \item \eqn{W} and \eqn{H} are such as in the standard model, i.e. non-negative matrices of dimension \eqn{n \times r}{n x r} and \eqn{r \times p}{r x p} respectively; \item \eqn{S} is a \eqn{r \times r} square matrix whose entries depends on an extra parameter \eqn{0\leq \theta \leq 1} in the following way: \deqn{S = (1-\theta)I + \frac{\theta}{r} 11^T ,} where \eqn{I} is the identity matrix and \eqn{1} is a vector of ones. } The interpretation of S as a smoothing matrix can be explained as follows: Let \eqn{X} be a positive, nonzero, vector. Consider the transformed vector \eqn{Y = S X}. If \eqn{\theta = 0}, then \eqn{Y = X} and no smoothing on \eqn{X} has occurred. However, as \eqn{\theta \to 1}{theta tends to 1}, the vector \eqn{Y} tends to the constant vector with all elements almost equal to the average of the elements of \eqn{X}. This is the smoothest possible vector in the sense of non-sparseness because all entries are equal to the same nonzero value, instead of having some values close to zero and others clearly nonzero. } \section{Methods}{ \describe{ \item{fitted}{\code{signature(object = "NMFns")}: Compute estimate for an NMFns object, according to the Nonsmooth NMF model (cf. \code{\link{NMFns-class}}). Extra arguments in \code{...} are passed to method \code{smoothing}, and are typically used to pass a value for \code{theta}, which is used to compute the smoothing matrix instead of the one stored in \code{object}. } \item{show}{\code{signature(object = "NMFns")}: Show method for objects of class \code{NMFns} } } } \section{Creating objects from the Class}{ Object of class \code{NMFns} can be created using the standard way with operator \code{\link{new}} However, as for all NMF model classes -- that extend class \code{\linkS4class{NMF}}, objects of class \code{NMFns} should be created using factory method \code{\link{nmfModel}} : \code{new('NMFns')} \code{nmfModel(model='NMFns')} \code{nmfModel(model='NMFns', W=w, theta=0.3} See \code{\link{nmfModel}} for more details on how to use the factory method. } \section{Algorithm}{ The Nonsmooth NMF algorithm uses a modified version of the multiplicative update equations in Lee & Seung's method for Kullback-Leibler divergence minimization. The update equations are modified to take into account the -- constant -- smoothing matrix. The modification reduces to using matrix \eqn{W S} instead of matrix \eqn{W} in the update of matrix \eqn{H}, and similarly using matrix \eqn{S H} instead of matrix \eqn{H} in the update of matrix \eqn{W}. After the matrix \eqn{W} has been updated, each of its columns is scaled so that it sums up to 1. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # create a completely empty NMFns object new('NMFns') # create a NMF object based on random (compatible) matrices n <- 50; r <- 3; p <- 20 w <- rmatrix(n, r) h <- rmatrix(r, p) nmfModel(model='NMFns', W=w, H=h) # apply Nonsmooth NMF algorithm to a random target matrix V <- rmatrix(n, p) \dontrun{nmf(V, r, 'ns')} # random nonsmooth NMF model rnmf(3, 10, 5, model='NMFns', theta=0.3) } \references{ Pascual-Montano A, Carazo JM, Kochi K, Lehmann D and Pascual-marqui RD (2006). "Nonsmooth nonnegative matrix factorization (nsNMF)." _IEEE Trans. Pattern Anal. Mach. Intell_, *28*, pp. 403-415. } \seealso{ Other NMF-model: \code{\link{initialize,NMFOffset-method}}, \code{\link{NMFOffset-class}}, \code{\link{NMFstd-class}} } NMF/man/NMFOffset-class.Rd0000644000176200001440000000744413620502674014644 0ustar liggesusers\docType{class} \name{NMFOffset-class} \alias{initialize,NMFOffset-method} \alias{NMFOffset-class} \title{NMF Model - Nonnegative Matrix Factorization with Offset} \usage{ \S4method{initialize}{NMFOffset}(.Object, ..., offset) } \arguments{ \item{offset}{optional numeric vector used to initialise slot \sQuote{offset}.} \item{.Object}{ An object: see the Details section.} \item{...}{data to include in the new object. Named arguments correspond to slots in the class definition. Unnamed arguments must be objects from classes that this class extends.} } \description{ This class implements the \emph{Nonnegative Matrix Factorization with Offset} model, required by the NMF with Offset algorithm. } \details{ The NMF with Offset algorithm is defined by \cite{Badea (2008)} as a modification of the euclidean based NMF algorithm from \code{Lee2001} (see section Details and references below). It aims at obtaining 'cleaner' factor matrices, by the introduction of an offset matrix, explicitly modelling a feature specific baseline -- constant across samples. } \section{Methods}{ \describe{ \item{fitted}{\code{signature(object = "NMFOffset")}: Computes the target matrix estimate for an NMFOffset object. The estimate is computed as: \deqn{ W H + offset } } \item{offset}{\code{signature(object = "NMFOffset")}: The function \code{offset} returns the offset vector from an NMF model that has an offset, e.g. an \code{NMFOffset} model. } \item{rnmf}{\code{signature(x = "NMFOffset", target = "numeric")}: Generates a random NMF model with offset, from class \code{NMFOffset}. The offset values are drawn from a uniform distribution between 0 and the maximum entry of the basis and coefficient matrices, which are drawn by the next suitable \code{\link{rnmf}} method, which is the workhorse method \code{rnmf,NMF,numeric}. } \item{show}{\code{signature(object = "NMFOffset")}: Show method for objects of class \code{NMFOffset} } } } \section{Creating objects from the Class}{ Object of class \code{NMFOffset} can be created using the standard way with operator \code{\link{new}} However, as for all NMF model classes -- that extend class \code{\linkS4class{NMF}}, objects of class \code{NMFOffset} should be created using factory method \code{\link{nmfModel}} : \code{new('NMFOffset')} \code{nmfModel(model='NMFOffset')} \code{nmfModel(model='NMFOffset', W=w, offset=rep(1, nrow(w)))} See \code{\link{nmfModel}} for more details on how to use the factory method. } \section{Initialize method}{ The initialize method for \code{NMFOffset} objects tries to correct the initial value passed for slot \code{offset}, so that it is consistent with the dimensions of the \code{NMF} model: it will pad the offset vector with NA values to get the length equal to the number of rows in the basis matrix. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # create a completely empty NMF object new('NMFOffset') # create a NMF object based on random (compatible) matrices n <- 50; r <- 3; p <- 20 w <- rmatrix(n, r) h <- rmatrix(r, p) nmfModel(model='NMFOffset', W=w, H=h, offset=rep(0.5, nrow(w))) # apply Nonsmooth NMF algorithm to a random target matrix V <- rmatrix(n, p) \dontrun{nmf(V, r, 'offset')} # random NMF model with offset rnmf(3, 10, 5, model='NMFOffset') } \references{ Badea L (2008). "Extracting gene expression profiles common to colon and pancreatic adenocarcinoma using simultaneous nonnegative matrix factorization." _Pacific Symposium on Biocomputing. Pacific Symposium on Biocomputing_, *290*, pp. 267-78. ISSN 1793-5091, . } \seealso{ Other NMF-model: \code{\link{NMFns-class}}, \code{\link{NMFstd-class}} } \keyword{methods} NMF/man/canFit.Rd0000644000176200001440000000255013620502674013147 0ustar liggesusers\docType{methods} \name{canFit} \alias{canFit} \alias{canFit,character,ANY-method} \alias{canFit-methods} \alias{canFit,NMFStrategy,character-method} \alias{canFit,NMFStrategy,NMF-method} \title{Testing Compatibility of Algorithm and Models} \usage{ canFit(x, y, ...) \S4method{canFit}{NMFStrategy,character}(x, y, exact = FALSE) } \arguments{ \item{x}{an object that describes an algorithm} \item{y}{an object that describes a model} \item{...}{extra arguments to allow extension} \item{exact}{for logical that indicates if an algorithm is considered able to fit only the models that it explicitly declares (\code{TRUE}), or if it should be considered able to also fit models that extend models that it explicitly fits.} } \description{ \code{canFit} is an S4 generic that tests if an algorithm can fit a particular model. } \section{Methods}{ \describe{ \item{canFit}{\code{signature(x = "NMFStrategy", y = "character")}: Tells if an NMF algorithm can fit a given class of NMF models } \item{canFit}{\code{signature(x = "NMFStrategy", y = "NMF")}: Tells if an NMF algorithm can fit the same class of models as \code{y} } \item{canFit}{\code{signature(x = "character", y = "ANY")}: Tells if a registered NMF algorithm can fit a given NMF model } } } \seealso{ Other regalgo: \code{\link{nmfAlgorithm}} } \keyword{methods} NMF/man/fcnnls.Rd0000644000176200001440000001252513620502674013231 0ustar liggesusers\docType{methods} \name{fcnnls} \alias{fcnnls} \alias{fcnnls,ANY,numeric-method} \alias{fcnnls,matrix,matrix-method} \alias{fcnnls-methods} \alias{fcnnls,numeric,matrix-method} \title{Fast Combinatorial Nonnegative Least-Square} \usage{ fcnnls(x, y, ...) \S4method{fcnnls}{matrix,matrix}(x, y, verbose = FALSE, pseudo = TRUE, ...) } \arguments{ \item{...}{extra arguments passed to the internal function \code{.fcnnls}. Currently not used.} \item{verbose}{toggle verbosity (default is \code{FALSE}).} \item{x}{the coefficient matrix} \item{y}{the target matrix to be approximated by \eqn{X K}.} \item{pseudo}{By default (\code{pseudo=FALSE}) the algorithm uses Gaussian elimination to solve the successive internal linear problems, using the \code{\link{solve}} function. If \code{pseudo=TRUE} the algorithm uses Moore-Penrose generalized \code{\link[corpcor]{pseudoinverse}} from the \code{corpcor} package instead of \link{solve}.} } \value{ A list containing the following components: \item{x}{ the estimated optimal matrix \eqn{K}.} \item{fitted}{ the fitted matrix \eqn{X K}.} \item{residuals}{ the residual matrix \eqn{Y - X K}.} \item{deviance}{ the residual sum of squares between the fitted matrix \eqn{X K} and the target matrix \eqn{Y}. That is the sum of the square residuals.} \item{passive}{ a \eqn{r x p} logical matrix containing the passive set, that is the set of entries in \eqn{K} that are not null (i.e. strictly positive).} \item{pseudo}{ a logical that is \code{TRUE} if the computation was performed using the pseudoinverse. See argument \code{pseudo}.} } \description{ This function solves the following nonnegative least square linear problem using normal equations and the fast combinatorial strategy from \cite{Van Benthem et al. (2004)}: \deqn{ \begin{array}{l} \min \|Y - X K\|_F\\ \mbox{s.t. } K>=0 \end{array} }{min ||Y - X K||_F, s.t. K>=0} where \eqn{Y} and \eqn{X} are two real matrices of dimension \eqn{n \times p}{n x p} and \eqn{n \times r}{n x r} respectively, and \eqn{\|.\|_F}{|.|_F} is the Frobenius norm. The algorithm is very fast compared to other approaches, as it is optimised for handling multiple right-hand sides. } \details{ Within the \code{NMF} package, this algorithm is used internally by the SNMF/R(L) algorithm from \cite{Kim et al. (2007)} to solve general Nonnegative Matrix Factorization (NMF) problems, using alternating nonnegative constrained least-squares. That is by iteratively and alternatively estimate each matrix factor. The algorithm is an active/passive set method, which rearrange the right-hand side to reduce the number of pseudo-inverse calculations. It uses the unconstrained solution \eqn{K_u} obtained from the unconstrained least squares problem, i.e. \eqn{\min \|Y - X K\|_F^2}{min ||Y - X K||_F^2} , so as to determine the initial passive sets. The function \code{fcnnls} is provided separately so that it can be used to solve other types of nonnegative least squares problem. For faster computation, when multiple nonnegative least square fits are needed, it is recommended to directly use the function \code{\link{.fcnnls}}. The code of this function is a port from the original MATLAB code provided by \cite{Kim et al. (2007)}. } \section{Methods}{ \describe{ \item{fcnnls}{\code{signature(x = "matrix", y = "matrix")}: This method wraps a call to the internal function \code{.fcnnls}, and formats the results in a similar way as other lest-squares methods such as \code{\link{lm}}. } \item{fcnnls}{\code{signature(x = "numeric", y = "matrix")}: Shortcut for \code{fcnnls(as.matrix(x), y, ...)}. } \item{fcnnls}{\code{signature(x = "ANY", y = "numeric")}: Shortcut for \code{fcnnls(x, as.matrix(y), ...)}. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } ## Define a random nonnegative matrix matrix n <- 200; p <- 20; r <- 3 V <- rmatrix(n, p) ## Compute the optimal matrix K for a given X matrix X <- rmatrix(n, r) res <- fcnnls(X, V) ## Compute the same thing using the Moore-Penrose generalized pseudoinverse res <- fcnnls(X, V, pseudo=TRUE) ## It also works in the case of single vectors y <- runif(n) res <- fcnnls(X, y) # or res <- fcnnls(X[,1], y) } \author{ Original MATLAB code : Van Benthem and Keenan Adaption of MATLAB code for SNMF/R(L): H. Kim Adaptation to the NMF package framework: Renaud Gaujoux } \references{ Original MATLAB code from Van Benthem and Keenan, slightly modified by H. Kim:\cr \url{http://www.cc.gatech.edu/~hpark/software/fcnnls.m} Van Benthem M and Keenan MR (2004). "Fast algorithm for the solution of large-scale non-negativity-constrained least squares problems." _Journal of Chemometrics_, *18*(10), pp. 441-450. ISSN 0886-9383, , . Kim H and Park H (2007). "Sparse non-negative matrix factorizations via alternating non-negativity-constrained least squares for microarray data analysis." _Bioinformatics (Oxford, England)_, *23*(12), pp. 1495-502. ISSN 1460-2059, , . } \seealso{ \code{\link{nmf}} } \keyword{methods} \keyword{multivariate} \keyword{optimize} \keyword{regression} NMF/man/ccSpec.Rd0000644000176200001440000000045213620502674013142 0ustar liggesusers\name{ccSpec} \alias{ccSpec} \title{Extract Colour Palette Specification} \usage{ ccSpec(x) } \arguments{ \item{a}{character string that specify a colour palette.} } \value{ a list with elements: palette, n and rev } \description{ Extract Colour Palette Specification } \keyword{internal} NMF/man/t.NMF.Rd0000644000176200001440000000164013620502674012624 0ustar liggesusers\name{t.NMF} \alias{t.NMF} \title{Transformation NMF Model Objects} \usage{ \method{t}{NMF} (x) } \arguments{ \item{x}{NMF model object.} } \description{ \code{t} transpose an NMF model, by transposing and swapping its basis and coefficient matrices: \eqn{t([W,H]) = [t(H), t(W)]}. } \details{ The function \code{t} is a generic defined in the \pkg{base} package. The method \code{t.NMF} defines the trasnformation for the general NMF interface. This method may need to be overloaded for NMF models, whose structure requires specific handling. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } x <- rnmf(3, 100, 20) x # transpose y <- t(x) y # factors are swapped-transposed stopifnot( identical(basis(y), t(coef(x))) ) stopifnot( identical(coef(y), t(basis(x))) ) } \seealso{ Other transforms: \code{\link{nneg}}, \code{\link{posneg}}, \code{\link{rposneg}} } NMF/man/txtProgressBar.Rd0000644000176200001440000000316313620502674014735 0ustar liggesusers\name{txtProgressBar} \alias{txtProgressBar} \title{Simple Progress Bar} \usage{ txtProgressBar(min = 0, max = 1, initial = 0, char = "=", width = NA, title = if (style == 3) " ", label, style = 1, file = "", shared = NULL) } \arguments{ \item{shared}{specification of a shared directory to use when the progress bar is to be used by multiple processes.} \item{min}{(finite) numeric values for the extremes of the progress bar. Must have \code{min < max}.} \item{max}{(finite) numeric values for the extremes of the progress bar. Must have \code{min < max}.} \item{initial}{initial or new value for the progress bar. See \sQuote{Details} for what happens with invalid values.} \item{char}{the character (or character string) to form the progress bar.} \item{width}{the width of the progress bar, as a multiple of the width of \code{char}. If \code{NA}, the default, the number of characters is that which fits into \code{getOption("width")}.} \item{title}{ignored, for compatibility with other progress bars.} \item{label}{ignored, for compatibility with other progress bars.} \item{style}{the \sQuote{style} of the bar -- see \sQuote{Details}.} \item{file}{an open connection object or \code{""} which indicates the console: \code{\link{stderr}()} might be useful here.} } \description{ Creates a simple progress bar with title. This function is identical to \code{utils::txtProgressBar} but allow adding a title to the progress bar, and can be shared by multiple processes, e.g., in multicore or multi-hosts computations. } \author{ R Core Team } \keyword{internal} NMF/man/scores.Rd0000644000176200001440000001752013620502674013244 0ustar liggesusers\docType{methods} \name{featureScore} \alias{extractFeatures} \alias{extractFeatures,matrix-method} \alias{extractFeatures-methods} \alias{extractFeatures,NMF-method} \alias{featureScore} \alias{featureScore,matrix-method} \alias{featureScore-methods} \alias{featureScore,NMF-method} \title{Feature Selection in NMF Models} \usage{ featureScore(object, ...) \S4method{featureScore}{matrix}(object, method = c("kim", "max")) extractFeatures(object, ...) \S4method{extractFeatures}{matrix}(object, method = c("kim", "max"), format = c("list", "combine", "subset"), nodups = TRUE) } \arguments{ \item{object}{an object from which scores/features are computed/extracted} \item{...}{extra arguments to allow extension} \item{method}{scoring or selection method. It specifies the name of one of the method described in sections \emph{Feature scores} and \emph{Feature selection}. Additionally for \code{extractFeatures}, it may be an integer vector that indicates the number of top most contributing features to extract from each column of \code{object}, when ordered in decreasing order, or a numeric value between 0 and 1 that indicates the minimum relative basis contribution above which a feature is selected (i.e. basis contribution threshold). In the case of a single numeric value (integer or percentage), it is used for all columns. Note that \code{extractFeatures(x, 1)} means relative contribution threshold of 100\%, to select the top contributing features one must explicitly specify an integer value as in \code{extractFeatures(x, 1L)}. However, if all elements in methods are > 1, they are automatically treated as if they were integers: \code{extractFeatures(x, 2)} means the top-2 most contributing features in each component.} \item{format}{output format. The following values are accepted: \describe{ \item{\sQuote{list}}{(default) returns a list with one element per column in \code{object}, each containing the indexes of the selected features, as an integer vector. If \code{object} has row names, these are used to name each index vector. Components for which no feature were selected are assigned a \code{NA} value.} \item{\sQuote{combine}}{ returns all indexes in a single vector. Duplicated indexes are made unique if \code{nodups=TRUE} (default).} \item{\sQuote{subset}}{ returns an object of the same class as \code{object}, but subset with the selected indexes, so that it contains data only from basis-specific features.} }} \item{nodups}{logical that indicates if duplicated indexes, i.e. features selected on multiple basis components (which should in theory not happen), should be only appear once in the result. Only used when \code{format='combine'}.} } \value{ \code{featureScore} returns a numeric vector of the length the number of rows in \code{object} (i.e. one score per feature). \code{extractFeatures} returns the selected features as a list of indexes, a single integer vector or an object of the same class as \code{object} that only contains the selected features. } \description{ The function \code{featureScore} implements different methods to computes basis-specificity scores for each feature in the data. The function \code{extractFeatures} implements different methods to select the most basis-specific features of each basis component. } \details{ One of the properties of Nonnegative Matrix Factorization is that is tend to produce sparse representation of the observed data, leading to a natural application to bi-clustering, that characterises groups of samples by a small number of features. In NMF models, samples are grouped according to the basis components that contributes the most to each sample, i.e. the basis components that have the greatest coefficient in each column of the coefficient matrix (see \code{\link{predict,NMF-method}}). Each group of samples is then characterised by a set of features selected based on basis-specifity scores that are computed on the basis matrix. } \section{Methods}{ \describe{ \item{extractFeatures}{\code{signature(object = "matrix")}: Select features on a given matrix, that contains the basis component in columns. } \item{extractFeatures}{\code{signature(object = "NMF")}: Select basis-specific features from an NMF model, by applying the method \code{extractFeatures,matrix} to its basis matrix. } \item{featureScore}{\code{signature(object = "matrix")}: Computes feature scores on a given matrix, that contains the basis component in columns. } \item{featureScore}{\code{signature(object = "NMF")}: Computes feature scores on the basis matrix of an NMF model. } } } \section{Feature scores}{ The function \code{featureScore} can compute basis-specificity scores using the following methods: \describe{ \item{\sQuote{kim}}{ Method defined by \cite{Kim et al. (2007)}. The score for feature \eqn{i} is defined as: \deqn{S_i = 1 + \frac{1}{\log_2 k} \sum_{q=1}^k p(i,q) \log_2 p(i,q)}{ S_i = 1 + 1/log2(k) sum_q [ p(i,q) log2( p(i,q) ) ] }, where \eqn{p(i,q)} is the probability that the \eqn{i}-th feature contributes to basis \eqn{q}: \deqn{p(i,q) = \frac{W(i,q)}{\sum_{r=1}^k W(i,r)} }{ p(i,q) = W(i,q) / (sum_r W(i,r)) } The feature scores are real values within the range [0,1]. The higher the feature score the more basis-specific the corresponding feature. } \item{\sQuote{max}}{Method defined by \cite{Carmona-Saez et al. (2006)}. The feature scores are defined as the row maximums. } } } \section{Feature selection}{ The function \code{extractFeatures} can select features using the following methods: \describe{ \item{\sQuote{kim}}{ uses \cite{Kim et al. (2007)} scoring schema and feature selection method. The features are first scored using the function \code{featureScore} with method \sQuote{kim}. Then only the features that fulfil both following criteria are retained: \itemize{ \item score greater than \eqn{\hat{\mu} + 3 \hat{\sigma}}, where \eqn{\hat{\mu}} and \eqn{\hat{\sigma}} are the median and the median absolute deviation (MAD) of the scores respectively; \item the maximum contribution to a basis component is greater than the median of all contributions (i.e. of all elements of W). } } \item{\sQuote{max}}{ uses the selection method used in the \code{bioNMF} software package and described in \cite{Carmona-Saez et al. (2006)}. For each basis component, the features are first sorted by decreasing contribution. Then, one selects only the first consecutive features whose highest contribution in the basis matrix is effectively on the considered basis. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # random NMF model x <- rnmf(3, 50,20) # probably no feature is selected extractFeatures(x) # extract top 5 for each basis extractFeatures(x, 5L) # extract features that have a relative basis contribution above a threshold extractFeatures(x, 0.5) # ambiguity? extractFeatures(x, 1) # means relative contribution above 100\% extractFeatures(x, 1L) # means top contributing feature in each component } \references{ Kim H and Park H (2007). "Sparse non-negative matrix factorizations via alternating non-negativity-constrained least squares for microarray data analysis." _Bioinformatics (Oxford, England)_, *23*(12), pp. 1495-502. ISSN 1460-2059, , . Carmona-Saez P, Pascual-Marqui RD, Tirado F, Carazo JM and Pascual-Montano A (2006). "Biclustering of gene expression data by Non-smooth Non-negative Matrix Factorization." _BMC bioinformatics_, *7*, pp. 78. ISSN 1471-2105, , . } \keyword{methods} NMF/man/NMFfitX-class.Rd0000644000176200001440000001442713710764102014323 0ustar liggesusers\docType{class} \name{NMFfitX-class} \alias{NMFfitX-class} \title{Virtual Class to Handle Results from Multiple Runs of NMF Algorithms} \description{ This class defines a common interface to handle the results from multiple runs of a single NMF algorithm, performed with the \code{\link{nmf}} method. } \details{ Currently, this interface is implemented by two classes, \code{\linkS4class{NMFfitX1}} and \code{\linkS4class{NMFfitXn}}, which respectively handle the case where only the best fit is kept, and the case where the list of all the fits is returned. See \code{\link{nmf}} for more details on the method arguments. } \section{Slots}{ \describe{ \item{runtime.all}{Object of class \code{\link[=proc.time]{proc_time}} that contains CPU times required to perform all the runs.} } } \section{Methods}{ \describe{ \item{basismap}{\code{signature(object = "NMFfitX")}: Plots a heatmap of the basis matrix of the best fit in \code{object}. } \item{coefmap}{\code{signature(object = "NMFfitX")}: Plots a heatmap of the coefficient matrix of the best fit in \code{object}. This method adds: \itemize{ \item an extra special column annotation track for multi-run NMF fits, \code{'consensus:'}, that shows the consensus cluster associated to each sample. \item a column sorting schema \code{'consensus'} that can be passed to argument \code{Colv} and orders the columns using the hierarchical clustering of the consensus matrix with average linkage, as returned by \code{\link{consensushc}(object)}. This is also the ordering that is used by default for the heatmap of the consensus matrix as ploted by \code{\link{consensusmap}}. } } \item{consensus}{\code{signature(object = "NMFfitX")}: Pure virtual method defined to ensure \code{consensus} is defined for sub-classes of \code{NMFfitX}. It throws an error if called. } \item{consensushc}{\code{signature(object = "NMFfitX")}: Compute the hierarchical clustering on the consensus matrix of \code{object}, or on the connectivity matrix of the best fit in \code{object}. } \item{consensusmap}{\code{signature(object = "NMFfitX")}: Plots a heatmap of the consensus matrix obtained when fitting an NMF model with multiple runs. } \item{cophcor}{\code{signature(object = "NMFfitX")}: Computes the cophenetic correlation coefficient on the consensus matrix of \code{object}. All arguments in \code{...} are passed to the method \code{cophcor,matrix}. } \item{deviance}{\code{signature(object = "NMFfitX")}: Returns the deviance achieved by the best fit object, i.e. the lowest deviance achieved across all NMF runs. } \item{dispersion}{\code{signature(object = "NMFfitX")}: Computes the dispersion on the consensus matrix obtained from multiple NMF runs. } \item{fit}{\code{signature(object = "NMFfitX")}: Returns the model object that achieves the lowest residual approximation error across all the runs. It is a pure virtual method defined to ensure \code{fit} is defined for sub-classes of \code{NMFfitX}, which throws an error if called. } \item{getRNG1}{\code{signature(object = "NMFfitX")}: Returns the RNG settings used for the first NMF run of multiple NMF runs. } \item{ibterms}{\code{signature(object = "NMFfitX")}: Method for multiple NMF fit objects, which returns the indexes of fixed basis terms from the best fitted model. } \item{metaHeatmap}{\code{signature(object = "NMFfitX")}: Deprecated method subsituted by \code{\link{consensusmap}}. } \item{minfit}{\code{signature(object = "NMFfitX")}: Returns the fit object that achieves the lowest residual approximation error across all the runs. It is a pure virtual method defined to ensure \code{minfit} is defined for sub-classes of \code{NMFfitX}, which throws an error if called. } \item{nmf.equal}{\code{signature(x = "NMFfitX", y = "NMF")}: Compares two NMF models when at least one comes from multiple NMF runs. } \item{NMFfitX}{\code{signature(object = "NMFfitX")}: Provides a way to aggregate \code{NMFfitXn} objects into an \code{NMFfitX1} object. } \item{nrun}{\code{signature(object = "NMFfitX")}: Returns the number of NMF runs performed to create \code{object}. It is a pure virtual method defined to ensure \code{nrun} is defined for sub-classes of \code{NMFfitX}, which throws an error if called. See \code{\link{nrun,NMFfitX-method}} for more details. } \item{predict}{\code{signature(object = "NMFfitX")}: Returns the cluster membership index from an NMF model fitted with multiple runs. Besides the type of clustering available for any NMF models (\code{'columns', 'rows', 'samples', 'features'}), this method can return the cluster membership index based on the consensus matrix, computed from the multiple NMF runs. See \code{\link{predict,NMFfitX-method}} for more details. } \item{residuals}{\code{signature(object = "NMFfitX")}: Returns the residuals achieved by the best fit object, i.e. the lowest residual approximation error achieved across all NMF runs. } \item{runtime.all}{\code{signature(object = "NMFfitX")}: Returns the CPU time required to compute all the NMF runs. It returns \code{NULL} if no CPU data is available. } \item{show}{\code{signature(object = "NMFfitX")}: Show method for objects of class \code{NMFfitX} } \item{summary}{\code{signature(object = "NMFfitX")}: Computes a set of measures to help evaluate the quality of the \emph{best fit} of the set. The result is similar to the result from the \code{summary} method of \code{NMFfit} objects. See \code{\linkS4class{NMF}} for details on the computed measures. In addition, the cophenetic correlation (\code{\link{cophcor}}) and \code{\link{dispersion}} coefficients of the consensus matrix are returned, as well as the total CPU time (\code{\link{runtime.all}}). } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # generate a synthetic dataset with known classes n <- 20; counts <- c(5, 2, 3); V <- syntheticNMF(n, counts) # perform multiple runs of one algorithm (default is to keep only best fit) res <- nmf(V, 3, nrun=3) res # plot a heatmap of the consensus matrix \dontrun{ consensusmap(res) } } \seealso{ Other multipleNMF: \code{\link{NMFfitX1-class}}, \code{\link{NMFfitXn-class}} } NMF/man/plot-commaNMFfit-commamissing-method.Rd0000644000176200001440000000443613620502674021026 0ustar liggesusers\docType{methods} \name{plot,NMFfit,missing-method} \alias{plot,NMFfit,missing-method} \title{Plots the residual track computed at regular interval during the fit of the NMF model \code{x}.} \usage{ \S4method{plot}{NMFfit,missing}(x, y, skip = -1, ...) } \arguments{ \item{skip}{an integer that indicates the number of points to skip/remove from the beginning of the curve. If \code{skip=1L} (default) only the initial residual -- that is computed before any iteration, is skipped, if present in the track (it associated with iteration 0).} \item{x}{the coordinates of points in the plot. Alternatively, a single plotting structure, function or \emph{any \R object with a \code{plot} method} can be provided.} \item{y}{the y coordinates of points in the plot, \emph{optional} if \code{x} is an appropriate structure.} \item{...}{Arguments to be passed to methods, such as \link{graphical parameters} (see \code{\link{par}}). Many methods will accept the following arguments: \describe{ \item{\code{type}}{what type of plot should be drawn. Possible types are \itemize{ \item \code{"p"} for \bold{p}oints, \item \code{"l"} for \bold{l}ines, \item \code{"b"} for \bold{b}oth, \item \code{"c"} for the lines part alone of \code{"b"}, \item \code{"o"} for both \sQuote{\bold{o}verplotted}, \item \code{"h"} for \sQuote{\bold{h}istogram} like (or \sQuote{high-density}) vertical lines, \item \code{"s"} for stair \bold{s}teps, \item \code{"S"} for other \bold{s}teps, see \sQuote{Details} below, \item \code{"n"} for no plotting. } All other \code{type}s give a warning or an error; using, e.g., \code{type = "punkte"} being equivalent to \code{type = "p"} for S compatibility. Note that some methods, e.g. \code{\link{plot.factor}}, do not accept this. } \item{\code{main}}{an overall title for the plot: see \code{\link{title}}.} \item{\code{sub}}{a sub title for the plot: see \code{\link{title}}.} \item{\code{xlab}}{a title for the x axis: see \code{\link{title}}.} \item{\code{ylab}}{a title for the y axis: see \code{\link{title}}.} \item{\code{asp}}{the \eqn{y/x} aspect ratio, see \code{\link{plot.window}}.} } } } \description{ Plots the residual track computed at regular interval during the fit of the NMF model \code{x}. } \keyword{methods} NMF/man/show-commaNMFns-method.Rd0000644000176200001440000000044513620502674016176 0ustar liggesusers\docType{methods} \name{show,NMFns-method} \alias{show,NMFns-method} \title{Show method for objects of class \code{NMFns}} \usage{ \S4method{show}{NMFns}(object) } \arguments{ \item{object}{Any R object} } \description{ Show method for objects of class \code{NMFns} } \keyword{methods} NMF/man/parallel.Rd0000644000176200001440000000436113620502674013541 0ustar liggesusers\name{parallel-NMF} \alias{gVariable} \alias{hostfile} \alias{parallel-NMF} \alias{ts_eval} \alias{ts_tempfile} \title{Utilities for Parallel Computations} \usage{ ts_eval(mutex = synchronicity::boost.mutex(), verbose = FALSE) ts_tempfile(pattern = "file", ..., host = TRUE, pid = TRUE) hostfile(pattern = "file", tmpdir = tempdir(), fileext = "", host = TRUE, pid = TRUE) gVariable(init, shared = FALSE) } \arguments{ \item{mutex}{a mutex or a mutex descriptor. If missing, a new mutex is created via the function \emph{boost.mutex} from the \emph{synchronicity} package.} \item{verbose}{a logical that indicates if messages should be printed when locking and unlocking the mutex.} \item{...}{extra arguments passed to \code{\link[base]{tempfile}}.} \item{host}{logical that indicates if the host machine name should be appear in the filename.} \item{pid}{logical that indicates if the current process id be appear in the filename.} \item{init}{initial value} \item{shared}{a logical that indicates if the variable should be stored in shared memory or in a local environment.} \item{pattern}{a non-empty character vector giving the initial part of the name.} \item{tmpdir}{a non-empty character vector giving the directory name} \item{fileext}{a non-empty character vector giving the file extension} } \description{ Utilities for Parallel Computations \code{ts_eval} generates a thread safe version of \code{\link{eval}}. It uses boost mutexes provided by the \emph{synchronicity} package. The generated function has arguments \code{expr} and \code{envir}, which are passed to \code{\link{eval}}. \code{ts_tempfile} generates a \emph{unique} temporary filename that includes the name of the host machine and/or the caller's process id, so that it is thread safe. \code{hostfile} generates a temporary filename composed with the name of the host machine and/or the current process id. \code{gVariable} generates a function that access a global static variable, possibly in shared memory (only for numeric matrix-coercible data in this case). It is used primarily in parallel computations, to preserve data accross computations that are performed by the same process. } NMF/man/basiscor.Rd0000644000176200001440000000642213620502674013552 0ustar liggesusers\docType{methods} \name{basiscor} \alias{basiscor} \alias{basiscor,matrix,NMF-method} \alias{basiscor-methods} \alias{basiscor,NMF,matrix-method} \alias{basiscor,NMF,missing-method} \alias{basiscor,NMF,NMF-method} \alias{profcor} \alias{profcor,matrix,NMF-method} \alias{profcor-methods} \alias{profcor,NMF,matrix-method} \alias{profcor,NMF,missing-method} \alias{profcor,NMF,NMF-method} \title{Correlations in NMF Models} \usage{ basiscor(x, y, ...) profcor(x, y, ...) } \arguments{ \item{x}{a matrix or an object with suitable methods \code{\link{basis}} or \code{\link{coef}}.} \item{y}{a matrix or an object with suitable methods \code{\link{basis}} or \code{\link{coef}}, and dimensions compatible with \code{x}. If missing the correlations are computed between \code{x} and \code{y=x}.} \item{...}{extra arguments passed to \code{\link{cor}}.} } \description{ \code{basiscor} computes the correlation matrix between basis vectors, i.e. the \emph{columns} of its basis matrix -- which is the model's first matrix factor. \code{profcor} computes the correlation matrix between basis profiles, i.e. the \emph{rows} of the coefficient matrix -- which is the model's second matrix factor. } \details{ Each generic has methods defined for computing correlations between NMF models and/or compatible matrices. The computation is performed by the base function \code{\link{cor}}. } \section{Methods}{ \describe{ \item{basiscor}{\code{signature(x = "NMF", y = "matrix")}: Computes the correlations between the basis vectors of \code{x} and the columns of \code{y}. } \item{basiscor}{\code{signature(x = "matrix", y = "NMF")}: Computes the correlations between the columns of \code{x} and the the basis vectors of \code{y}. } \item{basiscor}{\code{signature(x = "NMF", y = "NMF")}: Computes the correlations between the basis vectors of \code{x} and \code{y}. } \item{basiscor}{\code{signature(x = "NMF", y = "missing")}: Computes the correlations between the basis vectors of \code{x}. } \item{profcor}{\code{signature(x = "NMF", y = "matrix")}: Computes the correlations between the basis profiles of \code{x} and the rows of \code{y}. } \item{profcor}{\code{signature(x = "matrix", y = "NMF")}: Computes the correlations between the rows of \code{x} and the basis profiles of \code{y}. } \item{profcor}{\code{signature(x = "NMF", y = "NMF")}: Computes the correlations between the basis profiles of \code{x} and \code{y}. } \item{profcor}{\code{signature(x = "NMF", y = "missing")}: Computes the correlations between the basis profiles of \code{x}. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # generate two random NMF models a <- rnmf(3, 100, 20) b <- rnmf(3, 100, 20) # Compute auto-correlations basiscor(a) profcor(a) # Compute correlations with b basiscor(a, b) profcor(a, b) # try to recover the underlying NMF model 'a' from noisy data res <- nmf(fitted(a) + rmatrix(a), 3) # Compute correlations with the true model basiscor(a, res) profcor(a, res) # Compute correlations with a random compatible matrix W <- rmatrix(basis(a)) basiscor(a, W) identical(basiscor(a, W), basiscor(W, a)) H <- rmatrix(coef(a)) profcor(a, H) identical(profcor(a, H), profcor(H, a)) } \keyword{methods} NMF/man/nmfWrapper.Rd0000644000176200001440000000371613620502674014071 0ustar liggesusers\name{nmfWrapper} \alias{nmfWrapper} \title{Wrapping NMF Algorithms} \usage{ nmfWrapper(method, ..., .FIXED = FALSE) } \arguments{ \item{method}{Name of the NMF algorithm to be wrapped. It should be the name of a registered algorithm as returned by \code{\link{nmfAlgorithm}}, or an NMF algorithm object (i.e. an instance of \code{\linkS4class{NMFStrategy}}).} \item{...}{extra named arguments that define default values for any arguments of \code{\link{nmf}} or the algorithm itself.} \item{.FIXED}{a logical that indicates if the default arguments defined in \code{...} must be considered as fixed, i.e. that they are forced to have the defined values and cannot be used in a call to the wrapper function, in which case, a warning about discarding them is thrown if they are used. Non fixed arguments may have their value changed at call time, in which case it is honoured and passed to the \code{nmf} call. \code{.FIXED} may also be a character vector that specifies which argument amongst \code{...} should be considered as fixed.} } \value{ a function with argument \code{...} and a set of default arguments defined in \code{...} in the call to \code{nmfWrapper}. } \description{ This function creates a wrapper function for calling the function \code{\link{nmf}} with a given NMF algorithm. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # wrap Lee & Seung algorithm into a function lee <- nmfWrapper('lee', seed=12345) args(lee) # test on random data x <- rmatrix(100,20) res <- nmf(x, 3, 'lee', seed=12345) res2 <- lee(x, 3) nmf.equal(res, res2) res3 <- lee(x, 3, seed=123) nmf.equal(res, res3) \dontshow{ stopifnot(nmf.equal(res, res2)) stopifnot( !nmf.equal(res, res3)) } # argument 'method' has no effect res4 <- lee(x, 3, method='brunet') nmf.equal(res, res4) \dontshow{ stopifnot(nmf.equal(res, res4)) } } \seealso{ \code{\link{nmfAlgorithm}}, \code{\link{nmf}} } \keyword{internal} NMF/man/nmf-compare.Rd0000644000176200001440000001513413620502674014151 0ustar liggesusers\docType{methods} \name{compare-NMF} \alias{compare,list-method} \alias{compare-NMF} \alias{compare,NMFfit-method} \alias{consensusmap,list-method} \alias{consensusmap,NMF.rank-method} \alias{plot,NMFList,missing-method} \alias{summary,NMFList-method} \title{Comparing Results from Different NMF Runs} \usage{ \S4method{compare}{NMFfit}(object, ...) \S4method{compare}{list}(object, ...) \S4method{summary}{NMFList}(object, sort.by = NULL, select = NULL, ...) \S4method{plot}{NMFList,missing}(x, y, skip = -1, ...) \S4method{consensusmap}{NMF.rank}(object, ...) \S4method{consensusmap}{list}(object, layout, Rowv = FALSE, main = names(object), ...) } \arguments{ \item{...}{extra arguments passed by \code{compare} to \code{summary,NMFList} or to the \code{summary} method of each fit.} \item{select}{the columns to be output in the result \code{data.frame}. The column are given by their names (partially matched). The column names are the names of the summary measures returned by the \code{summary} methods of the corresponding NMF results.} \item{sort.by}{the sorting criteria, i.e. a partial match of a column name, by which the result \code{data.frame} is sorted. The sorting direction (increasing or decreasing) is computed internally depending on the chosen criteria (e.g. decreasing for the cophenetic coefficient, increasing for the residuals).} \item{x}{an \code{NMFList} object that contains fits from separate NMF runs.} \item{y}{missing} \item{layout}{specification of the layout. It may be a single numeric or a numeric couple, to indicate a square or rectangular layout respectively, that is filled row by row. It may also be a matrix that is directly passed to the function \code{\link[graphics]{layout}} from the package \code{graphics}.} \item{object}{an object computed using some algorithm, or that describes an algorithm itself.} \item{skip}{an integer that indicates the number of points to skip/remove from the beginning of the curve. If \code{skip=1L} (default) only the initial residual -- that is computed before any iteration, is skipped, if present in the track (it associated with iteration 0).} \item{Rowv}{clustering specification(s) for the rows. It allows to specify the distance/clustering/ordering/display parameters to be used for the \emph{rows only}. Possible values are: \itemize{ \item \code{TRUE} or \code{NULL} (to be consistent with \code{\link{heatmap}}): compute a dendrogram from hierarchical clustering using the distance and clustering methods \code{distfun} and \code{hclustfun}. \item \code{NA}: disable any ordering. In this case, and if not otherwise specified with argument \code{revC=FALSE}, the heatmap shows the input matrix with the rows in their original order, with the first row on top to the last row at the bottom. Note that this differ from the behaviour or \code{\link{heatmap}}, but seemed to be a more sensible choice when vizualizing a matrix without reordering. \item an integer vector of length the number of rows of the input matrix (\code{nrow(x)}), that specifies the row order. As in the case \code{Rowv=NA}, the ordered matrix is shown first row on top, last row at the bottom. \item a character vector or a list specifying values to use instead of arguments \code{distfun}, \code{hclustfun} and \code{reorderfun} when clustering the rows (see the respective argument descriptions for a list of accepted values). If \code{Rowv} has no names, then the first element is used for \code{distfun}, the second (if present) is used for \code{hclustfun}, and the third (if present) is used for \code{reorderfun}. \item a numeric vector of weights, of length the number of rows of the input matrix, used to reorder the internally computed dendrogram \code{d} by \code{reorderfun(d, Rowv)}. \item \code{FALSE}: the dendrogram \emph{is} computed using methods \code{distfun}, \code{hclustfun}, and \code{reorderfun} but is not shown. \item a single integer that specifies how many subtrees (i.e. clusters) from the computed dendrogram should have their root faded out. This can be used to better highlight the different clusters. \item a single double that specifies how much space is used by the computed dendrogram. That is that this value is used in place of \code{treeheight}. }} \item{main}{Main title as a character string or a grob.} } \description{ The functions documented here allow to compare the fits computed in different NMF runs. The fits do not need to be from the same algorithm, nor have the same dimension. } \details{ The methods \code{compare} enables to compare multiple NMF fits either passed as arguments or as a list of fits. These methods eventually call the method \code{summary,NMFList}, so that all its arguments can be passed \strong{named} in \code{...}. } \section{Methods}{ \describe{ \item{compare}{\code{signature(object = "NMFfit")}: Compare multiple NMF fits passed as arguments. } \item{compare}{\code{signature(object = "list")}: Compares multiple NMF fits passed as a standard list. } \item{consensusmap}{\code{signature(object = "NMF.rank")}: Draw a single plot with a heatmap of the consensus matrix obtained for each value of the rank, in the range tested with \code{\link{nmfEstimateRank}}. } \item{consensusmap}{\code{signature(object = "list")}: Draw a single plot with a heatmap of the consensus matrix of each element in the list \code{object}. } \item{plot}{\code{signature(x = "NMFList", y = "missing")}: \code{plot} plot on a single graph the residuals tracks for each fit in \code{x}. See function \code{\link{nmf}} for details on how to enable the tracking of residuals. } \item{summary}{\code{signature(object = "NMFList")}: \code{summary,NMFList} computes summary measures for each NMF result in the list and return them in rows in a \code{data.frame}. By default all the measures are included in the result, and \code{NA} values are used where no data is available or the measure does not apply to the result object (e.g. the dispersion for single' NMF runs is not meaningful). This method is very useful to compare and evaluate the performance of different algorithms. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } #---------- # compare,NMFfit-method #---------- x <- rmatrix(20,10) res <- nmf(x, 3) res2 <- nmf(x, 2, 'lee') # compare arguments compare(res, res2, target=x) #---------- # compare,list-method #---------- # compare elements of a list compare(list(res, res2), target=x) } \keyword{methods} NMF/man/objective-commaNMFfit-method.Rd0000644000176200001440000000140413620502674017326 0ustar liggesusers\docType{methods} \name{objective,NMFfit-method} \alias{objective,NMFfit-method} \title{Returns the objective function associated with the algorithm that computed the fitted NMF model \code{object}, or the objective value with respect to a given target matrix \code{y} if it is supplied.} \usage{ \S4method{objective}{NMFfit}(object, y) } \arguments{ \item{y}{optional target matrix used to compute the objective value.} \item{object}{an object computed using some algorithm, or that describes an algorithm itself.} } \description{ Returns the objective function associated with the algorithm that computed the fitted NMF model \code{object}, or the objective value with respect to a given target matrix \code{y} if it is supplied. } \keyword{methods} NMF/man/nmf_update_euclidean.Rd0000644000176200001440000000647413620502674016107 0ustar liggesusers\name{nmf_update.euclidean.h} \alias{nmf_update.euclidean} \alias{nmf_update.euclidean.h} \alias{nmf_update.euclidean.h_R} \alias{nmf_update.euclidean.w} \alias{nmf_update.euclidean.w_R} \title{NMF Multiplicative Updates for Euclidean Distance} \usage{ nmf_update.euclidean.h(v, w, h, eps = 10^-9, nbterms = 0L, ncterms = 0L, copy = TRUE) nmf_update.euclidean.h_R(v, w, h, wh = NULL, eps = 10^-9) nmf_update.euclidean.w(v, w, h, eps = 10^-9, nbterms = 0L, ncterms = 0L, weight = NULL, copy = TRUE) nmf_update.euclidean.w_R(v, w, h, wh = NULL, eps = 10^-9) } \arguments{ \item{eps}{small numeric value used to ensure numeric stability, by shifting up entries from zero to this fixed value.} \item{wh}{already computed NMF estimate used to compute the denominator term.} \item{weight}{numeric vector of sample weights, e.g., used to normalise samples coming from multiple datasets. It must be of the same length as the number of samples/columns in \code{v} -- and \code{h}.} \item{v}{target matrix} \item{w}{current basis matrix} \item{h}{current coefficient matrix} \item{nbterms}{number of fixed basis terms} \item{ncterms}{number of fixed coefficient terms} \item{copy}{logical that indicates if the update should be made on the original matrix directly (\code{FALSE}) or on a copy (\code{TRUE} - default). With \code{copy=FALSE} the memory footprint is very small, and some speed-up may be achieved in the case of big matrices. However, greater care should be taken due the side effect. We recommend that only experienced users use \code{copy=TRUE}.} } \value{ a matrix of the same dimension as the input matrix to update (i.e. \code{w} or \code{h}). If \code{copy=FALSE}, the returned matrix uses the same memory as the input object. } \description{ Multiplicative updates from \cite{Lee et al. (2001)} for standard Nonnegative Matrix Factorization models \eqn{V \approx W H}, where the distance between the target matrix and its NMF estimate is measured by the -- euclidean -- Frobenius norm. \code{nmf_update.euclidean.w} and \code{nmf_update.euclidean.h} compute the updated basis and coefficient matrices respectively. They use a \emph{C++} implementation which is optimised for speed and memory usage. \code{nmf_update.euclidean.w_R} and \code{nmf_update.euclidean.h_R} implement the same updates in \emph{plain R}. } \details{ The coefficient matrix (\code{H}) is updated as follows: \deqn{ H_{kj} \leftarrow \frac{\max(H_{kj} W^T V)_{kj}, \varepsilon) }{(W^T W H)_{kj} + \varepsilon} }{ H_kj <- max(H_kj (W^T V)_kj, eps) / ( (W^T W H)_kj + eps ) } These updates are used by the built-in NMF algorithms \code{\link[=Frobenius-nmf]{Frobenius}} and \code{\link[=lee-nmf]{lee}}. The basis matrix (\code{W}) is updated as follows: \deqn{ W_ik \leftarrow \frac{\max(W_ik (V H^T)_ik, \varepsilon) }{ (W H H^T)_ik + \varepsilon} }{ W_ik <- max(W_ik (V H^T)_ik, eps) / ( (W H H^T)_ik + eps ) } } \author{ Update definitions by \cite{Lee2001}. C++ optimised implementation by Renaud Gaujoux. } \references{ Lee DD and Seung H (2001). "Algorithms for non-negative matrix factorization." _Advances in neural information processing systems_. . } NMF/man/gfile.Rd0000644000176200001440000000031313620502674013024 0ustar liggesusers\name{gfile} \alias{gfile} \title{Open a File Graphic Device} \usage{ gfile(filename, width, height, ...) } \description{ Opens a graphic device depending on the file extension } \keyword{internal} NMF/man/terms-internal.Rd0000644000176200001440000000226713620502674014714 0ustar liggesusers\docType{methods} \name{bterms<-} \alias{bterms<-} \alias{bterms<--methods} \alias{bterms<-,NMFstd-method} \alias{cterms<-} \alias{cterms<--methods} \alias{cterms<-,NMFstd-method} \title{Fixed Terms in NMF Models} \usage{ bterms(object)<-value cterms(object)<-value } \arguments{ \item{object}{NMF object to be updated.} \item{value}{specification of the replacement value for fixed-terms.} } \description{ These functions are for internal use and should not be called by the end-user. \code{cterms<-} sets fixed coefficient terms or indexes and should only be called on a newly created NMF object, i.e. in the constructor/factory generic \code{\link{nmfModel}}. } \details{ They use \code{\link{model.matrix}(~ -1 + ., data=value)} to generate suitable term matrices. } \section{Methods}{ \describe{ \item{bterms<-}{\code{signature(object = "NMFstd")}: Default method tries to coerce \code{value} into a \code{data.frame} with \code{\link{as.data.frame}}. } \item{cterms<-}{\code{signature(object = "NMFstd")}: Default method tries to coerce \code{value} into a \code{data.frame} with \code{\link{as.data.frame}}. } } } \keyword{internal} \keyword{methods} NMF/man/cluster_mat.Rd0000644000176200001440000000154213620502674014265 0ustar liggesusers\name{cluster_mat} \alias{cluster_mat} \title{Cluster Matrix Rows in Annotated Heatmaps} \usage{ cluster_mat(mat, param, distfun, hclustfun, reorderfun, na.rm = TRUE, subset = NULL, verbose = FALSE) } \arguments{ \item{mat}{original input matrix that has already been appropriately subset in the caller function (\code{aheatmap})} \item{param}{clustering specifications} \item{distfun}{Default distance method/function} \item{hclustfun}{Default clustering (linkage) method/function} \item{reorderfun}{Default reordering function} \item{na.rm}{Logical that specifies if NA values should be removed} \item{subset}{index (integer) vector specifying the subset indexes used to subset mat. This is required to be able to return the original indexes.} } \description{ Cluster Matrix Rows in Annotated Heatmaps } \keyword{internal} NMF/man/types.Rd0000644000176200001440000000777613710555603013126 0ustar liggesusers\name{is.nmf} \alias{hasBasis} \alias{hasCoef} \alias{is.empty.nmf} \alias{is.nmf} \alias{isNMFfit} \alias{is.partial.nmf} \title{Testing NMF Objects} \usage{ is.nmf(x) is.empty.nmf(x, ...) hasBasis(x) hasCoef(x) is.partial.nmf(x) isNMFfit(object, recursive = TRUE) } \arguments{ \item{x}{an R object. See section \emph{Details}, for how each function uses this argument.} \item{...}{extra parameters to allow extension or passed to subsequent calls} \item{object}{any R object.} \item{recursive}{if \code{TRUE} and \code{object} is a plain list then \code{isNMFfit} tests each element of the list. Note that the recursive test only applies in the case of lists that are not themselves NMFfit objects, like \code{NMFfitXn} objects for which the result of \code{isNMFfit} will always be \code{TRUE}, although they are list objects (a single logical value).} } \value{ \code{isNMFfit} returns a \code{logical} vector (or a list if \code{object} is a list of list) of the same length as \code{object}. } \description{ The functions documented here tests different characteristics of NMF objects. \code{is.nmf} tests if an object is an NMF model or a class that extends the class NMF. \code{hasBasis} tests whether an objects contains a basis matrix -- returned by a suitable method \code{basis} -- with at least one row. \code{hasBasis} tests whether an objects contains a coefficient matrix -- returned by a suitable method \code{coef} -- with at least one column. \code{is.partial.nmf} tests whether an NMF model object contains either an empty basis or coefficient matrix. It is a shorcut for \code{!hasCoef(x) || !hasBasis(x)}. } \details{ \code{is.nmf} tests if \code{object} is the name of a class (if a \code{character} string), or inherits from a class, that extends \code{\linkS4class{NMF}}. \code{is.empty.nmf} returns \code{TRUE} if the basis and coefficient matrices of \code{x} have respectively zero rows and zero columns. It returns \code{FALSE} otherwise. In particular, this means that an empty model can still have a non-zero number of basis components, i.e. a factorization rank that is not null. This happens, for example, in the case of NMF models created calling the factory method \code{\link{nmfModel}} with a value only for the factorization rank. \emph{isNMFfit} checks if \code{object} inherits from class \code{\linkS4class{NMFfit}} or \code{\linkS4class{NMFfitX}}, which are the two types of objects returned by the function \code{\link{nmf}}. If \code{object} is a plain \code{list} and \code{recursive=TRUE}, then the test is performed on each element of the list, and the return value is a logical vector (or a list if \code{object} is a list of list) of the same length as \code{object}. } \note{ The function \code{is.nmf} does some extra work with the namespace as this function needs to return correct results even when called in \code{.onLoad}. See discussion on r-devel: \url{https://stat.ethz.ch/pipermail/r-devel/2011-June/061357.html} } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } #---------- # is.nmf #---------- # test if an object is an NMF model, i.e. that it implements the NMF interface is.nmf(1:4) is.nmf( nmfModel(3) ) is.nmf( nmf(rmatrix(10, 5), 2) ) #---------- # is.empty.nmf #---------- # empty model is.empty.nmf( nmfModel(3) ) # non empty models is.empty.nmf( nmfModel(3, 10, 0) ) is.empty.nmf( rnmf(3, 10, 5) ) #---------- # isNMFfit #---------- ## Testing results of fits # generate a random V <- rmatrix(20, 10) # single run -- using very low value for maxIter to speed up the example res <- nmf(V, 3, maxIter=3L) isNMFfit(res) # multiple runs - keeping single fit resm <- nmf(V, 3, nrun=2, maxIter=3L) isNMFfit(resm) # with a list of results isNMFfit(list(res, resm, 'not a result')) isNMFfit(list(res, resm, 'not a result'), recursive=FALSE) } \seealso{ \code{\linkS4class{NMFfit}}, \code{\linkS4class{NMFfitX}}, \code{\linkS4class{NMFfitXn}} } NMF/man/algorithm-commaNMFList-method.Rd0000644000176200001440000000154013620502674017474 0ustar liggesusers\docType{methods} \name{algorithm,NMFList-method} \alias{algorithm,NMFList-method} \title{Returns the method names used to compute the NMF fits in the list. It returns \code{NULL} if the list is empty.} \usage{ \S4method{algorithm}{NMFList}(object, string = FALSE, unique = TRUE) } \arguments{ \item{string}{a logical that indicate whether the names should be collapsed into a comma-separated string.} \item{unique}{a logical that indicates whether the result should contain the set of method names, removing duplicated names. This argument is forced to \code{TRUE} when \code{string=TRUE}.} \item{object}{an object computed using some algorithm, or that describes an algorithm itself.} } \description{ Returns the method names used to compute the NMF fits in the list. It returns \code{NULL} if the list is empty. } \keyword{methods} NMF/man/NMFStrategy-class.Rd0000644000176200001440000001034513620502674015212 0ustar liggesusers\docType{class} \name{NMFStrategy-class} \alias{is.mixed} \alias{NMFStrategy-class} \alias{objective<-,NMFStrategy,character-method} \alias{objective<-,NMFStrategy,function-method} \alias{objective,NMFStrategy-method} \alias{show,NMFStrategy-method} \title{Virtual Interface for NMF Algorithms} \usage{ \S4method{show}{NMFStrategy}(object) \S4method{objective}{NMFStrategy}(object) \S4method{objective}{NMFStrategy,character}(object)<-value \S4method{objective}{NMFStrategy,function}(object)<-value is.mixed(object) } \arguments{ \item{object}{Any R object} \item{value}{replacement value} } \description{ This class partially implements the generic interface defined for general algorithms defined in the \pkg{NMF} package (see \code{\link{algorithmic-NMF}}). \code{is.mixed} tells if an NMF algorithm works on mixed-sign data. } \section{Slots}{ \describe{ \item{objective}{the objective function associated with the algorithm (Frobenius, Kullback-Leibler, etc...). It is either an access key of a registered objective function or a function definition. In the latter case, the given function must have the following signature \code{(x="NMF", y="matrix")} and return a nonnegative real value.} \item{model}{a character string giving either the (sub)class name of the NMF-class instance used and returned by the strategy, or a function name.} \item{mixed}{a logical that indicates if the algorithm works on mixed-sign data.} } } \section{Methods}{ \describe{ \item{canFit}{\code{signature(x = "NMFStrategy", y = "character")}: Tells if an NMF algorithm can fit a given class of NMF models } \item{canFit}{\code{signature(x = "NMFStrategy", y = "NMF")}: Tells if an NMF algorithm can fit the same class of models as \code{y} } \item{deviance}{\code{signature(object = "NMFStrategy")}: Computes the value of the objective function between the estimate \code{x} and the target \code{y}. } \item{modelname}{\code{signature(object = "NMFStrategy")}: Returns the model(s) that an NMF algorithm can fit. } \item{NMFStrategy}{\code{signature(name = "NMFStrategy", method = "missing")}: Creates an \code{NMFStrategy} based on a template object (Constructor-Copy), in particular it uses the \strong{same} name. } \item{objective}{\code{signature(object = "NMFStrategy")}: Gets the objective function associated with an NMF algorithm. It is used in \code{\link[=deviance,NMFStrategy-method]{deviance}} to compute the objective value for an NMF model with respect to a given target matrix. } \item{objective}{\code{signature(object = "NMFStrategy")}: Gets the objective function associated with an NMF algorithm. It is used in \code{\link[=deviance,NMFStrategy-method]{deviance}} to compute the objective value for an NMF model with respect to a given target matrix. } \item{objective<-}{\code{signature(object = "NMFStrategy", value = "character")}: Sets the objective function associated with an NMF algorithm, with a character string that must be a registered objective function. } \item{objective<-}{\code{signature(object = "NMFStrategy", value = "character")}: Sets the objective function associated with an NMF algorithm, with a character string that must be a registered objective function. } \item{objective<-}{\code{signature(object = "NMFStrategy", value = "function")}: Sets the objective function associated with an NMF algorithm, with a function that computes the approximation error between an NMF model and a target matrix. } \item{objective<-}{\code{signature(object = "NMFStrategy", value = "function")}: Sets the objective function associated with an NMF algorithm, with a function that computes the approximation error between an NMF model and a target matrix. } \item{run}{\code{signature(object = "NMFStrategy", y = "matrix", x = "NMFfit")}: Pure virtual method defined for all NMF algorithms to ensure that a method \code{run} is defined by sub-classes of \code{NMFStrategy}. It throws an error if called directly. } \item{run}{\code{signature(object = "NMFStrategy", y = "matrix", x = "NMF")}: Method to run an NMF algorithm directly starting from a given NMF model. } } } \keyword{internal} \keyword{methods} NMF/man/NMFfitX.Rd0000644000176200001440000000241713620502674013220 0ustar liggesusers\docType{methods} \name{NMFfitX} \alias{NMFfitX} \alias{NMFfitX,list-method} \alias{NMFfitX-methods} \alias{NMFfitX,NMFfit-method} \alias{NMFfitX,NMFfitX-method} \title{Factory Method for Multiple NMF Run Objects} \usage{ NMFfitX(object, ...) \S4method{NMFfitX}{list}(object, ..., .merge = FALSE) } \arguments{ \item{object}{an object from which is created an \code{NMFfitX} object} \item{...}{extra arguments used to pass values for slots} \item{.merge}{a logical that indicates if the fits should be aggregated, only keeping the best fit, and return an \code{NMFfitX1} object. If \code{FALSE}, an \code{NMFfitXn} object containing the data of all the fits is returned.} } \description{ Factory Method for Multiple NMF Run Objects } \section{Methods}{ \describe{ \item{NMFfitX}{\code{signature(object = "list")}: Create an \code{NMFfitX} object from a list of fits. } \item{NMFfitX}{\code{signature(object = "NMFfit")}: Creates an \code{NMFfitX1} object from a single fit. This is used in \code{\link{nmf}} when only the best fit is kept in memory or on disk. } \item{NMFfitX}{\code{signature(object = "NMFfitX")}: Provides a way to aggregate \code{NMFfitXn} objects into an \code{NMFfitX1} object. } } } \keyword{internal} \keyword{methods} NMF/man/show-commaNMFStrategyIterative-method.Rd0000644000176200001440000000056213620502674021235 0ustar liggesusers\docType{methods} \name{show,NMFStrategyIterative-method} \alias{show,NMFStrategyIterative-method} \title{Show method for objects of class \code{NMFStrategyIterative}} \usage{ \S4method{show}{NMFStrategyIterative}(object) } \arguments{ \item{object}{Any R object} } \description{ Show method for objects of class \code{NMFStrategyIterative} } \keyword{methods} NMF/man/NMF-package.Rd0000644000176200001440000000211413620502674013750 0ustar liggesusers\docType{package} \name{NMF-package} \alias{NMF} \alias{NMF-package} \title{Algorithms and framework for Nonnegative Matrix Factorization (NMF).} \description{ This package provides a framework to perform Non-negative Matrix Factorization (NMF). It implements a set of already published algorithms and seeding methods, and provides a framework to test, develop and plug new/custom algorithms. Most of the built-in algorithms have been optimized in C++, and the main interface function provides an easy way of performing parallel computations on multicore machines. } \details{ \code{\link{nmf}} Run a given NMF algorithm } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # generate a synthetic dataset with known classes n <- 50; counts <- c(5, 5, 8); V <- syntheticNMF(n, counts) # perform a 3-rank NMF using the default algorithm res <- nmf(V, 3) basismap(res) coefmap(res) } \author{ Renaud Gaujoux \email{renaud@cbio.uct.ac.za} } \references{ \url{https://cran.r-project.org/} } \seealso{ \code{\link{nmf}} } \keyword{package} NMF/man/NMFStrategy.Rd0000644000176200001440000001504113620502674014105 0ustar liggesusers\docType{methods} \name{NMFStrategy} \alias{NMFStrategy} \alias{NMFStrategy,character,character-method} \alias{NMFStrategy,character,function-method} \alias{NMFStrategy,character,missing-method} \alias{NMFStrategy,character,NMFStrategy-method} \alias{NMFStrategy-methods} \alias{NMFStrategy,missing,character-method} \alias{NMFStrategy,NMFStrategy,missing-method} \alias{NMFStrategy,NULL,character-method} \alias{NMFStrategy,NULL,NMFStrategy-method} \alias{run,NMFStrategyFunction,matrix,NMFfit-method} \alias{run,NMFStrategyIterative,matrix,NMFfit-method} \alias{run,NMFStrategyIterativeX,matrix,NMFfit-method} \alias{run,NMFStrategy,matrix,NMFfit-method} \alias{run,NMFStrategy,matrix,NMF-method} \alias{run,NMFStrategyOctave,matrix,NMFfit-method} \title{Factory Method for NMFStrategy Objects} \usage{ NMFStrategy(name, method, ...) \S4method{run}{NMFStrategy,matrix,NMFfit}(object, y, x, ...) \S4method{run}{NMFStrategy,matrix,NMF}(object, y, x, ...) \S4method{run}{NMFStrategyFunction,matrix,NMFfit}(object, y, x, ...) \S4method{run}{NMFStrategyIterative,matrix,NMFfit}(object, y, x, .stop = NULL, maxIter = nmf.getOption("maxIter") \%||\% 2000, ...) \S4method{run}{NMFStrategyIterativeX,matrix,NMFfit}(object, y, x, maxIter, ...) \S4method{run}{NMFStrategyOctave,matrix,NMFfit}(object, y, x, ...) } \arguments{ \item{name}{name/key of an NMF algorithm.} \item{method}{definition of the algorithm} \item{...}{extra arguments passed to \code{\link{new}}.} \item{.stop}{specification of a stopping criterion, that is used instead of the one associated to the NMF algorithm. It may be specified as: \itemize{ \item the access key of a registered stopping criterion; \item a single integer that specifies the exact number of iterations to perform, which will be honoured unless a lower value is explicitly passed in argument \code{maxIter}. \item a single numeric value that specifies the stationnarity threshold for the objective function, used in with \code{\link{nmf.stop.stationary}}; \item a function with signature \code{(object="NMFStrategy", i="integer", y="matrix", x="NMF", ...)}, where \code{object} is the \code{NMFStrategy} object that describes the algorithm being run, \code{i} is the current iteration, \code{y} is the target matrix and \code{x} is the current value of the NMF model. }} \item{maxIter}{maximum number of iterations to perform.} \item{object}{an object computed using some algorithm, or that describes an algorithm itself.} \item{y}{data object, e.g. a target matrix} \item{x}{a model object used as a starting point by the algorithm, e.g. a non-empty NMF model.} } \description{ Creates NMFStrategy objects that wraps implementation of NMF algorithms into a unified interface. } \section{Methods}{ \describe{ \item{NMFStrategy}{\code{signature(name = "character", method = "function")}: Creates an \code{NMFStrategyFunction} object that wraps the function \code{method} into a unified interface. \code{method} must be a function with signature \code{(y="matrix", x="NMFfit", ...)}, and return an object of class \code{\linkS4class{NMFfit}}. } \item{NMFStrategy}{\code{signature(name = "character", method = "NMFStrategy")}: Creates an \code{NMFStrategy} object based on a template object (Constructor-Copy). } \item{NMFStrategy}{\code{signature(name = "NMFStrategy", method = "missing")}: Creates an \code{NMFStrategy} based on a template object (Constructor-Copy), in particular it uses the \strong{same} name. } \item{NMFStrategy}{\code{signature(name = "missing", method = "character")}: Creates an \code{NMFStrategy} based on a registered NMF algorithm that is used as a template (Constructor-Copy), in particular it uses the \strong{same} name. It is a shortcut for \code{NMFStrategy(nmfAlgorithm(method, exact=TRUE), ...)}. } \item{NMFStrategy}{\code{signature(name = "NULL", method = "NMFStrategy")}: Creates an \code{NMFStrategy} based on a template object (Constructor-Copy) but using a randomly generated name. } \item{NMFStrategy}{\code{signature(name = "character", method = "character")}: Creates an \code{NMFStrategy} based on a registered NMF algorithm that is used as a template. } \item{NMFStrategy}{\code{signature(name = "NULL", method = "character")}: Creates an \code{NMFStrategy} based on a registered NMF algorithm (Constructor-Copy) using a randomly generated name. It is a shortcut for \code{NMFStrategy(NULL, nmfAlgorithm(method), ...)}. } \item{NMFStrategy}{\code{signature(name = "character", method = "missing")}: Creates an NMFStrategy, determining its type from the extra arguments passed in \code{...}: if there is an argument named \code{Update} then an \code{NMFStrategyIterative} is created, or if there is an argument named \code{algorithm} then an \code{NMFStrategyFunction} is created. Calls other than these generates an error. } \item{run}{\code{signature(object = "NMFStrategy", y = "matrix", x = "NMFfit")}: Pure virtual method defined for all NMF algorithms to ensure that a method \code{run} is defined by sub-classes of \code{NMFStrategy}. It throws an error if called directly. } \item{run}{\code{signature(object = "NMFStrategy", y = "matrix", x = "NMF")}: Method to run an NMF algorithm directly starting from a given NMF model. } \item{run}{\code{signature(object = "NMFStrategyFunction", y = "matrix", x = "NMFfit")}: Runs the NMF algorithms implemented by the single R function -- and stored in slot \code{'algorithm'} of \code{object}, on the data object \code{y}, using \code{x} as starting point. It is equivalent to calling \code{object@algorithm(y, x, ...)}. This method is usually not called directly, but only via the function \code{\link{nmf}}, which takes care of many other details such as seeding the computation, handling RNG settings, or setting up parallelisation. } \item{run}{\code{signature(object = "NMFStrategyIterative", y = "matrix", x = "NMFfit")}: Runs an NMF iterative algorithm on a target matrix \code{y}. } \item{run}{\code{signature(object = "NMFStrategyOctave", y = "matrix", x = "NMFfit")}: Runs the NMF algorithms implemented by the Octave/Matlab function associated with the strategy -- and stored in slot \code{'algorithm'} of \code{object}. This method is usually not called directly, but only via the function \code{\link{nmf}}, which takes care of many other details such as seeding the computation, handling RNG settings, or setting up parallel computations. } } } \keyword{methods} NMF/man/consensushc.Rd0000644000176200001440000000335413620502674014301 0ustar liggesusers\docType{methods} \name{consensushc} \alias{consensushc} \alias{consensushc,matrix-method} \alias{consensushc-methods} \alias{consensushc,NMFfitX-method} \alias{consensushc,NMF-method} \title{Hierarchical Clustering of a Consensus Matrix} \usage{ consensushc(object, ...) \S4method{consensushc}{matrix}(object, method = "average", dendrogram = TRUE) \S4method{consensushc}{NMFfitX}(object, what = c("consensus", "fit"), ...) } \arguments{ \item{object}{a matrix or an \code{NMFfitX} object, as returned by multiple NMF runs.} \item{...}{extra arguments passed to next method calls} \item{method}{linkage method passed to \code{\link{hclust}}.} \item{dendrogram}{a logical that specifies if the result of the hierarchical clustering (en \code{hclust} object) should be converted into a dendrogram. Default value is \code{TRUE}.} \item{what}{character string that indicates which matrix to use in the computation.} } \value{ an object of class \code{dendrogram} or \code{hclust} depending on the value of argument \code{dendrogram}. } \description{ The function \code{consensushc} computes the hierarchical clustering of a consensus matrix, using the matrix itself as a similarity matrix and average linkage. It is } \section{Methods}{ \describe{ \item{consensushc}{\code{signature(object = "matrix")}: Workhorse method for matrices. } \item{consensushc}{\code{signature(object = "NMF")}: Compute the hierarchical clustering on the connectivity matrix of \code{object}. } \item{consensushc}{\code{signature(object = "NMFfitX")}: Compute the hierarchical clustering on the consensus matrix of \code{object}, or on the connectivity matrix of the best fit in \code{object}. } } } \keyword{methods} NMF/man/show-commaNMFList-method.Rd0000644000176200001440000000045713620502674016474 0ustar liggesusers\docType{methods} \name{show,NMFList-method} \alias{show,NMFList-method} \title{Show method for objects of class \code{NMFList}} \usage{ \S4method{show}{NMFList}(object) } \arguments{ \item{object}{Any R object} } \description{ Show method for objects of class \code{NMFList} } \keyword{methods} NMF/man/setup.Rd0000644000176200001440000000476513620502674013115 0ustar liggesusers\name{setupBackend} \alias{setupBackend} \alias{setupLibPaths} \alias{setupRNG} \alias{setupSharedMemory} \alias{setupTempDirectory} \title{Computational Setup Functions} \usage{ setupBackend(spec, backend, optional = FALSE, verbose = FALSE) setupSharedMemory(verbose) setupTempDirectory(verbose) setupLibPaths(pkg = "NMF", verbose = FALSE) setupRNG(seed, n, verbose = FALSE) } \arguments{ \item{spec}{target parallel specification: either \code{TRUE} or \code{FALSE}, or a single numeric value that specifies the number of cores to setup.} \item{backend}{value from argument \code{.pbackend} of \code{nmf}.} \item{optional}{a logical that indicates if the specification must be fully satisfied, throwing an error if it is not, or if one can switch back to sequential, only outputting a verbose message.} \item{verbose}{logical or integer level of verbosity for message outputs.} \item{pkg}{package name whose path should be exported the workers.} \item{seed}{initial RNG seed specification} \item{n}{number of RNG seeds to generate} } \value{ Returns \code{FALSE} if no foreach backend is to be used, \code{NA} if the currently registered backend is to be used, or, if this function call registered a new backend, the previously registered backend as a \code{foreach} object, so that it can be restored after the computation is over. } \description{ Functions used internally to setup the computational environment. \code{setupBackend} sets up a foreach backend given some specifications. \code{setupSharedMemory} checks if one can use the packages \emph{bigmemory} and \emph{sychronicity} to speed-up parallel computations when not keeping all the fits. When both these packages are available, only one result per host is written on disk, with its achieved deviance stored in shared memory, that is accessible to all cores on a same host. It returns \code{TRUE} if both packages are available and NMF option \code{'shared'} is toggled on. \code{setupTempDirectory} creates a temporary directory to store the best fits computed on each host. It ensures each worker process has access to it. \code{setupLibPaths} add the path to the NMF package to each workers' libPaths. \code{setupRNG} sets the RNG for use by the function nmf. It returns the old RNG as an rstream object or the result of set.seed if the RNG is not changed due to one of the following reason: - the settings are not compatible with rstream } \keyword{internals} NMF/man/nmf_update_KL.Rd0000644000176200001440000000551613620502674014460 0ustar liggesusers\name{nmf_update.KL.h} \alias{nmf_update.KL} \alias{nmf_update.KL.h} \alias{nmf_update.KL.h_R} \alias{nmf_update.KL.w} \alias{nmf_update.KL.w_R} \title{NMF Multiplicative Updates for Kullback-Leibler Divergence} \usage{ nmf_update.KL.h(v, w, h, nbterms = 0L, ncterms = 0L, copy = TRUE) nmf_update.KL.h_R(v, w, h, wh = NULL) nmf_update.KL.w(v, w, h, nbterms = 0L, ncterms = 0L, copy = TRUE) nmf_update.KL.w_R(v, w, h, wh = NULL) } \arguments{ \item{v}{target matrix} \item{w}{current basis matrix} \item{h}{current coefficient matrix} \item{nbterms}{number of fixed basis terms} \item{ncterms}{number of fixed coefficient terms} \item{copy}{logical that indicates if the update should be made on the original matrix directly (\code{FALSE}) or on a copy (\code{TRUE} - default). With \code{copy=FALSE} the memory footprint is very small, and some speed-up may be achieved in the case of big matrices. However, greater care should be taken due the side effect. We recommend that only experienced users use \code{copy=TRUE}.} \item{wh}{already computed NMF estimate used to compute the denominator term.} } \value{ a matrix of the same dimension as the input matrix to update (i.e. \code{w} or \code{h}). If \code{copy=FALSE}, the returned matrix uses the same memory as the input object. } \description{ Multiplicative updates from \cite{Lee et al. (2001)} for standard Nonnegative Matrix Factorization models \eqn{V \approx W H}, where the distance between the target matrix and its NMF estimate is measured by the Kullback-Leibler divergence. \code{nmf_update.KL.w} and \code{nmf_update.KL.h} compute the updated basis and coefficient matrices respectively. They use a \emph{C++} implementation which is optimised for speed and memory usage. \code{nmf_update.KL.w_R} and \code{nmf_update.KL.h_R} implement the same updates in \emph{plain R}. } \details{ The coefficient matrix (\code{H}) is updated as follows: \deqn{ H_{kj} \leftarrow H_{kj} \frac{\left( sum_i \frac{W_{ik} V_{ij}}{(WH)_{ij}} \right)}{ sum_i W_{ik} }. }{ H_kj <- H_kj ( sum_i [ W_ik V_ij / (WH)_ij ] ) / ( sum_i W_ik ) } These updates are used in built-in NMF algorithms \code{\link[=KL-nmf]{KL}} and \code{\link[=brunet-nmf]{brunet}}. The basis matrix (\code{W}) is updated as follows: \deqn{ W_{ik} \leftarrow W_{ik} \frac{ sum_j [\frac{H_{kj} A_{ij}}{(WH)_{ij}} ] }{sum_j H_{kj} } }{ W_ik <- W_ik ( sum_u [H_kl A_il / (WH)_il ] ) / ( sum_l H_kl ) } } \author{ Update definitions by \cite{Lee2001}. C++ optimised implementation by Renaud Gaujoux. } \references{ Lee DD and Seung H (2001). "Algorithms for non-negative matrix factorization." _Advances in neural information processing systems_. . } NMF/man/show-commaNMFfitX-method.Rd0000644000176200001440000000045713620502674016473 0ustar liggesusers\docType{methods} \name{show,NMFfitX-method} \alias{show,NMFfitX-method} \title{Show method for objects of class \code{NMFfitX}} \usage{ \S4method{show}{NMFfitX}(object) } \arguments{ \item{object}{Any R object} } \description{ Show method for objects of class \code{NMFfitX} } \keyword{methods} NMF/man/smoothing.Rd0000644000176200001440000000172113620502674013751 0ustar liggesusers\name{smoothing} \alias{smoothing} \title{Smoothing Matrix in Nonsmooth NMF Models} \usage{ smoothing(x, theta = x@theta, ...) } \arguments{ \item{x}{a object of class \code{NMFns}.} \item{theta}{the smoothing parameter (numeric) between 0 and 1.} \item{...}{extra arguments to allow extension (not used)} } \value{ if \code{x} estimates a \eqn{r}-rank NMF, then the result is a \eqn{r \times r} square matrix. } \description{ The function \code{smoothing} builds a smoothing matrix for using in Nonsmooth NMF models. } \details{ For a \eqn{r}-rank NMF, the smoothing matrix of parameter \eqn{\theta} is built as follows: \deqn{S = (1-\theta)I + \frac{\theta}{r} 11^T ,} where \eqn{I} is the identity matrix and \eqn{1} is a vector of ones (cf. \code{\link{NMFns-class}} for more details). } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } x <- nmfModel(3, model='NMFns') smoothing(x) smoothing(x, 0.1) } NMF/man/setNMFMethod.Rd0000644000176200001440000000310013620502674014230 0ustar liggesusers\name{setNMFMethod} \alias{nmfRegisterAlgorithm} \alias{setNMFMethod} \title{Registering NMF Algorithms} \usage{ setNMFMethod(name, method, ..., overwrite = isLoadingNamespace(), verbose = TRUE) nmfRegisterAlgorithm(name, method, ..., overwrite = isLoadingNamespace(), verbose = TRUE) } \arguments{ \item{...}{arguments passed to the factory function \code{\link{NMFStrategy}}, which instantiate the \code{\linkS4class{NMFStrategy}} object that is stored in registry.} \item{overwrite}{logical that indicates if any existing NMF method with the same name should be overwritten (\code{TRUE}) or not (\code{FALSE}), in which case an error is thrown.} \item{verbose}{a logical that indicates if information about the registration should be printed (\code{TRUE}) or not (\code{FALSE}).} \item{name}{name/key of an NMF algorithm.} \item{method}{definition of the algorithm} } \description{ Adds a new algorithm to the registry of algorithms that perform Nonnegative Matrix Factorization. \code{nmfRegisterAlgorithm} is an alias to \code{setNMFMethod} for backward compatibility. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # define/regsiter a new -- dummy -- NMF algorithm with the minimum arguments # y: target matrix # x: initial NMF model (i.e. the seed) # NB: this algorithm simply return the seed unchanged setNMFMethod('mynmf', function(y, x, ...){ x }) # check algorithm on toy data res <- nmfCheck('mynmf') # the NMF seed is not changed stopifnot( nmf.equal(res, nmfCheck('mynmf', seed=res)) ) } NMF/man/nmfReport.Rd0000644000176200001440000000270513620502674013721 0ustar liggesusers\name{nmfReport} \alias{nmfReport} \title{Run NMF Methods and Generate a Report} \usage{ nmfReport(x, rank, method, colClass = NULL, ..., output = NULL, template = NULL) } \arguments{ \item{x}{target matrix} \item{rank}{factorization rank} \item{method}{list of methods to apply} \item{colClass}{reference class to assess accuracy} \item{...}{extra paramters passed to \code{\link{nmf}}} \item{output}{output HTML file} \item{template}{template Rmd file} } \value{ a list with the following elements: \item{fits}{the fit(s) for each method and each value of the rank.} \item{accuracy}{a data.frame that contains the summary assessment measures, for each fit.} } \description{ Generates an HTML report from running a set of method on a given target matrix, for a set of factorization ranks. } \details{ The report is based on an .Rmd document \code{'report.Rmd'} stored in the package installation sub-directory \code{scripts/}, and is compiled using \pkg{knitr}. At the beginning of the document, a file named \code{'functions.R'} is looked for in the current directory, and sourced if present. This enables the definition of custom NMF methods (see \code{\link{setNMFMethod}}) or setting global options. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } \dontrun{ x <- rmatrix(20, 10) gr <- gl(2, 5) nmfReport(x, 2:4, method = list('br', 'lee'), colClass = gr, nrun = 5) } } NMF/man/consensus-commaNMFfitXn-method.Rd0000644000176200001440000000173113620502674017705 0ustar liggesusers\docType{methods} \name{consensus,NMFfitXn-method} \alias{consensus,NMFfitXn-method} \alias{plot.NMF.consensus} \title{Computes the consensus matrix of the set of fits stored in \code{object}, as the mean connectivity matrix across runs.} \usage{ \S4method{consensus}{NMFfitXn}(object, ..., no.attrib = FALSE) } \arguments{ \item{object}{an object with a suitable \code{\link{predict}} method.} \item{...}{extra arguments to allow extension. They are passed to \code{\link{predict}}, except for the \code{vector} and \code{factor} methods.} \item{no.attrib}{a logical that indicates if attributes containing information about the NMF model should be attached to the result (\code{TRUE}) or not (\code{FALSE}).} } \description{ This method returns \code{NULL} on an empty object. The result is a matrix with several attributes attached, that are used by plotting functions such as \code{\link{consensusmap}} to annotate the plots. } \keyword{methods} NMF/man/grid.Rd0000644000176200001440000000230113620502674012662 0ustar liggesusers\name{tryViewport} \alias{current.vpPath_patched} \alias{tryViewport} \alias{.use.grid.patch} \title{Internal Grid Extension} \usage{ tryViewport(name, verbose = FALSE) current.vpPath_patched() .use.grid.patch() } \arguments{ \item{name}{viewport name} \item{verbose}{toggle verbosity} } \description{ These functions enable mixing base and grid graphics in \code{\link{aheatmap}}, by avoiding calls to the grid internal function \code{'L_gridDirty'}. They are not exported (i.e. not tampering core functions) and are only meant for internal use within the \pkg{NMF} package. \code{tryViewport} tries to go down to a viewport in the current tree, given its name. \code{current.vpPath_patched} aims at substituting \code{\link[grid]{current.vpPath}}, so that the graphic engine is not reset. This is essentially to prevent outputting a blank page at the beginning of PDF graphic engines. \code{.use.grid.patch} tells if the user enabled patching grid. } \details{ \code{tryViewport} uses \code{\link[grid]{grid.ls}} and not \code{\link{seekViewport}} as the latter would reset the graphic device and break the mix grid/base graphic capability. } \keyword{internal} NMF/man/nmfObject.Rd0000644000176200001440000000136713620502674013657 0ustar liggesusers\name{nmfObject} \alias{nmfObject} \title{Updating NMF Objects} \usage{ nmfObject(object, verbose = FALSE) } \arguments{ \item{object}{an R object created by the NMF package, e.g., an object of class \code{\linkS4class{NMF}} or \code{\linkS4class{NMFfit}}.} \item{verbose}{logical to toggle verbose messages.} } \description{ This function serves to update an objects created with previous versions of the NMF package, which would otherwise be incompatible with the current version, due to changes in their S4 class definition. } \details{ This function makes use of heuristics to automatically update object slots, which have been borrowed from the BiocGenerics package, the function \code{updateObjectFromSlots} in particular. } NMF/man/nmfApply.Rd0000644000176200001440000000467413620502674013542 0ustar liggesusers\name{nmfApply} \alias{nmfApply} \title{Apply Function for NMF Objects} \usage{ nmfApply(X, MARGIN, FUN, ..., simplify = TRUE, USE.NAMES = TRUE) } \arguments{ \item{X}{an object that has suitable \code{\link{basis}} and \code{coef} methods, e.g. an NMF model.} \item{MARGIN}{a single numeric (integer) value that specifies over which margin(s) the function \code{FUN} is applied. See section \emph{Details} for a list of possible values.} \item{FUN}{a function to apply over the specified margins.} \item{...}{extra arguments passed to \code{FUN}} \item{simplify}{a logical only used when \code{MARGIN=3}, that indicates if \code{sapply} should try to simplify result if possible. Since this argument follows \sQuote{...} its name cannot be abbreviated.} \item{USE.NAMES}{a logical only used when \code{MARGIN=3}, that indicates if \code{sapply} should use the names of the basis components to name the results if present. Since this argument follows \sQuote{...} its name cannot be abbreviated.} } \value{ a vector or a list. See \code{\link[base]{apply}} and \code{\link[base]{sapply}} for more details on the output format. } \description{ The function \code{nmfApply} provides exteneded \code{apply}-like functionality for objects of class \code{NMF}. It enables to easily apply a function over different margins of NMF models. } \details{ The function \code{FUN} is applied via a call to \code{\link{apply}} or \code{\link{sapply}} according to the value of argument \code{MARGIN} as follows: \describe{ \item{MARGIN=1}{ apply \code{FUN} to each \emph{row} of the basis matrix: \code{apply(basis(X), 1L, FUN, ...)}.} \item{MARGIN=2}{ apply \code{FUN} to each \emph{column} of the coefficient matrix: \code{apply(coef(X), 2L, FUN, ...)}.} \item{MARGIN=3}{ apply \code{FUN} to each \emph{pair} of associated basis component and basis profile: more or less \code{sapply(seq(nbasis(X)), function(i, ...) FUN(basis(X)[,i], coef(X)[i, ], ...), ...)}. In this case \code{FUN} must be have at least two arguments, to which are passed each basis components and basis profiles respectively -- as numeric vectors.} \item{MARGIN=4}{ apply \code{FUN} to each \emph{column} of the basis matrix, i.e. to each basis component: \code{apply(basis(X), 2L, FUN, ...)}.} \item{MARGIN=5}{ apply \code{FUN} to each \emph{row} of the coefficient matrix: \code{apply(coef(X), 1L, FUN, ...)}.} } } NMF/man/offset-commaNMFfit-method.Rd0000644000176200001440000000050313620502674016641 0ustar liggesusers\docType{methods} \name{offset,NMFfit-method} \alias{offset,NMFfit-method} \title{Returns the offset from the fitted model.} \usage{ \S4method{offset}{NMFfit}(object) } \arguments{ \item{object}{An offset to be included in a model frame} } \description{ Returns the offset from the fitted model. } \keyword{methods} NMF/man/dims.Rd0000644000176200001440000000577613620502674012714 0ustar liggesusers\docType{methods} \name{nbasis} \alias{dim-NMF} \alias{dim,NMFfitXn-method} \alias{dim,NMF-method} \alias{nbasis} \alias{nbasis,ANY-method} \alias{nbasis-methods} \alias{nbasis,NMFfitXn-method} \title{Dimension of NMF Objects} \usage{ nbasis(x, ...) \S4method{dim}{NMF}(x) \S4method{dim}{NMFfitXn}(x) } \arguments{ \item{x}{an object with suitable \code{basis} and \code{coef} methods, such as an object that inherit from \code{\linkS4class{NMF}}.} \item{...}{extra arguments to allow extension.} } \value{ a single integer value or, for \code{dim}, a length-3 integer vector, e.g. \code{c(2000, 30, 3)} for an \code{NMF} model that fits a 2000 x 30 matrix using 3 basis components. } \description{ The methods \code{dim}, \code{nrow}, \code{ncol} and \code{nbasis} return the different dimensions associated with an NMF model. \code{dim} returns all dimensions in a length-3 integer vector: the number of row and columns of the estimated target matrix, as well as the factorization rank (i.e. the number of basis components). \code{nrow}, \code{ncol} and \code{nbasis} provide separate access to each of these dimensions respectively. } \details{ The NMF package does not implement specific functions \code{nrow} and \code{ncol}, but rather the S4 method \code{dim} for objects of class \code{\linkS4class{NMF}}. This allows the base methods \code{\link{nrow}} and \code{\link{ncol}} to directly work with such objects, to get the number of rows and columns of the target matrix estimated by an NMF model. The function \code{nbasis} is a new S4 generic defined in the package NMF, that returns the number of basis components of an object. Its default method should work for any object, that has a suitable \code{basis} method defined for its class. } \section{Methods}{ \describe{ \item{dim}{\code{signature(x = "NMF")}: method for NMF objects for the base generic \code{\link{dim}}. It returns all dimensions in a length-3 integer vector: the number of row and columns of the estimated target matrix, as well as the factorization rank (i.e. the number of basis components). } \item{dim}{\code{signature(x = "NMFfitXn")}: Returns the dimension common to all fits. Since all fits have the same dimensions, it returns the dimension of the first fit. This method returns \code{NULL} if the object is empty. } \item{nbasis}{\code{signature(x = "ANY")}: Default method which returns the number of columns of the basis matrix extracted from \code{x} using a suitable method \code{basis}, or, if the latter is \code{NULL}, the value of attributes \code{'nbasis'}. For NMF models, this also corresponds to the number of rows in the coefficient matrix. } \item{nbasis}{\code{signature(x = "NMFfitXn")}: Returns the number of basis components common to all fits. Since all fits have been computed using the same rank, it returns the factorization rank of the first fit. This method returns \code{NULL} if the object is empty. } } } \keyword{methods} NMF/man/show-commaNMFSeed-method.Rd0000644000176200001440000000045713620502674016441 0ustar liggesusers\docType{methods} \name{show,NMFSeed-method} \alias{show,NMFSeed-method} \title{Show method for objects of class \code{NMFSeed}} \usage{ \S4method{show}{NMFSeed}(object) } \arguments{ \item{object}{Any R object} } \description{ Show method for objects of class \code{NMFSeed} } \keyword{methods} NMF/man/cutdendro.Rd0000644000176200001440000000042213620502674013726 0ustar liggesusers\name{cutdendro} \alias{cutdendro} \title{Fade Out the Upper Branches from a Dendrogram} \usage{ cutdendro(x, n) } \arguments{ \item{x}{a dendrogram} \item{n}{the number of groups} } \description{ Fade Out the Upper Branches from a Dendrogram } \keyword{internal} NMF/man/connectivity.Rd0000644000176200001440000001061013620502674014455 0ustar liggesusers\docType{methods} \name{connectivity} \alias{connectivity} \alias{connectivity,ANY-method} \alias{connectivity,factor-method} \alias{connectivity-methods} \alias{connectivity,NMF-method} \alias{connectivity,numeric-method} \alias{consensus} \alias{consensus-methods} \alias{consensus,NMFfitX-method} \alias{consensus,NMF-method} \title{Clustering Connectivity and Consensus Matrices} \usage{ connectivity(object, ...) \S4method{connectivity}{NMF}(object, no.attrib = FALSE) consensus(object, ...) } \arguments{ \item{object}{an object with a suitable \code{\link{predict}} method.} \item{...}{extra arguments to allow extension. They are passed to \code{\link{predict}}, except for the \code{vector} and \code{factor} methods.} \item{no.attrib}{a logical that indicates if attributes containing information about the NMF model should be attached to the result (\code{TRUE}) or not (\code{FALSE}).} } \value{ a square matrix of dimension the number of samples in the model, full of 0s or 1s. } \description{ \code{connectivity} is an S4 generic that computes the connectivity matrix based on the clustering of samples obtained from a model's \code{\link{predict}} method. The consensus matrix has been proposed by \cite{Brunet et al. (2004)} to help visualising and measuring the stability of the clusters obtained by NMF approaches. For objects of class \code{NMF} (e.g. results of a single NMF run, or NMF models), the consensus matrix reduces to the connectivity matrix. } \details{ The connectivity matrix of a given partition of a set of samples (e.g. given as a cluster membership index) is the matrix \eqn{C} containing only 0 or 1 entries such that: \deqn{C_{ij} = \left\{\begin{array}{l} 1\mbox{ if sample }i\mbox{ belongs to the same cluster as sample }j\\ 0\mbox{ otherwise} \end{array}\right..}{ C_{ij} = 1 if sample i belongs to the same cluster as sample j, 0 otherwise} } \section{Methods}{ \describe{ \item{connectivity}{\code{signature(object = "ANY")}: Default method which computes the connectivity matrix using the result of \code{predict(x, ...)} as cluster membership index. } \item{connectivity}{\code{signature(object = "factor")}: Computes the connectivity matrix using \code{x} as cluster membership index. } \item{connectivity}{\code{signature(object = "numeric")}: Equivalent to \code{connectivity(as.factor(x))}. } \item{connectivity}{\code{signature(object = "NMF")}: Computes the connectivity matrix for an NMF model, for which cluster membership is given by the most contributing basis component in each sample. See \code{\link{predict,NMF-method}}. } \item{consensus}{\code{signature(object = "NMFfitX")}: Pure virtual method defined to ensure \code{consensus} is defined for sub-classes of \code{NMFfitX}. It throws an error if called. } \item{consensus}{\code{signature(object = "NMF")}: This method is provided for completeness and is identical to \code{\link{connectivity}}, and returns the connectivity matrix, which, in the case of a single NMF model, is also the consensus matrix. } \item{consensus}{\code{signature(object = "NMFfitX1")}: The result is the matrix stored in slot \sQuote{consensus}. This method returns \code{NULL} if the consensus matrix is empty. See \code{\link{consensus,NMFfitX1-method}} for more details. } \item{consensus}{\code{signature(object = "NMFfitXn")}: This method returns \code{NULL} on an empty object. The result is a matrix with several attributes attached, that are used by plotting functions such as \code{\link{consensusmap}} to annotate the plots. See \code{\link{consensus,NMFfitXn-method}} for more details. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } #---------- # connectivity,ANY-method #---------- # clustering of random data h <- hclust(dist(rmatrix(10,20))) connectivity(cutree(h, 2)) #---------- # connectivity,factor-method #---------- connectivity(gl(2, 4)) } \references{ Brunet J, Tamayo P, Golub TR and Mesirov JP (2004). "Metagenes and molecular pattern discovery using matrix factorization." _Proceedings of the National Academy of Sciences of the United States of America_, *101*(12), pp. 4164-9. ISSN 0027-8424, , . } \seealso{ \code{\link{predict}} } \keyword{methods} NMF/man/NMFList-class.Rd0000644000176200001440000000244113620502674014321 0ustar liggesusers\docType{class} \name{NMFList-class} \alias{NMFList-class} \title{Class for Storing Heterogeneous NMF fits} \description{ This class wraps a list of NMF fit objects, which may come from different runs of the function \code{\link{nmf}}, using different parameters, methods, etc.. These can be either from a single run (NMFfit) or multiple runs (NMFfitX). Note that its definition/interface is very likely to change in the future. } \section{Methods}{ \describe{ \item{algorithm}{\code{signature(object = "NMFList")}: Returns the method names used to compute the NMF fits in the list. It returns \code{NULL} if the list is empty. } \item{runtime}{\code{signature(object = "NMFList")}: Returns the CPU time required to compute all NMF fits in the list. It returns \code{NULL} if the list is empty. If no timing data are available, the sequential time is returned. } \item{seqtime}{\code{signature(object = "NMFList")}: Returns the CPU time that would be required to sequentially compute all NMF fits stored in \code{object}. This method calls the function \code{runtime} on each fit and sum up the results. It returns \code{NULL} on an empty object. } \item{show}{\code{signature(object = "NMFList")}: Show method for objects of class \code{NMFList} } } } NMF/man/consensus-commaNMFfitX1-method.Rd0000644000176200001440000000132513620502674017607 0ustar liggesusers\docType{methods} \name{consensus,NMFfitX1-method} \alias{consensus,NMFfitX1-method} \title{Returns the consensus matrix computed while performing all NMF runs, amongst which \code{object} was selected as the best fit.} \usage{ \S4method{consensus}{NMFfitX1}(object, no.attrib = FALSE) } \arguments{ \item{object}{an object with a suitable \code{\link{predict}} method.} \item{no.attrib}{a logical that indicates if attributes containing information about the NMF model should be attached to the result (\code{TRUE}) or not (\code{FALSE}).} } \description{ The result is the matrix stored in slot \sQuote{consensus}. This method returns \code{NULL} if the consensus matrix is empty. } \keyword{methods} NMF/man/NMFstd-class.Rd0000644000176200001440000001437113620502674014205 0ustar liggesusers\docType{class} \name{NMFstd-class} \alias{NMFstd-class} \title{NMF Model - Standard model} \description{ This class implements the standard model of Nonnegative Matrix Factorization. It provides a general structure and generic functions to manage factorizations that follow the standard NMF model, as defined by \cite{Lee et al. (2001)}. } \details{ Let \eqn{V} be a \eqn{n \times m} non-negative matrix and \eqn{r} a positive integer. In its standard form (see references below), a NMF of \eqn{V} is commonly defined as a pair of matrices \eqn{(W, H)} such that: \deqn{V \equiv W H,} where: \itemize{ \item \eqn{W} and \eqn{H} are \eqn{n \times r} and \eqn{r \times m} matrices respectively with non-negative entries; \item \eqn{\equiv} is to be understood with respect to some loss function. Common choices of loss functions are based on Frobenius norm or Kullback-Leibler divergence. } Integer \eqn{r} is called the \emph{factorization rank}. Depending on the context of application of NMF, the columns of \eqn{W} and \eqn{H} are given different names: \describe{ \item{columns of \code{W}}{basis vector, metagenes, factors, source, image basis} \item{columns of \code{H}}{mixture coefficients, metagene sample expression profiles, weights} \item{rows of \code{H}}{basis profiles, metagene expression profiles} } NMF approaches have been successfully applied to several fields. The package NMF was implemented trying to use names as generic as possible for objects and methods. The following terminology is used: \describe{ \item{samples}{the columns of the target matrix \eqn{V}} \item{features}{the rows of the target matrix \eqn{V}} \item{basis matrix}{the first matrix factor \eqn{W}} \item{basis vectors}{the columns of first matrix factor \eqn{W}} \item{mixture matrix}{the second matrix factor \eqn{H}} \item{mixtures coefficients}{the columns of second matrix factor \eqn{H}} } However, because the package NMF was primarily implemented to work with gene expression microarray data, it also provides a layer to easily and intuitively work with objects from the Bioconductor base framework. See \link{bioc-NMF} for more details. } \section{Slots}{ \describe{ \item{W}{A \code{matrix} that contains the basis matrix, i.e. the \emph{first} matrix factor of the factorisation} \item{H}{A \code{matrix} that contains the coefficient matrix, i.e. the \emph{second} matrix factor of the factorisation} \item{bterms}{a \code{data.frame} that contains the primary data that define fixed basis terms. See \code{\link{bterms}}.} \item{ibterms}{integer vector that contains the indexes of the basis components that are fixed, i.e. for which only the coefficient are estimated. IMPORTANT: This slot is set on construction of an NMF model via \code{\link[=nmfModel,formula,ANY-method]{nmfModel}} and is not recommended to not be subsequently changed by the end-user.} \item{cterms}{a \code{data.frame} that contains the primary data that define fixed coefficient terms. See \code{\link{cterms}}.} \item{icterms}{integer vector that contains the indexes of the basis components that have fixed coefficients, i.e. for which only the basis vectors are estimated. IMPORTANT: This slot is set on construction of an NMF model via \code{\link[=nmfModel,formula,ANY-method]{nmfModel}} and is not recommended to not be subsequently changed by the end-user.} } } \section{Methods}{ \describe{ \item{.basis}{\code{signature(object = "NMFstd")}: Get the basis matrix in standard NMF models This function returns slot \code{W} of \code{object}. } \item{.basis<-}{\code{signature(object = "NMFstd", value = "matrix")}: Set the basis matrix in standard NMF models This function sets slot \code{W} of \code{object}. } \item{bterms<-}{\code{signature(object = "NMFstd")}: Default method tries to coerce \code{value} into a \code{data.frame} with \code{\link{as.data.frame}}. } \item{.coef}{\code{signature(object = "NMFstd")}: Get the mixture coefficient matrix in standard NMF models This function returns slot \code{H} of \code{object}. } \item{.coef<-}{\code{signature(object = "NMFstd", value = "matrix")}: Set the mixture coefficient matrix in standard NMF models This function sets slot \code{H} of \code{object}. } \item{cterms<-}{\code{signature(object = "NMFstd")}: Default method tries to coerce \code{value} into a \code{data.frame} with \code{\link{as.data.frame}}. } \item{fitted}{\code{signature(object = "NMFstd")}: Compute the target matrix estimate in \emph{standard NMF models}. The estimate matrix is computed as the product of the two matrix slots \code{W} and \code{H}: \deqn{\hat{V} = W H}{V ~ W H} } \item{ibterms}{\code{signature(object = "NMFstd")}: Method for standard NMF models, which returns the integer vector that is stored in slot \code{ibterms} when a formula-based NMF model is instantiated. } \item{icterms}{\code{signature(object = "NMFstd")}: Method for standard NMF models, which returns the integer vector that is stored in slot \code{icterms} when a formula-based NMF model is instantiated. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # create a completely empty NMFstd object new('NMFstd') # create a NMF object based on one random matrix: the missing matrix is deduced # Note this only works when using factory method NMF n <- 50; r <- 3; w <- rmatrix(n, r) nmfModel(W=w) # create a NMF object based on random (compatible) matrices p <- 20 h <- rmatrix(r, p) nmfModel(W=w, H=h) # create a NMF object based on incompatible matrices: generate an error h <- rmatrix(r+1, p) try( new('NMFstd', W=w, H=h) ) try( nmfModel(w, h) ) # Giving target dimensions to the factory method allow for coping with dimension # incompatibilty (a warning is thrown in such case) nmfModel(r, W=w, H=h) } \references{ Lee DD and Seung H (2001). "Algorithms for non-negative matrix factorization." _Advances in neural information processing systems_. . } \seealso{ Other NMF-model: \code{\link{initialize,NMFOffset-method}}, \code{\link{NMFns-class}}, \code{\link{NMFOffset-class}} } NMF/man/nmfCheck.Rd0000644000176200001440000000154013620502674013457 0ustar liggesusers\name{nmfCheck} \alias{nmfCheck} \title{Checking NMF Algorithm} \usage{ nmfCheck(method = NULL, rank = max(ncol(x)/5, 3), x = NULL, seed = 1234, ...) } \arguments{ \item{method}{name of the NMF algorithm to be tested.} \item{rank}{rank of the factorization} \item{x}{target data. If \code{NULL}, a random 20 x 10 matrix is generated} \item{seed}{specifies a seed or seeding method for the computation.} \item{...}{other arguments passed to the call to \code{\link{nmf}}.} } \value{ the result of the NMF fit invisibly. } \description{ \code{nmfCheck} enables to quickly check that a given NMF algorithm runs properly, by applying it to some small random data. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # test default algorithm nmfCheck() # test 'lee' algorithm nmfCheck('lee') } NMF/man/dispersion.Rd0000644000176200001440000000353113620502674014122 0ustar liggesusers\docType{methods} \name{dispersion} \alias{dispersion} \alias{dispersion,matrix-method} \alias{dispersion-methods} \alias{dispersion,NMFfitX-method} \title{Dispersion of a Matrix} \usage{ dispersion(object, ...) } \arguments{ \item{object}{an object from which the dispersion is computed} \item{...}{extra arguments to allow extension} } \description{ Computes the dispersion coefficient of a -- consensus -- matrix \code{object}, generally obtained from multiple NMF runs. } \details{ The dispersion coefficient is based on the consensus matrix (i.e. the average of connectivity matrices) and was proposed by \cite{Kim et al. (2007)} to measure the reproducibility of the clusters obtained from NMF. It is defined as: \deqn{\rho = \sum_{i,j=1}^n 4 (C_{ij} - \frac{1}{2})^2 , } where \eqn{n} is the total number of samples. By construction, \eqn{0 \leq \rho \leq 1} and \eqn{\rho = 1} only for a perfect consensus matrix, where all entries 0 or 1. A perfect consensus matrix is obtained only when all the connectivity matrices are the same, meaning that the algorithm gave the same clusters at each run. See \cite{Kim et al. (2007)}. } \section{Methods}{ \describe{ \item{dispersion}{\code{signature(object = "matrix")}: Workhorse method that computes the dispersion on a given matrix. } \item{dispersion}{\code{signature(object = "NMFfitX")}: Computes the dispersion on the consensus matrix obtained from multiple NMF runs. } } } \references{ Kim H and Park H (2007). "Sparse non-negative matrix factorizations via alternating non-negativity-constrained least squares for microarray data analysis." _Bioinformatics (Oxford, England)_, *23*(12), pp. 1495-502. ISSN 1460-2059, , . } \keyword{methods} NMF/man/options.Rd0000644000176200001440000001144213620502674013436 0ustar liggesusers\name{options-NMF} \alias{nmf.getOption} \alias{nmf.options} \alias{nmf.printOptions} \alias{nmf.resetOptions} \alias{options-NMF} \title{NMF Package Specific Options} \usage{ nmf.options(...) nmf.getOption(x, default = NULL) nmf.resetOptions(..., ALL = FALSE) nmf.printOptions() } \arguments{ \item{...}{option specifications. For \code{nmf.options} this can be named arguments or a single unnamed argument that is a named list (see \code{\link{options}}. For \code{nmf.resetOptions}, this must be the names of the options to reset. Note that \pkg{pkgmaker} version >= 0.9.1 is required for this to work correctly, when options other than the default ones have been set after the package is loaded.} \item{ALL}{logical that indicates if options that are not part of the default set of options should be removed. Note that in \pkg{pkgmaker <= 0.9} this argument is only taken into account when no other argument is present. This is fixed in version 0.9.1.} \item{x}{a character string holding an option name.} \item{default}{if the specified option is not set in the options list, this value is returned. This facilitates retrieving an option and checking whether it is set and setting it separately if not.} } \description{ NMF Package Specific Options \code{nmf.options} sets/get single or multiple options, that are specific to the NMF package. It behaves in the same way as \code{\link[base]{options}}. \code{nmf.getOption} returns the value of a single option, that is specific to the NMF package. It behaves in the same way as \code{\link[base]{getOption}}. \code{nmf.resetOptions} reset all NMF specific options to their default values. \code{nmf.printOptions} prints all NMF specific options along with their default values, in a relatively compact way. } \section{Available options}{ \describe{ \item{cores}{Default number of cores to use to perform parallel NMF computations. Note that this option is effectively used only if the global option \code{'cores'} is not set. Moreover, the number of cores can also be set at runtime, in the call to \code{\link{nmf}}, via arguments \code{.pbackend} or \code{.options} (see \code{\link{nmf}} for more details).} \item{default.algorithm}{Default NMF algorithm used by the \code{nmf} function when argument \code{method} is missing. The value should the key of one of the registered NMF algorithms or a valid specification of an NMF algorithm. See \code{?nmfAlgorithm}.} \item{default.seed}{Default seeding method used by the \code{nmf} function when argument \code{seed} is missing. The value should the key of one of the registered seeding methods or a vallid specification of a seeding method. See \code{?nmfSeed}.} \item{track}{Toggle default residual tracking. When \code{TRUE}, the \code{nmf} function compute and store the residual track in the result -- if not otherwise specified in argument \code{.options}. Note that tracking may significantly slow down the computations.} \item{track.interval}{Number of iterations between two points in the residual track. This option is relevant only when residual tracking is enabled. See \code{?nmf}.} \item{error.track}{this is a symbolic link to option \code{track} for backward compatibility.} \item{pbackend}{Default loop/parallel foreach backend used by the \code{nmf} function when argument \code{.pbackend} is missing. Currently the following values are supported: \code{'par'} for multicore, \code{'seq'} for sequential, \code{NA} for standard \code{sapply} (i.e. do not use a foreach loop), \code{NULL} for using the currently registered foreach backend.} \item{parallel.backend}{this is a symbolic link to option \code{pbackend} for backward compatibility.} \item{gc}{Interval/frequency (in number of runs) at which garbage collection is performed.} \item{verbose}{Default level of verbosity.} \item{debug}{Toogles debug mode. In this mode the console output may be very -- very -- messy, and is aimed at debugging only.} \item{maxIter}{ Default maximum number of iteration to use (default NULL). This option is for internal/technical usage only, to globally speed up examples or tests of NMF algorithms. To be used with care at one's own risk... It is documented here so that advanced users are aware of its existence, and can avoid possible conflict with their own custom options. } } % end description } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # show all NMF specific options nmf.printOptions() # get some options nmf.getOption('verbose') nmf.getOption('pbackend') # set new values nmf.options(verbose=TRUE) nmf.options(pbackend='mc', default.algorithm='lee') nmf.printOptions() # reset to default nmf.resetOptions() nmf.printOptions() } NMF/man/registry-algorithm.Rd0000644000176200001440000000326313620502674015601 0ustar liggesusers\name{methods-NMF} \alias{existsNMFMethod} \alias{getNMFMethod} \alias{methods-NMF} \alias{removeNMFMethod} \alias{selectNMFMethod} \title{Registry for NMF Algorithms} \usage{ selectNMFMethod(name, model, load = FALSE, exact = FALSE, all = FALSE, quiet = FALSE) getNMFMethod(...) existsNMFMethod(name, exact = TRUE) removeNMFMethod(name, ...) } \arguments{ \item{name}{name of a registered NMF algorithm} \item{model}{class name of an NMF model, i.e. a class that inherits from class \code{\linkS4class{NMF}}.} \item{load}{a logical that indicates if the selected algorithms should be loaded into \code{NMFStrategy} objects} \item{all}{a logical that indicates if all algorithms that can fit \code{model} should be returned or only the default or first found.} \item{quiet}{a logical that indicates if the operation should be performed quietly, without throwing errors or warnings.} \item{...}{extra arguments passed to \code{\link[pkgmaker]{pkgreg_fetch}} or \code{\link[pkgmaker]{pkgreg_remove}}.} \item{exact}{a logical that indicates if the access key should be matched exactly (\code{TRUE}) or partially (\code{FALSE}).} } \value{ \code{selectNMFMethod} returns a character vector or \code{NMFStrategy} objects, or NULL if no suitable algorithm was found. } \description{ Registry for NMF Algorithms \code{selectNMFMethod} tries to select an appropriate NMF algorithm that is able to fit a given the NMF model. \code{getNMFMethod} retrieves NMF algorithm objects from the registry. \code{existsNMFMethod} tells if an NMF algorithm is registered under the \code{removeNMFMethod} removes an NMF algorithm from the registry. } NMF/man/show-commaNMFfit-method.Rd0000644000176200001440000000045213620502674016336 0ustar liggesusers\docType{methods} \name{show,NMFfit-method} \alias{show,NMFfit-method} \title{Show method for objects of class \code{NMFfit}} \usage{ \S4method{show}{NMFfit}(object) } \arguments{ \item{object}{Any R object} } \description{ Show method for objects of class \code{NMFfit} } \keyword{methods} NMF/man/silhouette.NMF.Rd0000644000176200001440000000435213710763560014554 0ustar liggesusers\name{silhouette.NMF} \alias{silhouette.NMF} \title{Silhouette of NMF Clustering} \usage{ \method{silhouette}{NMF} (x, what = NULL, order = NULL, ...) } \arguments{ \item{x}{an NMF object, as returned by \code{\link{nmf}}.} \item{what}{defines the type of clustering the computed silhouettes are meant to assess: \code{'samples'} for the clustering of samples (i.e. the columns of the target matrix), \code{'features'} for the clustering of features (i.e. the rows of the target matrix), and \code{'chc'} for the consensus clustering of samples as defined by hierarchical clustering dendrogram, \code{'consensus'} for the consensus clustering of samples, with clustered ordered as in the \strong{default} hierarchical clustering used by \code{\link{consensusmap}} when plotting the heatmap of the consensus matrix (for multi-run NMF fits). That is \code{dist = 1 - consensus(x)}, average linkage and reordering based on row means.} \item{order}{integer indexing vector that can be used to force the silhouette order.} \item{...}{extra arguments not used.} } \description{ Silhouette of NMF Clustering } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } x <- rmatrix(75, 15, dimnames = list(paste0('a', 1:75), letters[1:15])) # NB: using low value for maxIter for the example purpose only res <- nmf(x, 4, nrun = 3, maxIter = 20) # sample clustering from best fit plot(silhouette(res)) # average silhouette are computed in summary measures summary(res) # consensus silhouettes are ordered as on default consensusmap heatmap \dontrun{ op <- par(mfrow = c(1,2)) } consensusmap(res) si <- silhouette(res, what = 'consensus') plot(si) \dontrun{ par(op) } # if the order is based on some custom numeric weights \dontrun{ op <- par(mfrow = c(1,2)) } cm <- consensusmap(res, Rowv = runif(ncol(res))) # NB: use reverse order because silhouettes are plotted top-down si <- silhouette(res, what = 'consensus', order = rev(cm$rowInd)) plot(si) \dontrun{ par(op) } # do the reverse: order the heatmap as a set of silhouettes si <- silhouette(res, what = 'features') \dontrun{ op <- par(mfrow = c(1,2)) } basismap(res, Rowv = si) plot(si) \dontrun{ par(op) } } \seealso{ \code{\link[NMF]{predict}} } NMF/man/show-commaNMFfitX1-method.Rd0000644000176200001440000000046413620502674016552 0ustar liggesusers\docType{methods} \name{show,NMFfitX1-method} \alias{show,NMFfitX1-method} \title{Show method for objects of class \code{NMFfitX1}} \usage{ \S4method{show}{NMFfitX1}(object) } \arguments{ \item{object}{Any R object} } \description{ Show method for objects of class \code{NMFfitX1} } \keyword{methods} NMF/man/stop-NMF.Rd0000644000176200001440000001124413620502674013346 0ustar liggesusers\name{NMFStop} \alias{NMFStop} \alias{nmf.stop.connectivity} \alias{nmf.stop.iteration} \alias{nmf.stop.stationary} \alias{nmf.stop.threshold} \alias{stop-NMF} \title{Stopping Criteria for NMF Iterative Strategies} \usage{ NMFStop(s, check = TRUE) nmf.stop.iteration(n) nmf.stop.threshold(threshold) nmf.stop.stationary(object, i, y, x, stationary.th = .Machine$double.eps, check.interval = 5 * check.niter, check.niter = 10L, ...) nmf.stop.connectivity(object, i, y, x, stopconv = 40, check.interval = 10, ...) } \arguments{ \item{s}{specification of the stopping criterion. See section \emph{Details} for the supported formats and how they are processed.} \item{check}{logical that indicates if the validity of the stopping criterion function should be checked before returning it.} \item{n}{maximum number of iteration to perform.} \item{threshold}{default stationarity threshold} \item{object}{an NMF strategy object} \item{i}{the current iteration} \item{y}{the target matrix} \item{x}{the current NMF model} \item{stationary.th}{maximum absolute value of the gradient, for the objective function to be considered stationary.} \item{check.interval}{interval (in number of iterations) on which the stopping criterion is computed.} \item{check.niter}{number of successive iteration used to compute the stationnary criterion.} \item{...}{extra arguments passed to the function \code{\link{objective}}, which computes the objective value between \code{x} and \code{y}.} \item{stopconv}{number of iterations intervals over which the connectivity matrix must not change for stationarity to be achieved.} } \value{ a function that can be passed to argument \code{.stop} of function \code{\link{nmf}}, which is typically used when the algorith is implemented as an iterative strategy. a function that can be used as a stopping criterion for NMF algorithms defined as \code{\linkS4class{NMFStrategyIterative}} objects. That is a function with arguments \code{(strategy, i, target, data, ...)} that returns \code{TRUE} if the stopping criterion is satisfied -- which in turn stops the iterative process, and \code{FALSE} otherwise. } \description{ The function documented here implement stopping/convergence criteria commonly used in NMF algorithms. \code{NMFStop} acts as a factory method that creates stopping criterion functions from different types of values, which are subsequently used by \code{\linkS4class{NMFStrategyIterative}} objects to determine when to stop their iterative process. \code{nmf.stop.iteration} generates a function that implements the stopping criterion that limits the number of iterations to a maximum of \code{n}), i.e. that returns \code{TRUE} if \code{i>=n}, \code{FALSE} otherwise. \code{nmf.stop.threshold} generates a function that implements the stopping criterion that stops when a given stationarity threshold is achieved by successive iterations. The returned function is identical to \code{nmf.stop.stationary}, but with the default threshold set to \code{threshold}. More precisely, the objective function is computed over \eqn{n} successive iterations (specified in argument \code{check.niter}), every \code{check.interval} iterations. The criterion stops when the absolute difference between the maximum and the minimum objective values over these iterations is lower than a given threshold \eqn{\alpha} (specified in \code{stationary.th}): \code{nmf.stop.connectivity} implements the stopping criterion that is based on the stationarity of the connectivity matrix. } \details{ \code{NMFStop} can take the following values: \describe{ \item{function}{ is returned unchanged, except when it has no arguments, in which case it assumed to be a generator, which is immediately called and should return a function that implements the actual stopping criterion;} \item{integer}{ the value is used to create a stopping criterion that stops at that exact number of iterations via \code{nmf.stop.iteration};} \item{numeric}{ the value is used to create a stopping criterion that stops when at that stationary threshold via \code{nmf.stop.threshold};} \item{character}{ must be a single string which must be an access key for registered criteria (currently available: \dQuote{connectivity} and \dQuote{stationary}), or the name of a function in the global environment or the namespace of the loading package.} } \deqn{ \left| \frac{\max_{i- N_s + 1 \leq k \leq i} D_k - \min_{i - N_s +1 \leq k \leq i} D_k}{n} \right| \leq \alpha, }{ | [max( D(i- N_s + 1), ..., D(i) ) - min( D(i- N_s + 1), ..., D(i) )] / n | <= alpha } } NMF/man/nmfSeed.Rd0000644000176200001440000000464713620502674013335 0ustar liggesusers\name{nmfSeed} \alias{existsNMFSeed} \alias{getNMFSeed} \alias{nmfSeed} \title{Seeding Strategies for NMF Algorithms} \usage{ nmfSeed(name = NULL, ...) getNMFSeed(name = NULL, ...) existsNMFSeed(name, exact = TRUE) } \arguments{ \item{name}{access key of a seeding method stored in registry. If missing, \code{nmfSeed} returns the list of all available seeding methods.} \item{...}{extra arguments used for internal calls} \item{exact}{a logical that indicates if the access key should be matched exactly or partially.} } \description{ \code{nmfSeed} lists and retrieves NMF seeding methods. \code{getNMFSeed} is an alias for \code{nmfSeed}. \code{existsNMFSeed} tells if a given seeding method exists in the registry. } \details{ Currently the internal registry contains the following seeding methods, which may be specified to the function \code{\link{nmf}} via its argument \code{seed} using their access keys: \describe{ \item{random}{ The entries of each factors are drawn from a uniform distribution over \eqn{[0, max(x)]}, where $x$ is the target matrix.} \item{nndsvd}{ Nonnegative Double Singular Value Decomposition. The basic algorithm contains no randomization and is based on two SVD processes, one approximating the data matrix, the other approximating positive sections of the resulting partial SVD factors utilising an algebraic property of unit rank matrices. It is well suited to initialise NMF algorithms with sparse factors. Simple practical variants of the algorithm allows to generate dense factors. \strong{Reference:} \cite{Boutsidis et al. (2008)}} \item{ica}{ Uses the result of an Independent Component Analysis (ICA) (from the \code{fastICA} package). Only the positive part of the result are used to initialise the factors.} \item{none}{ Fixed seed. This method allows the user to manually provide initial values for both matrix factors.} } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # list all registered seeding methods nmfSeed() # retrieve one of the methods nmfSeed('ica') } \references{ Boutsidis C and Gallopoulos E (2008). "SVD based initialization: A head start for nonnegative matrix factorization." _Pattern Recognition_, *41*(4), pp. 1350-1362. ISSN 00313203, , . } NMF/man/randomize.Rd0000644000176200001440000000153313620502674013733 0ustar liggesusers\name{randomize} \alias{randomize} \title{Randomizing Data} \usage{ randomize(x, ...) } \arguments{ \item{x}{data to be permutated. It must be an object suitable to be passed to the function \code{\link{apply}}.} \item{...}{extra arguments passed to the function \code{\link{sample}}.} } \value{ a matrix } \description{ \code{randomize} permutates independently the entries in each column of a matrix-like object, to produce random data that can be used in permutation tests or bootstrap analysis. } \details{ In the context of NMF, it may be used to generate random data, whose factorization serves as a reference for selecting a factorization rank, that does not overfit the data. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } x <- matrix(1:32, 4, 8) randomize(x) randomize(x) } NMF/man/RNG.Rd0000644000176200001440000000407713710764404012400 0ustar liggesusers\docType{methods} \name{getRNG1} \alias{.getRNG} \alias{getRNG1} \alias{getRNG1-methods} \alias{getRNG1,NMFfitX1-method} \alias{getRNG1,NMFfitX-method} \alias{getRNG1,NMFfitXn-method} \alias{.getRNG-methods} \alias{.getRNG,NMFfitXn-method} \title{Extracting RNG Data from NMF Objects} \usage{ getRNG1(object, ...) .getRNG(object, ...) } \arguments{ \item{object}{an R object from which RNG settings can be extracted, e.g. an integer vector containing a suitable value for \code{.Random.seed} or embedded RNG data, e.g., in S3/S4 slot \code{rng} or \code{rng$noise}.} \item{...}{extra arguments to allow extension and passed to a suitable S4 method \code{.getRNG} or \code{.setRNG}.} } \description{ The \code{\link{nmf}} function returns objects that contain embedded RNG data, that can be used to exactly reproduce any computation. These data can be extracted using dedicated methods for the S4 generics \code{\link[rngtools]{getRNG}} and \code{\link[rngtools]{getRNG1}}. } \section{Methods}{ \describe{ \item{.getRNG}{\code{signature(object = "NMFfitXn")}: Returns the RNG settings used for the best fit. This method throws an error if the object is empty. } \item{getRNG1}{\code{signature(object = "NMFfitX")}: Returns the RNG settings used for the first NMF run of multiple NMF runs. } \item{getRNG1}{\code{signature(object = "NMFfitX1")}: Returns the RNG settings used to compute the first of all NMF runs, amongst which \code{object} was selected as the best fit. } \item{getRNG1}{\code{signature(object = "NMFfitXn")}: Returns the RNG settings used for the first run. This method throws an error if the object is empty. } } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # For multiple NMF runs, the RNG settings used for the first run is also stored V <- rmatrix(20,10) res <- nmf(V, 3, nrun=3) # RNG used for the best fit getRNG(res) # RNG used for the first of all fits getRNG1(res) # they may differ if the best fit is not the first one rng.equal(res, getRNG1(res)) } \keyword{methods} NMF/man/subset-NMF.Rd0000644000176200001440000001155513620502674013673 0ustar liggesusers\docType{methods} \name{[,NMF-method} \alias{[,NMF-method} \title{Sub-setting NMF Objects} \usage{ \S4method{[}{NMF}(x, i, j, ..., drop = FALSE) } \arguments{ \item{i}{index used to subset on the \strong{rows} of the basis matrix (i.e. the features). It can be a \code{numeric}, \code{logical}, or \code{character} vector (whose elements must match the row names of \code{x}). In the case of a \code{logical} vector the entries are recycled if necessary.} \item{j}{index used to subset on the \strong{columns} of the mixture coefficient matrix (i.e. the samples). It can be a \code{numeric}, \code{logical}, or \code{character} vector (whose elements must match the column names of \code{x}). In the case of a \code{logical} vector the entries are recycled if necessary.} \item{...}{used to specify a third index to subset on the basis components, i.e. on both the columns and rows of the basis matrix and mixture coefficient respectively. It can be a \code{numeric}, \code{logical}, or \code{character} vector (whose elements must match the basis names of \code{x}). In the case of a \code{logical} vector the entries are recycled if necessary. Note that only the first extra subset index is used. A warning is thrown if more than one extra argument is passed in \code{...}.} \item{drop}{single \code{logical} value used to drop the \code{NMF-class} wrapping and only return subsets of one of the factor matrices (see \emph{Details})} \item{x}{ object from which to extract element(s) or in which to replace element(s). } } \description{ This method provides a convenient way of sub-setting objects of class \code{NMF}, using a matrix-like syntax. It allows to consistently subset one or both matrix factors in the NMF model, as well as retrieving part of the basis components or part of the mixture coefficients with a reduced amount of code. } \details{ The returned value depends on the number of subset index passed and the value of argument \code{drop}: \itemize{ \item No index as in \code{x[]} or \code{x[,]}: the value is the object \code{x} unchanged. \item One single index as in \code{x[i]}: the value is the complete NMF model composed of the selected basis components, subset by \code{i}, except if argument \code{drop=TRUE}, or if it is missing and \code{i} is of length 1. Then only the basis matrix is returned with dropped dimensions: \code{x[i, drop=TRUE]} <=> \code{drop(basis(x)[, i])}. This means for example that \code{x[1L]} is the first basis vector, and \code{x[1:3, drop = TRUE]} is the matrix composed of the 3 first basis vectors -- in columns. Note that in version <= 0.18.3, the call \code{x[i, drop = TRUE.or.FALSE]} was equivalent to \code{basis(x)[, i, drop=TRUE.or.FALSE]}. \item More than one index with \code{drop=FALSE} (default) as in \code{x[i,j]}, \code{x[i,]}, \code{x[,j]}, \code{x[i,j,k]}, \code{x[i,,k]}, etc...: the value is a \code{NMF} object whose basis and/or mixture coefficient matrices have been subset accordingly. The third index \code{k} affects simultaneously the columns of the basis matrix AND the rows of the mixture coefficient matrix. In this case argument \code{drop} is not used. \item More than one index with \code{drop=TRUE} and \code{i} xor \code{j} missing: the value returned is the matrix that is the more affected by the subset index. That is that \code{x[i, , drop=TRUE]} and \code{x[i, , k, drop=TRUE]} return the basis matrix subset by \code{[i,]} and \code{[i,k]} respectively, while \code{x[, j, drop=TRUE]} and \code{x[, j, k, drop=TRUE]} return the mixture coefficient matrix subset by \code{[,j]} and \code{[k,j]} respectively. } } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # create a dummy NMF object that highlight the different way of subsetting a <- nmfModel(W=outer(seq(1,5),10^(0:2)), H=outer(10^(0:2),seq(-1,-10))) basisnames(a) <- paste('b', 1:nbasis(a), sep='') rownames(a) <- paste('f', 1:nrow(a), sep='') colnames(a) <- paste('s', 1:ncol(a), sep='') # or alternatively: # dimnames(a) <- list( features=paste('f', 1:nrow(a), sep='') # , samples=paste('s', 1:ncol(a), sep='') # , basis=paste('b', 1:nbasis(a)) ) # look at the resulting NMF object a basis(a) coef(a) # extract basis components a[1] a[1, drop=FALSE] # not dropping matrix dimension a[2:3] # subset on the features a[1,] a[2:4,] # dropping the NMF-class wrapping => return subset basis matrix a[2:4,, drop=TRUE] # subset on the samples a[,1] a[,2:4] # dropping the NMF-class wrapping => return subset coef matrix a[,2:4, drop=TRUE] # subset on the basis => subsets simultaneously basis and coef matrix a[,,1] a[,,2:3] a[4:5,,2:3] a[4:5,,2:3, drop=TRUE] # return subset basis matrix a[,4:5,2:3, drop=TRUE] # return subset coef matrix # 'drop' has no effect here a[,,2:3, drop=TRUE] } \keyword{methods} NMF/man/syntheticNMF.Rd0000644000176200001440000000623113620502674014316 0ustar liggesusers\name{syntheticNMF} \alias{syntheticNMF} \title{Simulating Datasets} \usage{ syntheticNMF(n, r, p, offset = NULL, noise = TRUE, factors = FALSE, seed = NULL) } \arguments{ \item{n}{number of rows of the target matrix.} \item{r}{specification of the factorization rank. It may be a single \code{numeric}, in which case argument \code{p} is required and \code{r} groups of samples are generated from a draw from a multinomial distribution with equal probabilities, that provides their sizes. It may also be a numerical vector, which contains the number of samples in each class (i.e integers). In this case argument \code{p} is discarded and forced to be the sum of \code{r}.} \item{p}{number of columns of the synthetic target matrix. Not used if parameter \code{r} is a vector (see description of argument \code{r}).} \item{offset}{specification of a common offset to be added to the synthetic target matrix, before noisification. Its may be a numeric vector of length \code{n}, or a single numeric value that is used as the standard deviation of a centred normal distribution from which the actual offset values are drawn.} \item{noise}{a logical that indicate if noise should be added to the matrix.} \item{factors}{a logical that indicates if the NMF factors should be return together with the matrix.} \item{seed}{a single numeric value used to seed the random number generator before generating the matrix. The state of the RNG is restored on exit.} } \value{ a matrix, or a list if argument \code{factors=TRUE}. When \code{factors=FALSE}, the result is a matrix object, with the following attributes set: \describe{ \item{coefficients}{the true underlying coefficient matrix (i.e. \code{H});} \item{basis}{the true underlying coefficient matrix (i.e. \code{H});} \item{offset}{the offset if any;} \item{pData}{a \code{list} with one element \code{'Group'} that contains a factor that indicates the true groups of samples, i.e. the most contributing basis component for each sample;} \item{fData}{a \code{list} with one element \code{'Group'} that contains a factor that indicates the true groups of features, i.e. the basis component to which each feature contributes the most.} } Moreover, the result object is an \code{\link{ExposeAttribute}} object, which means that relevant attributes are accessible via \code{$}, e.g., \code{res$coefficients}. In particular, methods \code{\link{coef}} and \code{\link{basis}} will work as expected and return the true underlying coefficient and basis matrices respectively. } \description{ The function \code{syntheticNMF} generates random target matrices that follow some defined NMF model, and may be used to test NMF algorithms. It is designed to designed to produce data with known or clear classes of samples. } \examples{ \dontshow{# roxygen generated flag options(R_CHECK_RUNNING_EXAMPLES_=TRUE) } # generate a synthetic dataset with known classes: 50 features, 18 samples (5+5+8) n <- 50 counts <- c(5, 5, 8) # no noise V <- syntheticNMF(n, counts, noise=FALSE) \dontrun{aheatmap(V)} # with noise V <- syntheticNMF(n, counts) \dontrun{aheatmap(V)} } NMF/TODO0000644000176200001440000000133013620502674011364 0ustar liggesusersFeatures: o handle missing data in target matrix o NMF model/algorithm class for MCMC based NMF o function/option to run standard consensus clustering, i.e. with a different set sample for each run. Possibly also use subsets of features and consensus matrix for them (memory issue). o incorporate silhouette values/plots, notably in heatmaps o support for other type of matrix-like objects (e.g., bigmatrix, Matrix) o import/export functions Documentation: o improve/update vignettes Technical/Internals: o change plot.NMFrankestimate to screeplot, remove class NMFrankestimate, change it to normal NMFlist o Switch to the interface defined in the package modeltools NMF/DESCRIPTION0000644000176200001440000000422413711174456012413 0ustar liggesusersPackage: NMF Type: Package Title: Algorithms and Framework for Nonnegative Matrix Factorization (NMF) Version: 0.23.0 Date: 2020-07-30 Author: Renaud Gaujoux, Cathal Seoighe Maintainer: Nicolas Sauwen Description: Provides a framework to perform Non-negative Matrix Factorization (NMF). The package implements a set of already published algorithms and seeding methods, and provides a framework to test, develop and plug new/custom algorithms. Most of the built-in algorithms have been optimized in C++, and the main interface function provides an easy way of performing parallel computations on multicore machines. License: GPL (>= 2) URL: http://renozao.github.io/NMF LazyLoad: yes VignetteBuilder: knitr Depends: R (>= 3.0.0), methods, utils, pkgmaker (>= 0.20), registry, rngtools (>= 1.2.3), cluster Imports: graphics, stats, stringr (>= 1.0.0), digest, grid, grDevices, gridBase, colorspace, RColorBrewer, foreach, doParallel, ggplot2, reshape2, BiocManager, Biobase Suggests: fastICA, doMPI, bigmemory (>= 4.2), synchronicity(>= 1.3.2), corpcor, xtable, devtools, knitr, bibtex, RUnit, mail Collate: 'colorcode.R' 'options.R' 'grid.R' 'atracks.R' 'aheatmap.R' 'algorithmic.R' 'nmf-package.R' 'rmatrix.R' 'utils.R' 'versions.R' 'NMF-class.R' 'transforms.R' 'Bioc-layer.R' 'NMFstd-class.R' 'NMFOffset-class.R' 'heatmaps.R' 'NMFns-class.R' 'nmfModel.R' 'fixed-terms.R' 'NMFfit-class.R' 'NMFSet-class.R' 'NMFStrategy-class.R' 'registry.R' 'NMFSeed-class.R' 'NMFStrategyFunction-class.R' 'NMFStrategyIterative-class.R' 'NMFplots.R' 'registry-algorithms.R' 'algorithms-base.R' 'algorithms-lnmf.R' 'algorithms-lsnmf.R' 'algorithms-pe-nmf.R' 'algorithms-siNMF.R' 'algorithms-snmf.R' 'data.R' 'extractFeatures.R' 'parallel.R' 'registry-seed.R' 'nmf.R' 'rnmf.R' 'run.R' 'seed-base.R' 'seed-ica.R' 'seed-nndsvd.R' 'setNMFClass.R' 'simulation.R' 'tests.R' Packaged: 2020-07-31 10:09:52 UTC; nsauwen NeedsCompilation: yes Repository: CRAN Date/Publication: 2020-08-01 05:10:06 UTC RoxygenNote: 6.0.1.9000 NMF/build/0000755000176200001440000000000013710766760012006 5ustar liggesusersNMF/build/vignette.rds0000644000176200001440000000050513710766760014345 0ustar liggesusersPMO@m)-AcƻDh ueCl 79u)@1xɼ7fqtui3/O $p.*ÏCQ6\[s(dKH2Z"+ ^AL4:}`) T@\NE,~smu۹a'o:54;yMDOs̥"D-O2jBe'' Gc?Ɲj7Z^p>$]뜓rX96])({CT BjoKw 0p^71INMF/src/0000755000176200001440000000000013710766760011476 5ustar liggesusersNMF/src/divergence.cpp0000644000176200001440000001365613620502674014320 0ustar liggesusers#ifndef NMF_DIVERGENCE_H // include header only once #define NMF_DIVERGENCE_H #include #include #include extern "C" { SEXP divergence_update_H ( SEXP v, SEXP w, SEXP h, SEXP nbterms, SEXP ncterms, SEXP dup); SEXP divergence_update_W ( SEXP v, SEXP w, SEXP h, SEXP nbterms, SEXP ncterms, SEXP dup); } // include version for both double/integer storage.mode (defined as templates) #include "divergence.cpp" // define the exported versions (for SEXP) SEXP divergence_update_H ( SEXP v, SEXP w, SEXP h , SEXP nbterms=ScalarInteger(0), SEXP ncterms=ScalarInteger(0) , SEXP dup=ScalarLogical(1)) { if( TYPEOF(v) == REALSXP ){ return divergence_update_H(NUMERIC_POINTER(v), w, h , *INTEGER(nbterms), *INTEGER(ncterms) , *LOGICAL(dup)); }else{ return divergence_update_H(INTEGER_POINTER(v), w, h , *INTEGER(nbterms), *INTEGER(ncterms) , *LOGICAL(dup)); } } SEXP divergence_update_W ( SEXP v, SEXP w, SEXP h , SEXP nbterms=ScalarInteger(0), SEXP ncterms=ScalarInteger(0) , SEXP dup=ScalarLogical(1)) { if( TYPEOF(v) == REALSXP ) return divergence_update_W(NUMERIC_POINTER(v), w, h , *INTEGER(nbterms), *INTEGER(ncterms) , *LOGICAL(dup)); else return divergence_update_W(INTEGER_POINTER(v), w, h , *INTEGER(nbterms), *INTEGER(ncterms) , *LOGICAL(dup)); } #define NMF_DIVERGENCE_DONE #else // END OF NMF_DIVERGENCE_H #ifndef NMF_DIVERGENCE_DONE // START DEFINITION OF FUNCTIONS /** * Divergence based multiplicative update for the mixture coefficients matrix H * from Brunet et al. algorithm. * * @param pV target matrix * @param w basis vector matrix * @param h mixture coefficient matrix to be updated * @param nbterms number of fixed basis terms * @param ncterms number of fixed coefficient terms * @param dup boolean (flag) that specifies if the update must be perform directly on w or * on a duplicated version of w * * @return the updated mixture coefficient matrix. * */ template static SEXP divergence_update_H ( T_Rnumeric* pV, SEXP w, SEXP h, int nbterms=0, int ncterms=0, int dup=1) { SEXP res; int nprotect = 0; // retrieve dimensions from W and H int n = INTEGER(GET_DIM(w))[0]; int r = INTEGER(GET_DIM(w))[1]; int p = INTEGER(GET_DIM(h))[1]; // get number of non-fixed terms int vr = r - ncterms; // duplicate H (keeping attributes) or modify in place PROTECT(res = (dup != 0 ? duplicate(h) : h) ); nprotect++; // define internal pointers double* pW = NUMERIC_POINTER(w); double* pH = NUMERIC_POINTER(h); double* p_res = NUMERIC_POINTER(res); // allocate internal memory double* sumW = (double*) R_alloc(r, sizeof(double)); // will store column sums of W double* pWH = (double*) R_alloc(n, sizeof(double)); // will store the currently used column of WH // Compute update of H column by column for(int jH=0; jH < p; ++jH){ for (int iH=0; iH < vr; ++iH){ // compute value for H_ij (non-fixed terms only) // initialise values double tmp_res = 0.0; double &w_sum = sumW[iH]; if( jH == 0 ) w_sum = 0.0; // compute cross-product w_.i by (v/wh)_.j for( int u=0; u compute once and store the result for using for the next rows double wh_term = pWH[u]; if( iH == 0 ){ wh_term = 0.0; for (int k=0; k static SEXP divergence_update_W ( T_Rnumeric* pV, SEXP w, SEXP h, int nbterms=0, int ncterms=0, int dup=1) { SEXP res; int nprotect = 0; // retrieve dimensions int n = INTEGER(GET_DIM(w))[0]; int r = INTEGER(GET_DIM(w))[1]; int p = INTEGER(GET_DIM(h))[1]; // duplicate W (keeping attributes) PROTECT(res = (dup != 0 ? duplicate(w) : w) ); nprotect++; // define internal pointers double* pW = NUMERIC_POINTER(w); double* pH = NUMERIC_POINTER(h); double* p_res = NUMERIC_POINTER(res); // allocate internal memory double* sumH = (double*) R_alloc(r, sizeof(double)); // will store the row sums of H double* pWH = (double*) R_alloc(p, sizeof(double)); // will store currently used row of WH // Compute update of W row by row for(int iW=0; iW < n; iW++){ for (int jW=0; jW < r; jW++){ // compute value for W_ij // initialise values double tmp_res = 0.0; double &h_sum = sumH[jW]; if( iW == 0 ) h_sum = 0.0; // compute cross-product (v/wh)_i. by h_j. for( int u=0; u compute once and store the result for using for the next columns if( jW == 0 ){ double wh_term = 0.0; for (int k=0; k #include #include extern "C" { // NMF from Lee and Seung (based on Euclidean norm) SEXP euclidean_update_H ( SEXP v, SEXP w, SEXP h, SEXP eps, SEXP nbterms, SEXP ncterms, SEXP dup); SEXP euclidean_update_W ( SEXP v, SEXP w, SEXP h, SEXP eps, SEXP weight, SEXP nbterms, SEXP ncterms, SEXP dup); // NMF with offset SEXP offset_euclidean_update_H ( SEXP v, SEXP w, SEXP h, SEXP offset, SEXP eps, SEXP dup); SEXP offset_euclidean_update_W ( SEXP v, SEXP w, SEXP h, SEXP offset, SEXP eps, SEXP dup); } ////////////////////////////////// // STANDARD NMF: LEE ////////////////////////////////// // include version for both double/integer storage.mode (defined as templates) #include "euclidean.cpp" // define the exported versions (for SEXP) SEXP euclidean_update_H ( SEXP v, SEXP w, SEXP h, SEXP eps , SEXP nbterms=ScalarInteger(0), SEXP ncterms=ScalarInteger(0) , SEXP dup=ScalarLogical(1)){ if( TYPEOF(v) == REALSXP ){ return euclidean_update_H(NUMERIC_POINTER(v), w, h, eps , *INTEGER(nbterms), *INTEGER(ncterms) , *LOGICAL(dup)); }else{ return euclidean_update_H(INTEGER_POINTER(v), w, h, eps , *INTEGER(nbterms), *INTEGER(ncterms) , *LOGICAL(dup)); } } ////////////////////////////////// // NMF WITH WEIGHT ////////////////////////////////// #define NMF_WITH_WEIGHT #include "euclidean.cpp" #undef NMF_WITH_WEIGHT SEXP euclidean_update_W ( SEXP v, SEXP w, SEXP h, SEXP eps , SEXP weight = R_NilValue , SEXP nbterms=ScalarInteger(0), SEXP ncterms=ScalarInteger(0) , SEXP dup=ScalarLogical(1)){ int nb = *INTEGER(nbterms), nc = *INTEGER(ncterms); bool copy = *LOGICAL(dup); if( TYPEOF(v) == REALSXP ){ if( isNull(weight) ){ return euclidean_update_W(NUMERIC_POINTER(v), w, h, eps, nb, nc, copy); }else{ return weuclidean_update_W(NUMERIC_POINTER(v), w, h, eps, weight, nb, nc, copy); } }else{ if( isNull(weight) ){ return euclidean_update_W(INTEGER_POINTER(v), w, h, eps, nb, nc, copy); }else{ return weuclidean_update_W(INTEGER_POINTER(v), w, h, eps, weight, nb, nc, copy); } } } ////////////////////////////////// // NMF WITH OFFSET ////////////////////////////////// #define NMF_WITH_OFFSET // include version for both double/integer storage.mode (defined as templates) #include "euclidean.cpp" #undef NMF_WITH_OFFSET // define the exported versions (for SEXP) SEXP offset_euclidean_update_H ( SEXP v, SEXP w, SEXP h, SEXP offset, SEXP eps, SEXP dup=ScalarLogical(1)){ if( TYPEOF(v) == REALSXP ) return offset_euclidean_update_H(NUMERIC_POINTER(v), w, h, offset, eps, *LOGICAL(dup)); else return offset_euclidean_update_H(INTEGER_POINTER(v), w, h, offset, eps, *LOGICAL(dup)); } SEXP offset_euclidean_update_W ( SEXP v, SEXP w, SEXP h, SEXP offset, SEXP eps, SEXP dup=ScalarLogical(1)){ if( TYPEOF(v) == REALSXP ) return offset_euclidean_update_W(NUMERIC_POINTER(v), w, h, offset, eps, *LOGICAL(dup)); else return offset_euclidean_update_W(INTEGER_POINTER(v), w, h, offset, eps, *LOGICAL(dup)); } #define NMF_EUCLIDEAN_DONE #else // END OF NMF_EUCLIDEAN_H #ifndef NMF_EUCLIDEAN_DONE // START DEFINITION OF FUNCTIONS /** * Euclidean norm based multiplicative update for the mixture coefficients matrix H * from Lee and Seung. * Also used in the NMF with Offset algorithm * * Note: for performance reason the dimension names are NOT conserved. */ #ifndef NMF_WITH_WEIGHT template static #ifdef NMF_WITH_OFFSET SEXP offset_euclidean_update_H ( #else SEXP euclidean_update_H ( #endif T_Rnumeric* pV, SEXP w, SEXP h #ifdef NMF_WITH_OFFSET , SEXP offset #endif , SEXP eps #ifndef NMF_WITH_OFFSET , int nbterms=0, int ncterms=0 #endif , int dup=1) { SEXP res; int nprotect = 0; double eps_val = *NUMERIC_POINTER(eps); // retrieve dimensions int n = INTEGER(GET_DIM(w))[0]; int r = INTEGER(GET_DIM(w))[1]; int p = INTEGER(GET_DIM(h))[1]; // get number of non-fixed terms int vr = #ifdef NMF_WITH_OFFSET r; #else r - ncterms; #endif // duplicate H (keeping attributes) PROTECT( res = (dup != 0 ? duplicate(h) : h) ); nprotect++; // define internal pointers double* pW = NUMERIC_POINTER(w); double* pH = NUMERIC_POINTER(h); double* p_res = NUMERIC_POINTER(res); double* pH_buffer = (double*) R_alloc(r, sizeof(double)); // extra variables in the case of an optional offset #ifdef NMF_WITH_OFFSET double *pOffset = NULL, *den_addon = NULL; if( offset != R_NilValue ){ pOffset = NUMERIC_POINTER(offset); den_addon = (double*) R_alloc(r, sizeof(double)); //memset(den_addon, 0, r); } #endif // auxiliary temporary variable double temp = 0.0; // Pre-compute symmetric matrix t(W)W // -> allocate internal memory as a upper triangular in column major double* p_tWW = (double*) R_alloc((int) (r*(r+1))/2, sizeof(double)); double* p_row = NULL; for( int i=r-1; i>=0; --i){ p_row = pW + i*n; #ifdef NMF_WITH_OFFSET den_addon[i] = 0.0; #endif for( int j=r-1; j>=0; --j){ temp = 0.0; for( int u=n-1; u>=0; --u){ temp += p_row[u] * pW[u + j*n]; #ifdef NMF_WITH_OFFSET if( pOffset != NULL && j==0 ) den_addon[i] += p_row[u] * pOffset[u]; #endif } p_tWW[((j+1)*j)/2 + i] = temp; } } // H_au = H_au (W^T V)_au / (W^T W H)_au // Compute update of H column by column for (int j=p-1; j>=0; --j){ for(int i=vr-1; i>=0; --i){ // compute value for H_ij (only non-fixed entries) // numerator double numerator = 0.0; for( int u=n-1; u>=0; --u) numerator += pW[u + i*n] * pV[u + j*n]; double den = 0.0; for( int l=r-1; l>=0; --l){ // use all entries (fixed and non-fixed) // bufferize jth-column of H, as it can be changed at the end of the current i-loop if( i==vr-1 ) pH_buffer[l] = pH[l + j*r]; den += p_tWW[i > l ? ((i+1)*i)/2 + l : ((l+1)*l)/2 + i] * pH_buffer[l]; } // add offset addon if necessary #ifdef NMF_WITH_OFFSET if( pOffset != NULL ) den += den_addon[i]; #endif // multiplicative update p_res[i + j*r] = ((temp = pH_buffer[i] * numerator) > eps_val ? temp : eps_val) / (den + eps_val); } } // return result UNPROTECT(nprotect); return res; } #endif /** * Euclidean norm based multiplicative update for the basis matrix W * from Lee and Seung. * Also used in the NMF with Offset algorithm * * Note: for performance reason the dimension names are NOT conserved. */ template static SEXP #ifdef NMF_WITH_OFFSET offset_euclidean_update_W #else #ifdef NMF_WITH_WEIGHT weuclidean_update_W #else euclidean_update_W #endif #endif (T_Rnumeric* pV, SEXP w, SEXP h #ifdef NMF_WITH_OFFSET , SEXP offset #endif , SEXP eps #ifdef NMF_WITH_WEIGHT , SEXP weight #endif #ifndef NMF_WITH_OFFSET , int nbterms=0, int ncterms=0 #endif , int dup=1) { SEXP res; int nprotect = 0; // setup variables for enforcing a limit Inf on the entries double limInf = *NUMERIC_POINTER(eps); // retrieve dimensions int n = INTEGER(GET_DIM(w))[0]; int r = INTEGER(GET_DIM(w))[1]; int p = INTEGER(GET_DIM(h))[1]; // duplicate H (keeping attributes) //PROTECT(res = duplicate(w)); nprotect++; PROTECT(res = (dup != 0 ? duplicate(w) : w) ); nprotect++; // define internal pointers to data double* pW = NUMERIC_POINTER(w); double* pH = NUMERIC_POINTER(h); double* p_res = NUMERIC_POINTER(res); double* pW_buffer = (double*) R_alloc(r, sizeof(double)); // extra variables in the case of an optional offset #ifdef NMF_WITH_OFFSET double *pOffset = NULL, *rowSumsH = NULL; if( offset != R_NilValue ){ pOffset = NUMERIC_POINTER(offset); // pre-compute the row sums of H rowSumsH = (double*) R_alloc(r, sizeof(double)); for( int i=r-1; i>=0; --i){ rowSumsH[i] = 0.0; for( int j=p-1; j>=0; --j){ rowSumsH[i] += pH[i + j*r]; } } } #endif #ifdef NMF_WITH_WEIGHT // take sample weights into account double* p_weight = !isNull(weight) ? NUMERIC_POINTER(weight) : NULL; double beta = -1.0; if( p_weight == NULL ){// <=> no weights beta = 1.0; } else if( length(weight) == 1 ){// all weighted are the same // NB: theoretically this is equivalent to weight=1, but may be used // to test it in practice (with the numerical adjustments via eps) beta = *p_weight; } // fill weight vector with single value if( beta > 0 ){ double* pw = p_weight = (double*) R_alloc(p, sizeof(double)); for(int i=0; i allocate internal memory as a lower triangular in column major double* p_HtH = (double*) R_alloc((int) (r*(r+1))/2, sizeof(double)); for( int j=r-1; j>=0; --j){ for( int i=j; i=0; --u){ temp += pH[j + u*r] * pH[i + u*r] #ifdef NMF_WITH_WEIGHT * p_weight[u] #endif ; } p_HtH[((i+1)*i)/2 + j] = temp; } } // W_ia = W_ia (V H^T)_ia / (W H H^T)_ia and columns are rescaled after each iteration // Compute update of W row by row double numerator = 0.0; double den = 0.0; for(int i=n-1; i>=0; --i){ for (int j=r-1; j>=0; --j){// compute value for W_ij // numerator numerator = 0.0; for( int u=p-1; u>=0; --u){ numerator += pV[i + u*n] * pH[j + u*r] #ifdef NMF_WITH_WEIGHT * p_weight[u] #endif ; } den = 0.0; for( int l=r-1; l>=0; --l){ // bufferize ith-row of W, as it can be changed at the end of the current j-loop if( j==r-1 ) pW_buffer[l] = pW[i + l*n]; // compute index of the stored value of t(w)w_[iH,l] in column major den += pW_buffer[l] * p_HtH[l < j ? ((j+1)*j)/2 + l : ((l+1)*l)/2 + j]; } // add offset addon if necessary #ifdef NMF_WITH_OFFSET if( pOffset != NULL ) den += pOffset[i] * rowSumsH[j]; #endif // multiplicative update temp = pW_buffer[j] * numerator; p_res[i + j*n] = ( temp < limInf ? limInf : temp ) / (den + limInf); } } // return result UNPROTECT(nprotect); return res; } #endif //END ifndef NMF_EUCLIDEAN_DONE #endif //END ifdef NMF_EUCLIDEAN_H NMF/src/utils.cpp0000644000176200001440000001515213620502674013336 0ustar liggesusers#ifndef NMF_UTILS_H // include header only once #define NMF_UTILS_H #include #include #include extern "C" { /** Returns the pointer address of 'x' as a character string*/ SEXP ptr_address (SEXP x); /** Clone an object 'x'*/ SEXP clone_object (SEXP x); /** pmin in place with 'y' being a single numeric value*/ SEXP ptr_pmax (SEXP x, SEXP y, SEXP skip); /** Apply inequality constraints in place. */ SEXP ptr_neq_constraints(SEXP x, SEXP constraints, SEXP ratio=R_NilValue, SEXP value=R_NilValue); /** Minimum per column*/ SEXP colMin(SEXP x); /** Maximum per row*/ SEXP colMax(SEXP x); /** Test if an external pointer is null. * * Function taken from the package bigmemory (v4.2.11). */ SEXP ptr_isnil(SEXP address) { void *ptr = R_ExternalPtrAddr(address); SEXP ret = PROTECT(NEW_LOGICAL(1)); LOGICAL_DATA(ret)[0] = (ptr==NULL) ? (Rboolean)TRUE : Rboolean(FALSE); UNPROTECT(1); return(ret); } } // define the exported versions (for SEXP) SEXP ptr_address (SEXP x){ SEXP ans = R_NilValue; char tmp[15]; PROTECT(ans = allocVector(STRSXP, 1)); sprintf(tmp, "%p", (void *) x); SET_STRING_ELT(ans, 0, mkChar(tmp)); UNPROTECT(1); return ans; } SEXP clone_object (SEXP x){ return Rf_duplicate(x); } SEXP ptr_pmax(SEXP x, SEXP y, SEXP skip=R_NilValue){ int n = length(x); double* p_x = ( isNull(x) ? NULL : NUMERIC_POINTER(x) ); double lim = isNull(y) ? -1.0 : *NUMERIC_POINTER(y); // backup skipped values int n_skip = length(skip); int ncol = isNull(GET_DIM(x)) ? 1 : INTEGER(GET_DIM(x))[1]; int nrow = n / ncol; double* old_value = NULL; int* p_skip = NULL; if( !isNull(skip) && n_skip > 0 ){ old_value = (double*) R_alloc(n_skip*ncol, sizeof(double)); p_skip = INTEGER_POINTER(skip); for(int k=ncol-1; k>=0; --k){ for(int i=n_skip-1; i>=0; --i){ //Rprintf("skip %i x %i\n", i, k); int is = p_skip[i]-1; double val = p_x[k*nrow + is]; old_value[k*n_skip + i] = val; } } } // apply limit inf to all values double* p_x2 = p_x + n-1; for(int i=n-1; i>=0; --i){ if( *p_x2 < lim ) *p_x2 = lim; --p_x2; } p_x2 = NULL; // restore skipped values if( !isNull(skip) && n_skip > 0 ){ for(int k=ncol-1; k>=0; --k){ for(int i=n_skip-1; i>=0; --i){ //Rprintf("restore %i x %i\n", i, k); int is = p_skip[i]-1; p_x[k*nrow + is] = old_value[k*n_skip + i]; } } } // return modified x return x; } /** Apply inequality constraints in place. */ SEXP ptr_neq_constraints(SEXP x, SEXP constraints, SEXP ratio, SEXP value){ double* p_x = ( isNull(x) ? NULL : NUMERIC_POINTER(x) ); double d_ratio = isNull(ratio) ? 0 : *NUMERIC_POINTER(ratio); double* p_value = ( isNull(value) ? NULL : NUMERIC_POINTER(value) ); double eps = sqrt(DOUBLE_EPS); // get dimensions int ncol = isNull(GET_DIM(x)) ? 1 : INTEGER(GET_DIM(x))[1]; int nrow = isNull(GET_DIM(x)) ? length(x) : INTEGER(GET_DIM(x))[0]; int nc = length(constraints); if( nc != ncol ) error("There must be as many elements in list `constraints` as columns in `x`."); // apply each set of constraints (from first to last) double* _xj = p_x; // pointer to marked column double* _x_last = p_x + (ncol - 1) * nrow; // pointer to last column for(int j=0; j=0; --k){ double lim = d_ratio != 0.0 ? _xj[p_i[k]-1] / d_ratio - eps : 0.0; if( lim < 0 ) lim = 0; // apply constraints on each column // pointer to current row in last column double* _xi = _x_last + p_i[k]-1; for(int l=ncol-1; l>=0; --l){ //Rprintf("Before: xi=%f > lim=%f ? => ", lim, *_xi); if( l != j && *_xi > lim ){ // constrain column to 'lim' *_xi = lim; }else if( l == j && p_value != NULL ){ // constrain column to 'value' *_xi = *p_value; } //Rprintf("xi=%f\n", *_xi); // move to previous column _xi -= nrow; } _xi = NULL; } // move to next marked column _xj += nrow; } // return modified x return x; } template inline void colMin(T* x, int n, int p, T* res, const T& NA_value){ // do nothing if there is no data or fill with NAs if( n <= 0 ){ if( p <= 0 ) return; for(int j=p-1; j>=0; --j, ++res) *res = NA_value; } for(int j=p-1; j>=0; --j, ++res){ *res = *(x++); for(int i=n-2; i>=0; --i, ++x){ if( *res > *x ) *res = *x; } } } template inline void colMax(T* x, int n, int p, T* res, const T& NA_value){ // do nothing if there is no data or fill with NAs if( n <= 0 ){ if( p <= 0 ) return; for(int j=p-1; j>=0; --j, ++res) *res = NA_value; } for(int j=p-1; j>=0; --j, ++res){ *res = *(x++); for(int i=n-2; i>=0; --i, ++x){ if( *res < *x ) *res = *x; } } } /** * Minimum per column */ SEXP colMin(SEXP x){ SEXP ans, dims; // check that the argument is a matrix dims = GET_DIM(x); if (dims == R_NilValue) error("a matrix-like object is required as argument to 'colMin'"); // check that it is a numeric data if (!isNumeric(x)) error("a numeric object is required as argument to 'colMin'"); // get the dimension of the input matrix int n = INTEGER(dims)[0]; int p = INTEGER(dims)[1]; if( TYPEOF(x) == REALSXP ){ // allocate memory for the result (a vector of length the number of columns of x) PROTECT(ans = allocVector(REALSXP, p)); colMin(NUMERIC_POINTER(x), n, p, NUMERIC_POINTER(ans), NA_REAL); UNPROTECT(1); } else{ // allocate memory for the result (a vector of length the number of columns of x) PROTECT(ans = allocVector(INTSXP, p)); colMin(INTEGER_POINTER(x), n, p, INTEGER_POINTER(ans), NA_INTEGER); UNPROTECT(1); } return ans; } /** * Maximum per column */ SEXP colMax(SEXP x){ SEXP ans, dims; // check that the argument is a matrix dims = GET_DIM(x); if (dims == R_NilValue) error("a matrix-like object is required as argument to 'colMax'"); // check that it is a numeric data if (!isNumeric(x)) error("a numeric object is required as argument to 'colMax'"); // get the dimension of the input matrix int n = INTEGER(dims)[0]; int p = INTEGER(dims)[1]; if( TYPEOF(x) == REALSXP ){ // allocate memory for the result (a vector of length the number of columns of x) PROTECT(ans = allocVector(REALSXP, p)); colMax(NUMERIC_POINTER(x), n, p, NUMERIC_POINTER(ans), NA_REAL); UNPROTECT(1); } else{ // allocate memory for the result (a vector of length the number of columns of x) PROTECT(ans = allocVector(INTSXP, p)); colMax(INTEGER_POINTER(x), n, p, INTEGER_POINTER(ans), NA_INTEGER); UNPROTECT(1); } return ans; } #endif //END ifdef NMF_UTILS_H NMF/src/distance.cpp0000644000176200001440000000757113620502674013776 0ustar liggesusers#ifndef NMF_DISTANCE_H // include header only once #define NMF_DISTANCE_H #include #include #include extern "C" { SEXP Euclidean_rss( SEXP x, SEXP y); SEXP KL_divergence( SEXP x, SEXP y); } //define helper macro #define both_non_NA(a,b) (!ISNAN(a) && !ISNAN(b)) // include versions for double-double storage.mode #include "distance.cpp" // include versions for double-integer storage.mode #define NMF_ARG2_INT #include "distance.cpp" #undef NMF_ARG2_INT // include versions for integer-* storage.mode #define NMF_ARG1_INT #include "distance.cpp" // include versions for integer-integer storage.mode #define NMF_ARG2_INT #include "distance.cpp" #undef NMF_ARG2_INT #undef NMF_ARG1_INT // define the exported version of RSS (for SEXP) SEXP Euclidean_rss ( SEXP x, SEXP y){ // retrieve dimensions int n = INTEGER(GET_DIM(x))[0]; int p = INTEGER(GET_DIM(x))[1]; if( INTEGER(GET_DIM(y))[0] != n ) error("non-conformable arrays (rows)"); if( INTEGER(GET_DIM(y))[1] != p ) error("non-conformable arrays (columns)"); if( TYPEOF(x) == REALSXP ){// x is double if( TYPEOF(y) == REALSXP )// x and y are double return rss( NUMERIC_POINTER(x), NUMERIC_POINTER(y), n, p); else// x is double, y is integer return rss( NUMERIC_POINTER(x), INTEGER_POINTER(y), n, p); }else{ if( TYPEOF(y) == REALSXP ) // x is integer, y is double return rss( INTEGER_POINTER(x), NUMERIC_POINTER(y), n, p); else // x is integer, y is integer return rss( INTEGER_POINTER(x), INTEGER_POINTER(y), n, p); } } // define the exported version of KL (for SEXP) SEXP KL_divergence ( SEXP x, SEXP y){ // retrieve dimensions int n = INTEGER(GET_DIM(x))[0]; int p = INTEGER(GET_DIM(x))[1]; if( INTEGER(GET_DIM(y))[0] != n ) error("non-conformable arrays (rows)"); if( INTEGER(GET_DIM(y))[1] != p ) error("non-conformable arrays (columns)"); if( TYPEOF(x) == REALSXP ){// x is double if( TYPEOF(y) == REALSXP )// x and y are double return KL( NUMERIC_POINTER(x), NUMERIC_POINTER(y), n, p); else// x is double, y is integer return KL( NUMERIC_POINTER(x), INTEGER_POINTER(y), n, p); }else{ if( TYPEOF(y) == REALSXP ) // x is integer, y is double return KL( INTEGER_POINTER(x), NUMERIC_POINTER(y), n, p); else // x is integer, y is integer return KL( INTEGER_POINTER(x), INTEGER_POINTER(y), n, p); } } #define NMF_DISTANCE_DONE #else // END OF NMF_DISTANCE_H #ifndef NMF_DISTANCE_DONE // START DEFINITION OF FUNCTIONS // Internal function that computes the RSS SEXP rss( #ifdef NMF_ARG1_INT int* #else double* #endif px, #ifdef NMF_ARG2_INT int* #else double* #endif py , int n, int p){ double dev=0, dist=0; double xval, yval; //int count = 0; for(int i=n-1; i>=0; --i) { for(int j=p-1; j>=0; --j) { xval = px[i + j*n]; yval = py[i + j*n]; if (both_non_NA(xval, yval)) { dev = xval - yval; if (!ISNAN(dev)) { dist += dev * dev; //count++; } else return ScalarReal(NA_REAL); } else return ScalarReal(NA_REAL); } } //if (count == 0) return ScalarReal(NA_REAL); return ScalarReal(dist); } // Internal function that computes the KL divergence SEXP KL( #ifdef NMF_ARG1_INT int* #else double* #endif px, #ifdef NMF_ARG2_INT int* #else double* #endif py , int n, int p){ double dev=0, dist=0; double xval, yval; for(int i=n-1; i>=0; --i) { for(int j=p-1; j>=0; --j) { xval = px[i + j*n]; yval = py[i + j*n]; if( xval == 0 ) dev = yval; else if (both_non_NA(xval, yval)) dev = xval * log((double) xval / yval) - xval + yval; else return ScalarReal(NA_REAL); // only add and continue if the term is not NA if ( R_FINITE(dev) ) dist += dev; else return ScalarReal(dev); } } return ScalarReal(dist); } #endif //END ifndef NMF_DISTANCE_DONE #endif //END ifdef NMF_DISTANCE_H NMF/src/registerDynamicSymbol.c0000644000176200001440000000034513620502674016153 0ustar liggesusers// RegisteringDynamic Symbols #include #include #include void R_init_markovchain(DllInfo* info) { R_registerRoutines(info, NULL, NULL, NULL, NULL); R_useDynamicSymbols(info, TRUE); } NMF/vignettes/0000755000176200001440000000000013710766760012717 5ustar liggesusersNMF/vignettes/consensus.pdf0000644000176200001440000023327613620502674015437 0ustar liggesusers%PDF-1.4 %ρ\r 1 0 obj << /CreationDate (D:20111003210125) /ModDate (D:20111003210125) /Title (R Graphics Output) /Producer (R 2.13.1) /Creator (R) >> endobj 2 0 obj << /Type /Catalog /Pages 3 0 R >> endobj 7 0 obj << /Type /Page /Parent 3 0 R /Contents 8 0 R /Resources 4 0 R >> endobj 8 0 obj << /Length 9 0 R >> stream 1 J 1 j q Q q Q q Q q Q q 54.99 425.88 326.74 49.49 re W n /sRGB CS 0.000 0.000 0.000 SCN 0.75 w [] 0 d 1 J 1 j 10.00 M 132.36 475.37 m 67.85 475.37 l S 67.85 475.37 m 67.85 425.88 l S 67.85 425.88 m 59.29 425.88 l S 59.29 425.88 m 59.29 425.88 l S 67.85 425.88 m 76.42 425.88 l S 76.42 425.88 m 76.42 425.88 l S 76.42 425.88 m 67.88 425.88 l S 67.88 425.88 m 67.88 425.88 l S 76.42 425.88 m 84.95 425.88 l S 84.95 425.88 m 84.95 425.88 l S 84.95 425.88 m 76.48 425.88 l S 76.48 425.88 m 76.48 425.88 l S 84.95 425.88 m 93.41 425.88 l S 93.41 425.88 m 93.41 425.88 l S 93.41 425.88 m 85.08 425.88 l S 85.08 425.88 m 85.08 425.88 l S 93.41 425.88 m 101.74 425.88 l S 101.74 425.88 m 101.74 425.88 l S 101.74 425.88 m 93.68 425.88 l S 93.68 425.88 m 93.68 425.88 l S 101.74 425.88 m 109.80 425.88 l S 109.80 425.88 m 109.80 425.88 l S 109.80 425.88 m 102.28 425.88 l S 102.28 425.88 m 102.28 425.88 l S 109.80 425.88 m 117.33 425.88 l S 117.33 425.88 m 117.33 425.88 l S 117.33 425.88 m 110.88 425.88 l S 110.88 425.88 m 110.88 425.88 l S 117.33 425.88 m 123.77 425.88 l S 123.77 425.88 m 123.77 425.88 l S 123.77 425.88 m 119.48 425.88 l S 119.48 425.88 m 119.48 425.88 l S 123.77 425.88 m 128.07 425.88 l S 128.07 425.88 m 128.07 425.88 l S 132.36 475.37 m 196.86 475.37 l S 196.86 475.37 m 196.86 474.08 l S 196.86 474.08 m 145.27 474.08 l S 145.27 474.08 m 145.27 440.73 l S 145.27 440.73 m 136.67 440.73 l S 136.67 440.73 m 136.67 425.88 l S 145.27 440.73 m 153.86 440.73 l S 153.86 440.73 m 153.86 425.88 l S 153.86 425.88 m 145.27 425.88 l S 145.27 425.88 m 145.27 425.88 l S 153.86 425.88 m 162.45 425.88 l S 162.45 425.88 m 162.45 425.88 l S 162.45 425.88 m 153.87 425.88 l S 153.87 425.88 m 153.87 425.88 l S 162.45 425.88 m 171.03 425.88 l S 171.03 425.88 m 171.03 425.88 l S 171.03 425.88 m 162.47 425.88 l S 162.47 425.88 m 162.47 425.88 l S 171.03 425.88 m 179.60 425.88 l S 179.60 425.88 m 179.60 425.88 l S 179.60 425.88 m 171.07 425.88 l S 171.07 425.88 m 171.07 425.88 l S 179.60 425.88 m 188.13 425.88 l S 188.13 425.88 m 188.13 425.88 l S 188.13 425.88 m 179.66 425.88 l S 179.66 425.88 m 179.66 425.88 l S 188.13 425.88 m 196.59 425.88 l S 196.59 425.88 m 196.59 425.88 l S 196.59 425.88 m 188.26 425.88 l S 188.26 425.88 m 188.26 425.88 l S 196.59 425.88 m 204.92 425.88 l S 204.92 425.88 m 204.92 425.88 l S 204.92 425.88 m 196.86 425.88 l S 196.86 425.88 m 196.86 425.88 l S 204.92 425.88 m 212.98 425.88 l S 212.98 425.88 m 212.98 425.88 l S 212.98 425.88 m 205.46 425.88 l S 205.46 425.88 m 205.46 425.88 l S 212.98 425.88 m 220.51 425.88 l S 220.51 425.88 m 220.51 425.88 l S 220.51 425.88 m 214.06 425.88 l S 214.06 425.88 m 214.06 425.88 l S 220.51 425.88 m 226.96 425.88 l S 226.96 425.88 m 226.96 425.88 l S 226.96 425.88 m 222.66 425.88 l S 222.66 425.88 m 222.66 425.88 l S 226.96 425.88 m 231.26 425.88 l S 231.26 425.88 m 231.26 425.88 l S 196.86 474.08 m 248.45 474.08 l S 248.45 474.08 m 248.45 426.87 l S 248.45 426.87 m 239.85 426.87 l S 239.85 426.87 m 239.85 425.88 l S 248.45 426.87 m 257.05 426.87 l S 257.05 426.87 m 257.05 425.88 l S 257.05 425.88 m 248.45 425.88 l S 248.45 425.88 m 248.45 425.88 l S 257.05 425.88 m 265.65 425.88 l S 265.65 425.88 m 265.65 425.88 l S 265.65 425.88 m 257.05 425.88 l S 257.05 425.88 m 257.05 425.88 l S 265.65 425.88 m 274.25 425.88 l S 274.25 425.88 m 274.25 425.88 l S 274.25 425.88 m 265.65 425.88 l S 265.65 425.88 m 265.65 425.88 l S 274.25 425.88 m 282.84 425.88 l S 282.84 425.88 m 282.84 425.88 l S 282.84 425.88 m 274.25 425.88 l S 274.25 425.88 m 274.25 425.88 l S 282.84 425.88 m 291.44 425.88 l S 291.44 425.88 m 291.44 425.88 l S 291.44 425.88 m 282.85 425.88 l S 282.85 425.88 m 282.85 425.88 l S 291.44 425.88 m 300.04 425.88 l S 300.04 425.88 m 300.04 425.88 l S 300.04 425.88 m 291.45 425.88 l S 291.45 425.88 m 291.45 425.88 l S 300.04 425.88 m 308.63 425.88 l S 308.63 425.88 m 308.63 425.88 l S 308.63 425.88 m 300.04 425.88 l S 300.04 425.88 m 300.04 425.88 l S 308.63 425.88 m 317.21 425.88 l S 317.21 425.88 m 317.21 425.88 l S 317.21 425.88 m 308.64 425.88 l S 308.64 425.88 m 308.64 425.88 l S 317.21 425.88 m 325.77 425.88 l S 325.77 425.88 m 325.77 425.88 l S 325.77 425.88 m 317.24 425.88 l S 317.24 425.88 m 317.24 425.88 l S 325.77 425.88 m 334.30 425.88 l S 334.30 425.88 m 334.30 425.88 l S 334.30 425.88 m 325.84 425.88 l S 325.84 425.88 m 325.84 425.88 l S 334.30 425.88 m 342.77 425.88 l S 342.77 425.88 m 342.77 425.88 l S 342.77 425.88 m 334.44 425.88 l S 334.44 425.88 m 334.44 425.88 l S 342.77 425.88 m 351.10 425.88 l S 351.10 425.88 m 351.10 425.88 l S 351.10 425.88 m 343.04 425.88 l S 343.04 425.88 m 343.04 425.88 l S 351.10 425.88 m 359.16 425.88 l S 359.16 425.88 m 359.16 425.88 l S 359.16 425.88 m 351.63 425.88 l S 351.63 425.88 m 351.63 425.88 l S 359.16 425.88 m 366.68 425.88 l S 366.68 425.88 m 366.68 425.88 l S 366.68 425.88 m 360.23 425.88 l S 360.23 425.88 m 360.23 425.88 l S 366.68 425.88 m 373.13 425.88 l S 373.13 425.88 m 373.13 425.88 l S 373.13 425.88 m 368.83 425.88 l S 368.83 425.88 m 368.83 425.88 l S 373.13 425.88 m 377.43 425.88 l S 377.43 425.88 m 377.43 425.88 l S Q q Q q Q q Q q Q q 0.00 19.81 49.49 378.55 re W n /sRGB CS 0.000 0.000 0.000 SCN 0.75 w [] 0 d 1 J 1 j 10.00 M 0.00 308.72 m 0.00 233.99 l S 0.00 233.99 m 1.30 233.99 l S 1.30 233.99 m 1.30 174.22 l S 1.30 174.22 m 48.50 174.22 l S 48.50 174.22 m 48.50 164.26 l S 48.50 164.26 m 49.49 164.26 l S 49.49 164.26 m 49.49 154.29 l S 49.49 154.29 m 49.49 154.29 l S 49.49 154.29 m 49.49 144.33 l S 49.49 144.33 m 49.49 144.33 l S 49.49 144.33 m 49.49 134.37 l S 49.49 134.37 m 49.49 134.37 l S 49.49 134.37 m 49.49 124.41 l S 49.49 124.41 m 49.49 124.41 l S 49.49 124.41 m 49.49 114.46 l S 49.49 114.46 m 49.49 114.46 l S 49.49 114.46 m 49.49 104.50 l S 49.49 104.50 m 49.49 104.50 l S 49.49 104.50 m 49.49 94.56 l S 49.49 94.56 m 49.49 94.56 l S 49.49 94.56 m 49.49 84.64 l S 49.49 84.64 m 49.49 84.64 l S 49.49 84.64 m 49.49 74.75 l S 49.49 74.75 m 49.49 74.75 l S 49.49 74.75 m 49.49 64.95 l S 49.49 64.95 m 49.49 64.95 l S 49.49 64.95 m 49.49 55.30 l S 49.49 55.30 m 49.49 55.30 l S 49.49 55.30 m 49.49 45.96 l S 49.49 45.96 m 49.49 45.96 l S 49.49 45.96 m 49.49 37.24 l S 49.49 37.24 m 49.49 37.24 l S 49.49 37.24 m 49.49 29.77 l S 49.49 29.77 m 49.49 29.77 l S 49.49 29.77 m 49.49 24.79 l S 49.49 24.79 m 49.49 24.79 l S 49.49 29.77 m 49.49 34.75 l S 49.49 34.75 m 49.49 34.75 l S 49.49 37.24 m 49.49 44.71 l S 49.49 44.71 m 49.49 44.71 l S 49.49 45.96 m 49.49 54.67 l S 49.49 54.67 m 49.49 54.67 l S 49.49 55.30 m 49.49 64.64 l S 49.49 64.64 m 49.49 64.64 l S 49.49 64.95 m 49.49 74.60 l S 49.49 74.60 m 49.49 74.60 l S 49.49 74.75 m 49.49 84.56 l S 49.49 84.56 m 49.49 84.56 l S 49.49 84.64 m 49.49 94.52 l S 49.49 94.52 m 49.49 94.52 l S 49.49 94.56 m 49.49 104.48 l S 49.49 104.48 m 49.49 104.48 l S 49.49 104.50 m 49.49 114.45 l S 49.49 114.45 m 49.49 114.45 l S 49.49 114.46 m 49.49 124.41 l S 49.49 124.41 m 49.49 124.41 l S 49.49 124.41 m 49.49 134.37 l S 49.49 134.37 m 49.49 134.37 l S 49.49 134.37 m 49.49 144.33 l S 49.49 144.33 m 49.49 144.33 l S 49.49 144.33 m 49.49 154.29 l S 49.49 154.29 m 49.49 154.29 l S 49.49 154.29 m 49.49 164.26 l S 49.49 164.26 m 49.49 164.26 l S 49.49 164.26 m 49.49 174.22 l S 49.49 174.22 m 49.49 174.22 l S 48.50 174.22 m 48.50 184.18 l S 48.50 184.18 m 49.49 184.18 l S 1.30 233.99 m 1.30 293.77 l S 1.30 293.77 m 34.64 293.77 l S 34.64 293.77 m 34.64 283.81 l S 34.64 283.81 m 49.49 283.81 l S 49.49 283.81 m 49.49 273.86 l S 49.49 273.86 m 49.49 273.86 l S 49.49 273.86 m 49.49 263.91 l S 49.49 263.91 m 49.49 263.91 l S 49.49 263.91 m 49.49 253.99 l S 49.49 253.99 m 49.49 253.99 l S 49.49 253.99 m 49.49 244.11 l S 49.49 244.11 m 49.49 244.11 l S 49.49 244.11 m 49.49 234.30 l S 49.49 234.30 m 49.49 234.30 l S 49.49 234.30 m 49.49 224.65 l S 49.49 224.65 m 49.49 224.65 l S 49.49 224.65 m 49.49 215.31 l S 49.49 215.31 m 49.49 215.31 l S 49.49 215.31 m 49.49 206.59 l S 49.49 206.59 m 49.49 206.59 l S 49.49 206.59 m 49.49 199.12 l S 49.49 199.12 m 49.49 199.12 l S 49.49 199.12 m 49.49 194.14 l S 49.49 194.14 m 49.49 194.14 l S 49.49 199.12 m 49.49 204.10 l S 49.49 204.10 m 49.49 204.10 l S 49.49 206.59 m 49.49 214.07 l S 49.49 214.07 m 49.49 214.07 l S 49.49 215.31 m 49.49 224.03 l S 49.49 224.03 m 49.49 224.03 l S 49.49 224.65 m 49.49 233.99 l S 49.49 233.99 m 49.49 233.99 l S 49.49 234.30 m 49.49 243.95 l S 49.49 243.95 m 49.49 243.95 l S 49.49 244.11 m 49.49 253.91 l S 49.49 253.91 m 49.49 253.91 l S 49.49 253.99 m 49.49 263.88 l S 49.49 263.88 m 49.49 263.88 l S 49.49 263.91 m 49.49 273.84 l S 49.49 273.84 m 49.49 273.84 l S 49.49 273.86 m 49.49 283.80 l S 49.49 283.80 m 49.49 283.80 l S 49.49 283.81 m 49.49 293.76 l S 49.49 293.76 m 49.49 293.76 l S 34.64 293.77 m 34.64 303.72 l S 34.64 303.72 m 49.49 303.72 l S 0.00 308.72 m 0.00 383.46 l S 0.00 383.46 m 49.49 383.46 l S 49.49 383.46 m 49.49 373.53 l S 49.49 373.53 m 49.49 373.53 l S 49.49 373.53 m 49.49 363.65 l S 49.49 363.65 m 49.49 363.65 l S 49.49 363.65 m 49.49 353.84 l S 49.49 353.84 m 49.49 353.84 l S 49.49 353.84 m 49.49 344.19 l S 49.49 344.19 m 49.49 344.19 l S 49.49 344.19 m 49.49 334.85 l S 49.49 334.85 m 49.49 334.85 l S 49.49 334.85 m 49.49 326.14 l S 49.49 326.14 m 49.49 326.14 l S 49.49 326.14 m 49.49 318.67 l S 49.49 318.67 m 49.49 318.67 l S 49.49 318.67 m 49.49 313.68 l S 49.49 313.68 m 49.49 313.68 l S 49.49 318.67 m 49.49 323.65 l S 49.49 323.65 m 49.49 323.65 l S 49.49 326.14 m 49.49 333.61 l S 49.49 333.61 m 49.49 333.61 l S 49.49 334.85 m 49.49 343.57 l S 49.49 343.57 m 49.49 343.57 l S 49.49 344.19 m 49.49 353.53 l S 49.49 353.53 m 49.49 353.53 l S 49.49 353.84 m 49.49 363.49 l S 49.49 363.49 m 49.49 363.49 l S 49.49 363.65 m 49.49 373.46 l S 49.49 373.46 m 49.49 373.46 l S 49.49 373.53 m 49.49 383.42 l S 49.49 383.42 m 49.49 383.42 l S 49.49 383.46 m 49.49 393.38 l S 49.49 393.38 m 49.49 393.38 l S Q q Q q Q q /sRGB cs 0.192 0.212 0.584 scn 55.00 19.79 8.60 9.96 re f 55.00 29.75 8.60 9.96 re f 55.00 39.71 8.60 9.96 re f 55.00 49.67 8.60 9.96 re f 55.00 59.64 8.60 9.96 re f 55.00 69.60 8.60 9.96 re f 55.00 79.56 8.60 9.96 re f 55.00 89.53 8.60 9.96 re f 55.00 99.49 8.60 9.96 re f 55.00 109.45 8.60 9.96 re f 55.00 119.42 8.60 9.96 re f 55.00 129.38 8.60 9.96 re f 55.00 139.34 8.60 9.96 re f 55.00 149.31 8.60 9.96 re f 55.00 159.27 8.60 9.96 re f 55.00 169.23 8.60 9.96 re f 55.00 179.20 8.60 9.96 re f 55.00 189.16 8.60 9.96 re f 55.00 199.12 8.60 9.96 re f 55.00 209.08 8.60 9.96 re f 55.00 219.05 8.60 9.96 re f 55.00 229.01 8.60 9.96 re f 55.00 238.97 8.60 9.96 re f 55.00 248.94 8.60 9.96 re f 55.00 258.90 8.60 9.96 re f 55.00 268.86 8.60 9.96 re f 55.00 278.83 8.60 9.96 re f 55.00 288.79 8.60 9.96 re f 55.00 298.75 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 55.00 308.72 8.60 9.96 re f 55.00 318.68 8.60 9.96 re f 55.00 328.64 8.60 9.96 re f 55.00 338.61 8.60 9.96 re f 55.00 348.57 8.60 9.96 re f 55.00 358.53 8.60 9.96 re f 55.00 368.49 8.60 9.96 re f 55.00 378.46 8.60 9.96 re f 55.00 388.42 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 63.60 19.79 8.60 9.96 re f 63.60 29.75 8.60 9.96 re f 63.60 39.71 8.60 9.96 re f 63.60 49.67 8.60 9.96 re f 63.60 59.64 8.60 9.96 re f 63.60 69.60 8.60 9.96 re f 63.60 79.56 8.60 9.96 re f 63.60 89.53 8.60 9.96 re f 63.60 99.49 8.60 9.96 re f 63.60 109.45 8.60 9.96 re f 63.60 119.42 8.60 9.96 re f 63.60 129.38 8.60 9.96 re f 63.60 139.34 8.60 9.96 re f 63.60 149.31 8.60 9.96 re f 63.60 159.27 8.60 9.96 re f 63.60 169.23 8.60 9.96 re f 63.60 179.20 8.60 9.96 re f 63.60 189.16 8.60 9.96 re f 63.60 199.12 8.60 9.96 re f 63.60 209.08 8.60 9.96 re f 63.60 219.05 8.60 9.96 re f 63.60 229.01 8.60 9.96 re f 63.60 238.97 8.60 9.96 re f 63.60 248.94 8.60 9.96 re f 63.60 258.90 8.60 9.96 re f 63.60 268.86 8.60 9.96 re f 63.60 278.83 8.60 9.96 re f 63.60 288.79 8.60 9.96 re f 63.60 298.75 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 63.60 308.72 8.60 9.96 re f 63.60 318.68 8.60 9.96 re f 63.60 328.64 8.60 9.96 re f 63.60 338.61 8.60 9.96 re f 63.60 348.57 8.60 9.96 re f 63.60 358.53 8.60 9.96 re f 63.60 368.49 8.60 9.96 re f 63.60 378.46 8.60 9.96 re f 63.60 388.42 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 72.20 19.79 8.60 9.96 re f 72.20 29.75 8.60 9.96 re f 72.20 39.71 8.60 9.96 re f 72.20 49.67 8.60 9.96 re f 72.20 59.64 8.60 9.96 re f 72.20 69.60 8.60 9.96 re f 72.20 79.56 8.60 9.96 re f 72.20 89.53 8.60 9.96 re f 72.20 99.49 8.60 9.96 re f 72.20 109.45 8.60 9.96 re f 72.20 119.42 8.60 9.96 re f 72.20 129.38 8.60 9.96 re f 72.20 139.34 8.60 9.96 re f 72.20 149.31 8.60 9.96 re f 72.20 159.27 8.60 9.96 re f 72.20 169.23 8.60 9.96 re f 72.20 179.20 8.60 9.96 re f 72.20 189.16 8.60 9.96 re f 72.20 199.12 8.60 9.96 re f 72.20 209.08 8.60 9.96 re f 72.20 219.05 8.60 9.96 re f 72.20 229.01 8.60 9.96 re f 72.20 238.97 8.60 9.96 re f 72.20 248.94 8.60 9.96 re f 72.20 258.90 8.60 9.96 re f 72.20 268.86 8.60 9.96 re f 72.20 278.83 8.60 9.96 re f 72.20 288.79 8.60 9.96 re f 72.20 298.75 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 72.20 308.72 8.60 9.96 re f 72.20 318.68 8.60 9.96 re f 72.20 328.64 8.60 9.96 re f 72.20 338.61 8.60 9.96 re f 72.20 348.57 8.60 9.96 re f 72.20 358.53 8.60 9.96 re f 72.20 368.49 8.60 9.96 re f 72.20 378.46 8.60 9.96 re f 72.20 388.42 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 80.79 19.79 8.60 9.96 re f 80.79 29.75 8.60 9.96 re f 80.79 39.71 8.60 9.96 re f 80.79 49.67 8.60 9.96 re f 80.79 59.64 8.60 9.96 re f 80.79 69.60 8.60 9.96 re f 80.79 79.56 8.60 9.96 re f 80.79 89.53 8.60 9.96 re f 80.79 99.49 8.60 9.96 re f 80.79 109.45 8.60 9.96 re f 80.79 119.42 8.60 9.96 re f 80.79 129.38 8.60 9.96 re f 80.79 139.34 8.60 9.96 re f 80.79 149.31 8.60 9.96 re f 80.79 159.27 8.60 9.96 re f 80.79 169.23 8.60 9.96 re f 80.79 179.20 8.60 9.96 re f 80.79 189.16 8.60 9.96 re f 80.79 199.12 8.60 9.96 re f 80.79 209.08 8.60 9.96 re f 80.79 219.05 8.60 9.96 re f 80.79 229.01 8.60 9.96 re f 80.79 238.97 8.60 9.96 re f 80.79 248.94 8.60 9.96 re f 80.79 258.90 8.60 9.96 re f 80.79 268.86 8.60 9.96 re f 80.79 278.83 8.60 9.96 re f 80.79 288.79 8.60 9.96 re f 80.79 298.75 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 80.79 308.72 8.60 9.96 re f 80.79 318.68 8.60 9.96 re f 80.79 328.64 8.60 9.96 re f 80.79 338.61 8.60 9.96 re f 80.79 348.57 8.60 9.96 re f 80.79 358.53 8.60 9.96 re f 80.79 368.49 8.60 9.96 re f 80.79 378.46 8.60 9.96 re f 80.79 388.42 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 89.39 19.79 8.60 9.96 re f 89.39 29.75 8.60 9.96 re f 89.39 39.71 8.60 9.96 re f 89.39 49.67 8.60 9.96 re f 89.39 59.64 8.60 9.96 re f 89.39 69.60 8.60 9.96 re f 89.39 79.56 8.60 9.96 re f 89.39 89.53 8.60 9.96 re f 89.39 99.49 8.60 9.96 re f 89.39 109.45 8.60 9.96 re f 89.39 119.42 8.60 9.96 re f 89.39 129.38 8.60 9.96 re f 89.39 139.34 8.60 9.96 re f 89.39 149.31 8.60 9.96 re f 89.39 159.27 8.60 9.96 re f 89.39 169.23 8.60 9.96 re f 89.39 179.20 8.60 9.96 re f 89.39 189.16 8.60 9.96 re f 89.39 199.12 8.60 9.96 re f 89.39 209.08 8.60 9.96 re f 89.39 219.05 8.60 9.96 re f 89.39 229.01 8.60 9.96 re f 89.39 238.97 8.60 9.96 re f 89.39 248.94 8.60 9.96 re f 89.39 258.90 8.60 9.96 re f 89.39 268.86 8.60 9.96 re f 89.39 278.83 8.60 9.96 re f 89.39 288.79 8.60 9.96 re f 89.39 298.75 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 89.39 308.72 8.60 9.96 re f 89.39 318.68 8.60 9.96 re f 89.39 328.64 8.60 9.96 re f 89.39 338.61 8.60 9.96 re f 89.39 348.57 8.60 9.96 re f 89.39 358.53 8.60 9.96 re f 89.39 368.49 8.60 9.96 re f 89.39 378.46 8.60 9.96 re f 89.39 388.42 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 97.99 19.79 8.60 9.96 re f 97.99 29.75 8.60 9.96 re f 97.99 39.71 8.60 9.96 re f 97.99 49.67 8.60 9.96 re f 97.99 59.64 8.60 9.96 re f 97.99 69.60 8.60 9.96 re f 97.99 79.56 8.60 9.96 re f 97.99 89.53 8.60 9.96 re f 97.99 99.49 8.60 9.96 re f 97.99 109.45 8.60 9.96 re f 97.99 119.42 8.60 9.96 re f 97.99 129.38 8.60 9.96 re f 97.99 139.34 8.60 9.96 re f 97.99 149.31 8.60 9.96 re f 97.99 159.27 8.60 9.96 re f 97.99 169.23 8.60 9.96 re f 97.99 179.20 8.60 9.96 re f 97.99 189.16 8.60 9.96 re f 97.99 199.12 8.60 9.96 re f 97.99 209.08 8.60 9.96 re f 97.99 219.05 8.60 9.96 re f 97.99 229.01 8.60 9.96 re f 97.99 238.97 8.60 9.96 re f 97.99 248.94 8.60 9.96 re f 97.99 258.90 8.60 9.96 re f 97.99 268.86 8.60 9.96 re f 97.99 278.83 8.60 9.96 re f 97.99 288.79 8.60 9.96 re f 97.99 298.75 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 97.99 308.72 8.60 9.96 re f 97.99 318.68 8.60 9.96 re f 97.99 328.64 8.60 9.96 re f 97.99 338.61 8.60 9.96 re f 97.99 348.57 8.60 9.96 re f 97.99 358.53 8.60 9.96 re f 97.99 368.49 8.60 9.96 re f 97.99 378.46 8.60 9.96 re f 97.99 388.42 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 106.59 19.79 8.60 9.96 re f 106.59 29.75 8.60 9.96 re f 106.59 39.71 8.60 9.96 re f 106.59 49.67 8.60 9.96 re f 106.59 59.64 8.60 9.96 re f 106.59 69.60 8.60 9.96 re f 106.59 79.56 8.60 9.96 re f 106.59 89.53 8.60 9.96 re f 106.59 99.49 8.60 9.96 re f 106.59 109.45 8.60 9.96 re f 106.59 119.42 8.60 9.96 re f 106.59 129.38 8.60 9.96 re f 106.59 139.34 8.60 9.96 re f 106.59 149.31 8.60 9.96 re f 106.59 159.27 8.60 9.96 re f 106.59 169.23 8.60 9.96 re f 106.59 179.20 8.60 9.96 re f 106.59 189.16 8.60 9.96 re f 106.59 199.12 8.60 9.96 re f 106.59 209.08 8.60 9.96 re f 106.59 219.05 8.60 9.96 re f 106.59 229.01 8.60 9.96 re f 106.59 238.97 8.60 9.96 re f 106.59 248.94 8.60 9.96 re f 106.59 258.90 8.60 9.96 re f 106.59 268.86 8.60 9.96 re f 106.59 278.83 8.60 9.96 re f 106.59 288.79 8.60 9.96 re f 106.59 298.75 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 106.59 308.72 8.60 9.96 re f 106.59 318.68 8.60 9.96 re f 106.59 328.64 8.60 9.96 re f 106.59 338.61 8.60 9.96 re f 106.59 348.57 8.60 9.96 re f 106.59 358.53 8.60 9.96 re f 106.59 368.49 8.60 9.96 re f 106.59 378.46 8.60 9.96 re f 106.59 388.42 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 115.18 19.79 8.60 9.96 re f 115.18 29.75 8.60 9.96 re f 115.18 39.71 8.60 9.96 re f 115.18 49.67 8.60 9.96 re f 115.18 59.64 8.60 9.96 re f 115.18 69.60 8.60 9.96 re f 115.18 79.56 8.60 9.96 re f 115.18 89.53 8.60 9.96 re f 115.18 99.49 8.60 9.96 re f 115.18 109.45 8.60 9.96 re f 115.18 119.42 8.60 9.96 re f 115.18 129.38 8.60 9.96 re f 115.18 139.34 8.60 9.96 re f 115.18 149.31 8.60 9.96 re f 115.18 159.27 8.60 9.96 re f 115.18 169.23 8.60 9.96 re f 115.18 179.20 8.60 9.96 re f 115.18 189.16 8.60 9.96 re f 115.18 199.12 8.60 9.96 re f 115.18 209.08 8.60 9.96 re f 115.18 219.05 8.60 9.96 re f 115.18 229.01 8.60 9.96 re f 115.18 238.97 8.60 9.96 re f 115.18 248.94 8.60 9.96 re f 115.18 258.90 8.60 9.96 re f 115.18 268.86 8.60 9.96 re f 115.18 278.83 8.60 9.96 re f 115.18 288.79 8.60 9.96 re f 115.18 298.75 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 115.18 308.72 8.60 9.96 re f 115.18 318.68 8.60 9.96 re f 115.18 328.64 8.60 9.96 re f 115.18 338.61 8.60 9.96 re f 115.18 348.57 8.60 9.96 re f 115.18 358.53 8.60 9.96 re f 115.18 368.49 8.60 9.96 re f 115.18 378.46 8.60 9.96 re f 115.18 388.42 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 123.78 19.79 8.60 9.96 re f 123.78 29.75 8.60 9.96 re f 123.78 39.71 8.60 9.96 re f 123.78 49.67 8.60 9.96 re f 123.78 59.64 8.60 9.96 re f 123.78 69.60 8.60 9.96 re f 123.78 79.56 8.60 9.96 re f 123.78 89.53 8.60 9.96 re f 123.78 99.49 8.60 9.96 re f 123.78 109.45 8.60 9.96 re f 123.78 119.42 8.60 9.96 re f 123.78 129.38 8.60 9.96 re f 123.78 139.34 8.60 9.96 re f 123.78 149.31 8.60 9.96 re f 123.78 159.27 8.60 9.96 re f 123.78 169.23 8.60 9.96 re f 123.78 179.20 8.60 9.96 re f 123.78 189.16 8.60 9.96 re f 123.78 199.12 8.60 9.96 re f 123.78 209.08 8.60 9.96 re f 123.78 219.05 8.60 9.96 re f 123.78 229.01 8.60 9.96 re f 123.78 238.97 8.60 9.96 re f 123.78 248.94 8.60 9.96 re f 123.78 258.90 8.60 9.96 re f 123.78 268.86 8.60 9.96 re f 123.78 278.83 8.60 9.96 re f 123.78 288.79 8.60 9.96 re f 123.78 298.75 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 123.78 308.72 8.60 9.96 re f 123.78 318.68 8.60 9.96 re f 123.78 328.64 8.60 9.96 re f 123.78 338.61 8.60 9.96 re f 123.78 348.57 8.60 9.96 re f 123.78 358.53 8.60 9.96 re f 123.78 368.49 8.60 9.96 re f 123.78 378.46 8.60 9.96 re f 123.78 388.42 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 132.38 19.79 8.60 9.96 re f 132.38 29.75 8.60 9.96 re f 132.38 39.71 8.60 9.96 re f 132.38 49.67 8.60 9.96 re f 132.38 59.64 8.60 9.96 re f 132.38 69.60 8.60 9.96 re f 132.38 79.56 8.60 9.96 re f 132.38 89.53 8.60 9.96 re f 132.38 99.49 8.60 9.96 re f 132.38 109.45 8.60 9.96 re f 132.38 119.42 8.60 9.96 re f 132.38 129.38 8.60 9.96 re f 132.38 139.34 8.60 9.96 re f 132.38 149.31 8.60 9.96 re f 132.38 159.27 8.60 9.96 re f 132.38 169.23 8.60 9.96 re f /sRGB cs 0.710 0.871 0.922 scn 132.38 179.20 8.60 9.96 re f /sRGB cs 0.992 0.682 0.380 scn 132.38 189.16 8.60 9.96 re f 132.38 199.12 8.60 9.96 re f 132.38 209.08 8.60 9.96 re f 132.38 219.05 8.60 9.96 re f 132.38 229.01 8.60 9.96 re f 132.38 238.97 8.60 9.96 re f 132.38 248.94 8.60 9.96 re f 132.38 258.90 8.60 9.96 re f 132.38 268.86 8.60 9.96 re f 132.38 278.83 8.60 9.96 re f 132.38 288.79 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 132.38 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 132.38 308.72 8.60 9.96 re f 132.38 318.68 8.60 9.96 re f 132.38 328.64 8.60 9.96 re f 132.38 338.61 8.60 9.96 re f 132.38 348.57 8.60 9.96 re f 132.38 358.53 8.60 9.96 re f 132.38 368.49 8.60 9.96 re f 132.38 378.46 8.60 9.96 re f 132.38 388.42 8.60 9.96 re f 140.98 19.79 8.60 9.96 re f 140.98 29.75 8.60 9.96 re f 140.98 39.71 8.60 9.96 re f 140.98 49.67 8.60 9.96 re f 140.98 59.64 8.60 9.96 re f 140.98 69.60 8.60 9.96 re f 140.98 79.56 8.60 9.96 re f 140.98 89.53 8.60 9.96 re f 140.98 99.49 8.60 9.96 re f 140.98 109.45 8.60 9.96 re f 140.98 119.42 8.60 9.96 re f 140.98 129.38 8.60 9.96 re f 140.98 139.34 8.60 9.96 re f 140.98 149.31 8.60 9.96 re f 140.98 159.27 8.60 9.96 re f 140.98 169.23 8.60 9.96 re f /sRGB cs 0.208 0.259 0.608 scn 140.98 179.20 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 140.98 189.16 8.60 9.96 re f 140.98 199.12 8.60 9.96 re f 140.98 209.08 8.60 9.96 re f 140.98 219.05 8.60 9.96 re f 140.98 229.01 8.60 9.96 re f 140.98 238.97 8.60 9.96 re f 140.98 248.94 8.60 9.96 re f 140.98 258.90 8.60 9.96 re f 140.98 268.86 8.60 9.96 re f 140.98 278.83 8.60 9.96 re f 140.98 288.79 8.60 9.96 re f /sRGB cs 0.992 0.682 0.380 scn 140.98 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 140.98 308.72 8.60 9.96 re f 140.98 318.68 8.60 9.96 re f 140.98 328.64 8.60 9.96 re f 140.98 338.61 8.60 9.96 re f 140.98 348.57 8.60 9.96 re f 140.98 358.53 8.60 9.96 re f 140.98 368.49 8.60 9.96 re f 140.98 378.46 8.60 9.96 re f 140.98 388.42 8.60 9.96 re f 149.57 19.79 8.60 9.96 re f 149.57 29.75 8.60 9.96 re f 149.57 39.71 8.60 9.96 re f 149.57 49.67 8.60 9.96 re f 149.57 59.64 8.60 9.96 re f 149.57 69.60 8.60 9.96 re f 149.57 79.56 8.60 9.96 re f 149.57 89.53 8.60 9.96 re f 149.57 99.49 8.60 9.96 re f 149.57 109.45 8.60 9.96 re f 149.57 119.42 8.60 9.96 re f 149.57 129.38 8.60 9.96 re f 149.57 139.34 8.60 9.96 re f 149.57 149.31 8.60 9.96 re f 149.57 159.27 8.60 9.96 re f 149.57 169.23 8.60 9.96 re f /sRGB cs 0.208 0.259 0.608 scn 149.57 179.20 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 149.57 189.16 8.60 9.96 re f 149.57 199.12 8.60 9.96 re f 149.57 209.08 8.60 9.96 re f 149.57 219.05 8.60 9.96 re f 149.57 229.01 8.60 9.96 re f 149.57 238.97 8.60 9.96 re f 149.57 248.94 8.60 9.96 re f 149.57 258.90 8.60 9.96 re f 149.57 268.86 8.60 9.96 re f 149.57 278.83 8.60 9.96 re f 149.57 288.79 8.60 9.96 re f /sRGB cs 0.992 0.682 0.380 scn 149.57 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 149.57 308.72 8.60 9.96 re f 149.57 318.68 8.60 9.96 re f 149.57 328.64 8.60 9.96 re f 149.57 338.61 8.60 9.96 re f 149.57 348.57 8.60 9.96 re f 149.57 358.53 8.60 9.96 re f 149.57 368.49 8.60 9.96 re f 149.57 378.46 8.60 9.96 re f 149.57 388.42 8.60 9.96 re f 158.17 19.79 8.60 9.96 re f 158.17 29.75 8.60 9.96 re f 158.17 39.71 8.60 9.96 re f 158.17 49.67 8.60 9.96 re f 158.17 59.64 8.60 9.96 re f 158.17 69.60 8.60 9.96 re f 158.17 79.56 8.60 9.96 re f 158.17 89.53 8.60 9.96 re f 158.17 99.49 8.60 9.96 re f 158.17 109.45 8.60 9.96 re f 158.17 119.42 8.60 9.96 re f 158.17 129.38 8.60 9.96 re f 158.17 139.34 8.60 9.96 re f 158.17 149.31 8.60 9.96 re f 158.17 159.27 8.60 9.96 re f 158.17 169.23 8.60 9.96 re f /sRGB cs 0.208 0.259 0.608 scn 158.17 179.20 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 158.17 189.16 8.60 9.96 re f 158.17 199.12 8.60 9.96 re f 158.17 209.08 8.60 9.96 re f 158.17 219.05 8.60 9.96 re f 158.17 229.01 8.60 9.96 re f 158.17 238.97 8.60 9.96 re f 158.17 248.94 8.60 9.96 re f 158.17 258.90 8.60 9.96 re f 158.17 268.86 8.60 9.96 re f 158.17 278.83 8.60 9.96 re f 158.17 288.79 8.60 9.96 re f /sRGB cs 0.992 0.682 0.380 scn 158.17 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 158.17 308.72 8.60 9.96 re f 158.17 318.68 8.60 9.96 re f 158.17 328.64 8.60 9.96 re f 158.17 338.61 8.60 9.96 re f 158.17 348.57 8.60 9.96 re f 158.17 358.53 8.60 9.96 re f 158.17 368.49 8.60 9.96 re f 158.17 378.46 8.60 9.96 re f 158.17 388.42 8.60 9.96 re f 166.77 19.79 8.60 9.96 re f 166.77 29.75 8.60 9.96 re f 166.77 39.71 8.60 9.96 re f 166.77 49.67 8.60 9.96 re f 166.77 59.64 8.60 9.96 re f 166.77 69.60 8.60 9.96 re f 166.77 79.56 8.60 9.96 re f 166.77 89.53 8.60 9.96 re f 166.77 99.49 8.60 9.96 re f 166.77 109.45 8.60 9.96 re f 166.77 119.42 8.60 9.96 re f 166.77 129.38 8.60 9.96 re f 166.77 139.34 8.60 9.96 re f 166.77 149.31 8.60 9.96 re f 166.77 159.27 8.60 9.96 re f 166.77 169.23 8.60 9.96 re f /sRGB cs 0.208 0.259 0.608 scn 166.77 179.20 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 166.77 189.16 8.60 9.96 re f 166.77 199.12 8.60 9.96 re f 166.77 209.08 8.60 9.96 re f 166.77 219.05 8.60 9.96 re f 166.77 229.01 8.60 9.96 re f 166.77 238.97 8.60 9.96 re f 166.77 248.94 8.60 9.96 re f 166.77 258.90 8.60 9.96 re f 166.77 268.86 8.60 9.96 re f 166.77 278.83 8.60 9.96 re f 166.77 288.79 8.60 9.96 re f /sRGB cs 0.992 0.682 0.380 scn 166.77 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 166.77 308.72 8.60 9.96 re f 166.77 318.68 8.60 9.96 re f 166.77 328.64 8.60 9.96 re f 166.77 338.61 8.60 9.96 re f 166.77 348.57 8.60 9.96 re f 166.77 358.53 8.60 9.96 re f 166.77 368.49 8.60 9.96 re f 166.77 378.46 8.60 9.96 re f 166.77 388.42 8.60 9.96 re f 175.37 19.79 8.60 9.96 re f 175.37 29.75 8.60 9.96 re f 175.37 39.71 8.60 9.96 re f 175.37 49.67 8.60 9.96 re f 175.37 59.64 8.60 9.96 re f 175.37 69.60 8.60 9.96 re f 175.37 79.56 8.60 9.96 re f 175.37 89.53 8.60 9.96 re f 175.37 99.49 8.60 9.96 re f 175.37 109.45 8.60 9.96 re f 175.37 119.42 8.60 9.96 re f 175.37 129.38 8.60 9.96 re f 175.37 139.34 8.60 9.96 re f 175.37 149.31 8.60 9.96 re f 175.37 159.27 8.60 9.96 re f 175.37 169.23 8.60 9.96 re f /sRGB cs 0.208 0.259 0.608 scn 175.37 179.20 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 175.37 189.16 8.60 9.96 re f 175.37 199.12 8.60 9.96 re f 175.37 209.08 8.60 9.96 re f 175.37 219.05 8.60 9.96 re f 175.37 229.01 8.60 9.96 re f 175.37 238.97 8.60 9.96 re f 175.37 248.94 8.60 9.96 re f 175.37 258.90 8.60 9.96 re f 175.37 268.86 8.60 9.96 re f 175.37 278.83 8.60 9.96 re f 175.37 288.79 8.60 9.96 re f /sRGB cs 0.992 0.682 0.380 scn 175.37 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 175.37 308.72 8.60 9.96 re f 175.37 318.68 8.60 9.96 re f 175.37 328.64 8.60 9.96 re f 175.37 338.61 8.60 9.96 re f 175.37 348.57 8.60 9.96 re f 175.37 358.53 8.60 9.96 re f 175.37 368.49 8.60 9.96 re f 175.37 378.46 8.60 9.96 re f 175.37 388.42 8.60 9.96 re f 183.96 19.79 8.60 9.96 re f 183.96 29.75 8.60 9.96 re f 183.96 39.71 8.60 9.96 re f 183.96 49.67 8.60 9.96 re f 183.96 59.64 8.60 9.96 re f 183.96 69.60 8.60 9.96 re f 183.96 79.56 8.60 9.96 re f 183.96 89.53 8.60 9.96 re f 183.96 99.49 8.60 9.96 re f 183.96 109.45 8.60 9.96 re f 183.96 119.42 8.60 9.96 re f 183.96 129.38 8.60 9.96 re f 183.96 139.34 8.60 9.96 re f 183.96 149.31 8.60 9.96 re f 183.96 159.27 8.60 9.96 re f 183.96 169.23 8.60 9.96 re f /sRGB cs 0.208 0.259 0.608 scn 183.96 179.20 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 183.96 189.16 8.60 9.96 re f 183.96 199.12 8.60 9.96 re f 183.96 209.08 8.60 9.96 re f 183.96 219.05 8.60 9.96 re f 183.96 229.01 8.60 9.96 re f 183.96 238.97 8.60 9.96 re f 183.96 248.94 8.60 9.96 re f 183.96 258.90 8.60 9.96 re f 183.96 268.86 8.60 9.96 re f 183.96 278.83 8.60 9.96 re f 183.96 288.79 8.60 9.96 re f /sRGB cs 0.992 0.682 0.380 scn 183.96 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 183.96 308.72 8.60 9.96 re f 183.96 318.68 8.60 9.96 re f 183.96 328.64 8.60 9.96 re f 183.96 338.61 8.60 9.96 re f 183.96 348.57 8.60 9.96 re f 183.96 358.53 8.60 9.96 re f 183.96 368.49 8.60 9.96 re f 183.96 378.46 8.60 9.96 re f 183.96 388.42 8.60 9.96 re f 192.56 19.79 8.60 9.96 re f 192.56 29.75 8.60 9.96 re f 192.56 39.71 8.60 9.96 re f 192.56 49.67 8.60 9.96 re f 192.56 59.64 8.60 9.96 re f 192.56 69.60 8.60 9.96 re f 192.56 79.56 8.60 9.96 re f 192.56 89.53 8.60 9.96 re f 192.56 99.49 8.60 9.96 re f 192.56 109.45 8.60 9.96 re f 192.56 119.42 8.60 9.96 re f 192.56 129.38 8.60 9.96 re f 192.56 139.34 8.60 9.96 re f 192.56 149.31 8.60 9.96 re f 192.56 159.27 8.60 9.96 re f 192.56 169.23 8.60 9.96 re f /sRGB cs 0.208 0.259 0.608 scn 192.56 179.20 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 192.56 189.16 8.60 9.96 re f 192.56 199.12 8.60 9.96 re f 192.56 209.08 8.60 9.96 re f 192.56 219.05 8.60 9.96 re f 192.56 229.01 8.60 9.96 re f 192.56 238.97 8.60 9.96 re f 192.56 248.94 8.60 9.96 re f 192.56 258.90 8.60 9.96 re f 192.56 268.86 8.60 9.96 re f 192.56 278.83 8.60 9.96 re f 192.56 288.79 8.60 9.96 re f /sRGB cs 0.992 0.682 0.380 scn 192.56 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 192.56 308.72 8.60 9.96 re f 192.56 318.68 8.60 9.96 re f 192.56 328.64 8.60 9.96 re f 192.56 338.61 8.60 9.96 re f 192.56 348.57 8.60 9.96 re f 192.56 358.53 8.60 9.96 re f 192.56 368.49 8.60 9.96 re f 192.56 378.46 8.60 9.96 re f 192.56 388.42 8.60 9.96 re f 201.16 19.79 8.60 9.96 re f 201.16 29.75 8.60 9.96 re f 201.16 39.71 8.60 9.96 re f 201.16 49.67 8.60 9.96 re f 201.16 59.64 8.60 9.96 re f 201.16 69.60 8.60 9.96 re f 201.16 79.56 8.60 9.96 re f 201.16 89.53 8.60 9.96 re f 201.16 99.49 8.60 9.96 re f 201.16 109.45 8.60 9.96 re f 201.16 119.42 8.60 9.96 re f 201.16 129.38 8.60 9.96 re f 201.16 139.34 8.60 9.96 re f 201.16 149.31 8.60 9.96 re f 201.16 159.27 8.60 9.96 re f 201.16 169.23 8.60 9.96 re f /sRGB cs 0.208 0.259 0.608 scn 201.16 179.20 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 201.16 189.16 8.60 9.96 re f 201.16 199.12 8.60 9.96 re f 201.16 209.08 8.60 9.96 re f 201.16 219.05 8.60 9.96 re f 201.16 229.01 8.60 9.96 re f 201.16 238.97 8.60 9.96 re f 201.16 248.94 8.60 9.96 re f 201.16 258.90 8.60 9.96 re f 201.16 268.86 8.60 9.96 re f 201.16 278.83 8.60 9.96 re f 201.16 288.79 8.60 9.96 re f /sRGB cs 0.992 0.682 0.380 scn 201.16 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 201.16 308.72 8.60 9.96 re f 201.16 318.68 8.60 9.96 re f 201.16 328.64 8.60 9.96 re f 201.16 338.61 8.60 9.96 re f 201.16 348.57 8.60 9.96 re f 201.16 358.53 8.60 9.96 re f 201.16 368.49 8.60 9.96 re f 201.16 378.46 8.60 9.96 re f 201.16 388.42 8.60 9.96 re f 209.76 19.79 8.60 9.96 re f 209.76 29.75 8.60 9.96 re f 209.76 39.71 8.60 9.96 re f 209.76 49.67 8.60 9.96 re f 209.76 59.64 8.60 9.96 re f 209.76 69.60 8.60 9.96 re f 209.76 79.56 8.60 9.96 re f 209.76 89.53 8.60 9.96 re f 209.76 99.49 8.60 9.96 re f 209.76 109.45 8.60 9.96 re f 209.76 119.42 8.60 9.96 re f 209.76 129.38 8.60 9.96 re f 209.76 139.34 8.60 9.96 re f 209.76 149.31 8.60 9.96 re f 209.76 159.27 8.60 9.96 re f 209.76 169.23 8.60 9.96 re f /sRGB cs 0.208 0.259 0.608 scn 209.76 179.20 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 209.76 189.16 8.60 9.96 re f 209.76 199.12 8.60 9.96 re f 209.76 209.08 8.60 9.96 re f 209.76 219.05 8.60 9.96 re f 209.76 229.01 8.60 9.96 re f 209.76 238.97 8.60 9.96 re f 209.76 248.94 8.60 9.96 re f 209.76 258.90 8.60 9.96 re f 209.76 268.86 8.60 9.96 re f 209.76 278.83 8.60 9.96 re f 209.76 288.79 8.60 9.96 re f /sRGB cs 0.992 0.682 0.380 scn 209.76 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 209.76 308.72 8.60 9.96 re f 209.76 318.68 8.60 9.96 re f 209.76 328.64 8.60 9.96 re f 209.76 338.61 8.60 9.96 re f 209.76 348.57 8.60 9.96 re f 209.76 358.53 8.60 9.96 re f 209.76 368.49 8.60 9.96 re f 209.76 378.46 8.60 9.96 re f 209.76 388.42 8.60 9.96 re f 218.35 19.79 8.60 9.96 re f 218.35 29.75 8.60 9.96 re f 218.35 39.71 8.60 9.96 re f 218.35 49.67 8.60 9.96 re f 218.35 59.64 8.60 9.96 re f 218.35 69.60 8.60 9.96 re f 218.35 79.56 8.60 9.96 re f 218.35 89.53 8.60 9.96 re f 218.35 99.49 8.60 9.96 re f 218.35 109.45 8.60 9.96 re f 218.35 119.42 8.60 9.96 re f 218.35 129.38 8.60 9.96 re f 218.35 139.34 8.60 9.96 re f 218.35 149.31 8.60 9.96 re f 218.35 159.27 8.60 9.96 re f 218.35 169.23 8.60 9.96 re f /sRGB cs 0.208 0.259 0.608 scn 218.35 179.20 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 218.35 189.16 8.60 9.96 re f 218.35 199.12 8.60 9.96 re f 218.35 209.08 8.60 9.96 re f 218.35 219.05 8.60 9.96 re f 218.35 229.01 8.60 9.96 re f 218.35 238.97 8.60 9.96 re f 218.35 248.94 8.60 9.96 re f 218.35 258.90 8.60 9.96 re f 218.35 268.86 8.60 9.96 re f 218.35 278.83 8.60 9.96 re f 218.35 288.79 8.60 9.96 re f /sRGB cs 0.992 0.682 0.380 scn 218.35 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 218.35 308.72 8.60 9.96 re f 218.35 318.68 8.60 9.96 re f 218.35 328.64 8.60 9.96 re f 218.35 338.61 8.60 9.96 re f 218.35 348.57 8.60 9.96 re f 218.35 358.53 8.60 9.96 re f 218.35 368.49 8.60 9.96 re f 218.35 378.46 8.60 9.96 re f 218.35 388.42 8.60 9.96 re f 226.95 19.79 8.60 9.96 re f 226.95 29.75 8.60 9.96 re f 226.95 39.71 8.60 9.96 re f 226.95 49.67 8.60 9.96 re f 226.95 59.64 8.60 9.96 re f 226.95 69.60 8.60 9.96 re f 226.95 79.56 8.60 9.96 re f 226.95 89.53 8.60 9.96 re f 226.95 99.49 8.60 9.96 re f 226.95 109.45 8.60 9.96 re f 226.95 119.42 8.60 9.96 re f 226.95 129.38 8.60 9.96 re f 226.95 139.34 8.60 9.96 re f 226.95 149.31 8.60 9.96 re f 226.95 159.27 8.60 9.96 re f 226.95 169.23 8.60 9.96 re f /sRGB cs 0.208 0.259 0.608 scn 226.95 179.20 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 226.95 189.16 8.60 9.96 re f 226.95 199.12 8.60 9.96 re f 226.95 209.08 8.60 9.96 re f 226.95 219.05 8.60 9.96 re f 226.95 229.01 8.60 9.96 re f 226.95 238.97 8.60 9.96 re f 226.95 248.94 8.60 9.96 re f 226.95 258.90 8.60 9.96 re f 226.95 268.86 8.60 9.96 re f 226.95 278.83 8.60 9.96 re f 226.95 288.79 8.60 9.96 re f /sRGB cs 0.992 0.682 0.380 scn 226.95 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 226.95 308.72 8.60 9.96 re f 226.95 318.68 8.60 9.96 re f 226.95 328.64 8.60 9.96 re f 226.95 338.61 8.60 9.96 re f 226.95 348.57 8.60 9.96 re f 226.95 358.53 8.60 9.96 re f 226.95 368.49 8.60 9.96 re f 226.95 378.46 8.60 9.96 re f 226.95 388.42 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 235.55 19.79 8.60 9.96 re f 235.55 29.75 8.60 9.96 re f 235.55 39.71 8.60 9.96 re f 235.55 49.67 8.60 9.96 re f 235.55 59.64 8.60 9.96 re f 235.55 69.60 8.60 9.96 re f 235.55 79.56 8.60 9.96 re f 235.55 89.53 8.60 9.96 re f 235.55 99.49 8.60 9.96 re f 235.55 109.45 8.60 9.96 re f 235.55 119.42 8.60 9.96 re f 235.55 129.38 8.60 9.96 re f 235.55 139.34 8.60 9.96 re f 235.55 149.31 8.60 9.96 re f 235.55 159.27 8.60 9.96 re f 235.55 169.23 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 235.55 179.20 8.60 9.96 re f /sRGB cs 0.208 0.259 0.608 scn 235.55 189.16 8.60 9.96 re f 235.55 199.12 8.60 9.96 re f 235.55 209.08 8.60 9.96 re f 235.55 219.05 8.60 9.96 re f 235.55 229.01 8.60 9.96 re f 235.55 238.97 8.60 9.96 re f 235.55 248.94 8.60 9.96 re f 235.55 258.90 8.60 9.96 re f 235.55 268.86 8.60 9.96 re f 235.55 278.83 8.60 9.96 re f 235.55 288.79 8.60 9.96 re f /sRGB cs 0.710 0.871 0.922 scn 235.55 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 235.55 308.72 8.60 9.96 re f 235.55 318.68 8.60 9.96 re f 235.55 328.64 8.60 9.96 re f 235.55 338.61 8.60 9.96 re f 235.55 348.57 8.60 9.96 re f 235.55 358.53 8.60 9.96 re f 235.55 368.49 8.60 9.96 re f 235.55 378.46 8.60 9.96 re f 235.55 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 244.15 19.79 8.60 9.96 re f 244.15 29.75 8.60 9.96 re f 244.15 39.71 8.60 9.96 re f 244.15 49.67 8.60 9.96 re f 244.15 59.64 8.60 9.96 re f 244.15 69.60 8.60 9.96 re f 244.15 79.56 8.60 9.96 re f 244.15 89.53 8.60 9.96 re f 244.15 99.49 8.60 9.96 re f 244.15 109.45 8.60 9.96 re f 244.15 119.42 8.60 9.96 re f 244.15 129.38 8.60 9.96 re f 244.15 139.34 8.60 9.96 re f 244.15 149.31 8.60 9.96 re f 244.15 159.27 8.60 9.96 re f 244.15 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 244.15 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 244.15 189.16 8.60 9.96 re f 244.15 199.12 8.60 9.96 re f 244.15 209.08 8.60 9.96 re f 244.15 219.05 8.60 9.96 re f 244.15 229.01 8.60 9.96 re f 244.15 238.97 8.60 9.96 re f 244.15 248.94 8.60 9.96 re f 244.15 258.90 8.60 9.96 re f 244.15 268.86 8.60 9.96 re f 244.15 278.83 8.60 9.96 re f 244.15 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 244.15 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 244.15 308.72 8.60 9.96 re f 244.15 318.68 8.60 9.96 re f 244.15 328.64 8.60 9.96 re f 244.15 338.61 8.60 9.96 re f 244.15 348.57 8.60 9.96 re f 244.15 358.53 8.60 9.96 re f 244.15 368.49 8.60 9.96 re f 244.15 378.46 8.60 9.96 re f 244.15 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 252.75 19.79 8.60 9.96 re f 252.75 29.75 8.60 9.96 re f 252.75 39.71 8.60 9.96 re f 252.75 49.67 8.60 9.96 re f 252.75 59.64 8.60 9.96 re f 252.75 69.60 8.60 9.96 re f 252.75 79.56 8.60 9.96 re f 252.75 89.53 8.60 9.96 re f 252.75 99.49 8.60 9.96 re f 252.75 109.45 8.60 9.96 re f 252.75 119.42 8.60 9.96 re f 252.75 129.38 8.60 9.96 re f 252.75 139.34 8.60 9.96 re f 252.75 149.31 8.60 9.96 re f 252.75 159.27 8.60 9.96 re f 252.75 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 252.75 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 252.75 189.16 8.60 9.96 re f 252.75 199.12 8.60 9.96 re f 252.75 209.08 8.60 9.96 re f 252.75 219.05 8.60 9.96 re f 252.75 229.01 8.60 9.96 re f 252.75 238.97 8.60 9.96 re f 252.75 248.94 8.60 9.96 re f 252.75 258.90 8.60 9.96 re f 252.75 268.86 8.60 9.96 re f 252.75 278.83 8.60 9.96 re f 252.75 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 252.75 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 252.75 308.72 8.60 9.96 re f 252.75 318.68 8.60 9.96 re f 252.75 328.64 8.60 9.96 re f 252.75 338.61 8.60 9.96 re f 252.75 348.57 8.60 9.96 re f 252.75 358.53 8.60 9.96 re f 252.75 368.49 8.60 9.96 re f 252.75 378.46 8.60 9.96 re f 252.75 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 261.34 19.79 8.60 9.96 re f 261.34 29.75 8.60 9.96 re f 261.34 39.71 8.60 9.96 re f 261.34 49.67 8.60 9.96 re f 261.34 59.64 8.60 9.96 re f 261.34 69.60 8.60 9.96 re f 261.34 79.56 8.60 9.96 re f 261.34 89.53 8.60 9.96 re f 261.34 99.49 8.60 9.96 re f 261.34 109.45 8.60 9.96 re f 261.34 119.42 8.60 9.96 re f 261.34 129.38 8.60 9.96 re f 261.34 139.34 8.60 9.96 re f 261.34 149.31 8.60 9.96 re f 261.34 159.27 8.60 9.96 re f 261.34 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 261.34 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 261.34 189.16 8.60 9.96 re f 261.34 199.12 8.60 9.96 re f 261.34 209.08 8.60 9.96 re f 261.34 219.05 8.60 9.96 re f 261.34 229.01 8.60 9.96 re f 261.34 238.97 8.60 9.96 re f 261.34 248.94 8.60 9.96 re f 261.34 258.90 8.60 9.96 re f 261.34 268.86 8.60 9.96 re f 261.34 278.83 8.60 9.96 re f 261.34 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 261.34 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 261.34 308.72 8.60 9.96 re f 261.34 318.68 8.60 9.96 re f 261.34 328.64 8.60 9.96 re f 261.34 338.61 8.60 9.96 re f 261.34 348.57 8.60 9.96 re f 261.34 358.53 8.60 9.96 re f 261.34 368.49 8.60 9.96 re f 261.34 378.46 8.60 9.96 re f 261.34 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 269.94 19.79 8.60 9.96 re f 269.94 29.75 8.60 9.96 re f 269.94 39.71 8.60 9.96 re f 269.94 49.67 8.60 9.96 re f 269.94 59.64 8.60 9.96 re f 269.94 69.60 8.60 9.96 re f 269.94 79.56 8.60 9.96 re f 269.94 89.53 8.60 9.96 re f 269.94 99.49 8.60 9.96 re f 269.94 109.45 8.60 9.96 re f 269.94 119.42 8.60 9.96 re f 269.94 129.38 8.60 9.96 re f 269.94 139.34 8.60 9.96 re f 269.94 149.31 8.60 9.96 re f 269.94 159.27 8.60 9.96 re f 269.94 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 269.94 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 269.94 189.16 8.60 9.96 re f 269.94 199.12 8.60 9.96 re f 269.94 209.08 8.60 9.96 re f 269.94 219.05 8.60 9.96 re f 269.94 229.01 8.60 9.96 re f 269.94 238.97 8.60 9.96 re f 269.94 248.94 8.60 9.96 re f 269.94 258.90 8.60 9.96 re f 269.94 268.86 8.60 9.96 re f 269.94 278.83 8.60 9.96 re f 269.94 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 269.94 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 269.94 308.72 8.60 9.96 re f 269.94 318.68 8.60 9.96 re f 269.94 328.64 8.60 9.96 re f 269.94 338.61 8.60 9.96 re f 269.94 348.57 8.60 9.96 re f 269.94 358.53 8.60 9.96 re f 269.94 368.49 8.60 9.96 re f 269.94 378.46 8.60 9.96 re f 269.94 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 278.54 19.79 8.60 9.96 re f 278.54 29.75 8.60 9.96 re f 278.54 39.71 8.60 9.96 re f 278.54 49.67 8.60 9.96 re f 278.54 59.64 8.60 9.96 re f 278.54 69.60 8.60 9.96 re f 278.54 79.56 8.60 9.96 re f 278.54 89.53 8.60 9.96 re f 278.54 99.49 8.60 9.96 re f 278.54 109.45 8.60 9.96 re f 278.54 119.42 8.60 9.96 re f 278.54 129.38 8.60 9.96 re f 278.54 139.34 8.60 9.96 re f 278.54 149.31 8.60 9.96 re f 278.54 159.27 8.60 9.96 re f 278.54 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 278.54 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 278.54 189.16 8.60 9.96 re f 278.54 199.12 8.60 9.96 re f 278.54 209.08 8.60 9.96 re f 278.54 219.05 8.60 9.96 re f 278.54 229.01 8.60 9.96 re f 278.54 238.97 8.60 9.96 re f 278.54 248.94 8.60 9.96 re f 278.54 258.90 8.60 9.96 re f 278.54 268.86 8.60 9.96 re f 278.54 278.83 8.60 9.96 re f 278.54 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 278.54 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 278.54 308.72 8.60 9.96 re f 278.54 318.68 8.60 9.96 re f 278.54 328.64 8.60 9.96 re f 278.54 338.61 8.60 9.96 re f 278.54 348.57 8.60 9.96 re f 278.54 358.53 8.60 9.96 re f 278.54 368.49 8.60 9.96 re f 278.54 378.46 8.60 9.96 re f 278.54 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 287.14 19.79 8.60 9.96 re f 287.14 29.75 8.60 9.96 re f 287.14 39.71 8.60 9.96 re f 287.14 49.67 8.60 9.96 re f 287.14 59.64 8.60 9.96 re f 287.14 69.60 8.60 9.96 re f 287.14 79.56 8.60 9.96 re f 287.14 89.53 8.60 9.96 re f 287.14 99.49 8.60 9.96 re f 287.14 109.45 8.60 9.96 re f 287.14 119.42 8.60 9.96 re f 287.14 129.38 8.60 9.96 re f 287.14 139.34 8.60 9.96 re f 287.14 149.31 8.60 9.96 re f 287.14 159.27 8.60 9.96 re f 287.14 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 287.14 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 287.14 189.16 8.60 9.96 re f 287.14 199.12 8.60 9.96 re f 287.14 209.08 8.60 9.96 re f 287.14 219.05 8.60 9.96 re f 287.14 229.01 8.60 9.96 re f 287.14 238.97 8.60 9.96 re f 287.14 248.94 8.60 9.96 re f 287.14 258.90 8.60 9.96 re f 287.14 268.86 8.60 9.96 re f 287.14 278.83 8.60 9.96 re f 287.14 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 287.14 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 287.14 308.72 8.60 9.96 re f 287.14 318.68 8.60 9.96 re f 287.14 328.64 8.60 9.96 re f 287.14 338.61 8.60 9.96 re f 287.14 348.57 8.60 9.96 re f 287.14 358.53 8.60 9.96 re f 287.14 368.49 8.60 9.96 re f 287.14 378.46 8.60 9.96 re f 287.14 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 295.73 19.79 8.60 9.96 re f 295.73 29.75 8.60 9.96 re f 295.73 39.71 8.60 9.96 re f 295.73 49.67 8.60 9.96 re f 295.73 59.64 8.60 9.96 re f 295.73 69.60 8.60 9.96 re f 295.73 79.56 8.60 9.96 re f 295.73 89.53 8.60 9.96 re f 295.73 99.49 8.60 9.96 re f 295.73 109.45 8.60 9.96 re f 295.73 119.42 8.60 9.96 re f 295.73 129.38 8.60 9.96 re f 295.73 139.34 8.60 9.96 re f 295.73 149.31 8.60 9.96 re f 295.73 159.27 8.60 9.96 re f 295.73 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 295.73 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 295.73 189.16 8.60 9.96 re f 295.73 199.12 8.60 9.96 re f 295.73 209.08 8.60 9.96 re f 295.73 219.05 8.60 9.96 re f 295.73 229.01 8.60 9.96 re f 295.73 238.97 8.60 9.96 re f 295.73 248.94 8.60 9.96 re f 295.73 258.90 8.60 9.96 re f 295.73 268.86 8.60 9.96 re f 295.73 278.83 8.60 9.96 re f 295.73 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 295.73 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 295.73 308.72 8.60 9.96 re f 295.73 318.68 8.60 9.96 re f 295.73 328.64 8.60 9.96 re f 295.73 338.61 8.60 9.96 re f 295.73 348.57 8.60 9.96 re f 295.73 358.53 8.60 9.96 re f 295.73 368.49 8.60 9.96 re f 295.73 378.46 8.60 9.96 re f 295.73 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 304.33 19.79 8.60 9.96 re f 304.33 29.75 8.60 9.96 re f 304.33 39.71 8.60 9.96 re f 304.33 49.67 8.60 9.96 re f 304.33 59.64 8.60 9.96 re f 304.33 69.60 8.60 9.96 re f 304.33 79.56 8.60 9.96 re f 304.33 89.53 8.60 9.96 re f 304.33 99.49 8.60 9.96 re f 304.33 109.45 8.60 9.96 re f 304.33 119.42 8.60 9.96 re f 304.33 129.38 8.60 9.96 re f 304.33 139.34 8.60 9.96 re f 304.33 149.31 8.60 9.96 re f 304.33 159.27 8.60 9.96 re f 304.33 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 304.33 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 304.33 189.16 8.60 9.96 re f 304.33 199.12 8.60 9.96 re f 304.33 209.08 8.60 9.96 re f 304.33 219.05 8.60 9.96 re f 304.33 229.01 8.60 9.96 re f 304.33 238.97 8.60 9.96 re f 304.33 248.94 8.60 9.96 re f 304.33 258.90 8.60 9.96 re f 304.33 268.86 8.60 9.96 re f 304.33 278.83 8.60 9.96 re f 304.33 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 304.33 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 304.33 308.72 8.60 9.96 re f 304.33 318.68 8.60 9.96 re f 304.33 328.64 8.60 9.96 re f 304.33 338.61 8.60 9.96 re f 304.33 348.57 8.60 9.96 re f 304.33 358.53 8.60 9.96 re f 304.33 368.49 8.60 9.96 re f 304.33 378.46 8.60 9.96 re f 304.33 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 312.93 19.79 8.60 9.96 re f 312.93 29.75 8.60 9.96 re f 312.93 39.71 8.60 9.96 re f 312.93 49.67 8.60 9.96 re f 312.93 59.64 8.60 9.96 re f 312.93 69.60 8.60 9.96 re f 312.93 79.56 8.60 9.96 re f 312.93 89.53 8.60 9.96 re f 312.93 99.49 8.60 9.96 re f 312.93 109.45 8.60 9.96 re f 312.93 119.42 8.60 9.96 re f 312.93 129.38 8.60 9.96 re f 312.93 139.34 8.60 9.96 re f 312.93 149.31 8.60 9.96 re f 312.93 159.27 8.60 9.96 re f 312.93 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 312.93 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 312.93 189.16 8.60 9.96 re f 312.93 199.12 8.60 9.96 re f 312.93 209.08 8.60 9.96 re f 312.93 219.05 8.60 9.96 re f 312.93 229.01 8.60 9.96 re f 312.93 238.97 8.60 9.96 re f 312.93 248.94 8.60 9.96 re f 312.93 258.90 8.60 9.96 re f 312.93 268.86 8.60 9.96 re f 312.93 278.83 8.60 9.96 re f 312.93 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 312.93 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 312.93 308.72 8.60 9.96 re f 312.93 318.68 8.60 9.96 re f 312.93 328.64 8.60 9.96 re f 312.93 338.61 8.60 9.96 re f 312.93 348.57 8.60 9.96 re f 312.93 358.53 8.60 9.96 re f 312.93 368.49 8.60 9.96 re f 312.93 378.46 8.60 9.96 re f 312.93 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 321.53 19.79 8.60 9.96 re f 321.53 29.75 8.60 9.96 re f 321.53 39.71 8.60 9.96 re f 321.53 49.67 8.60 9.96 re f 321.53 59.64 8.60 9.96 re f 321.53 69.60 8.60 9.96 re f 321.53 79.56 8.60 9.96 re f 321.53 89.53 8.60 9.96 re f 321.53 99.49 8.60 9.96 re f 321.53 109.45 8.60 9.96 re f 321.53 119.42 8.60 9.96 re f 321.53 129.38 8.60 9.96 re f 321.53 139.34 8.60 9.96 re f 321.53 149.31 8.60 9.96 re f 321.53 159.27 8.60 9.96 re f 321.53 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 321.53 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 321.53 189.16 8.60 9.96 re f 321.53 199.12 8.60 9.96 re f 321.53 209.08 8.60 9.96 re f 321.53 219.05 8.60 9.96 re f 321.53 229.01 8.60 9.96 re f 321.53 238.97 8.60 9.96 re f 321.53 248.94 8.60 9.96 re f 321.53 258.90 8.60 9.96 re f 321.53 268.86 8.60 9.96 re f 321.53 278.83 8.60 9.96 re f 321.53 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 321.53 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 321.53 308.72 8.60 9.96 re f 321.53 318.68 8.60 9.96 re f 321.53 328.64 8.60 9.96 re f 321.53 338.61 8.60 9.96 re f 321.53 348.57 8.60 9.96 re f 321.53 358.53 8.60 9.96 re f 321.53 368.49 8.60 9.96 re f 321.53 378.46 8.60 9.96 re f 321.53 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 330.12 19.79 8.60 9.96 re f 330.12 29.75 8.60 9.96 re f 330.12 39.71 8.60 9.96 re f 330.12 49.67 8.60 9.96 re f 330.12 59.64 8.60 9.96 re f 330.12 69.60 8.60 9.96 re f 330.12 79.56 8.60 9.96 re f 330.12 89.53 8.60 9.96 re f 330.12 99.49 8.60 9.96 re f 330.12 109.45 8.60 9.96 re f 330.12 119.42 8.60 9.96 re f 330.12 129.38 8.60 9.96 re f 330.12 139.34 8.60 9.96 re f 330.12 149.31 8.60 9.96 re f 330.12 159.27 8.60 9.96 re f 330.12 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 330.12 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 330.12 189.16 8.60 9.96 re f 330.12 199.12 8.60 9.96 re f 330.12 209.08 8.60 9.96 re f 330.12 219.05 8.60 9.96 re f 330.12 229.01 8.60 9.96 re f 330.12 238.97 8.60 9.96 re f 330.12 248.94 8.60 9.96 re f 330.12 258.90 8.60 9.96 re f 330.12 268.86 8.60 9.96 re f 330.12 278.83 8.60 9.96 re f 330.12 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 330.12 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 330.12 308.72 8.60 9.96 re f 330.12 318.68 8.60 9.96 re f 330.12 328.64 8.60 9.96 re f 330.12 338.61 8.60 9.96 re f 330.12 348.57 8.60 9.96 re f 330.12 358.53 8.60 9.96 re f 330.12 368.49 8.60 9.96 re f 330.12 378.46 8.60 9.96 re f 330.12 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 338.72 19.79 8.60 9.96 re f 338.72 29.75 8.60 9.96 re f 338.72 39.71 8.60 9.96 re f 338.72 49.67 8.60 9.96 re f 338.72 59.64 8.60 9.96 re f 338.72 69.60 8.60 9.96 re f 338.72 79.56 8.60 9.96 re f 338.72 89.53 8.60 9.96 re f 338.72 99.49 8.60 9.96 re f 338.72 109.45 8.60 9.96 re f 338.72 119.42 8.60 9.96 re f 338.72 129.38 8.60 9.96 re f 338.72 139.34 8.60 9.96 re f 338.72 149.31 8.60 9.96 re f 338.72 159.27 8.60 9.96 re f 338.72 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 338.72 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 338.72 189.16 8.60 9.96 re f 338.72 199.12 8.60 9.96 re f 338.72 209.08 8.60 9.96 re f 338.72 219.05 8.60 9.96 re f 338.72 229.01 8.60 9.96 re f 338.72 238.97 8.60 9.96 re f 338.72 248.94 8.60 9.96 re f 338.72 258.90 8.60 9.96 re f 338.72 268.86 8.60 9.96 re f 338.72 278.83 8.60 9.96 re f 338.72 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 338.72 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 338.72 308.72 8.60 9.96 re f 338.72 318.68 8.60 9.96 re f 338.72 328.64 8.60 9.96 re f 338.72 338.61 8.60 9.96 re f 338.72 348.57 8.60 9.96 re f 338.72 358.53 8.60 9.96 re f 338.72 368.49 8.60 9.96 re f 338.72 378.46 8.60 9.96 re f 338.72 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 347.32 19.79 8.60 9.96 re f 347.32 29.75 8.60 9.96 re f 347.32 39.71 8.60 9.96 re f 347.32 49.67 8.60 9.96 re f 347.32 59.64 8.60 9.96 re f 347.32 69.60 8.60 9.96 re f 347.32 79.56 8.60 9.96 re f 347.32 89.53 8.60 9.96 re f 347.32 99.49 8.60 9.96 re f 347.32 109.45 8.60 9.96 re f 347.32 119.42 8.60 9.96 re f 347.32 129.38 8.60 9.96 re f 347.32 139.34 8.60 9.96 re f 347.32 149.31 8.60 9.96 re f 347.32 159.27 8.60 9.96 re f 347.32 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 347.32 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 347.32 189.16 8.60 9.96 re f 347.32 199.12 8.60 9.96 re f 347.32 209.08 8.60 9.96 re f 347.32 219.05 8.60 9.96 re f 347.32 229.01 8.60 9.96 re f 347.32 238.97 8.60 9.96 re f 347.32 248.94 8.60 9.96 re f 347.32 258.90 8.60 9.96 re f 347.32 268.86 8.60 9.96 re f 347.32 278.83 8.60 9.96 re f 347.32 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 347.32 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 347.32 308.72 8.60 9.96 re f 347.32 318.68 8.60 9.96 re f 347.32 328.64 8.60 9.96 re f 347.32 338.61 8.60 9.96 re f 347.32 348.57 8.60 9.96 re f 347.32 358.53 8.60 9.96 re f 347.32 368.49 8.60 9.96 re f 347.32 378.46 8.60 9.96 re f 347.32 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 355.92 19.79 8.60 9.96 re f 355.92 29.75 8.60 9.96 re f 355.92 39.71 8.60 9.96 re f 355.92 49.67 8.60 9.96 re f 355.92 59.64 8.60 9.96 re f 355.92 69.60 8.60 9.96 re f 355.92 79.56 8.60 9.96 re f 355.92 89.53 8.60 9.96 re f 355.92 99.49 8.60 9.96 re f 355.92 109.45 8.60 9.96 re f 355.92 119.42 8.60 9.96 re f 355.92 129.38 8.60 9.96 re f 355.92 139.34 8.60 9.96 re f 355.92 149.31 8.60 9.96 re f 355.92 159.27 8.60 9.96 re f 355.92 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 355.92 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 355.92 189.16 8.60 9.96 re f 355.92 199.12 8.60 9.96 re f 355.92 209.08 8.60 9.96 re f 355.92 219.05 8.60 9.96 re f 355.92 229.01 8.60 9.96 re f 355.92 238.97 8.60 9.96 re f 355.92 248.94 8.60 9.96 re f 355.92 258.90 8.60 9.96 re f 355.92 268.86 8.60 9.96 re f 355.92 278.83 8.60 9.96 re f 355.92 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 355.92 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 355.92 308.72 8.60 9.96 re f 355.92 318.68 8.60 9.96 re f 355.92 328.64 8.60 9.96 re f 355.92 338.61 8.60 9.96 re f 355.92 348.57 8.60 9.96 re f 355.92 358.53 8.60 9.96 re f 355.92 368.49 8.60 9.96 re f 355.92 378.46 8.60 9.96 re f 355.92 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 364.51 19.79 8.60 9.96 re f 364.51 29.75 8.60 9.96 re f 364.51 39.71 8.60 9.96 re f 364.51 49.67 8.60 9.96 re f 364.51 59.64 8.60 9.96 re f 364.51 69.60 8.60 9.96 re f 364.51 79.56 8.60 9.96 re f 364.51 89.53 8.60 9.96 re f 364.51 99.49 8.60 9.96 re f 364.51 109.45 8.60 9.96 re f 364.51 119.42 8.60 9.96 re f 364.51 129.38 8.60 9.96 re f 364.51 139.34 8.60 9.96 re f 364.51 149.31 8.60 9.96 re f 364.51 159.27 8.60 9.96 re f 364.51 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 364.51 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 364.51 189.16 8.60 9.96 re f 364.51 199.12 8.60 9.96 re f 364.51 209.08 8.60 9.96 re f 364.51 219.05 8.60 9.96 re f 364.51 229.01 8.60 9.96 re f 364.51 238.97 8.60 9.96 re f 364.51 248.94 8.60 9.96 re f 364.51 258.90 8.60 9.96 re f 364.51 268.86 8.60 9.96 re f 364.51 278.83 8.60 9.96 re f 364.51 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 364.51 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 364.51 308.72 8.60 9.96 re f 364.51 318.68 8.60 9.96 re f 364.51 328.64 8.60 9.96 re f 364.51 338.61 8.60 9.96 re f 364.51 348.57 8.60 9.96 re f 364.51 358.53 8.60 9.96 re f 364.51 368.49 8.60 9.96 re f 364.51 378.46 8.60 9.96 re f 364.51 388.42 8.60 9.96 re f /sRGB cs 0.647 0.000 0.149 scn 373.11 19.79 8.60 9.96 re f 373.11 29.75 8.60 9.96 re f 373.11 39.71 8.60 9.96 re f 373.11 49.67 8.60 9.96 re f 373.11 59.64 8.60 9.96 re f 373.11 69.60 8.60 9.96 re f 373.11 79.56 8.60 9.96 re f 373.11 89.53 8.60 9.96 re f 373.11 99.49 8.60 9.96 re f 373.11 109.45 8.60 9.96 re f 373.11 119.42 8.60 9.96 re f 373.11 129.38 8.60 9.96 re f 373.11 139.34 8.60 9.96 re f 373.11 149.31 8.60 9.96 re f 373.11 159.27 8.60 9.96 re f 373.11 169.23 8.60 9.96 re f /sRGB cs 0.686 0.035 0.149 scn 373.11 179.20 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 373.11 189.16 8.60 9.96 re f 373.11 199.12 8.60 9.96 re f 373.11 209.08 8.60 9.96 re f 373.11 219.05 8.60 9.96 re f 373.11 229.01 8.60 9.96 re f 373.11 238.97 8.60 9.96 re f 373.11 248.94 8.60 9.96 re f 373.11 258.90 8.60 9.96 re f 373.11 268.86 8.60 9.96 re f 373.11 278.83 8.60 9.96 re f 373.11 288.79 8.60 9.96 re f /sRGB cs 0.667 0.847 0.910 scn 373.11 298.75 8.60 9.96 re f /sRGB cs 0.192 0.212 0.584 scn 373.11 308.72 8.60 9.96 re f 373.11 318.68 8.60 9.96 re f 373.11 328.64 8.60 9.96 re f 373.11 338.61 8.60 9.96 re f 373.11 348.57 8.60 9.96 re f 373.11 358.53 8.60 9.96 re f 373.11 368.49 8.60 9.96 re f 373.11 378.46 8.60 9.96 re f 373.11 388.42 8.60 9.96 re f Q q Q q BT /sRGB cs 0.000 0.000 0.000 scn /F2 1 Tf 0.00 -5.00 5.00 0.00 57.50 14.79 Tm (27) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 66.10 14.79 Tm (26) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 74.70 14.79 Tm (25) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 83.30 14.79 Tm (24) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 91.89 14.79 Tm (23) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 100.49 14.79 Tm (22) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 109.09 14.79 Tm (21) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 117.69 14.79 Tm (10) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 126.28 14.79 Tm (20) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 134.88 14.79 Tm (6) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 143.48 14.79 Tm (38) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 152.08 14.79 Tm (37) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 160.68 14.79 Tm (36) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 169.27 14.79 Tm (35) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 177.87 14.79 Tm (34) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 186.47 14.79 Tm (33) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 195.07 14.79 Tm (32) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 203.66 14.79 Tm (31) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 212.26 14.79 Tm (30) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 220.86 14.79 Tm (28) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 229.46 14.79 Tm (29) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 238.05 14.79 Tm (17) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 246.65 14.79 Tm (19) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 255.25 14.79 Tm (18) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 263.85 14.79 Tm (16) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 272.44 14.79 Tm (15) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 281.04 14.79 Tm (14) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 289.64 14.79 Tm (13) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 298.24 14.79 Tm (12) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 306.83 14.79 Tm (11) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 315.43 14.79 Tm (9) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 324.03 14.79 Tm (8) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 332.63 14.79 Tm (7) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 341.23 14.79 Tm (5) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 349.82 14.79 Tm (4) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 358.42 14.79 Tm (3) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 367.02 14.79 Tm (1) Tj ET BT /F2 1 Tf 0.00 -5.00 5.00 0.00 375.62 14.79 Tm (2) Tj ET Q q Q q BT /sRGB cs 0.000 0.000 0.000 scn /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 22.61 Tm (2) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 32.58 Tm (1) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 42.54 Tm (3) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 52.50 Tm (4) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 62.47 Tm (5) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 72.43 Tm (7) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 82.39 Tm (8) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 92.35 Tm (9) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 102.32 Tm (11) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 112.28 Tm (12) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 122.24 Tm (13) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 132.21 Tm (14) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 142.17 Tm (15) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 152.13 Tm (16) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 162.10 Tm (18) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 172.06 Tm (19) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 182.02 Tm (17) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 191.99 Tm (29) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 201.95 Tm (28) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 211.91 Tm (30) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 221.88 Tm (31) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 231.84 Tm (32) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 241.80 Tm (33) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 251.76 Tm (34) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 261.73 Tm (35) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 271.69 Tm (36) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 281.65 Tm (37) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 291.62 Tm (38) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 301.58 Tm (6) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 311.54 Tm (20) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 321.51 Tm (10) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 331.47 Tm (21) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 341.43 Tm (22) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 351.40 Tm (23) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 361.36 Tm (24) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 371.32 Tm (25) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 381.29 Tm (26) Tj ET BT /F2 1 Tf 6.00 0.00 -0.00 6.00 386.71 391.25 Tm (27) Tj ET Q q Q q /sRGB cs 0.616 0.624 1.000 scn 55.00 400.38 8.60 8.00 re f /sRGB cs 0.827 0.545 1.000 scn 55.00 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 63.60 400.38 8.60 8.00 re f /sRGB cs 0.827 0.545 1.000 scn 63.60 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 72.20 400.38 8.60 8.00 re f /sRGB cs 0.827 0.545 1.000 scn 72.20 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 80.79 400.38 8.60 8.00 re f /sRGB cs 0.827 0.545 1.000 scn 80.79 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 89.39 400.38 8.60 8.00 re f /sRGB cs 0.827 0.545 1.000 scn 89.39 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 97.99 400.38 8.60 8.00 re f /sRGB cs 0.827 0.545 1.000 scn 97.99 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 106.59 400.38 8.60 8.00 re f /sRGB cs 0.827 0.545 1.000 scn 106.59 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 115.18 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 115.18 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 123.78 400.38 8.60 8.00 re f /sRGB cs 0.827 0.545 1.000 scn 123.78 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 132.38 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 132.38 410.38 8.60 8.00 re f /sRGB cs 0.961 0.482 1.000 scn 140.98 400.38 8.60 8.00 re f 149.57 400.38 8.60 8.00 re f 158.17 400.38 8.60 8.00 re f 166.77 400.38 8.60 8.00 re f 175.37 400.38 8.60 8.00 re f 183.96 400.38 8.60 8.00 re f 192.56 400.38 8.60 8.00 re f 201.16 400.38 8.60 8.00 re f 209.76 400.38 8.60 8.00 re f 218.35 400.38 8.60 8.00 re f 226.95 400.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 235.55 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 235.55 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 244.15 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 244.15 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 252.75 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 252.75 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 261.34 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 261.34 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 269.94 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 269.94 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 278.54 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 278.54 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 287.14 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 287.14 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 295.73 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 295.73 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 304.33 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 304.33 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 312.93 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 312.93 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 321.53 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 321.53 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 330.12 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 330.12 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 338.72 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 338.72 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 347.32 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 347.32 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 355.92 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 355.92 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 364.51 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 364.51 410.38 8.60 8.00 re f /sRGB cs 0.616 0.624 1.000 scn 373.11 400.38 8.60 8.00 re f /sRGB cs 0.227 0.694 1.000 scn 373.11 410.38 8.60 8.00 re f Q q Q q BT /sRGB cs 0.000 0.000 0.000 scn /F3 1 Tf 10.00 0.00 -0.00 10.00 455.12 391.20 Tm (ALL.AML) Tj ET /sRGB cs 0.616 0.624 1.000 scn 455.12 380.43 7.18 7.18 re f BT /sRGB cs 0.000 0.000 0.000 scn /F2 1 Tf 10.00 0.00 -0.00 10.00 464.45 380.43 Tm (ALL) Tj ET /sRGB cs 0.961 0.482 1.000 scn 455.12 369.66 7.18 7.18 re f BT /sRGB cs 0.000 0.000 0.000 scn /F2 1 Tf 10.00 0.00 -0.00 10.00 464.45 369.66 Tm (AML) Tj ET BT /F3 1 Tf 10.00 0.00 -0.00 10.00 455.12 348.12 Tm (Cell) Tj ET /sRGB cs 0.227 0.694 1.000 scn 455.12 337.35 7.18 7.18 re f BT /sRGB cs 0.000 0.000 0.000 scn /F2 1 Tf 10.00 0.00 -0.00 10.00 464.45 337.35 Tm (B-cell) Tj ET /sRGB cs 0.827 0.545 1.000 scn 455.12 326.58 7.18 7.18 re f BT /sRGB cs 0.000 0.000 0.000 scn /F2 1 Tf 10.00 0.00 -0.00 10.00 464.45 326.58 Tm (T-cell) Tj ET Q q Q q /sRGB cs 0.192 0.212 0.584 scn 402.38 248.38 10.00 2.94 re f /sRGB cs 0.208 0.259 0.608 scn 402.38 251.33 10.00 2.94 re f /sRGB cs 0.220 0.310 0.631 scn 402.38 254.27 10.00 2.94 re f /sRGB cs 0.235 0.357 0.655 scn 402.38 257.21 10.00 2.94 re f /sRGB cs 0.255 0.408 0.678 scn 402.38 260.15 10.00 2.94 re f /sRGB cs 0.271 0.459 0.706 scn 402.38 263.09 10.00 2.94 re f /sRGB cs 0.306 0.502 0.725 scn 402.38 266.03 10.00 2.94 re f /sRGB cs 0.341 0.545 0.749 scn 402.38 268.97 10.00 2.94 re f /sRGB cs 0.380 0.588 0.773 scn 402.38 271.91 10.00 2.94 re f /sRGB cs 0.416 0.631 0.796 scn 402.38 274.85 10.00 2.94 re f /sRGB cs 0.455 0.678 0.820 scn 402.38 277.80 10.00 2.94 re f /sRGB cs 0.494 0.710 0.835 scn 402.38 280.74 10.00 2.94 re f /sRGB cs 0.537 0.745 0.855 scn 402.38 283.68 10.00 2.94 re f /sRGB cs 0.580 0.780 0.875 scn 402.38 286.62 10.00 2.94 re f /sRGB cs 0.627 0.816 0.894 scn 402.38 289.56 10.00 2.94 re f /sRGB cs 0.667 0.847 0.910 scn 402.38 292.50 10.00 2.94 re f /sRGB cs 0.710 0.871 0.922 scn 402.38 295.44 10.00 2.94 re f /sRGB cs 0.753 0.890 0.937 scn 402.38 298.38 10.00 2.94 re f /sRGB cs 0.792 0.910 0.949 scn 402.38 301.33 10.00 2.94 re f /sRGB cs 0.835 0.929 0.957 scn 402.38 304.27 10.00 2.94 re f /sRGB cs 0.878 0.953 0.973 scn 402.38 307.21 10.00 2.94 re f /sRGB cs 0.902 0.961 0.925 scn 402.38 310.15 10.00 2.94 re f /sRGB cs 0.925 0.969 0.882 scn 402.38 313.09 10.00 2.94 re f /sRGB cs 0.949 0.980 0.835 scn 402.38 316.03 10.00 2.94 re f /sRGB cs 0.973 0.988 0.792 scn 402.38 318.97 10.00 2.94 re f /sRGB cs 1.000 1.000 0.749 scn 402.38 321.91 10.00 2.94 re f /sRGB cs 0.996 0.973 0.710 scn 402.38 324.85 10.00 2.94 re f /sRGB cs 0.996 0.949 0.675 scn 402.38 327.80 10.00 2.94 re f /sRGB cs 0.996 0.925 0.635 scn 402.38 330.74 10.00 2.94 re f /sRGB cs 0.996 0.902 0.600 scn 402.38 333.68 10.00 2.94 re f /sRGB cs 0.996 0.878 0.565 scn 402.38 336.62 10.00 2.94 re f /sRGB cs 0.992 0.839 0.525 scn 402.38 339.56 10.00 2.94 re f /sRGB cs 0.992 0.800 0.490 scn 402.38 342.50 10.00 2.94 re f /sRGB cs 0.992 0.761 0.451 scn 402.38 345.44 10.00 2.94 re f /sRGB cs 0.992 0.722 0.416 scn 402.38 348.38 10.00 2.94 re f /sRGB cs 0.992 0.682 0.380 scn 402.38 351.33 10.00 2.94 re f /sRGB cs 0.984 0.631 0.357 scn 402.38 354.27 10.00 2.94 re f /sRGB cs 0.976 0.580 0.333 scn 402.38 357.21 10.00 2.94 re f /sRGB cs 0.969 0.529 0.310 scn 402.38 360.15 10.00 2.94 re f /sRGB cs 0.961 0.478 0.286 scn 402.38 363.09 10.00 2.94 re f /sRGB cs 0.957 0.427 0.263 scn 402.38 366.03 10.00 2.94 re f /sRGB cs 0.933 0.376 0.239 scn 402.38 368.97 10.00 2.94 re f /sRGB cs 0.910 0.329 0.216 scn 402.38 371.91 10.00 2.94 re f /sRGB cs 0.886 0.282 0.196 scn 402.38 374.85 10.00 2.94 re f /sRGB cs 0.863 0.235 0.173 scn 402.38 377.80 10.00 2.94 re f /sRGB cs 0.843 0.188 0.153 scn 402.38 380.74 10.00 2.94 re f /sRGB cs 0.800 0.149 0.149 scn 402.38 383.68 10.00 2.94 re f /sRGB cs 0.765 0.110 0.149 scn 402.38 386.62 10.00 2.94 re f /sRGB cs 0.725 0.075 0.149 scn 402.38 389.56 10.00 2.94 re f /sRGB cs 0.686 0.035 0.149 scn 402.38 392.50 10.00 2.94 re f /sRGB cs 0.647 0.000 0.149 scn 402.38 395.44 10.00 2.94 re f BT /sRGB cs 0.000 0.000 0.000 scn /F2 1 Tf 10.00 0.00 -0.00 10.00 414.38 244.79 Tm (0) Tj ET BT /F2 1 Tf 10.00 0.00 -0.00 10.00 414.38 274.79 Tm (0.2) Tj ET BT /F2 1 Tf 10.00 0.00 -0.00 10.00 414.38 304.79 Tm (0.4) Tj ET BT /F2 1 Tf 10.00 0.00 -0.00 10.00 414.38 334.79 Tm (0.6) Tj ET BT /F2 1 Tf 10.00 0.00 -0.00 10.00 414.38 364.79 Tm (0.8) Tj ET BT /F2 1 Tf 10.00 0.00 -0.00 10.00 414.38 394.79 Tm (1) Tj ET Q q Q q Q q BT /sRGB cs 0.000 0.000 0.000 scn /F3 1 Tf 12.00 0.00 -0.00 12.00 166.34 485.38 Tm (Consensus matrix) Tj ET Q q Q q Q q Q endstream endobj 9 0 obj 68611 endobj 3 0 obj << /Type /Pages /Kids [ 7 0 R ] /Count 1 /MediaBox [0 0 504 503] >> endobj 4 0 obj << /ProcSet [/PDF /Text] /Font <> /ExtGState << >> /ColorSpace << /sRGB 5 0 R >> >> endobj 5 0 obj [/ICCBased 6 0 R] endobj 6 0 obj << /N 3 /Alternate /DeviceRGB /Length 9433 /Filter /ASCIIHexDecode >> stream 00 00 0c 48 4c 69 6e 6f 02 10 00 00 6d 6e 74 72 52 47 42 20 58 59 5a 20 07 ce 00 02 00 09 00 06 00 31 00 00 61 63 73 70 4d 53 46 54 00 00 00 00 49 45 43 20 73 52 47 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f6 d6 00 01 00 00 00 00 d3 2d 48 50 20 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 63 70 72 74 00 00 01 50 00 00 00 33 64 65 73 63 00 00 01 84 00 00 00 6c 77 74 70 74 00 00 01 f0 00 00 00 14 62 6b 70 74 00 00 02 04 00 00 00 14 72 58 59 5a 00 00 02 18 00 00 00 14 67 58 59 5a 00 00 02 2c 00 00 00 14 62 58 59 5a 00 00 02 40 00 00 00 14 64 6d 6e 64 00 00 02 54 00 00 00 70 64 6d 64 64 00 00 02 c4 00 00 00 88 76 75 65 64 00 00 03 4c 00 00 00 86 76 69 65 77 00 00 03 d4 00 00 00 24 6c 75 6d 69 00 00 03 f8 00 00 00 14 6d 65 61 73 00 00 04 0c 00 00 00 24 74 65 63 68 00 00 04 30 00 00 00 0c 72 54 52 43 00 00 04 3c 00 00 08 0c 67 54 52 43 00 00 04 3c 00 00 08 0c 62 54 52 43 00 00 04 3c 00 00 08 0c 74 65 78 74 00 00 00 00 43 6f 70 79 72 69 67 68 74 20 28 63 29 20 31 39 39 38 20 48 65 77 6c 65 74 74 2d 50 61 63 6b 61 72 64 20 43 6f 6d 70 61 6e 79 00 00 64 65 73 63 00 00 00 00 00 00 00 12 73 52 47 42 20 49 45 43 36 31 39 36 36 2d 32 2e 31 00 00 00 00 00 00 00 00 00 00 00 12 73 52 47 42 20 49 45 43 36 31 39 36 36 2d 32 2e 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 58 59 5a 20 00 00 00 00 00 00 f3 51 00 01 00 00 00 01 16 cc 58 59 5a 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 58 59 5a 20 00 00 00 00 00 00 6f a2 00 00 38 f5 00 00 03 90 58 59 5a 20 00 00 00 00 00 00 62 99 00 00 b7 85 00 00 18 da 58 59 5a 20 00 00 00 00 00 00 24 a0 00 00 0f 84 00 00 b6 cf 64 65 73 63 00 00 00 00 00 00 00 16 49 45 43 20 68 74 74 70 3a 2f 2f 77 77 77 2e 69 65 63 2e 63 68 00 00 00 00 00 00 00 00 00 00 00 16 49 45 43 20 68 74 74 70 3a 2f 2f 77 77 77 2e 69 65 63 2e 63 68 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 65 73 63 00 00 00 00 00 00 00 2e 49 45 43 20 36 31 39 36 36 2d 32 2e 31 20 44 65 66 61 75 6c 74 20 52 47 42 20 63 6f 6c 6f 75 72 20 73 70 61 63 65 20 2d 20 73 52 47 42 00 00 00 00 00 00 00 00 00 00 00 2e 49 45 43 20 36 31 39 36 36 2d 32 2e 31 20 44 65 66 61 75 6c 74 20 52 47 42 20 63 6f 6c 6f 75 72 20 73 70 61 63 65 20 2d 20 73 52 47 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 65 73 63 00 00 00 00 00 00 00 2c 52 65 66 65 72 65 6e 63 65 20 56 69 65 77 69 6e 67 20 43 6f 6e 64 69 74 69 6f 6e 20 69 6e 20 49 45 43 36 31 39 36 36 2d 32 2e 31 00 00 00 00 00 00 00 00 00 00 00 2c 52 65 66 65 72 65 6e 63 65 20 56 69 65 77 69 6e 67 20 43 6f 6e 64 69 74 69 6f 6e 20 69 6e 20 49 45 43 36 31 39 36 36 2d 32 2e 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 76 69 65 77 00 00 00 00 00 13 a4 fe 00 14 5f 2e 00 10 cf 14 00 03 ed cc 00 04 13 0b 00 03 5c 9e 00 00 00 01 58 59 5a 20 00 00 00 00 00 4c 09 56 00 50 00 00 00 57 1f e7 6d 65 61 73 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 8f 00 00 00 02 73 69 67 20 00 00 00 00 43 52 54 20 63 75 72 76 00 00 00 00 00 00 04 00 00 00 00 05 00 0a 00 0f 00 14 00 19 00 1e 00 23 00 28 00 2d 00 32 00 37 00 3b 00 40 00 45 00 4a 00 4f 00 54 00 59 00 5e 00 63 00 68 00 6d 00 72 00 77 00 7c 00 81 00 86 00 8b 00 90 00 95 00 9a 00 9f 00 a4 00 a9 00 ae 00 b2 00 b7 00 bc 00 c1 00 c6 00 cb 00 d0 00 d5 00 db 00 e0 00 e5 00 eb 00 f0 00 f6 00 fb 01 01 01 07 01 0d 01 13 01 19 01 1f 01 25 01 2b 01 32 01 38 01 3e 01 45 01 4c 01 52 01 59 01 60 01 67 01 6e 01 75 01 7c 01 83 01 8b 01 92 01 9a 01 a1 01 a9 01 b1 01 b9 01 c1 01 c9 01 d1 01 d9 01 e1 01 e9 01 f2 01 fa 02 03 02 0c 02 14 02 1d 02 26 02 2f 02 38 02 41 02 4b 02 54 02 5d 02 67 02 71 02 7a 02 84 02 8e 02 98 02 a2 02 ac 02 b6 02 c1 02 cb 02 d5 02 e0 02 eb 02 f5 03 00 03 0b 03 16 03 21 03 2d 03 38 03 43 03 4f 03 5a 03 66 03 72 03 7e 03 8a 03 96 03 a2 03 ae 03 ba 03 c7 03 d3 03 e0 03 ec 03 f9 04 06 04 13 04 20 04 2d 04 3b 04 48 04 55 04 63 04 71 04 7e 04 8c 04 9a 04 a8 04 b6 04 c4 04 d3 04 e1 04 f0 04 fe 05 0d 05 1c 05 2b 05 3a 05 49 05 58 05 67 05 77 05 86 05 96 05 a6 05 b5 05 c5 05 d5 05 e5 05 f6 06 06 06 16 06 27 06 37 06 48 06 59 06 6a 06 7b 06 8c 06 9d 06 af 06 c0 06 d1 06 e3 06 f5 07 07 07 19 07 2b 07 3d 07 4f 07 61 07 74 07 86 07 99 07 ac 07 bf 07 d2 07 e5 07 f8 08 0b 08 1f 08 32 08 46 08 5a 08 6e 08 82 08 96 08 aa 08 be 08 d2 08 e7 08 fb 09 10 09 25 09 3a 09 4f 09 64 09 79 09 8f 09 a4 09 ba 09 cf 09 e5 09 fb 0a 11 0a 27 0a 3d 0a 54 0a 6a 0a 81 0a 98 0a ae 0a c5 0a dc 0a f3 0b 0b 0b 22 0b 39 0b 51 0b 69 0b 80 0b 98 0b b0 0b c8 0b e1 0b f9 0c 12 0c 2a 0c 43 0c 5c 0c 75 0c 8e 0c a7 0c c0 0c d9 0c f3 0d 0d 0d 26 0d 40 0d 5a 0d 74 0d 8e 0d a9 0d c3 0d de 0d f8 0e 13 0e 2e 0e 49 0e 64 0e 7f 0e 9b 0e b6 0e d2 0e ee 0f 09 0f 25 0f 41 0f 5e 0f 7a 0f 96 0f b3 0f cf 0f ec 10 09 10 26 10 43 10 61 10 7e 10 9b 10 b9 10 d7 10 f5 11 13 11 31 11 4f 11 6d 11 8c 11 aa 11 c9 11 e8 12 07 12 26 12 45 12 64 12 84 12 a3 12 c3 12 e3 13 03 13 23 13 43 13 63 13 83 13 a4 13 c5 13 e5 14 06 14 27 14 49 14 6a 14 8b 14 ad 14 ce 14 f0 15 12 15 34 15 56 15 78 15 9b 15 bd 15 e0 16 03 16 26 16 49 16 6c 16 8f 16 b2 16 d6 16 fa 17 1d 17 41 17 65 17 89 17 ae 17 d2 17 f7 18 1b 18 40 18 65 18 8a 18 af 18 d5 18 fa 19 20 19 45 19 6b 19 91 19 b7 19 dd 1a 04 1a 2a 1a 51 1a 77 1a 9e 1a c5 1a ec 1b 14 1b 3b 1b 63 1b 8a 1b b2 1b da 1c 02 1c 2a 1c 52 1c 7b 1c a3 1c cc 1c f5 1d 1e 1d 47 1d 70 1d 99 1d c3 1d ec 1e 16 1e 40 1e 6a 1e 94 1e be 1e e9 1f 13 1f 3e 1f 69 1f 94 1f bf 1f ea 20 15 20 41 20 6c 20 98 20 c4 20 f0 21 1c 21 48 21 75 21 a1 21 ce 21 fb 22 27 22 55 22 82 22 af 22 dd 23 0a 23 38 23 66 23 94 23 c2 23 f0 24 1f 24 4d 24 7c 24 ab 24 da 25 09 25 38 25 68 25 97 25 c7 25 f7 26 27 26 57 26 87 26 b7 26 e8 27 18 27 49 27 7a 27 ab 27 dc 28 0d 28 3f 28 71 28 a2 28 d4 29 06 29 38 29 6b 29 9d 29 d0 2a 02 2a 35 2a 68 2a 9b 2a cf 2b 02 2b 36 2b 69 2b 9d 2b d1 2c 05 2c 39 2c 6e 2c a2 2c d7 2d 0c 2d 41 2d 76 2d ab 2d e1 2e 16 2e 4c 2e 82 2e b7 2e ee 2f 24 2f 5a 2f 91 2f c7 2f fe 30 35 30 6c 30 a4 30 db 31 12 31 4a 31 82 31 ba 31 f2 32 2a 32 63 32 9b 32 d4 33 0d 33 46 33 7f 33 b8 33 f1 34 2b 34 65 34 9e 34 d8 35 13 35 4d 35 87 35 c2 35 fd 36 37 36 72 36 ae 36 e9 37 24 37 60 37 9c 37 d7 38 14 38 50 38 8c 38 c8 39 05 39 42 39 7f 39 bc 39 f9 3a 36 3a 74 3a b2 3a ef 3b 2d 3b 6b 3b aa 3b e8 3c 27 3c 65 3c a4 3c e3 3d 22 3d 61 3d a1 3d e0 3e 20 3e 60 3e a0 3e e0 3f 21 3f 61 3f a2 3f e2 40 23 40 64 40 a6 40 e7 41 29 41 6a 41 ac 41 ee 42 30 42 72 42 b5 42 f7 43 3a 43 7d 43 c0 44 03 44 47 44 8a 44 ce 45 12 45 55 45 9a 45 de 46 22 46 67 46 ab 46 f0 47 35 47 7b 47 c0 48 05 48 4b 48 91 48 d7 49 1d 49 63 49 a9 49 f0 4a 37 4a 7d 4a c4 4b 0c 4b 53 4b 9a 4b e2 4c 2a 4c 72 4c ba 4d 02 4d 4a 4d 93 4d dc 4e 25 4e 6e 4e b7 4f 00 4f 49 4f 93 4f dd 50 27 50 71 50 bb 51 06 51 50 51 9b 51 e6 52 31 52 7c 52 c7 53 13 53 5f 53 aa 53 f6 54 42 54 8f 54 db 55 28 55 75 55 c2 56 0f 56 5c 56 a9 56 f7 57 44 57 92 57 e0 58 2f 58 7d 58 cb 59 1a 59 69 59 b8 5a 07 5a 56 5a a6 5a f5 5b 45 5b 95 5b e5 5c 35 5c 86 5c d6 5d 27 5d 78 5d c9 5e 1a 5e 6c 5e bd 5f 0f 5f 61 5f b3 60 05 60 57 60 aa 60 fc 61 4f 61 a2 61 f5 62 49 62 9c 62 f0 63 43 63 97 63 eb 64 40 64 94 64 e9 65 3d 65 92 65 e7 66 3d 66 92 66 e8 67 3d 67 93 67 e9 68 3f 68 96 68 ec 69 43 69 9a 69 f1 6a 48 6a 9f 6a f7 6b 4f 6b a7 6b ff 6c 57 6c af 6d 08 6d 60 6d b9 6e 12 6e 6b 6e c4 6f 1e 6f 78 6f d1 70 2b 70 86 70 e0 71 3a 71 95 71 f0 72 4b 72 a6 73 01 73 5d 73 b8 74 14 74 70 74 cc 75 28 75 85 75 e1 76 3e 76 9b 76 f8 77 56 77 b3 78 11 78 6e 78 cc 79 2a 79 89 79 e7 7a 46 7a a5 7b 04 7b 63 7b c2 7c 21 7c 81 7c e1 7d 41 7d a1 7e 01 7e 62 7e c2 7f 23 7f 84 7f e5 80 47 80 a8 81 0a 81 6b 81 cd 82 30 82 92 82 f4 83 57 83 ba 84 1d 84 80 84 e3 85 47 85 ab 86 0e 86 72 86 d7 87 3b 87 9f 88 04 88 69 88 ce 89 33 89 99 89 fe 8a 64 8a ca 8b 30 8b 96 8b fc 8c 63 8c ca 8d 31 8d 98 8d ff 8e 66 8e ce 8f 36 8f 9e 90 06 90 6e 90 d6 91 3f 91 a8 92 11 92 7a 92 e3 93 4d 93 b6 94 20 94 8a 94 f4 95 5f 95 c9 96 34 96 9f 97 0a 97 75 97 e0 98 4c 98 b8 99 24 99 90 99 fc 9a 68 9a d5 9b 42 9b af 9c 1c 9c 89 9c f7 9d 64 9d d2 9e 40 9e ae 9f 1d 9f 8b 9f fa a0 69 a0 d8 a1 47 a1 b6 a2 26 a2 96 a3 06 a3 76 a3 e6 a4 56 a4 c7 a5 38 a5 a9 a6 1a a6 8b a6 fd a7 6e a7 e0 a8 52 a8 c4 a9 37 a9 a9 aa 1c aa 8f ab 02 ab 75 ab e9 ac 5c ac d0 ad 44 ad b8 ae 2d ae a1 af 16 af 8b b0 00 b0 75 b0 ea b1 60 b1 d6 b2 4b b2 c2 b3 38 b3 ae b4 25 b4 9c b5 13 b5 8a b6 01 b6 79 b6 f0 b7 68 b7 e0 b8 59 b8 d1 b9 4a b9 c2 ba 3b ba b5 bb 2e bb a7 bc 21 bc 9b bd 15 bd 8f be 0a be 84 be ff bf 7a bf f5 c0 70 c0 ec c1 67 c1 e3 c2 5f c2 db c3 58 c3 d4 c4 51 c4 ce c5 4b c5 c8 c6 46 c6 c3 c7 41 c7 bf c8 3d c8 bc c9 3a c9 b9 ca 38 ca b7 cb 36 cb b6 cc 35 cc b5 cd 35 cd b5 ce 36 ce b6 cf 37 cf b8 d0 39 d0 ba d1 3c d1 be d2 3f d2 c1 d3 44 d3 c6 d4 49 d4 cb d5 4e d5 d1 d6 55 d6 d8 d7 5c d7 e0 d8 64 d8 e8 d9 6c d9 f1 da 76 da fb db 80 dc 05 dc 8a dd 10 dd 96 de 1c de a2 df 29 df af e0 36 e0 bd e1 44 e1 cc e2 53 e2 db e3 63 e3 eb e4 73 e4 fc e5 84 e6 0d e6 96 e7 1f e7 a9 e8 32 e8 bc e9 46 e9 d0 ea 5b ea e5 eb 70 eb fb ec 86 ed 11 ed 9c ee 28 ee b4 ef 40 ef cc f0 58 f0 e5 f1 72 f1 ff f2 8c f3 19 f3 a7 f4 34 f4 c2 f5 50 f5 de f6 6d f6 fb f7 8a f8 19 f8 a8 f9 38 f9 c7 fa 57 fa e7 fb 77 fc 07 fc 98 fd 29 fd ba fe 4b fe dc ff 6d ff ff > endstream endobj 10 0 obj << /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [ 45/minus 96/quoteleft 144/dotlessi /grave /acute /circumflex /tilde /macron /breve /dotaccent /dieresis /.notdef /ring /cedilla /.notdef /hungarumlaut /ogonek /caron /space] >> endobj 11 0 obj << /Type /Font /Subtype /Type1 /Name /F2 /BaseFont /Helvetica /Encoding 10 0 R >> endobj 12 0 obj << /Type /Font /Subtype /Type1 /Name /F3 /BaseFont /Helvetica-Bold /Encoding 10 0 R >> endobj xref 0 13 0000000000 65535 f 0000000021 00000 n 0000000164 00000 n 0000068977 00000 n 0000069060 00000 n 0000069183 00000 n 0000069216 00000 n 0000000213 00000 n 0000000293 00000 n 0000068956 00000 n 0000078752 00000 n 0000079010 00000 n 0000079108 00000 n trailer << /Size 13 /Info 1 0 R /Root 2 0 R >> startxref 79211 %%EOF NMF/vignettes/heatmaps.Rnw0000644000176200001440000004465313620530505015207 0ustar liggesusers%\VignetteIndexEntry{NMF: generating heatmaps} %\VignetteDepends{utils,NMF,RColorBrewer,knitr,bibtex} %\VignetteKeyword{aplot} %\VignetteCompiler{knitr} %\VignetteEngine{knitr::knitr} \documentclass[a4paper]{article} %\usepackage[OT1]{fontenc} \usepackage[colorlinks]{hyperref} \usepackage{a4wide} \usepackage{xspace} \usepackage[all]{hypcap} % for linking to the top of the figures or tables % add preamble from pkgmaker <>= pkgmaker::latex_preamble() if(!requireNamespace("Biobase")) BiocManager::install("Biobase") @ \newcommand{\nmfpack}{\pkgname{NMF}} \newcommand{\MATLAB}{MATLAB\textsuperscript{\textregistered}\xspace} \newcommand{\refeqn}[1]{(\ref{#1})} % REFERENCES \usepackage[citestyle=authoryear-icomp , doi=true , url=true , maxnames=1 , maxbibnames=15 , backref=true , backend=bibtex]{biblatex} \AtEveryCitekey{\clearfield{url}} <>= pkgmaker::latex_bibliography('NMF') @ \newcommand{\citet}[1]{\textcite{#1}} \renewcommand{\cite}[1]{\parencite{#1}} \DefineBibliographyStrings{english}{% backrefpage = {see p.}, % for single page number backrefpages = {see pp.} % for multiple page numbers } % % boxed figures \usepackage{float} \floatstyle{boxed} \restylefloat{figure} \usepackage{array} \usepackage{tabularx} \usepackage{mathabx} \usepackage{url} \urlstyle{rm} % use cleveref for automatic reference label formatting \usepackage[capitalise, noabbrev]{cleveref} % define commands for notes \usepackage{todonotes} \newcommand{\nbnote}[1]{\ \bigskip\todo[inline, backgroundcolor=blue!20!white]{\scriptsize\textsf{\textbf{NB:} #1}}\ \\} % put table of contents on two columns \usepackage[toc]{multitoc} \setkeys{Gin}{width=0.95\textwidth} \begin{document} <>= #options(prompt=' ') #options(continue=' ') set.seed(123456) library(NMF) @ \title{Generating heatmaps for Nonnegative Matrix Factorization\\ \small Package \nmfpack\ - Version \Sexpr{utils::packageVersion('NMF')}} \author{Renaud Gaujoux} \maketitle \begin{abstract} This vignette describes how to produce different informative heatmaps from NMF objects, such as returned by the function \code{nmf} in the \citeCRANpkg{NMF}. The main drawing engine is implemented by the function \code{aheatmap}, which is a highly enhanced modification of the function \code{pheatmap} from the \CRANpkg{pheatmap}, and provides convenient and quick ways of producing high quality and customizable annotated heatmaps. Currently this function is part of the package \nmfpack, but may eventually compose a separate package on its own. \end{abstract} {\small \tableofcontents} \section{Preliminaries} \subsection{Quick reminder on NMF models} Given a nonnegative target matrix $X$ of dimension $n\times p$, NMF algorithms aim at finding a rank $k$ approximation of the form: $$ X \approx W H, $$ where $W$ and $H$ are nonnegative matrices of dimensions $n\times k$ and $k\times p$ respectively. The matrix $W$ is the basis matrix, whose columns are the basis components. The matrix $H$ is the mixture coefficient or weight matrix, whose columns contain the contribution of each basis component to the corresponding column of $X$. We call the rows of $H$ the basis profiles. \subsection{Heatmaps for NMF} Because NMF objects essentially wrap up a pair of matrices, heatmaps are convenient to visualise the results of NMF runs. The package \nmfpack provides several specialised heatmap functions, designed to produce heatmaps with sensible default configurations according to the data being drawn. Being all based on a common drawing engine, they share almost identical interfaces and capabilities. The following specialised functions are currently implemented: \begin{description} \item[\code{basismap}] draws heatmaps of the basis matrix \item[\code{coefmap}] draws heatmaps of the mixture coefficient matrix \item[\code{consensusmap}] draws heatmaps of the consensus matrix, for results of multiple NMF runs. \end{description} \subsection{Heatmap engine} All the above functions eventually call a common heatmap engine, with different default parameters, chosen to be relevant for the given underlying data. The engine is implemented by the function \code{aheatmap}. Its development started as modification of the function \code{pheatmap} from the \pkgname{pheatmap} package. The initial objective was to improve and increase its capabilities, as well as defining a simplified interface, more consistent with the R core function \code{heatmap}. We eventually aim at providing a general, flexible, powerful and easy to use engine for drawing annotated heatmaps. The function \code{aheatmap} has many advantages compared to other heatmap functions such as \code{heatmap}, \code{gplots::heatmap2}, \code{heatmap.plus::heatmap.plus} , or even \code{pheatmap}: \begin{itemize} \item Annotations: unlimited number of annotation tracks can be added to \emph{both} columns and rows, with automated colouring for categorical and numeric variables. \item Compatibility with both base and grid graphics: the function can be directly called in drawing contexts such as grid, mfrow or layout. This is a feature many R users were looking for, and that was strictly impossible with base heatmaps. \item Legends: default automatic legend and colouring; \item Customisation: clustering methods, annotations, colours and legend can all be customised, even separately for rows and columns; \item Convenient interface: many arguments provide multiple ways of specifying their value(s), which speeds up developping/writing and reduce the amount of code required to generate customised plots (e.g. see \cref{sec:colour_spec}). \item Aesthetics: the heatmaps look globally cleaner, the image and text components are by default well proportioned relatively to each other, and all fit within the graphic device. \end{itemize} \subsection{Data and model} \label{sec:data} For the purpose of illustrating the use of each heatmap function, we generate a random target matrix, as well as some annotations or covariates: <>= # random data that follow an 3-rank NMF model (with quite some noise: sd=2) X <- syntheticNMF(100, 3, 20, noise=2) # row annotations and covariates n <- nrow(X) d <- rnorm(n) e <- unlist(mapply(rep, c('X', 'Y', 'Z'), 10)) e <- c(e, rep(NA, n-length(e))) rdata <- data.frame(Var=d, Type=e) # column annotations and covariates p <- ncol(X) a <- sample(c('alpha', 'beta', 'gamma'), p, replace=TRUE) # define covariates: true groups and some numeric variable c <- rnorm(p) # gather them in a data.frame covariates <- data.frame(a, X$pData, c) @ %\SweaveOpts{fig.width=14,fig.height=7} <>= library(knitr) opts_chunk$set(fig.width=14, fig.height=7) @ Note that in the code above, the object \code{X} returned by \code{syntheticNMF} \emph{really is} a matrix object, but wrapped through the function \code{ExposedAttribute} object, which exposes its attributes via a more friendly and access controlled interface \code{\$}. Of particular interests are attributes \code{'pData'} and \code{'fData'}, which are lists that contain a factor named \code{'Group'} that indicates the true underlying clusters. These are respectively defined as each sample's most contrbuting basis component and the basis component to which each feature contributes the most. They are useful to annotate heatmaps and assess the ability of NMF methods to recover the true clusters. As an example, one can conveniently visualize the target matrix as a heatmap, with or without the relevant sample and feature annotations, using simple calls to the \code{aheatmap} function: <>= par(mfrow=c(1,2)) aheatmap(X, annCol=covariates, annRow=X$fData) aheatmap(X) @ Then, we fit an NMF model using multiple runs, that will be used throughtout this vignette to illustrate the use of NMF heatmaps: <>= res <- nmf(X, 3, nrun=10) res @ \nbnote{To keep the vignette simple, we always use the default NMF method (i.e. \code{'brunet'}), but all steps could be performed using a different method, or multiple methods in order to compare their perfromances.} \section{Mixture Coefficient matrix: \texttt{coefmap}} The coefficient matrix of the result can be plotted using the function \code{coefmap}. The default behaviour for multiple NMF runs is to add two annotation tracks that show the clusters obtained by the best fit and the hierarchical clustering of the consensus matrix\footnote{The hierarchical clustering is computed using the consensus matrix itself as a similarity measure, and average linkage. See \code{?consensushc}.}. In the legend, these tracks are named \emph{basis} and \emph{consensus} respectively. For single NMF run or NMF model objects, no consensus data are available, and only the clusters from the fit are displayed. <>= opar <- par(mfrow=c(1,2)) # coefmap from multiple run fit: includes a consensus track coefmap(res) # coefmap of a single run fit: no consensus track coefmap(minfit(res)) par(opar) @ \nbnote{Note how both heatmaps were drawn on the same plot, simply using the standard call to \code{par(mfrow=c(1,2)}. This is impossible to achieve with the R core function \code{heatmap}. See \cref{sec:aheatmap} for more details about compatibility with base and grid graphics.} By default: \begin{itemize} \item the rows are not ordered; \item the columns use the default ordering of \code{aheatmap}, but may easily be ordered according to the clusters defined by the dominant basis component for each column with \code{Colv="basis"}, or according to those implied by the consensus matrix, i.e. as in \code{consensusmap}, with \code{Colv="consensus"}; \item each column is scaled to sum up to one; \item the color palette used is \code{'YlOrRd'} from the \citeCRANpkg{RColorBrewer}, with 50 breaks. \end{itemize} In term of arguments passed to the heatmap engine \code{aheatmap}, these default settings translate as: <>= Rowv = NA Colv = TRUE scale = 'c1' color = 'YlOrRd:50' annCol = predict(object) + predict(object, 'consensus') @ If the ordering does not come from a hierarchical clustering (e.g., if \code{Colv='basis'}), then no dendrogram is displayed. The default behaviour of \code{aheatmap} can be obtained by setting arguments \code{Rowv=TRUE, Colv=TRUE, scale='none'}. \medskip The automatic annotation tracks can be hidden all together by setting argument \code{tracks=NA}, displayed separately by passing only one of the given names (e.g. \code{tracks=':basis'} or \code{tracks='basis:'} for the row or column respectively), and their legend names may be changed by specifying e.g. \code{tracks=c(Metagene=':basis', 'consensus')}. Beside this, they are handled by the heatmap engine function \code{aheatmap} and can be customised as any other annotation tracks -- that can be added via the same argument \code{annCol} (see \cref{sec:aheatmap} or \code{?aheatmap} for more details). <>= opar <- par(mfrow=c(1,2)) # removing all automatic annotation tracks coefmap(res, tracks=NA) # customized plot coefmap(res, Colv = 'euclidean' , main = "Metagene contributions in each sample", labCol = NULL , annRow = list(Metagene=':basis'), annCol = list(':basis', Class=a, Index=c) , annColors = list(Metagene='Set2') , info = TRUE) par(opar) @ \nbnote{The feature that allows to display some information about the fit at the bottom of the plot via argument \code{info=TRUE} is still experimental. It is helpful mostly when developing algorithms or doing an analysis, but would seldom be used in publications.} \section{Basis matrix: \texttt{basismap}} The basis matrix can be plotted using the function \code{basismap}. The default behaviour is to add an annotation track that shows for each row the dominant basis component. That is, for each row, the index of the basis component with the highest loading. This track can be disabled by setting \code{tracks=NA}, and extra row annotations can be added using the same argument \code{annRow}. <>= opar <- par(mfrow=c(1,2)) # default plot basismap(res) # customized plot: only use row special annotation track. basismap(res, main="Metagenes", annRow=list(d, e), tracks=c(Metagene=':basis')) par(opar) @ By default: \begin{itemize} \item the columns are not ordered; \item the rows are ordered by hierarchical clustering using default distance and linkage methods (\code{'eculidean'} and \code{'complete'}); \item each row is scaled to sum up to one; \item the color palette used is \code{'YlOrRd'} from the \citeCRANpkg{RColorBrewer}, with 50 breaks. \end{itemize} In term of arguments passed to the heatmap engine \code{aheatmap}, these default settings translate as: <>= Colv = NA scale = 'r1' color = 'YlOrRd:50' annRow = predict(object, 'features') @ \section{Consensus matrix: \texttt{consensusmap}} When doing clustering with NMF, a common way of assessing the stability of the clusters obtained for a given rank is to consider the consensus matrix computed over multiple independent NMF runs, which is the average of the connectivity matrices of each separate run \footnote{Hence, stability here means robustness with regards to the initial starting point, and shall not be interpreted as in e.g. cross-validation/bootstrap analysis. However, one can argue that having very consistent clusters across runs somehow supports for a certain regularity or the presence of an underlying pattern in the data.}. This procedure is usually repeated over a certain range of factorization ranks, and the results are compared to identify which rank gives the best clusters, possibly in the light of some extra knowledge one could have about the samples (e.g. covariates). The functions \code{nmf} and \code{consensusmap} make it easy to implement this whole process. \nbnote{The consensus plots can also be generated for fits obtained from single NMF runs, in which case the consensus matrix simply reduces to a single connectivity matrix. This is a binary matrix (i.e. entries are either 0 or 1), that will always produce a bi-colour heatmap, and by default clear blocks for each cluster.} \subsection{Single fit} In section \cref{sec:data}, the NMF fit \code{res} was computed with argument \code{nrun=10}, and therefore contains the best fit over 10 runs, as well as the consensus matrix computed over all the runs \footnote{If one were interested in keeping the fits from all the runs, the function \code{nmf} should have been called with argument \code{.options='k'}. See section \emph{Options} in \code{?nmf}. The downstream hanlding of the result would remain identical.}. This can be ploted using the function \code{consensusmap}, which allows for the same kind of customization as the other NMF heatmap functions: <>= opar <- par(mfrow=c(1,2)) # default plot consensusmap(res) # customized plot consensusmap(res, annCol=covariates, annColors=list(c='blue') , labCol='sample ', main='Cluster stability' , sub='Consensus matrix and all covariates') par(opar) @ By default: \begin{itemize} \item the rows and columns of the consensus heatmap are symmetrically ordered by hierarchical clustering using the consensus matrix as a similarity measure and average linkage, and the associated dendrogram is displayed; \item the color palette used is the reverse of \code{'RdYlBu'} from the \citeCRANpkg{RColorBrewer}. \end{itemize} In term of arguments passed to the heatmap engine \code{aheatmap}, these default settings translate as: <>= distfun = function(x) as.dist(1-x) # x being the consensus matrix hclustfun = 'average' Rowv = TRUE Colv = "Rowv" color = '-RdYlBu' @ \subsection{Single method over a range of ranks} The function \code{nmf} accepts a range of value for the rank (argument \code{rank}), making it fit NMF models for each value in the given range \footnote{Before version 0.6, this feature was provided by the function \code{nmfEstimateRank}. From version 0.6, the function \code{nmf} accepts ranges of ranks, and internally calls the function \code{nmfEstimateRank} -- that remains exported and can still be called directly. See documentation \code{?nmfEstimateRank} for more details on the returned value.}: <>= res2_7 <- nmf(X, 2:7, nrun=10, .options='v') class(res2_7) @ The result \code{res2\_7} is an S3 object of class \code{'NMF.rank'}, that contains -- amongst other data -- a list of the best fits obtained for each value of the rank in range $\ldbrack 2, 7\rdbrack]$. The method of \code{consensusmap} defined for class \code{'NMF.rank'}, which plots all the consensus matrices on the same plot: <>= consensusmap(res2_7) @ \nbnote{ The main title of each consensus heatmap can be customized by passing to argument \code{main} a character vector or a list whose elements specify each title. All other arguments are used in each internal call to consensusmap, and will therefore affect all the plots simultaneously. The layout can be specified via argument \code{layout} as a numeric vector giving the number of rows and columns in a \code{mfrow}-like way, or as a matrix that will be passed to R core function \code{layout}. See \code{?consensusmap} for more details and example code. } \subsection{Single rank over a range of methods} If one is interested in comparing methods, for a given factorization rank, then on can fit an NMF model for each method by providing the function \code{nmf} with a \code{list} in argument \code{method}: <>= res_methods <- nmf(X, 3, list('lee', 'brunet', 'nsNMF'), nrun=10) class(res_methods) @ The result \code{res\_methods} is an S4 object of class \code{NMFList}, which is essentially a named list, that contains each fits and the CPU time required by the whole computation. As previously, the sequence of consensus matrices is plotted with \code{consensusmap}: <>= consensusmap(res_methods) @ \section{Generic heatmap engine: \texttt{aheatmap}} \label{sec:aheatmap} This section still needs to be written, but many examples of annotated heatmaps can be found in the demos \code{'aheatmap'} and \code{'heatmaps'}: <>= demo('aheatmap') # or demo('heatmaps') @ These demos and the plots they generate can also be browsed online at \url{http://nmf.r-forge.r-project.org/_DEMOS.html}. \section{Session Info} <>= toLatex(sessionInfo()) @ \printbibliography[heading=bibintoc] \end{document} NMF/vignettes/.install_extras0000644000176200001440000000001513620502674015740 0ustar liggesusersconsensus.pdfNMF/vignettes/src/0000755000176200001440000000000013620502674013476 5ustar liggesusersNMF/vignettes/src/bmc.R0000644000176200001440000000250613620564051014362 0ustar liggesusers# Scripts runs to produce the figures in the BMC paper # # Author: renaud ############################################################################### # install and load NMF package lib.dir <- 'lib' dir.create(lib.dir, showWarnings=FALSE) install.packages('NMF_0.1.tar.gz', repos=NULL, lib=lib.dir) library(NMF, lib=lib.dir) # define a seed .seed <- 123456 # load Golub data if(!requireNamespace("Biobase")) BiocManager::install("Biobase") data(esGolub) #esGolub <- syntheticNMF(500, 3, 20, noise=TRUE) # estimate rank for Golub dataset rank.nrun <- 50 rank.range <- seq(2,6) res.estimate <- nmfEstimateRank(esGolub, rank.range, method='brunet' , nrun=rank.nrun, conf.interval=TRUE, seed=.seed) save(res.estimate, file='res.estimate.rda') # Full run of Brunet algorithm nmf.nrun <- 200 res.brunet <- nmf(esGolub, 3, 'brunet', nrun=nmf.nrun, seed=.seed, .options='tv') save(res.brunet, file='res.brunet.rda') # Comparison of methods res.comp <- nmf(esGolub, 3, list('brunet', 'lee', 'ns', 'lnmf'), seed='nndsvd', .options='tv') save(res.comp, file='res.comp.rda') # save all in one file save(res.estimate, res.brunet, res.comp, file='res.bmc.rda') if( FALSE ){ # generate plots png('consensus.png') metaHeatmap(res.brunet, class=esGolub$Cell) dev.off() png('metagenes.png') metaHeatmap(fit(res.brunet), class=esGolub$Cell) dev.off() }NMF/vignettes/NMF-vignette.Rnw0000644000176200001440000016126013620530420015636 0ustar liggesusers%\VignetteIndexEntry{An introduction to the package NMF} %\VignetteDepends{utils,NMF,Biobase,bigmemory,xtable,RColorBrewer,knitr,bibtex} %\VignetteKeyword{math} %\VignetteCompiler{knitr} %\VignetteEngine{knitr::knitr} \documentclass[a4paper]{article} %\usepackage[OT1]{fontenc} \usepackage[colorlinks]{hyperref} % for hyperlinks \usepackage{a4wide} \usepackage{xspace} \usepackage[all]{hypcap} % for linking to the top of the figures or tables % add preamble from pkgmaker <>= pkgmaker::latex_preamble() if(!requireNamespace("Biobase")) BiocManager::install("Biobase") @ \newcommand{\nmfpack}{\Rpkg{NMF}} \newcommand{\RcppOctave}{\textit{RcppOctave}\xspace} \newcommand{\matlab}{Matlab$^\circledR$\xspace} \newcommand{\MATLAB}{\matlab} \newcommand{\gauss}{GAUSS$^\circledR$\xspace} \newcommand{\graphwidth}{0.9\columnwidth} \newcommand{\refeqn}[1]{(\ref{#1})} % REFERENCES \usepackage[citestyle=authoryear-icomp , doi=true , url=true , maxnames=1 , maxbibnames=15 , backref=true , backend=bibtex]{biblatex} \AtEveryCitekey{\clearfield{url}} <>= pkgmaker::latex_bibliography('NMF') @ \newcommand{\citet}[1]{\textcite{#1}} \renewcommand{\cite}[1]{\parencite{#1}} \DefineBibliographyStrings{english}{% backrefpage = {see p.}, % for single page number backrefpages = {see pp.} % for multiple page numbers } %% % boxed figures \usepackage{float} \floatstyle{boxed} \restylefloat{figure} \usepackage{array} \usepackage{tabularx} \usepackage{xcolor} \usepackage{url} \urlstyle{rm} <>= set.seed(123456) library(knitr) knit_hooks$set(try = pkgmaker::hook_try, backspace = pkgmaker::hook_backspace()) @ % use cleveref for automatic reference label formatting \usepackage[capitalise, noabbrev]{cleveref} % multiple columns \usepackage{multicol} % define commands for notes \usepackage{todonotes} \newcommand{\nbnote}[1]{\todo[inline, backgroundcolor=blue!20!white]{\scriptsize\textsf{\textbf{NB:} #1}}\ \\} % default graphic width \setkeys{Gin}{width=0.95\textwidth} \begin{document} <>= # Load library(NMF) # limit number of cores used nmf.options(cores = 2) @ \title{An introduction to NMF package\\ \small Version \Sexpr{utils::packageVersion('NMF')}} \author{Renaud Gaujoux} % \\Address Computational Biology - University of Cape Town, South Africa, \maketitle This vignette presents the \citeCRANpkg{NMF}, which implements a framework for Nonnegative Matrix Factorization (NMF) algorithms in R \cite{R}. The objective is to provide an implementation of some standard algorithms, while allowing the user to easily implement new methods that integrate into a common framework, which facilitates analysis, result visualisation or performance benchmarking. If you use the package \nmfpack in your analysis and publications please cite: \bigskip \todo[inline, backgroundcolor=blue!10!white]{\fullcite{Rpackage:NMF}} Note that the \nmfpack includes several NMF algorithms, published by different authors. Please make sure to also cite the paper(s) associated with the algorithm(s) you used. Citations for those can be found in \cref{tab:algo} and in the dedicated help pages \code{?gedAlgorithm.}, e.g., \code{?gedAlgorithm.SNMF\_R}. \bigskip \paragraph{Installation:} The latest stable version of the package can be installed from any \href{http://cran.r-project.org}{CRAN} repository mirror: <>= # Install install.packages('NMF') # Load library(NMF) @ The \nmfpack is a project hosted on \emph{R-forge}\footnote{\url{https://r-forge.r-project.org/projects/nmf}}. The latest development version is available from \url{https://r-forge.r-project.org/R/?group_id=649} and may be installed from there\footnote{\code{install.packages("NMF", repos = "http://R-Forge.R-project.org")}}. \paragraph{Support:} UseRs interested in this package are encouraged to subscribe to the user mailing list (\href{https://lists.r-forge.r-project.org/mailman/listinfo/nmf-user}{nmf-user@lists.r-forge.r-project.org}), which is the preferred channel for enquiries, bug reports, feature requests, suggestions or NMF-related discussions. This will enable better tracking as well as fruitful community exchange. \paragraph{Important:} Note that some of the classes defined in the NMF package have gained new slots. If you need to load objects saved in versions prior 0.8.14 please use: <>= # eg., load from some RData file load('object.RData') # update class definition object <- nmfObject(object) @ \pagebreak \tableofcontents \pagebreak \section{Overview} \subsection{Package features} This section provides a quick overview of the \nmfpack package's features. \Cref{sec:usecase} provides more details, as well as sample code on how to actually perform common tasks in NMF analysis. <>= nalgo <- length(nmfAlgorithm()) nseed <- length(nmfSeed()) @ The \nmfpack package provides: \begin{itemize} \item \Sexpr{nalgo} built-in algorithms; \item \Sexpr{nseed} built-in seeding methods; \item Single interface to perform all algorithms, and combine them with the seeding methods; \item Provides a common framework to test, compare and develop NMF methods; \item Accept custom algorithms and seeding methods; \item Plotting utility functions to visualize and help in the interpretation of the results; \item Transparent parallel computations; \item Optimized and memory efficient C++ implementations of the standard algorithms; \item Optional layer for bioinformatics using BioConductor \cite{Gentleman2004}; \end{itemize} \subsection{Nonnegative Matrix Factorization} This section gives a formal definition for Nonnegative Matrix Factorization problems, and defines the notations used throughout the vignette. Let $X$ be a $n \times p$ non-negative matrix, (i.e with $x_{ij} \geq 0$, denoted $X \geq 0$), and $r > 0$ an integer. Non-negative Matrix Factorization (NMF) consists in finding an approximation \begin{equation} X \approx W H\ , \label{NMFstd} \end{equation} where $W, H$ are $n\times r$ and $r \times p$ non-negative matrices, respectively. In practice, the factorization rank $r$ is often chosen such that $r \ll \min(n, p)$. The objective behind this choice is to summarize and split the information contained in $X$ into $r$ factors: the columns of $W$. Depending on the application field, these factors are given different names: basis images, metagenes, source signals. In this vignette we equivalently and alternatively use the terms \emph{basis matrix} or \emph{metagenes} to refer to matrix $W$, and \emph{mixture coefficient matrix} and \emph{metagene expression profiles} to refer to matrix $H$. The main approach to NMF is to estimate matrices $W$ and $H$ as a local minimum: \begin{equation} \min_{W, H \geq 0}\ \underbrace{[D(X, WH) + R(W, H)]}_{=F(W,H)} \label{nmf_min} \end{equation} where \begin{itemize} \item $D$ is a loss function that measures the quality of the approximation. Common loss functions are based on either the Frobenius distance $$D: A,B\mapsto \frac{Tr(AB^t)}{2} = \frac{1}{2} \sum_{ij} (a_{ij} - b_{ij})^2,$$ or the Kullback-Leibler divergence. $$D: A,B\mapsto KL(A||B) = \sum_{i,j} a_{ij} \log \frac{a_{ij}}{b_{ij}} - a_{ij} + b_{ij}.$$ \item $R$ is an optional regularization function, defined to enforce desirable properties on matrices $W$ and $H$, such as smoothness or sparsity \cite{Cichocki2008}. \end{itemize} \subsection{Algorithms} NMF algorithms generally solve problem \refeqn{nmf_min} iteratively, by building a sequence of matrices $(W_k,H_k)$ that reduces at each step the value of the objective function $F$. Beside some variations in the specification of $F$, they also differ in the optimization techniques that are used to compute the updates for $(W_k,H_k)$. For reviews on NMF algorithms see \cite{Berry2007, Chu2004} and references therein. The \nmfpack package implements a number of published algorithms, and provides a general framework to implement other ones. \Cref{tab:algo} gives a short description of each one of the built-in algorithms: The built-in algorithms are listed or retrieved with function \code{nmfAlgorithm}. A given algorithm is retrieved by its name (a \code{character} key), that is partially matched against the list of available algorithms: <>= # list all available algorithms nmfAlgorithm() # retrieve a specific algorithm: 'brunet' nmfAlgorithm('brunet') # partial match is also fine identical(nmfAlgorithm('br'), nmfAlgorithm('brunet')) @ \begin{table}[h!t] \begin{tabularx}{\textwidth}{lX} \hline Key & Description\\ \hline \code{brunet} & Standard NMF. Based on Kullback-Leibler divergence, it uses simple multiplicative updates from \cite{Lee2001}, enhanced to avoid numerical underflow. \begin{eqnarray} H_{kj} & \leftarrow & H_{kj} \frac{\left( \sum_l \frac{W_{lk} V_{lj}}{(WH)_{lj}} \right)}{ \sum_l W_{lk} }\\ W_{ik} & \leftarrow & W_{ik} \frac{ \sum_l [H_{kl} A_{il} / (WH)_{il} ] }{\sum_l H_{kl} } \end{eqnarray} \textbf{Reference:} \cite{Brunet2004}\\ \hline % \code{lee} & Standard NMF. Based on euclidean distance, it uses simple multiplicative updates \begin{eqnarray} H_{kj} & \leftarrow & H_{kj} \frac{(W^T V)_{kj}}{(W^T W H)_{kj}}\\ W_{ik} & \leftarrow & W_{ik} \frac{(V H^T)_{ik}}{(W H H^T)_{ik}} \end{eqnarray} \textbf{Reference:} \cite{Lee2001}\\ \hline % %\code{lnmf} & Local Nonnegative Matrix Factorization. Based on a %regularized Kullback-Leibler divergence, it uses a modified version of %Lee and Seung's multiplicative updates. % %\textbf{Reference:} \cite{Li2001}\\ % \code{nsNMF} & Non-smooth NMF. Uses a modified version of Lee and Seung's multiplicative updates for Kullback-Leibler divergence to fit a extension of the standard NMF model. It is meant to give sparser results. \textbf{Reference:} \cite{Pascual-Montano2006}\\ \hline % \code{offset} & Uses a modified version of Lee and Seung's multiplicative updates for euclidean distance, to fit a NMF model that includes an intercept. \textbf{Reference:} \cite{Badea2008}\\ \hline % \code{pe-nmf} & Pattern-Expression NMF. Uses multiplicative updates to minimize an objective function based on the Euclidean distance and regularized for effective expression of patterns with basis vectors. \textbf{Reference:} \cite{Zhang2008}\\ \hline % \code{snmf/r}, \code{snmf/l} & Alternating Least Square (ALS) approach. It is meant to be very fast compared to other approaches. \textbf{Reference:} \cite{KimH2007}\\ \hline \end{tabularx} \caption{Description of the implemented NMF algorithms. The first column gives the key to use in the call to the \texttt{nmf} function.\label{tab:algo}} \end{table} \subsection{Initialization: seeding methods} NMF algorithms need to be initialized with a seed (i.e. a value for $W_0$ and/or $H_0$\footnote{Some algorithms only need one matrix factor (either $W$ or $H$) to be initialized. See for example the SNMF/R(L) algorithm of Kim and Park \cite{KimH2007}.}), from which to start the iteration process. Because there is no global minimization algorithm, and due to the problem's high dimensionality, the choice of the initialization is in fact very important to ensure meaningful results. The more common seeding method is to use a random starting point, where the entries of $W$ and/or $H$ are drawn from a uniform distribution, usually within the same range as the target matrix's entries. This method is very simple to implement. However, a drawback is that to achieve stability one has to perform multiple runs, each with a different starting point. This significantly increases the computation time needed to obtain the desired factorization. To tackle this problem, some methods have been proposed so as to compute a reasonable starting point from the target matrix itself. Their objective is to produce deterministic algorithms that need to run only once, still giving meaningful results. For a review on some existing NMF initializations see \cite{Albright2006} and references therein. The \nmfpack\ package implements a number of already published seeding methods, and provides a general framework to implement other ones. \Cref{tab:seed} gives a short description of each one of the built-in seeding methods: The built-in seeding methods are listed or retrieved with function \code{nmfSeed}. A given seeding method is retrieved by its name (a \code{character} key) that is partially matched against the list of available seeding methods: <>= # list all available seeding methods nmfSeed() # retrieve a specific method: 'nndsvd' nmfSeed('nndsvd') # partial match is also fine identical(nmfSeed('nn'), nmfSeed('nndsvd')) @ \begin{table}[h!t] \begin{tabularx}{\textwidth}{lX} \hline Key & Description\\ \hline \code{ica} & Uses the result of an Independent Component Analysis (ICA) (from the \citeCRANpkg{fastICA}). Only the positive part of the result are used to initialize the factors.\\ \hline % \code{nnsvd} & Nonnegative Double Singular Value Decomposition. The basic algorithm contains no randomization and is based on two SVD processes, one approximating the data matrix, the other approximating positive sections of the resulting partial SVD factors utilizing an algebraic property of unit rank matrices. It is well suited to initialize NMF algorithms with sparse factors. Simple practical variants of the algorithm allows to generate dense factors. \textbf{Reference:} \cite{Boutsidis2008}\\ \hline % \code{none} & Fix seed. This method allows the user to manually provide initial values for both matrix factors.\\ \hline % \code{random} & The entries of each factors are drawn from a uniform distribution over $[0, max(V)]$, where $V$ is the target matrix.\\ \hline \end{tabularx} \caption{Description of the implemented seeding methods to initialize NMF algorithms. The first column gives the key to use in the call to the \texttt{nmf} function.\label{tab:seed}} \end{table} \subsection{How to run NMF algorithms} Method \code{nmf} provides a single interface to run NMF algorithms. It can directly perform NMF on object of class \code{matrix} or \code{data.frame} and \code{ExpressionSet} -- if the \citeBioCpkg{Biobase} is installed. The interface has four main parameters: \medskip \fbox{\code{nmf(x, rank, method, seed, ...)}} \begin{description} \item[\code{x}] is the target \code{matrix}, \code{data.frame} or \code{ExpressionSet} \footnote{\code{ExpressionSet} is the base class for handling microarray data in BioConductor, and is defined in the \pkgname{Biobase} package.} \item[\code{rank}] is the factorization rank, i.e. the number of columns in matrix $W$. \item[\code{method}] is the algorithm used to estimate the factorization. The default algorithm is given by the package specific option \code{'default.algorithm'}, which defaults to \code{'brunet'} on installation \cite{Brunet2004}. \item[\code{seed}] is the seeding method used to compute the starting point. The default method is given by the package specific option \code{'default.seed'}, which defaults to \code{'random'} on initialization (see method \code{?rnmf} for details on its implementation). \end{description} See also \code{?nmf} for details on the interface and extra parameters. \subsection{Performances} Since version 0.4, some built-in algorithms are optimized in C++, which results in a significant speed-up and a more efficient memory management, especially on large scale data. The older R versions of the concerned algorithms are still available, and accessible by adding the prefix \code{'.R\#'} to the algorithms' access keys (e.g. the key \code{'.R\#offset'} corresponds to the R implementation of NMF with offset \cite{Badea2008}). Moreover they do not show up in the listing returned by the \code{nmfAlgorithm} function, unless argument \code{all=TRUE}: <>= nmfAlgorithm(all=TRUE) # to get all the algorithms that have a secondary R version nmfAlgorithm(version='R') @ \Cref{tab:perf} shows the speed-up achieved by the algorithms that benefit from the optimized code. All algorithms were run once with a factorization rank equal to 3, on the Golub data set which contains a $5000\times 38$ gene expression matrix. The same numeric random seed (\code{seed=123456}) was used for all factorizations. The columns \emph{C} and \emph{R} show the elapsed time (in seconds) achieved by the C++ version and R version respectively. The column \emph{Speed.up} contains the ratio $R/C$. <>= # retrieve all the methods that have a secondary R version meth <- nmfAlgorithm(version='R') meth <- c(names(meth), meth) meth if(requireNamespace("Biobase", quietly=TRUE)){ # load the Golub data data(esGolub) # compute NMF for each method res <- nmf(esGolub, 3, meth, seed=123456) # extract only the elapsed time t <- sapply(res, runtime)[3,] } @ <>= # speed-up m <- length(res)/2 su <- cbind( C=t[1:m], R=t[-(1:m)], Speed.up=t[-(1:m)]/t[1:m]) library(xtable) xtable(su, caption='Performance speed up achieved by the optimized C++ implementation for some of the NMF algorithms.', label='tab:perf') @ \subsection{How to cite the package NMF} To view all the package's bibtex citations, including all vignette(s) and manual(s): <>= # plain text citation('NMF') # or to get the bibtex entries toBibtex(citation('NMF')) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Use case: Golub dataset}\label{sec:usecase} We illustrate the functionalities and the usage of the \nmfpack package on the -- now standard -- Golub dataset on leukemia. It was used in several papers on NMF \cite{Brunet2004, Gao2005} and is included in the \nmfpack package's data, wrapped into an \code{ExpressionSet} object. For performance reason we use here only the first 200 genes. Therefore the results shown in the following are not meant to be biologically meaningful, but only illustrative: <>= if(requireNamespace("Biobase", quietly=TRUE)){ data(esGolub) esGolub esGolub <- esGolub[1:200,] # remove the uneeded variable 'Sample' from the phenotypic data esGolub$Sample <- NULL } @ % TODO: pass to 50 genes for dev \paragraph{Note:} To run this example, the \code{Biobase} package from BioConductor is required. \subsection{Single run}\label{sec:single_run} \subsubsection{Performing a single run} To run the default NMF algorithm on data \code{esGolub} with a factorization rank of 3, we call: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # default NMF algorithm res <- nmf(esGolub, 3) } @ Here we did not specify either the algorithm or the seeding method, so that the computation is done using the default algorithm and is seeded by the default seeding methods. These defaults are set in the package specific options \code{'default.algorithm'} and \code{'default.seed'} respectively. See also \cref{sec:algo,sec:seed} for how to explicitly specify the algorithm and/or the seeding method. \subsubsection{Handling the result} The result of a single NMF run is an object of class \code{NMFfit}, that holds both the fitted NMF model and data about the run: <>= if(requireNamespace("Biobase", quietly=TRUE)){ res } @ The fitted model can be retrieved via method \code{fit}, which returns an object of class \code{NMF}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ fit(res) } @ The estimated target matrix can be retrieved via the generic method \code{fitted}, which returns a -- generally big -- \code{matrix}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ V.hat <- fitted(res) dim(V.hat) } @ Quality and performance measures about the factorization are computed by method \code{summary}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ summary(res) # More quality measures are computed, if the target matrix is provided: summary(res, target=esGolub) } @ If there is some prior knowledge of classes present in the data, some other measures about the unsupervised clustering's performance are computed (purity, entropy, \ldots). Here we use the phenotypic variable \code{Cell} found in the Golub dataset, that gives the samples' cell-types (it is a factor with levels: T-cell, B-cell or \code{NA}): <>= if(requireNamespace("Biobase", quietly=TRUE)){ summary(res, class=esGolub$Cell) } @ The basis matrix (i.e. matrix $W$ or the metagenes) and the mixture coefficient matrix (i.e matrix $H$ or the metagene expression profiles) are retrieved using methods \code{basis} and \code{coef} respectively: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # get matrix W w <- basis(res) dim(w) # get matrix H h <- coef(res) dim(h) } @ If one wants to keep only part of the factorization, one can directly subset on the \code{NMF} object on features and samples (separately or simultaneously). The result is a \code{NMF} object composed of the selected rows and/or columns: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # keep only the first 10 features res.subset <- res[1:10,] class(res.subset) dim(res.subset) # keep only the first 10 samples dim(res[,1:10]) # subset both features and samples: dim(res[1:20,1:10]) } @ \subsubsection{Extracting metagene-specific features} In general NMF matrix factors are sparse, so that the metagenes can usually be characterized by a relatively small set of genes. Those are determined based on their relative contribution to each metagene. Kim and Park \cite{KimH2007} defined a procedure to extract the relevant genes for each metagene, based on a gene scoring schema. The NMF package implements this procedure in methods \code{featureScore} and \code{extractFeature}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # only compute the scores s <- featureScore(res) summary(s) # compute the scores and characterize each metagene s <- extractFeatures(res) str(s) } @ \subsection{Specifying the algorithm}\label{sec:algo} \subsubsection{Built-in algorithms} The \nmfpack package provides a number of built-in algorithms, that are listed or retrieved by function \code{nmfAlgorithm}. Each algorithm is identified by a unique name. The following algorithms are currently implemented (cf. \cref{tab:algo} for more details): <>= nmfAlgorithm() @ %\begin{tech} %Internally, all algorithms are stored in objects that inherit from class %\code{NMFStrategy}. This class defines the minimum interface %\end{tech} The algorithm used to compute the NMF is specified in the third argument (\code{method}). For example, to use the NMF algorithm from Lee and Seung \cite{Lee2001} based on the Frobenius euclidean norm, one make the following call: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # using Lee and Seung's algorithm res <- nmf(esGolub, 3, 'lee') algorithm(res) } @ To use the Nonsmooth NMF algorithm from \cite{Pascual-Montano2006}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # using the Nonsmooth NMF algorithm with parameter theta=0.7 res <- nmf(esGolub, 3, 'ns', theta=0.7) algorithm(res) fit(res) } @ Or to use the PE-NMF algorithm from \cite{Zhang2008}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # using the PE-NMF algorithm with parameters alpha=0.01, beta=1 res <- nmf(esGolub, 3, 'pe', alpha=0.01, beta=1) res } @ %\begin{tech} %Although the last two calls looks similar these are handled % %In the case of the nsNMF algorithm, the fitted model is an object of class %\code{NMFns} that extends the standard NMF model \code{NMFstd}, as it introduces %a smoothing matrix $S$, parametrised by a real number $\theta \in [0,1]$, such %that the fitted model is: %$$ %V \approx W S(\theta) H. %$$ % %Hence the call to function \code{nmf}, parameter $\theta$ is used to % %\end{tech} \subsubsection{Custom algorithms} The \nmfpack package provides the user the possibility to define his own algorithms, and benefit from all the functionalities available in the NMF framework. There are only few contraints on the way the custom algorithm must be defined. See the details in \cref{sec:algo_custom}. \subsection{Specifying the seeding method}\label{sec:seed} The seeding method used to compute the starting point for the chosen algorithm can be set via argument \code{seed}. Note that if the seeding method is deterministic there is no need to perform multiple run anymore. \subsubsection{Built-in seeding methods} Similarly to the algorithms, the \code{nmfSeed} function can be used to list or retrieve the built-in seeding methods. The following seeding methods are currently implemented: <>= nmfSeed() @ To use a specific method to seed the computation of a factorization, one simply passes its name to \code{nmf}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ res <- nmf(esGolub, 3, seed='nndsvd') res } @ \subsubsection{Numerical seed}\label{sec:numseed} Another possibility, useful when comparing methods or reproducing results, is to set the random number generator (RNG) by passing a numerical value in argument \code{seed}. This value is used to set the state of the RNG, and the initialization is performed by the built-in seeding method \code{'random'}. When the function \code{nmf} exits, the value of the random seed (\code{.Random.seed}) is restored to its original state -- as before the call. In the case of a single run (i.e. with \code{nrun=1}), the default is to use the current RNG, set with the R core function \code{set.seed}. In the case of multiple runs, the computations use RNGstream, as provided by the core RNG ``L'Ecuyer-CMRG" \cite{Lecuyer2002}, which generates multiple independent random streams (one per run). This ensures the complete reproducibility of any given set of runs, even when their computation is performed in parallel. Since RNGstream requires a 6-length numeric seed, a random one is generated if only a single numeric value is passed to \code{seed}. Moreover, single runs can also use RNGstream by passing a 6-length seed. <>= if(requireNamespace("Biobase", quietly=TRUE)){ # single run and single numeric seed res <- nmf(esGolub, 3, seed=123456) showRNG(res) # multiple runs and single numeric seed res <- nmf(esGolub, 3, seed=123456, nrun=2) showRNG(res) # single run with a 6-length seed res <- nmf(esGolub, 3, seed=rep(123456, 6)) showRNG(res) } @ \nbnote{To show the RNG changes happening during the computation use \texttt{.options='v4'} to turn on verbosity at level 4.\\ In versions prior 0.6, one could specify option \texttt{restore.seed=FALSE} or \texttt{'-r'}, this option is now deprecated.} \subsubsection{Fixed factorization} Yet another option is to completely specify the initial factorization, by passing values for matrices $W$ and $H$: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # initialize a "constant" factorization based on the target dimension init <- nmfModel(3, esGolub, W=0.5, H=0.3) head(basis(init)) # fit using this NMF model as a seed res <- nmf(esGolub, 3, seed=init) } @ \subsubsection{Custom function} The \nmfpack package provides the user the possibility to define his own seeding method, and benefit from all the functionalities available in the NMF framework. There are only few contraints on the way the custom seeding method must be defined. See the details in \cref{sec:seed_custom}. \subsection{Multiple runs} When the seeding method is stochastic, multiple runs are usually required to achieve stability or a resonable result. This can be done by setting argument \code{nrun} to the desired value. For performance reason we use \code{nrun=5} here, but a typical choice would lies between 100 and 200: <>= if(requireNamespace("Biobase", quietly=TRUE)){ res.multirun <- nmf(esGolub, 3, nrun=5) res.multirun } @ By default, the returned object only contains the best fit over all the runs. That is the factorization that achieved the lowest approximation error (i.e. the lowest objective value). Even during the computation, only the current best factorization is kept in memory. This limits the memory requirement for performing multiple runs, which in turn allows to perform more runs. The object \code{res.multirun} is of class \code{NMFfitX1} that extends class \code{NMFfit}, the class returned by single NMF runs. It can therefore be handled as the result of a single run and benefit from all the methods defined for single run results. \medskip If one is interested in keeping the results from all the runs, one can set the option \code{keep.all=TRUE}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # explicitly setting the option keep.all to TRUE res <- nmf(esGolub, 3, nrun=5, .options=list(keep.all=TRUE)) res } @ <>= if(requireNamespace("Biobase", quietly=TRUE)){ # or using letter code 'k' in argument .options nmf(esGolub, 3, nrun=5, .options='k') } @ In this case, the result is an object of class \code{NMFfitXn} that also inherits from class \code{list}. Note that keeping all the results may be memory consuming. For example, a 3-rank \code{NMF} fit\footnote{i.e. the result of a single NMF run with rank equal 3.} for the Golub gene expression matrix ($5000 \times 38$) takes about \Sexpr{round(object.size(fit(res.multirun))/1000)}Kb\footnote{This size might change depending on the architecture (32 or 64 bits)}. \subsection{Parallel computations}\label{multicore} To speed-up the analysis whenever possible, the \nmfpack package implements transparent parallel computations when run on multi-core machines. It uses the \code{foreach} framework developed by REvolution Computing \citeCRANpkg{foreach}, together with the related \code{doParallel} parallel backend from the \citeCRANpkg{doParallel} -- based on the \pkgname{parallel} package -- to make use of all the CPUs available on the system, with each core simultaneously performing part of the runs. \subsubsection{Memory considerations} Running multicore computations increases the required memory linearly with the number of cores used. When only the best run is of interest, memory usage is optimized to only keep the current best factorization. On non-Windows machine, further speed improvement are achieved by using shared memory and mutex objects from the \citeCRANpkg{bigmemory} and the \citeCRANpkg{synchronicity}. \subsubsection{Parallel foreach backends} The default parallel backend used by the \code{nmf} function is defined by the package specific option \code{'pbackend'}, which defaults to \code{'par'} -- for \code{doParallel}. The backend can also be set on runtime via argument \code{.pbackend}. \medskip \paragraph{IMPORTANT NOTE:} The parallel computation is based on the \pkgname{doParallel} and \pkgname{parallel} packages, and the same care should be taken as stated in the vignette of the \citeCRANpkg{doMC}: \begin{quote} \emph{... it usually isn't safe to run doMC and multicore from a GUI environment. In particular, it is not safe to use doMC from R.app on Mac OS X. Instead, you should use doMC from a terminal session, starting R from the command line.} \end{quote} Therefore, the \code{nmf} function does not allow to run multicore computation from the MacOS X GUI. From version 0.8, other parallel backends are supported, and may be specified via argument \code{.pbackend}: \begin{description} \item[\code{.pbackend='mpi'}] uses the parallel backend \citeCRANpkg{doParallel} and \citeCRANpkg{doMPI} \item[\code{.pbackend=NULL}]{} \end{description} It is possible to specify that the currently registered backend should be used, by setting argument \code{.pbackend=NULL}. This allow to perform parallel computations with ``permanent'' backends that are configured externally of the \code{nmf} call. \subsubsection{Runtime options} There are two other runtime options, \code{parallel} and \code{parallel.required}, that can be passed via argument \code{.options}, to control the behaviour of the parallel computation (see below). \medskip A call for multiple runs will be computed in parallel if one of the following condition is satisfied: \begin{itemize} \item call with option \code{'P'} or \code{parallel.required} set to TRUE (note the upper case in \code{'P'}). In this case, if for any reason the computation cannot be run in parallel (packages requirements, OS, ...), then an error is thrown. Use this mode to force the parallel execution. \item call with option \code{'p'} or \code{parallel} set to TRUE. In this case if something prevents a parallel computation, the factorizations will be done sequentially. \item a valid parallel backend is specified in argument \code{.pbackend}. For the moment it can either be the string \code{'mc'} or a single \code{numeric} value specifying the number of core to use. Unless option \code{'P'} is specified, it will run using option \code{'p'} (i.e. try-parallel mode). \end{itemize} \nbnote{The number of processors to use can also be specified in the runtime options as e.g. \texttt{.options='p4'} or \texttt{.options='P4'} -- to ask or request 4 CPUs.} \paragraph{Examples}\ \\ The following exmaples are run with \code{.options='v'} which turn on verbosity at level 1, that will show which parallell setting is used by each computation. Although we do not show the output here, the user is recommended to run these commands on his machine to see the internal differences of each call. <>= if(requireNamespace("Biobase", quietly=TRUE)){ # the default call will try to run in parallel using all the cores # => will be in parallel if all the requirements are satisfied nmf(esGolub, 3, nrun=5, .opt='v') # request a certain number of cores to use => no error if not possible nmf(esGolub, 3, nrun=5, .opt='vp8') # force parallel computation: use option 'P' nmf(esGolub, 3, nrun=5, .opt='vP') # require an improbable number of cores => error nmf(esGolub, 3, nrun=5, .opt='vP200') } @ \subsubsection{High Performance Computing on a cluster} To achieve further speed-up, the computation can be run on an HPC cluster. In our tests we used the \citeCRANpkg{doMPI} to perform 100 factorizations using hybrid parallel computation on 4 quadri-core machines -- making use of all the cores computation on each machine. <>= # file: mpi.R if(requireNamespace("Biobase", quietly=TRUE)){ ## 0. Create and register an MPI cluster library(doMPI) cl <- startMPIcluster() registerDoMPI(cl) library(NMF) # run on all workers using the current parallel backend data(esGolub) res <- nmf(esGolub, 3, 'brunet', nrun=n, .opt='p', .pbackend=NULL) # save result save(res, file='result.RData') ## 4. Shutdown the cluster and quit MPI closeCluster(cl) mpi.quit() } @ Passing the following shell script to \emph{qsub} should launch the execution on a Sun Grid Engine HPC cluster, with OpenMPI. Some adaptation might be necessary for other queueing systems/installations. \begin{shaded} \small \begin{verbatim} #!/bin/bash #$ -cwd #$ -q opteron.q #$ -pe mpich_4cpu 16 echo "Got $NSLOTS slots. $TMP/machines" orterun -v -n $NSLOTS -hostfile $TMP/machines R --slave -f mpi.R \end{verbatim} \end{shaded} \subsubsection{Forcing sequential execution} When running on a single core machine, \nmfpack package has no other option than performing the multiple runs sequentially, one after another. This is done via the \code{sapply} function. On multi-core machine, one usually wants to perform the runs in parallel, as it speeds up the computation (cf. \cref{multicore}). However in some situation (e.g. while debugging), it might be useful to force the sequential execution of the runs. This can be done via the option \code{'p1'} to run on a single core , or with \code{.pbackend='seq'} to use the foreach backend \code{doSEQ} or to \code{NA} to use a standard \code{sapply} call: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # parallel execution on 2 cores (if possible) res1 <- nmf(esGolub, 3, nrun=5, .opt='vp2', seed=123) # or use the doParallel with single core res2 <- nmf(esGolub, 3, nrun=5, .opt='vp1', seed=123) # force sequential computation by sapply: use option '-p' or .pbackend=NA res3 <- nmf(esGolub, 3, nrun=5, .opt='v-p', seed=123) res4 <- nmf(esGolub, 3, nrun=5, .opt='v', .pbackend=NA, seed=123) # or use the SEQ backend of foreach: .pbackend='seq' res5 <- nmf(esGolub, 3, nrun=5, .opt='v', .pbackend='seq', seed=123) # all results are all identical nmf.equal(list(res1, res2, res3, res4, res5)) } @ \subsection{Estimating the factorization rank} A critical parameter in NMF is the factorization rank $r$. It defines the number of metagenes used to approximate the target matrix. Given a NMF method and the target matrix, a common way of deciding on $r$ is to try different values, compute some quality measure of the results, and choose the best value according to this quality criteria. Several approaches have then been proposed to choose the optimal value of $r$. For example, \cite{Brunet2004} proposed to take the first value of $r$ for which the cophenetic coefficient starts decreasing, \cite{Hutchins2008} suggested to choose the first value where the RSS curve presents an inflection point, and \cite{Frigyesi2008} considered the smallest value at which the decrease in the RSS is lower than the decrease of the RSS obtained from random data. The \nmfpack package provides functions to help implement such procedures and plot the relevant quality measures. Note that this can be a lengthy computation, depending on the data size. Whereas the standard NMF procedure usually involves several hundreds of random initialization, performing 30-50 runs is considered sufficient to get a robust estimate of the factorization rank \cite{Brunet2004, Hutchins2008}. For performance reason, we perform here only 10 runs for each value of the rank. <>= if(requireNamespace("Biobase", quietly=TRUE)){ # perform 10 runs for each value of r in range 2:6 estim.r <- nmf(esGolub, 2:6, nrun=10, seed=123456) } @ The result is a S3 object of class \code{NMF.rank}, that contains a \code{data.frame} with the quality measures in column, and the values of $r$ in row. It also contains a list of the consensus matrix for each value of $r$. All the measures can be plotted at once with the method \code{plot} (\cref{fig:estim_all}), and the function \code{consensusmap} generates heatmaps of the consensus matrix for each value of the rank. In the context of class discovery, it is useful to see if the clusters obtained correspond to known classes. This is why in the particular case of the Golub dataset, we added annotation tracks for the two covariates available ('Cell' and 'ALL.AML'). Since we removed the variable 'Sample' in the preliminaries, these are the only variables in the phenotypic \code{data.frame} embedded within the \code{ExpressionSet} object, and we can simply pass the whole object to argument \code{annCol} (\cref{fig:estim_all_hm}). One can see that at rank 2, the clusters correspond to the ALL and AML samples respectively, while rank 3 separates AML from ALL/T-cell and ALL/B-cell\footnote{Remember that the plots shown in \cref{fig:estim_all_hm} come from only 10 runs, using the 200 first genes in the dataset, which explains the somewhat not so clean clusters. The results are in fact much cleaner when using the full dataset (\cref{fig:heatmap_consensus}).}. \begin{figure} <>= if(requireNamespace("Biobase", quietly=TRUE)){ plot(estim.r) } @ \caption{Estimation of the rank: Quality measures computed from 10 runs for each value of $r$. \label{fig:estim_all}} \end{figure} \begin{figure} <>= if(requireNamespace("Biobase", quietly=TRUE)){ consensusmap(estim.r, annCol=esGolub, labCol=NA, labRow=NA) } @ \caption{Estimation of the rank: Consensus matrices computed from 10 runs for each value of $r$. \label{fig:estim_all_hm}} \end{figure} \subsubsection{Overfitting} Even on random data, increasing the factorization rank would lead to decreasing residuals, as more variables are available to better fit the data. In other words, there is potentially an overfitting problem. In this context, the approach from \cite{Frigyesi2008} may be useful to prevent or detect overfitting as it takes into account the results for unstructured data. However it requires to compute the quality measure(s) for the random data. The \nmfpack package provides a function that shuffles the original data, by permuting the rows of each column, using each time a different permutation. The rank estimation procedure can then be applied to the randomized data, and the ``random'' measures added to the plot for comparison (\cref{fig:estim_all_rd}). \begin{figure} <>= if(requireNamespace("Biobase", quietly=TRUE)){ # shuffle original data V.random <- randomize(esGolub) # estimate quality measures from the shuffled data (use default NMF algorithm) estim.r.random <- nmf(V.random, 2:6, nrun=10, seed=123456) # plot measures on same graph plot(estim.r, estim.r.random) } @ \caption{Estimation of the rank: Comparison of the quality measures with those obtained from randomized data. The curves for the actual data are in blue and green, those for the randomized data are in red and pink. The estimation is based on Brunet's algorithm.} \label{fig:estim_all_rd} \end{figure} \subsection{Comparing algorithms} To compare the results from different algorithms, one can pass a list of methods in argument \code{method}. To enable a fair comparison, a deterministic seeding method should also be used. Here we fix the random seed to 123456. <>= if(requireNamespace("Biobase", quietly=TRUE)){ # fit a model for several different methods res.multi.method <- nmf(esGolub, 3, list('brunet', 'lee', 'ns'), seed=123456, .options='t') } @ Passing the result to method \code{compare} produces a \code{data.frame} that contains summary measures for each method. Again, prior knowledge of classes may be used to compute clustering quality measures: <>= if(requireNamespace("Biobase", quietly=TRUE)){ compare(res.multi.method) # If prior knowledge of classes is available compare(res.multi.method, class=esGolub$Cell) } @ Because the computation was performed with error tracking enabled, an error plot can be produced by method \code{plot} (\cref{fig:errorplot}). Each track is normalized so that its first value equals one, and stops at the iteration where the method's convergence criterion was fulfilled. \subsection{Visualization methods} \subsubsection*{Error track} If the NMF computation is performed with error tracking enabled -- using argument \code{.options} -- the trajectory of the objective value is computed during the fit. This computation is not enabled by default as it induces some overhead. <>= if(requireNamespace("Biobase", quietly=TRUE)){ # run nmf with .option='t' res <- nmf(esGolub, 3, .options='t') # or with .options=list(track=TRUE) } @ The trajectory can be plot with the method \code{plot} (\cref{fig:errorplot}): \begin{figure}[!htbp] <>= if(requireNamespace("Biobase", quietly=TRUE)){ plot(res) plot(res.multi.method) } @ \caption{Error track for a single NMF run (left) and multiple method runs (right)} \label{fig:errorplot} \end{figure} \subsubsection*{Heatmaps} The methods \code{basismap}, \code{coefmap} and \code{consensusmap} provide an easy way to visualize respectively the resulting basis matrix (i.e. metagenes), mixture coefficient matrix (i.e. metaprofiles) and the consensus matrix, in the case of multiple runs. It produces pre-configured heatmaps based on the function \code{aheatmap}, the underlying heatmap engine provided with the package NMF. The default heatmaps produced by these functions are shown in \cref{fig:heatmap_coef_basis,fig:heatmap_consensus}. They can be customized in many different ways (colours, annotations, labels). See the dedicated vignette \emph{``NMF: generating heatmaps"} or the help pages \code{?coefmap} and \code{?aheatmap} for more information. An important and unique feature of the function \code{aheatmap}, is that it makes it possible to combine several heatmaps on the same plot, using the both standard layout calls \texttt{par(mfrow=...)} and \texttt{layout(...)}, or grid viewports from \texttt{grid} graphics. The plotting context is automatically internally detected, and a correct behaviour is achieved thanks to the \citeCRANpkg{gridBase}. Examples are provided in the dedicated vignette mentioned above. The rows of the basis matrix often carry the high dimensionality of the data: genes, loci, pixels, features, etc\ldots The function \code{basismap} extends the use of argument \code{subsetRow} (from \code{aheatmap}) to the specification of a feature selection method. In \cref{fig:heatmap_coef_basis} we simply used \code{subsetRow=TRUE}, which subsets the rows using the method described in \cite{KimH2007}, to only keep the basis-specific features (e.g. the metagene-specific genes). We refer to the relevant help pages \code{?basismap} and \code{?aheatmap} for more details about other possible values for this argument. \begin{figure}[!htbp] \centering <>= if(requireNamespace("Biobase", quietly=TRUE)){ layout(cbind(1,2)) # basis components basismap(res, subsetRow=TRUE) # mixture coefficients coefmap(res) } @ \caption{Heatmap of the basis and the mixture coefficient matrices. The rows of the basis matrix were selected using the default feature selection method -- described in \cite{KimH2007}.} \label{fig:heatmap_coef_basis} \end{figure} In the case of multiple runs the function \code{consensusmap} plots the consensus matrix, i.e. the average connectivity matrix across the runs (see results in \cref{fig:heatmap_consensus} for a consensus matrix obtained with 100 runs of Brunet's algorithm on the complete Golub dataset): \begin{figure}[ht] <>= if(requireNamespace("Biobase", quietly=TRUE)){ # The cell type is used to label rows and columns consensusmap(res.multirun, annCol=esGolub, tracks=NA) plot(1:10) f2 <- fig_path("2.pdf") } @ <>= if(requireNamespace("Biobase", quietly=TRUE)){ file.copy('consensus.pdf', f2, overwrite=TRUE) } @ \caption{Heatmap of consensus matrices from 10 runs on the reduced dataset (left) and from 100 runs on the complete Golub dataset (right).} \label{fig:heatmap_consensus} \end{figure} \section{Extending the package} We developed the \nmfpack\ package with the objective to facilitate the integration of new NMF methods, trying to impose only few requirements on their implementations. All the built-in algorithms and seeding methods are implemented as strategies that are called from within the main interface method \code{nmf}. The user can define new strategies and those are handled in exactly the same way as the built-in ones, benefiting from the same utility functions to interpret the results and assess their performance. \subsection{Custom algorithm} %New NMF algrithms can be defined in two ways: % %\begin{itemize} %\item as a single \code{function} %\item as a set of functions that implement a pre-defined \emph{iterative schema} %\end{itemize} % %\subsubsection{Defined as a \code{function}} \subsubsection{Using a custom algorithm}\label{sec:algo_custom} To define a strategy, the user needs to provide a \code{function} that implements the complete algotihm. It must be of the form: <>= my.algorithm <- function(x, seed, param.1, param.2){ # do something with starting point # ... # return updated starting point return(seed) } @ Where: \begin{description} \item[target] is a \code{matrix}; \item[start] is an object that inherits from class \code{NMF}. This \code{S4} class is used to handle NMF models (matrices \code{W} and \code{H}, objective function, etc\dots); \item[param.1, param.2] are extra parameters specific to the algorithms; \end{description} The function must return an object that inherits from class \code{NMF}. For example: <>= my.algorithm <- function(x, seed, scale.factor=1){ # do something with starting point # ... # for example: # 1. compute principal components pca <- prcomp(t(x), retx=TRUE) # 2. use the absolute values of the first PCs for the metagenes # Note: the factorization rank is stored in object 'start' factorization.rank <- nbasis(seed) basis(seed) <- abs(pca$rotation[,1:factorization.rank]) # use the rotated matrix to get the mixture coefficient # use a scaling factor (just to illustrate the use of extra parameters) coef(seed) <- t(abs(pca$x[,1:factorization.rank])) / scale.factor # return updated data return(seed) } @ To use the new method within the package framework, one pass \code{my.algorithm} to main interface \code{nmf} via argument \code{method}. Here we apply the algorithm to some matrix \code{V} randomly generated: <>= n <- 50; r <- 3; p <- 20 V <-syntheticNMF(n, r, p) @ <>= nmf(V, 3, my.algorithm, scale.factor=10) @ \subsubsection{Using a custom distance measure} The default distance measure is based on the euclidean distance. If the algorithm is based on another distance measure, this one can be specified in argument \code{objective}, either as a \code{character} string corresponding to a built-in objective function, or a custom \code{function} definition\footnote{Note that from version 0.8, the arguments for custom objective functions have been swapped: (1) the current NMF model, (2) the target matrix}: <>= # based on Kullback-Leibler divergence nmf(V, 3, my.algorithm, scale.factor=10, objective='KL') # based on custom distance metric nmf(V, 3, my.algorithm, scale.factor=10 , objective=function(model, target, ...){ ( sum( (target-fitted(model))^4 ) )^{1/4} } ) @ %\subsubsection{Using the iterative schema} % %NMF algorithms generally implement the following common iterative schema: % %\begin{enumerate} %\item %\item %\end{enumerate} \subsubsection{Defining algorithms for mixed sign data} All the algorithms implemented in the \nmfpack package assume that the input data is nonnegative. However, some methods exist in the litterature that work with relaxed constraints, where the input data and one of the matrix factors ($W$ or $H$) are allowed to have negative entries (eg. semi-NMF \cite{Ding2010, Roux2008}). Strictly speaking these methods do not fall into the NMF category, but still solve constrained matrix factorization problems, and could be considered as NMF methods when applied to non-negative data. Moreover, we received user requests to enable the development of semi-NMF type methods within the package's framework. Therefore, we designed the \nmfpack package so that such algorithms -- that handle negative data -- can be integrated. This section documents how to do it. By default, as a safe-guard, the sign of the input data is checked before running any method, so that the \code{nmf} function throws an error if applied to data that contain negative entries \footnote{Note that on the other side, the sign of the factors returned by the algorithms is never checked, so that one can always return factors with negative entries.}. To extend the capabilities of the \nmfpack package in handling negative data, and plug mixed sign NMF methods into the framework, the user needs to specify the argument \code{mixed=TRUE} in the call to the \code{nmf} function. This will skip the sign check of the input data and let the custom algorithm perform the factorization. As an example, we reuse the previously defined custom algorithm\footnote{As it is defined here, the custom algorithm still returns nonnegative factors, which would not be desirable in a real example, as one would not be able to closely fit the negative entries.}: <>= # put some negative input data V.neg <- V; V.neg[1,] <- -1; # this generates an error try( nmf(V.neg, 3, my.algorithm, scale.factor=10) ) # this runs my.algorithm without error nmf(V.neg, 3, my.algorithm, mixed=TRUE, scale.factor=10) @ \subsubsection{Specifying the NMF model} If not specified in the call, the NMF model that is used is the standard one, as defined in \cref{NMFstd}. However, some NMF algorithms have different underlying models, such as non-smooth NMF \cite{Pascual-Montano2006} which uses an extra matrix factor that introduces an extra parameter, and change the way the target matrix is approximated. The NMF models are defined as S4 classes that extends class \code{NMF}. All the available models can be retreived calling the \code{nmfModel()} function with no argument: <>= nmfModel() @ One can specify the NMF model to use with a custom algorithm, using argument \code{model}. Here we first adapt a bit the custom algorithm, to justify and illustrate the use of a different model. We use model \code{NMFOffset} \cite{Badea2008}, that includes an offset to take into account genes that have constant expression levels accross the samples: <>= my.algorithm.offset <- function(x, seed, scale.factor=1){ # do something with starting point # ... # for example: # 1. compute principal components pca <- prcomp(t(x), retx=TRUE) # retrieve the model being estimated data.model <- fit(seed) # 2. use the absolute values of the first PCs for the metagenes # Note: the factorization rank is stored in object 'start' factorization.rank <- nbasis(data.model) basis(data.model) <- abs(pca$rotation[,1:factorization.rank]) # use the rotated matrix to get the mixture coefficient # use a scaling factor (just to illustrate the use of extra parameters) coef(data.model) <- t(abs(pca$x[,1:factorization.rank])) / scale.factor # 3. Compute the offset as the mean expression data.model@offset <- rowMeans(x) # return updated data fit(seed) <- data.model seed } @ Then run the algorithm specifying it needs model \code{NMFOffset}: <>= # run custom algorithm with NMF model with offset nmf(V, 3, my.algorithm.offset, model='NMFOffset', scale.factor=10) @ \subsection{Custom seeding method}\label{sec:seed_custom} The user can also define custom seeding method as a function of the form: <>= # start: object of class NMF # target: the target matrix my.seeding.method <- function(model, target){ # use only the largest columns for W w.cols <- apply(target, 2, function(x) sqrt(sum(x^2))) basis(model) <- target[,order(w.cols)[1:nbasis(model)]] # initialize H randomly coef(model) <- matrix(runif(nbasis(model)*ncol(target)) , nbasis(model), ncol(target)) # return updated object return(model) } @ To use the new seeding method: <>= nmf(V, 3, 'snmf/r', seed=my.seeding.method) @ \section{Advanced usage} \subsection{Package specific options} The package specific options can be retieved or changed using the \code{nmf.getOption} and \code{nmf.options} functions. These behave similarly as the \code{getOption} and \code{nmf.options} base functions: <>= #show default algorithm and seeding method nmf.options('default.algorithm', 'default.seed') # retrieve a single option nmf.getOption('default.seed') # All options nmf.options() @ Currently the following options are available: <>= RdSection2latex('nmf.options', package='NMF') @ The default/current values of each options can be displayed using the function \code{nmf.printOptions}: <>= nmf.printOptions() @ %% latex table generated in R 2.10.1 by xtable 1.5-6 package %% Wed Apr 7 15:27:05 2010 %\begin{table}[ht] %\begin{center} %\begin{tabularx}{\textwidth}{>{\ttfamily}rlX} % \hline %Option & Default value & Description\\ %\hline %default.algorithm & brunet & Default NMF algorithm used by the \code{nmf} function when argument \code{method} is missing. %The value should the key of one of the available NMF algorithms. %See \code{?nmfAlgorithm}.\\ %track.interval & 30 & Number of iterations between two points in the residual track. %This option is relevant only when residual tracking is enabled. %See \code{?nmf}.\\ %error.track & FALSE & Toggle default residual tracking. %When \code{TRUE}, the \code{nmf} function compute and store the residual track in the result -- if not otherwise specified in argument \code{.options}. %Note that tracking may significantly slow down the computations.\\ %default.seed & random & Default seeding method used by the \code{nmf} function when argument \code{seed} is missing. %The value should the key of one of the available seeding methods. %See \code{?nmfSeed}.\\ %backend & mc & Default parallel backend used used by the \code{nmf} function when argument \code{.pbackend} is missing. %Currently the following values are supported: \code{'mc'} for multicore, \code{'seq'} for sequential, \code{''} for \code{sapply}.\\ %verbose & FALSE & Toggle verbosity.\\ %debug & FALSE & Toggle debug mode, which is an extended verbose mode.\\ %\hline %\end{tabularx} %\end{center} %\caption{} %\end{table} \pagebreak \section{Session Info} <>= toLatex(sessionInfo()) @ \printbibliography[heading=bibintoc] \end{document} NMF/NEWS0000644000176200001440000006542513620502674011412 0ustar liggesusers************************************************************************* Changes in version 0.20.6 ************************************************************************* FIXES o fixed new NOTEs in R CMD check (about requireNamespace) o fixed error in heatmaps due to new version of stringr (>= 1.0.0) ************************************************************************* Changes in version 0.20 ************************************************************************* NEW FEATURES o aheatmap gains an argument txt that enables displaying text in each cell of the heatmap. FIXES o now all examples, vignettes and unit tests comply with CRAN policies on the maximum number of cores to be used when checking packages (2). ************************************************************************* Changes in version 0.18 ************************************************************************* NEW FEATURES o aheatmap gains distfun methods 'spearman', 'kendall', and, for completeness, 'pearson' (for which 'correlation' is now an alias), which specifies the correlation method to use when computing the distance matrix. CHANGES o In order to fully comply with CRAN policies, internals of the aheatmap function slightly changed. In particular, this brings an issue when directly plotting to PDF graphic devices, where a first blank page may appear. See the dedicated section in the man page ?aheatmap for a work around. ************************************************************************* Changes in version 0.17.3 ************************************************************************* NEW FEATURES o add silhouette computation for NMF results, which can be performed on samples, features or consensus matrix. The average silhouette width is also computed by the summary methods. (Thanks to Gordon Robertson for this suggestion) o New runtime option 'shared.memory' (or 'm') for toggling usage of shared memory (requires package synchronicity). CHANGES o some plots are -- finally -- generated using ggplot2 This adds an Imports dependency to ggplot2 and reshape2. BUG FIXES o fix a bug when running parallel NMF computation with algorithms defined in other packages (this most probably only affected my own packages, e.g., CellMix) ************************************************************************* Changes in version 0.17.1 ************************************************************************* CHANGES o Computations seeded with an NMF object now set slot @seed to 'NMF' o Added unit tests on seeding with an NMF object o Removed some obsolete comments BUG FIXES o an error was thrown when running multiple sequential NMF computations with nrun>=50: object '.MODE_SEQ' not found. (reported by Kenneth Lopiano) ************************************************************************* Changes in version 0.16.5 ************************************************************************* CHANGES o Now depends on pkgmaker 0.16 o Disabled shared memory on Mac machines by default, due to un-resolved bug that occurs with big-matrix descriptors. It can be restored using nmf.options(shared.memory=TRUE) o Package version is now shown on startup message o Re-enabled unit test checks on CRAN, using the new function pkgmaker::isCHECK, that definitely identifies if tests are run under R CMD check. ************************************************************************* * Changes in version 0.15.2 * ************************************************************************* NEW FEATURES o New function nmfObject() to update old versions of NMF objects, eg., saved on disk. o NMF strategies can now define default values for their parameters, eg., `seed` to associate them with a seeding method that is not the default method, `maxIter` to make default runs with more iterations (for algorithms defined as NMFStrategyIterative), or any other algorithm-specific parameter. o new general utility function hasArg2 that is identical to hasArg but takes the argument name as a character string, so that no check NOTE is thrown. CHANGES o All vignettes are now generated using knitr. o New dependency to pkgmaker which incorporated many of the general utility functions, initially defined for the NMF package. o example check is faster (as requested by CRAN) o the function selectMethodNMF was renamed into selectNMFMethod to be consistent with the other related *NMFMethod functions (get, set, etc..) FIXES o Fix computation of R-squared in profplot/corplot: now compute from a linear fit that includes an intercept. ************************************************************************* * Changes in version 0.8.6 * ************************************************************************* NEW FEATURES o Formula based NMF models that can incorporate fixed terms, which may be used to correct for covariates or define group specific offsets. CHANGES o Subsetting an NMF object with a single index now returns an NMF object, except if argument drop is not missing (i.e. either TRUE or FALSE). ************************************************************************* * Changes in version 0.6.03 * ************************************************************************* WARNING Due to the major changes made in the internal structure of the standard NMF models, previous NMF fits are not compatible with this version. NEW FEATURES o The factory function nmfModel has been enhanced and provides new methods that makes more easier the creation of NMF objects. See ?nmfModel. o A new heatmap drawing function 'aheatmap' (for annotated heatmap) is now used to generate the different heatmaps (basismap, coefmap and consensusmap). It is a enhancement/fork of the function pheatmap from package pheatmap and draw -- I think -- very nice heatmaps, providing a convenient and flexible interface to add annotation tracks to both the columns and rows, with sensible automatic legends. CHANGES o Method nmfModel when called with no arguments does not return anymore the list of available NMF models, but an empty NMF model. To list the available models, directly call `nmfModels()`. o The function `rmatrix` is now a S4 generic function. It gains methods for generating random matrices based on a template matrix or an NMF model. See ?rmatrix. o The function `rnmf` gains a method to generate a random NMF model given numerical dimensions. o The function nmfEstimateRank now returns the fits for each value of the rank in element 'fit' of the result list. See ?nmfEstimateRank. ************************************************************************* * Changes in version 0.5.3 * ************************************************************************* NEW FEATURES o The state of the random number generator is systematically stored in the 'NMFfit' object returned by function 'nmf'. It is stored in a new slot 'rng.seed' and can be access via the new method 'rngSeed' of class 'NMFfit'. See ?rngSeed for more details. o The number of cores to use in multicore computations can now also be specified by the 'p - parallel' runtime option (e.g. 'p4' to use 4 cores). Note that the value specified in option 'p' takes precedence on the one passed via argument '.pbackend'. See section 'Runtime options' in ?nmf for more details. o Function 'nmfApply' now allows values 3 and 4 for argument 'MARGIN' to apply a given function to the basis vectors (i.e. columns of the basis matrix W) and basis profiles (i.e. rows of the mixture coefficients H) respectively. See ?nmfApply for more details. o New S4 generic 'basiscor' and 'profcor' to compute the correlation matrices of the basis vectors and basis profiles respectively from two NMF models or from an NMF model and a given compatible matrix. See ?basiscor or ?profcor for more details. o New S4 generic 'fitcmp' to compare the NMF models fitted by two different runs of function 'nmf' (i.e. two 'NMFfit' objects). See ?fitcmp for more details. o New S4 generic 'canFit' that tells if an NMF method is able to exactly or partly fit a given NMF model. See ?canFit for more details. o New function 'selectMethodNMF' that selects an appropriate NMF method to fit a given NMF model. See ?selectMethodNMF for more details. o The verbosity level can be controlled more finely by the 'v - verbose' runtime option (e.g. using .options='v1' or 'v2'). The greater is the level the more information is printed. The verbose outputs have been cleaned-up and should be more consistent across the run mode (sequential or multicore). See section 'Runtime options' in ?nmf for more details. CHANGES o The standard update equations have been optimised further, by making them modify the factor matrices in place. This speeds up the computation and greatly improves the algorithms' memory footprint. o The package NMF now depends on the package digest that is used to display the state of random number generator in a concise way. o The methods 'metaHeatmap' are split into 3 new S4 generic 'basismap' to plot a heatmap of the basis matrix [formerly plotted by the call : 'metaHeatmap(object.of.class.NMF, 'features', ...) ], 'coefmap' to plot a heatmap of the mixture coefficient matrix [formerly plotted by the call : 'metaHeatmap(object.of.class.NMF, 'samples', ...) ], and 'consensusmap' to plot a heatmap of the consensus matrix associated with multiple runs of NMF [formerly plotted by the call : 'metaHeatmap(object.of.class.NMFfitX, ...) ]. BUG FIX o In factory method 'nmfModel': the colnames (resp. rownames) of matrix W (resp. H) are now correctly set when the rownames of H (resp. the colnames of W) are not null. NEWLY DEPRECATED CLASSES, METHODS, FUNCTIONS, DATA SETS o Deprecated Generics/Methods 1) 'metaHeatmap,NMF' and 'metaHeatmap,NMFfitX' - S4 methods remain with .Deprecated message. They are replaced by the definition of 3 new S4 generic 'basismap', 'coefmap' and 'consensusmap'. See the related point in section CHANGES above. They will be completely removed from the package in the next version. ************************************************************************* * Changes in version 0.5.1 * ************************************************************************* BUG FIX o fix a small bug in method 'rss' to allow argument 'target' to be a 'data.frame'. This was generating errors when computing the summary measures and especially when the function 'nmfEstimateRank' was called on a 'data.frame'. Thanks to Pavel Goldstein for reporting this. ************************************************************************* * Changes in version 0.5 * ************************************************************************* NEW FEATURES o Method 'fcnnls' provides a user-friendly interface for the internal function '.fcnnls' to solve non-nengative linear least square problems, using the fast combinatorial approach from Benthem and Keenan (2004). See '?fcnnls' for more details. o New argument 'callback' in method 'nmf' allows to provide a callback function when running multiple runs in default mode, that only keeps the best result. The callback function is applied to the result of each run before it possibly gets discarding. The results are stored in the miscellaneous slot '.callback' accessible via the '$' operator (e.g. res$.callback). See '?nmf' for more details. o New method 'niter' to retrieve the number of iterations performed to fit a NMF model. It is defined on objects of class 'NMFfit'. o New function 'isNMFfit' to check if an object is a result from NMF fitting. o New function 'rmatrix' to easily generate random matrices, and allow to specify the distribution from which the entries are drawn. For example: * 'rmatrix(100, 10)' generates a 100x10 matrix whose entries are drawn from the uniform distribution * 'rmatrix(100, 10, rnorm)' generates a 100x10 matrix whose entries are drawn from the standard Normal distribution. o New methods 'basisnames' and 'basisnames<-' to retrieve and set the basis vector names. See '?basisnames'. CHANGES o Add a CITATION file that provides the bibtex entries for citing the BMC Bioinformatics paper for the package (http://www.biomedcentral.com/1471-2105/11/367), the vignette and manual. See 'citation('NMF')' for complete references. o New argument 'ncol' in method 'nmfModel' to specify the target dimensions more easily by calls like 'nmfModel(r, n, p)' to create a r-rank NMF model that fits a n x p target matrix. o The subset method '[]' of objects of class 'NMF' has been changed to be more convenient. See BUG FIX o Method 'dimnames' for objects of class 'NMF' now correctly sets the names of each dimension. REMOVED CLASSES, METHODS, FUNCTIONS, DATA SETS o Method 'extra' has completely been removed. ************************************************************************* * Changes in version 0.4.8 * ************************************************************************* BUG FIX o When computing NMF with the SNMF/R(L) algorithms, an error could occur if a restart (recomputation of the initial value) for the H matrix (resp. W matrix) occured at the first iteration. Thanks to Joe Maisog for reporting this. ************************************************************************* * Changes in version 0.4.7 * ************************************************************************* BUG FIX o When computing the cophenetic correlation coefficient of a diagonal matrix, the 'cophcor' method was returning 'NA' with a warning from the 'cor' function. It now correctly returns 1. Thanks to Joe Maisog for reporting this. ************************************************************************* * Changes in version 0.4.4 * ************************************************************************* CHANGES o The major change is the explicit addition of the synchronicity package into the suggested package dependencies. Since the publication of versions 4.x of the bigmemory package, it is used by the NMF package to provide the mutex functionality required by multicore computations. Note This is relevant only for Linux/Mac-like platforms as the multicore package is not yet supported on MS Windows. Users using a recent version of the bigmemory package (i.e. >=4.x) DO NEED to install the synchronicity package to be able to run multicore NMF computations. Versions of bigmemory prior to 4.x include the mutex functionality. o Minor enhancement in error messages o Method 'nmfModel' can now be called with arguments 'rank' and 'target' swapped. This is for convenience and ease of use. BUG FIX o Argument 'RowSideColors' of the 'metaHeatmap' function is now correctly subset according to the value of argument 'filter'. However the argument must be named with its complete name 'RowSideColors', not assuming partial match. See KNOWN ISSUES. KNOWN ISSUES o In the 'metaHeatmap' function, when argument 'filter' is not 'FALSE': arguments 'RowSideColors', 'labRow', 'Rowv' that are intended to the 'heatmap.plus'-like function (and are used to label the rows) should be named using their complete name (i.e. not assuming partial match), otherwise the filtering is not applied to these argument and an error is generated. This issue will be fixed in a future release. ************************************************************************* * Changes in version 0.4.3 * ************************************************************************* CHANGES o function 'nmfEstimateRank' has been enhanced: -run options can be passed to each internal call to the 'nmf' function. See ?nmfEstiamteRank and ?nmf for details on the supported options. - a new argument 'stop' allows to run the estimation with fault tolerance, skipping runs that throw an error. Summary measures for these runs are set to NAs and a warning is thrown with details about the errors. o in function 'plot.NMF.rank': a new argument 'na.rm' allows to remove from the plots the ranks for which the measures are NAs (due to errors during the estimation process with 'nmfEstimateRank'). BUG FIX o Method 'consensus' is now exported in the NAMESPACE file. Thanks to Gang Su for reporting this. o Warnings and messages about loading packages are now suppressed. This was particularly confusing for users that do not have the packages and/or platform required for parallel computation: warnings were printed whereas the computation was -- sequentially -- performed without problem. Thanks to Joe Maisog for reporting this. ************************************************************************* * Changes in version 0.4.1 * ************************************************************************* BUG FIX o The 'metaHeatmap' function was not correctly handling row labels when argument filter was not FALSE. All usual row formating in heatmaps (label and ordering) are now working as expected. Thanks to Andreas Schlicker, from The Netherlands Cancer Institute for reporting this. o An error was thrown on some environments/platforms (e.g. Solaris) where not all the packages required for parallel computation were not available -- even when using option 'p' ('p' in lower case), which should have switched the computation to sequential. This is solved and the error is only thrown when running NMF with option 'P' (capital 'P'). o Not all the options were passed (e.g. 't' for tracking) in sequential mode (option '-p'). o verbose/debug global nmf.options were not restored if a numerical random seed were used. CHANGES o The 'metaHeatmap' function nows support passing the name of a filtering method in argument 'filter', which is passed to method 'extractFeatures'. See ?metaHeatmap. o Verbose and debug messages are better handled. When running a parallel computation of multiple runs, verbose messages from each run are shown only in debug mode. ************************************************************************* * Changes in version 0.4 * ************************************************************************* NEW FEATURES o Part of the code has been optimised in C++ for speed and memory efficiency: - the multiplicative updates for reducing the KL divergence and the euclidean distance have been optimised in C++. This significantly reduces the computation time of the methods that make use of them: 'lee', 'brunet', 'offset', 'nsNMF' and 'lnmf'. Old R version of the algorithm are still accessible with the suffix '.R#'. - the computation of euclidean distance and KL divergence are implemented in C++, and do not require the duplication of the original matrices as done in R. o Generic 'dimnames' is now defined for objects of class 'NMF' and returns a list with 3 elements: the row names of the basis matrix, the column names of the mixture coefficient matrix , and the column names of the basis matrix. This implies that methods 'rownames' and 'columnames' are also available for 'NMF' objects. o A new class structure has been developed to handle the results of multiple NMF runs in a cleaner and more structured way: - Class 'NMFfitX' defines a common interface for multiple NMF runs of a single algorithm. - Class 'NMFfitX1' handles the sub-case where only the best fit is returned. In particular, this class allows to handle such results as if coming from a single run. - Class 'NMFfitXn' handles the sub-case where the list of all the fits is returned. - Class 'NMFList' handles the case of heterogeneous NMF runs (different algorithms, different factorization rank, different dimension, etc...) o The vignette contains more examples and details about the use of package. o The package is compatible with both versions 3.x and 4.x of the bigmemory package. This package is used when running multicore parallel computations. With version 4.x of bigmemory, the synchronicity package is also required as it provides the mutex functionality that used to be provided by bigmemory 3.x. BUG FIX o Running in multicore mode from the GUI on MacOS X is not allowed anymore as it is not safe and were throwing an error ['The process has forked and ...']. Thanks to Stephen Henderson from the UCL Cancer Institute (UK) for reporting this. o Function 'nmf' now restores the random seed to its original value as before its call with a numeric seed. This behaviour can be disabled with option 'restore.seed=FALSE' or '-r' NEWLY DEPRECATED CLASSES, METHODS, FUNCTIONS, DATA SETS o Deprecated Generics/Methods 1) 'errorPlot' - S4 generic/methods remains with .Deprecated message. It is replaced by a definition of the 'plot' method for signatures 'NMFfit,missing' and 'NMFList,missing' It will be completely removed from the package in the next version. o Deprecated Class 1) 'NMFSet' - S4 class remains for backward compatibility, but is not used anymore. It is replaced by the classes 'NMFfitX1', 'NMFfitXn', 'NMFList'. ************************************************************************* * Changes in version 0.3 * ************************************************************************* NEW FEATURES o Now requires R 2.10 o New list slot 'misc' in class 'NMF' to be able to define new NMF models, without having to extend the S4 class. Access is done by methods '$' and '$<-'. o More robust and convenient interface 'nmf' o New built-in algorithm : PE-NMF [Zhang (2008)] o The vignette and documentation have been enriched with more examples and details on the functionalities. o When possible the computation is run in parallel on all the available cores. See option 'p' or 'parallel=TRUE' in argument '.options' of function 'nmf'. o Algorithms have been optimized and run faster o Plot for rank estimation: quality measure curves can be plotted together with a set of reference measures. The reference measures could for example come from the rank estimation of randomized data, to investigate overfitting. o New methods '$' and '$<-' to access slot 'extra' in class 'NMFfit' These methods replace method 'extra' that is now defunct. o Function 'randomize' allows to randomise a target matrix or 'ExpressionSet' by permuting the entries within each columns using a different permutation for each column. It is useful when checking for over-fitting. CHANGES o The 'random' method of class 'NMF' is renamed 'rnmf', but is still accessible through name 'random' via the 'seed' argument in the interface method 'nmf'. NEWLY DEFUNCT CLASSES, METHODS, FUNCTIONS, DATA SETS o Defunct Generics/Methods 1) 'extra' - S4 generic/methods remains with .Defunct message. It will be completely removed from the package in the next version. ************************************************************************* * Changes in version 0.2.4 * ************************************************************************* CHANGES o Class 'NMFStrategy' has a new slot 'mixed', that specify if the algorithm can handle mixed signed input matrices. o Method 'nmf-matrix,numeric,function' accepts a new parameter 'mixed' to specify if the custom algorithm can handle mixed signed input matrices. ************************************************************************* * Changes in version 0.2.3 * ************************************************************************* NEW FEATURES o Package 'Biobase' is not required anymore, and only suggested. The definition and export of the NMF-BioConductor layer is done at loading. o 'nmfApply' S4 generic/method: a 'apply'-like method for objects of class 'NMF'. o 'predict' S4 method for objects of class 'NMF': the method replace the now deprecated 'clusters' method. o 'featureNames' and 'sampleNames' S4 method for objects of class 'NMFSet'. o sub-setting S4 method '[' for objects of class 'NMF': row subsets are applied to the rows of the basis matrix, column subsets are applied to the columns of the mixture coefficient matrix. CHANGES o method 'featureScore' has a new argument 'method' to allow choosing between different scoring schema. o method 'extractFeatures' has two new arguments: 'method' allows choosing between different scoring and selection methods; 'format' allows to specify the output format. NOTE: the default output format has changed. The default result is now a list whose elements are integer vectors (one per basis component) that contain the indices of the basis-specific features. It is in practice what one is usually interested in. BUG FIXES o Methods 'basis<-' and 'coef<-' were not exported by file NAMESPACE. o Method 'featureNames' was returning the column names of the mixture coefficient matrix, instead of the row names of the basis matrix. o 'metaHeatmap' with argument 'class' set would throw an error. NEWLY DEPRECATED CLASSES, METHODS, FUNCTIONS, DATA SETS o Deprecated Generics/Methods 1) clusters - S4 generic/methods remain with .Deprecated message NMF/R/0000755000176200001440000000000013710761360011077 5ustar liggesusersNMF/R/rmatrix.R0000644000176200001440000001051513620502674012713 0ustar liggesusers# Generation of random matrices # # Defines the generic function `rmatrix` and basic methods for it. # # Author: Renaud Gaujoux ############################################################################### #' Generating Random Matrices #' #' The S4 generic \code{rmatrix} generates a random matrix from a given object. #' Methods are provided to generate matrices with entries drawn from any #' given random distribution function, e.g. \code{\link{runif}} or #' \code{\link{rnorm}}. #' #' @param x object from which to generate a random matrix #' #' @export setGeneric('rmatrix', function(x, ...) standardGeneric('rmatrix')) #' Generates a random matrix of given dimensions, whose entries #' are drawn using the distribution function \code{dist}. #' #' This is the workhorse method that is eventually called by all other methods. #' It returns a matrix with: #' \itemize{ #' \item \code{x} rows and \code{y} columns if \code{y} is not missing and #' not \code{NULL}; #' \item dimension \code{x[1]} x \code{x[2]} if \code{x} has at least two elements; #' \item dimension \code{x} (i.e. a square matrix) otherwise. #' } #' #' The default is to draw its entries from the standard uniform distribution using #' the base function \code{\link{runif}}, but any other function that generates #' random numeric vectors of a given length may be specified in argument \code{dist}. #' All arguments in \code{...} are passed to the function specified in \code{dist}. #' #' The only requirement is that the function in \code{dist} is of the following form: #' #' \samp{ #' function(n, ...){ #' # return vector of length n #' ... #' }} #' #' This is the case of all base random draw function such as \code{\link{rnorm}}, #' \code{\link{rgamma}}, etc\ldots #' #' #' @param y optional specification of number of columns #' @param dist a random distribution function or a numeric seed (see details of method #' \code{rmatrix,numeric}) #' @param byrow a logical passed in the internal call to the function #' \code{\link{matrix}} #' @param dimnames \code{NULL} or a \code{list} passed in the internal call to #' the function \code{\link{matrix}} #' @param ... extra arguments passed to the distribution function \code{dist}. #' #' @inline #' #' @examples #' ## Generate a random matrix of a given size #' rmatrix(5, 3) #' \dontshow{ stopifnot( identical(dim(rmatrix(5, 3)), c(5L,3L)) ) } #' #' ## Generate a random matrix of the same dimension of a template matrix #' a <- matrix(1, 3, 4) #' rmatrix(a) #' \dontshow{ stopifnot( identical(dim(rmatrix(a)), c(3L,4L)) ) } #' #' ## Specificy the distribution to use #' #' # the default is uniform #' a <- rmatrix(1000, 50) #' \dontrun{ hist(a) } #' #' # use normal ditribution #' a <- rmatrix(1000, 50, rnorm) #' \dontrun{ hist(a) } #' #' # extra arguments can be passed to the random variate generation function #' a <- rmatrix(1000, 50, rnorm, mean=2, sd=0.5) #' \dontrun{ hist(a) } #' setMethod('rmatrix', 'numeric', function(x, y=NULL, dist=runif, byrow = FALSE, dimnames = NULL, ...){ x <- as.integer(x) # early exit if x has length 0 if( length(x) == 0L ) stop("NMF::rmatrix - invalid empty vector in argument `x`.") # check/ensure that 'dist' is a function. if( is.null(dist) ) dist <- runif if( isNumber(dist) ){ os <- RNGseed() on.exit( RNGseed(os), add=TRUE) set.seed(dist) dist <- runif } if( !is.function(dist) ) stop("NMF::rmatrix - invalid value for argument 'dist': must be a function [class(dist)='", class(dist), "'].") # if 'y' is not specified: if( is.null(y) ){ if( length(x) == 1L ) y <- x # create a square matrix else{ # assume x contains all dimensions (e.g. returned by dim()) y <- x[2L] x <- x[1L] } }else{ y <- as.integer(y) y <- y[1L] # only use first element } # build the random matrix using the distribution function matrix(dist(x*y, ...), x, y, byrow=byrow, dimnames=dimnames) } ) #' Default method which calls \code{rmatrix,vector} on the dimensions of \code{x} #' that is assumed to be returned by a suitable \code{dim} method: #' it is equivalent to \code{rmatrix(dim(x), y=NULL, ...)}. #' #' @examples #' #' # random matrix of the same dimension as another matrix #' x <- matrix(3,4) #' dim(rmatrix(x)) #' setMethod('rmatrix', 'ANY', function(x, ...){ rmatrix(x=dim(x), y=NULL, ...) } ) NMF/R/aheatmap.R0000644000176200001440000021716113620502674013013 0ustar liggesusers#' @include atracks.R #' @include grid.R #' @include colorcode.R NULL library(grid) library(gridBase) # extends gpar objects c_gpar <- function(gp, ...){ x <- list(...) do.call(gpar, c(gp, x[!names(x) %in% names(gp)])) } lo <- function (rown, coln, nrow, ncol, cellheight = NA, cellwidth = NA , treeheight_col, treeheight_row, legend, main = NULL, sub = NULL, info = NULL , annTracks, annotation_legend , fontsize, fontsize_row, fontsize_col, gp = gpar()){ annotation_colors <- annTracks$colors row_annotation <- annTracks$annRow annotation <- annTracks$annCol gp0 <- gp coln_height <- unit(10, "bigpts") if(!is.null(coln)){ longest_coln = which.max(nchar(coln)) coln_height <- coln_height + unit(1.1, "grobheight", textGrob(coln[longest_coln], rot = 90, gp = c_gpar(gp, fontsize = fontsize_col))) } rown_width <- rown_width_min <- unit(10, "bigpts") if(!is.null(rown)){ longest_rown = which.max(nchar(rown)) rown_width <- rown_width_min + unit(1.2, "grobwidth", textGrob(rown[longest_rown], gp = c_gpar(gp, fontsize = fontsize_row))) } gp = c_gpar(gp, fontsize = fontsize) # Legend position if( !is_NA(legend) ){ longest_break = which.max(nchar(as.character(legend))) longest_break = unit(1.1, "grobwidth", textGrob(as.character(legend)[longest_break], gp = gp)) # minimum fixed width: plan for 2 decimals and a sign min_lw = unit(1.1, "grobwidth", textGrob("-00.00", gp = gp)) longest_break = max(longest_break, min_lw) title_length = unit(1.1, "grobwidth", textGrob("Scale", gp = c_gpar(gp0, fontface = "bold"))) legend_width = unit(12, "bigpts") + longest_break * 1.2 legend_width = max(title_length, legend_width) } else{ legend_width = unit(0, "bigpts") } .annLegend.dim <- function(annotation, fontsize){ # Width of the corresponding legend longest_ann <- unlist(lapply(annotation, names)) longest_ann <- longest_ann[which.max(nchar(longest_ann))] annot_legend_width = unit(1, "grobwidth", textGrob(longest_ann, gp = gp)) + unit(10, "bigpts") # width of the legend title annot_legend_title <- names(annotation)[which.max(nchar(names(annotation)))] annot_legend_title_width = unit(1, "grobwidth", textGrob(annot_legend_title, gp = c_gpar(gp, fontface = "bold"))) # total width max(annot_legend_width, annot_legend_title_width) + unit(5, "bigpts") } # Column annotations if( !is_NA(annotation) ){ # Column annotation height annot_height = unit(ncol(annotation) * (8 + 2) + 2, "bigpts") } else{ annot_height = unit(0, "bigpts") } # add a viewport for the row annotations if ( !is_NA(row_annotation) ) { # Row annotation width row_annot_width = unit(ncol(row_annotation) * (8 + 2) + 2, "bigpts") } else { row_annot_width = unit(0, "bigpts") } # Width of the annotation legend annot_legend_width <- if( annotation_legend && !is_NA(annotation_colors) ){ .annLegend.dim(annotation_colors, fontsize) }else unit(0, "bigpts") # Tree height treeheight_col = unit(treeheight_col, "bigpts") + unit(5, "bigpts") treeheight_row = unit(treeheight_row, "bigpts") + unit(5, "bigpts") # main title main_height <- if(!is.null(main)) unit(1, "grobheight", main) + unit(20, "bigpts") else unit(0, "bigpts") # sub title sub_height <- if(!is.null(sub)) unit(1, "grobheight", sub) + unit(10, "bigpts") else unit(0, "bigpts") # info panel if( !is.null(info) ){ info_height <- unit(1, "grobheight", info) + unit(20, "bigpts") info_width <- unit(1, "grobwidth", info) + unit(10, "bigpts") }else{ info_height <- unit(0, "bigpts") info_width <- unit(0, "bigpts") } # Set cell sizes if(is.na(cellwidth)){ matwidth = unit(1, "npc") - rown_width - legend_width - row_annot_width - treeheight_row - annot_legend_width } else{ matwidth = unit(cellwidth * ncol, "bigpts") } if(is.na(cellheight)){ matheight = unit(1, "npc") - treeheight_col - annot_height - main_height - coln_height - sub_height - info_height # recompute the cell width depending on the automatic fontsize if( is.na(cellwidth) && !is.null(rown) ){ cellheight <- convertHeight(unit(1, "grobheight", rectGrob(0,0, matwidth, matheight)), "bigpts", valueOnly = T) / nrow fontsize_row <- convertUnit(min(unit(fontsize_row, 'points'), unit(0.6*cellheight, 'bigpts')), 'points') rown_width <- rown_width_min + unit(1.2, "grobwidth", textGrob(rown[longest_rown], gp = c_gpar(gp0, fontsize = fontsize_row))) matwidth <- unit(1, "npc") - rown_width - legend_width - row_annot_width - treeheight_row - annot_legend_width } } else{ matheight = unit(cellheight * nrow, "bigpts") } # HACK: # - use 6 instead of 5 column for the row_annotation # - take into account the associated legend's width # Produce layout() unique.name <- vplayout(NULL) lo <- grid.layout(nrow = 7, ncol = 6 , widths = unit.c(treeheight_row, row_annot_width, matwidth, rown_width, legend_width, annot_legend_width) , heights = unit.c(main_height, treeheight_col, annot_height, matheight, coln_height, sub_height, info_height)) hvp <- viewport( name=paste('aheatmap', unique.name, sep='-'), layout = lo) pushViewport(hvp) #grid.show.layout(lo); stop('sas') # Get cell dimensions vplayout('mat') cellwidth = convertWidth(unit(1, "npc"), "bigpts", valueOnly = T) / ncol cellheight = convertHeight(unit(1, "npc"), "bigpts", valueOnly = T) / nrow upViewport() height <- as.numeric(convertHeight(sum(lo$height), "inches")) width <- as.numeric(convertWidth(sum(lo$width), "inches")) # Return minimal cell dimension in bigpts to decide if borders are drawn mindim = min(cellwidth, cellheight) return( list(width=width, height=height, vp=hvp, mindim=mindim, cellwidth=cellwidth, cellheight=cellheight) ) } draw_dendrogram = function(hc, horizontal = T){ # .draw.dendrodram <- function(hc){ # # # convert into an hclust if necessary # if( is(hc, 'dendrogram') ){ # hca <- attr(hc, 'hclust') # hc <- if( !is.null(hca) ) hca else as.hclust(hc) # } # # h = hc$height / max(hc$height) / 1.05 # m = hc$merge # o = hc$order # n = length(o) # # m[m > 0] = n + m[m > 0] # m[m < 0] = abs(m[m < 0]) # # dist = matrix(0, nrow = 2 * n - 1, ncol = 2, dimnames = list(NULL, c("x", "y"))) # dist[1:n, 1] = 1 / n / 2 + (1 / n) * (match(1:n, o) - 1) # # for(i in 1:nrow(m)){ # dist[n + i, 1] = (dist[m[i, 1], 1] + dist[m[i, 2], 1]) / 2 # dist[n + i, 2] = h[i] # } # # draw_connection = function(x1, x2, y1, y2, y){ # grid.lines(x = c(x1, x1), y = c(y1, y)) # grid.lines(x = c(x2, x2), y = c(y2, y)) # grid.lines(x = c(x1, x2), y = c(y, y)) # } # # # create a rotating viewport for vertical dendrogram # if(!horizontal){ # gr = rectGrob() # pushViewport(viewport(height = unit(1, "grobwidth", gr), width = unit(1, "grobheight", gr), angle = 90)) # on.exit(upViewport()) # } # # for(i in 1:nrow(m)){ # draw_connection(dist[m[i, 1], 1], dist[m[i, 2], 1], dist[m[i, 1], 2], dist[m[i, 2], 2], h[i]) # } # # } .draw.dendrodram <- function(hc, ...){ # suppressWarnings( opar <- par(plt = gridPLT(), new = TRUE) ) ( opar <- par(plt = gridPLT(), new = TRUE) ) on.exit(par(opar)) if( getOption('verbose') ) grid.rect(gp = gpar(col = "blue", lwd = 2)) if( !is(hc, 'dendrogram') ) hc <- as.dendrogram(hc) plot(hc, horiz=!horizontal, xaxs="i", yaxs="i", axes=FALSE, leaflab="none", ...) } # create a margin viewport if(!horizontal) pushViewport( viewport(x=0,y=0,width=0.9,height=1,just=c("left", "bottom")) ) else pushViewport( viewport(x=0,y=0.1,width=1,height=0.9,just=c("left", "bottom")) ) on.exit(upViewport()) .draw.dendrodram(hc) } # draw a matrix first row at bottom, last at top draw_matrix = function(matrix, border_color, txt = NULL, gp = gpar()){ n = nrow(matrix) m = ncol(matrix) x = (1:m)/m - 1/2/m y = (1:n)/n - 1/2/n # substitute NA values with empty strings if( !is.null(txt) ) txt[is.na(txt)] <- '' for(i in 1:m){ grid.rect(x = x[i], y = y, width = 1/m, height = 1/n, gp = gpar(fill = matrix[,i], col = border_color)) if( !is.null(txt) ){ grid.text(label=txt[, i], x=x[i], y=y, # just=just, # hjust=hjust, # vjust=vjust, rot=0, check.overlap= FALSE, #check.overlap, default.units= 'npc', #default.units, # name=name, gp=gp, # draw=draw, # vp=vp ) } } } draw_colnames = function(coln, gp = gpar()){ m = length(coln) # decide on the label orientation width <- m * unit(1, "grobwidth", textGrob(coln[i <- which.max(nchar(coln))], gp = gp)) width <- as.numeric(convertWidth(width, "inches")) gwidth <- as.numeric(convertWidth(unit(1, 'npc'), "inches")) y <- NULL if( gwidth < width ){ rot <- 270 vjust <- 0.5 hjust <- 0 y <- unit(1, 'npc') - unit(5, 'bigpts') }else{ rot <- 0 vjust <- 0.5 hjust <- 0.5 } if( is.null(y) ){ height <- unit(1, "grobheight", textGrob(coln[i], vjust = vjust, hjust = hjust, rot=rot, gp = gp)) y <- unit(1, 'npc') - height } x = (1:m)/m - 1/2/m grid.text(coln, x = x, y = y, vjust = vjust, hjust = hjust, rot=rot, gp = gp) } # draw rownames first row at bottom, last on top draw_rownames = function(rown, gp = gpar()){ n = length(rown) y = (1:n)/n - 1/2/n grid.text(rown, x = unit(5, "bigpts"), y = y, vjust = 0.5, hjust = 0, gp = gp) } draw_legend = function(color, breaks, legend, gp = gpar()){ height = min(unit(1, "npc"), unit(150, "bigpts")) pushViewport(viewport(x = 0, y = unit(1, "npc"), just = c(0, 1), height = height)) legend_pos = (legend - min(breaks)) / (max(breaks) - min(breaks)) breaks = (breaks - min(breaks)) / (max(breaks) - min(breaks)) h = breaks[-1] - breaks[-length(breaks)] grid.rect(x = 0, y = breaks[-length(breaks)], width = unit(10, "bigpts"), height = h, hjust = 0, vjust = 0, gp = gpar(fill = color, col = "#FFFFFF00")) grid.text(legend, x = unit(12, "bigpts"), y = legend_pos, hjust = 0, gp = gp) upViewport() } convert_annotations = function(annotation, annotation_colors){ #new = annotation x <- sapply(seq_along(annotation), function(i){ #for(i in 1:length(annotation)){ a = annotation[[i]] b <- attr(a, 'color') if( is.null(b) ) b = annotation_colors[[names(annotation)[i]]] if(class(a) %in% c("character", "factor")){ a = as.character(a) #print(names(b)) #print(unique(a)) if ( FALSE && length(setdiff(names(b), a)) > 0){ stop(sprintf("Factor levels on variable %s do not match with annotation_colors", names(annotation)[i])) } #new[, i] = b[a] b[match(a, names(b))] } else{ a = cut(a, breaks = 100) #new[, i] = colorRampPalette(b)(100)[a] ccRamp(b, 100)[a] } }) colnames(x) <- names(annotation) return(x) #return(as.matrix(new)) } draw_annotations = function(converted_annotations, border_color, horizontal=TRUE){ n = ncol(converted_annotations) m = nrow(converted_annotations) if( horizontal ){ x = (1:m)/m - 1/2/m y = cumsum(rep(8, n)) - 4 + cumsum(rep(2, n)) for(i in 1:m){ grid.rect(x = x[i], unit(y[n:1], "bigpts"), width = 1/m, height = unit(8, "bigpts"), gp = gpar(fill = converted_annotations[i, ], col = border_color)) } }else{ x = cumsum(rep(8, n)) - 4 + cumsum(rep(2, n)) y = (1:m)/m - 1/2/m for (i in 1:m) { grid.rect(x = unit(x[1:n], "bigpts"), y=y[i], width = unit(8, "bigpts"), height = 1/m, gp = gpar(fill = converted_annotations[i,] , col = border_color)) } } } draw_annotation_legend = function(annotation_colors, border_color, gp = gpar()){ y = unit(1, "npc") text_height = convertHeight(unit(1, "grobheight", textGrob("FGH", gp = gp)), "bigpts") for(i in names(annotation_colors)){ grid.text(i, x = 0, y = y, vjust = 1, hjust = 0, gp = c_gpar(gp, fontface = "bold")) y = y - 1.5 * text_height #if(class(annotation[[i]]) %in% c("character", "factor")){ acol <- annotation_colors[[i]] if( attr(acol, 'afactor') ){ sapply(seq_along(acol), function(j){ grid.rect(x = unit(0, "npc"), y = y, hjust = 0, vjust = 1, height = text_height, width = text_height, gp = gpar(col = border_color, fill = acol[j])) grid.text(names(acol)[j], x = text_height * 1.3, y = y, hjust = 0, vjust = 1, gp = gp) y <<- y - 1.5 * text_height }) } else{ yy = y - 4 * text_height + seq(0, 1, 0.01) * 4 * text_height h = 4 * text_height * 0.02 grid.rect(x = unit(0, "npc"), y = yy, hjust = 0, vjust = 1, height = h, width = text_height, gp = gpar(col = "#FFFFFF00", fill = ccRamp(acol, 100))) txt = c(tail(names(acol),1), head(names(acol))[1]) yy = y - c(0, 3) * text_height grid.text(txt, x = text_height * 1.3, y = yy, hjust = 0, vjust = 1, gp = gp) y = y - 4.5 * text_height } y = y - 1.5 * text_height } } vplayout <- function () { graphic.name <- NULL .index <- 0L function(x, y, verbose = getOption('verbose') ){ # initialize the graph name if( is.null(x) ){ .index <<- .index + 1L graphic.name <<- paste0("AHEATMAP.VP.", .index) #grid:::vpAutoName() return(graphic.name) } name <- NULL if( !is.numeric(x) ){ name <- paste(graphic.name, x, sep='-') if( !missing(y) && is(y, 'viewport') ){ y$name <- name return(pushViewport(y)) } if( !is.null(tryViewport(name, verbose=verbose)) ) return() switch(x , main={x<-1; y<-3;} , ctree={x<-2; y<-3;} , cann={x<-3; y<-3;} , rtree={x<-4; y<-1;} , rann={x<-4; y<-2;} , mat={x<-4; y<-3;} , rnam={x<-4; y<-4;} , leg={x<-4; y<-5;} , aleg={x<-4; y<-6;} , cnam={x<-5; y<-3;} , sub={x<-6; y<-3;} , info={x<-7; y<-3;} , stop("aheatmap - invalid viewport name") ) } if( verbose ) message("vp - create ", name) pushViewport(viewport(layout.pos.row = x, layout.pos.col = y, name=name)) } } vplayout <- vplayout() #' Open a File Graphic Device #' #' Opens a graphic device depending on the file extension #' #' @keywords internal gfile <- function(filename, width, height, ...){ # Get file type r = regexpr("\\.[a-zA-Z]*$", filename) if(r == -1) stop("Improper filename") ending = substr(filename, r + 1, r + attr(r, "match.length")) f = switch(ending, pdf = function(x, ...) pdf(x, ...), svg = function(x, ...) svg(x, ...), png = function(x, ...) png(x, ...), jpeg = function(x, ...) jpeg(x, ...), jpg = function(x, ...) jpeg(x, ...), tiff = function(x, ...) tiff(x, compression = "lzw", ...), bmp = function(x, ...) bmp(x, ...), stop("File type should be: pdf, svg, png, bmp, jpg, tiff") ) args <- c(list(filename), list(...)) if( !missing(width) ){ args$width <- as.numeric(width) args$height <- as.numeric(height) if( !ending %in% c('pdf','svg') && is.null(args[['res']]) ){ args$units <- "in" args$res <- 300 } } do.call('f', args) } #gt <- function(){ # # x <- rmatrix(20, 10) # z <- unit(0.1, "npc") # w <- unit(0.4, "npc") # h <- unit(0.3, "npc") # lo <- grid.layout(nrow = 7, ncol = 6 # , widths = unit.c(z, z, w, z, z, z) # , heights = unit.c(z, z, z, h, z, z, z)) # # nvp <- 0 # on.exit( upViewport(nvp) ) # # u <- vplayout(NULL) # vname <- function(x) basename(tempfile(x)) # # hvp <- viewport( name=u, layout = lo) # pushViewport(hvp) # nvp <- nvp + 1 # # pushViewport(viewport(layout.pos.row = 4, layout.pos.col = 3, name='test')) # #vplayout('mat') # nvp <- nvp + 1 # # grid.rect() # NULL #} #gt2 <- function(){ # # x <- rmatrix(10, 5) # lo(NULL, NULL, nrow(x), ncol(x), cellheight = NA, cellwidth = NA # , treeheight_col=0, treeheight_row=0, legend=FALSE, main = NULL, sub = NULL, info = NULL # , annTracks=list(colors=NA, annRow=NA, annCol=NA), annotation_legend=FALSE # , fontsize=NULL, fontsize_row=NULL, fontsize_col=NULL) # # #vplayout('mat') # vname <- function(x) basename(tempfile(x)) # pushViewport(viewport(layout.pos.row = 4, layout.pos.col = 3, name=vname('test'))) # print(current.vpPath()) # grid.rect() # upViewport(2) # NULL #} d <- function(x){ if( is.character(x) ) x <- rmatrix(dim(x)) nvp <- 0 on.exit(upViewport(nvp), add=TRUE) lo <- grid.layout(nrow = 4, ncol = 3) hvp <- viewport( name=basename(tempfile()), layout = lo) pushViewport(hvp) nvp <- nvp + 1 pushViewport(viewport(layout.pos.row = 2, layout.pos.col = 1)) nvp <- nvp + 1 w = convertWidth(unit(1, "npc"), "bigpts", valueOnly = T) / 10 h = convertHeight(unit(1, "npc"), "bigpts", valueOnly = T) / 10 grid.rect() upViewport() nvp <- nvp - 1 pushViewport(viewport(layout.pos.row = 1, layout.pos.col = 2)) nvp <- nvp + 1 # add inner padding viewport pushViewport( viewport(x=0,y=0,width=0.9,height=0.9,just=c("left", "bottom")) ) nvp <- nvp + 1 ( opar <- par(plt = gridPLT(), new = TRUE) ) on.exit(par(opar), add=TRUE) hc <- hclust(dist(x)) plot(as.dendrogram(hc), xaxs="i", yaxs="i", axes=FALSE, leaflab="none") invisible(basename(tempfile())) } heatmap_motor = function(matrix, border_color, cellwidth, cellheight , tree_col, tree_row, treeheight_col, treeheight_row , filename=NA, width=NA, height=NA , breaks, color, legend, txt = NULL , annTracks, annotation_legend=TRUE , new=TRUE, fontsize, fontsize_row, fontsize_col , main=NULL, sub=NULL, info=NULL , verbose=getOption('verbose') , gp = gpar()){ annotation_colors <- annTracks$colors row_annotation <- annTracks$annRow annotation <- annTracks$annCol writeToFile <- !is.na(filename) # open graphic device (dimensions will be changed after computation of the correct height) if( writeToFile ){ gfile(filename) on.exit(dev.off()) } # identify the plotting context: base or grid #NB: use custom function current.vpPath2 instead of official # grid::current.vpPath as this one creates a new page when called # on a fresh graphic device vpp <- current.vpPath_patched() if( is.null(vpp) ){ # we are at the root viewport if( verbose ) message("Detected path: [ROOT]") mf <- par('mfrow') #print(mf) # if in in mfrow/layout context: setup fake-ROOT viewports with gridBase # and do not call plot.new as it is called in grid.base.mix. new <- if( !identical(mf, c(1L,1L)) ){ if( verbose ) message("Detected mfrow: ", mf[1], " - ", mf[2], ' ... MIXED') opar <- grid.base.mix(trace=verbose>1) on.exit( grid.base.mix(opar) ) FALSE } else{ if( verbose ){ message("Detected mfrow: ", mf[1], " - ", mf[2]) message("Honouring ", if( missing(new) ) "default " ,"argument `new=", new, '` ... ' , if( new ) "NEW" else "OVERLAY") } new } }else{ if( verbose ) message("Detected path: ", vpp) # if new is not specified: change the default behaviour by not calling # plot.new so that drawing occurs in the current viewport if( missing(new) ){ if( verbose ) message("Missing argument `new` ... OVERLAY") new <- FALSE }else if( verbose ) message("Honouring argument `new=", new, '` ... ' , if( new ) "NEW" else "OVERLAY") } # reset device if necessary or requested if( new ){ if( verbose ) message("Call: plot.new") #grid.newpage() plot.new() } # define grob for main mainGrob <- if( !is.null(main) && !is.grob(main) ) textGrob(main, gp = c_gpar(gp, fontsize = 1.2 * fontsize, fontface="bold")) subGrob <- if( !is.null(sub) && !is.grob(sub) ) textGrob(sub, gp = c_gpar(gp, fontsize = 0.8 * fontsize)) infoGrob <- if( !is.null(info) && !is.grob(info) ){ # infotxt <- paste(strwrap(paste(info, collapse=" | "), width=20), collapse="\n") grobTree(gList(rectGrob(gp = gpar(fill = "grey80")) ,textGrob(paste(info, collapse=" | "), x=unit(5, 'bigpts'), y=0.5, just='left', gp = c_gpar(gp, fontsize = 0.8 * fontsize)))) } # Set layout glo = lo(coln = colnames(matrix), rown = rownames(matrix), nrow = nrow(matrix), ncol = ncol(matrix) , cellwidth = cellwidth, cellheight = cellheight , treeheight_col = treeheight_col, treeheight_row = treeheight_row , legend = legend , annTracks = annTracks, annotation_legend = annotation_legend , fontsize = fontsize, fontsize_row = fontsize_row, fontsize_col = fontsize_col , main = mainGrob, sub = subGrob, info = infoGrob, gp = gp) # resize the graphic file device if necessary if( writeToFile ){ if( verbose ) message("Compute size for file graphic device") m <- par('mar') if(is.na(height)) height <- glo$height if(is.na(width)) width <- glo$width dev.off() if( verbose ) message("Resize file graphic device to: ", width, " - ", height) gfile(filename, width=width, height=height) # re-call plot.new if it was called before if( new ){ if( verbose ) message("Call again plot.new") op <- par(mar=c(0,0,0,0)) plot.new() par(op) } if( verbose ) message("Push again top viewport") # repush the layout pushViewport(glo$vp) if( verbose ) grid.rect(width=unit(glo$width, 'inches'), height=unit(glo$height, 'inches'), gp = gpar(col='blue')) } #grid.show.layout(glo$layout); return() mindim <- glo$mindim # Omit border color if cell size is too small if(mindim < 3) border_color = NA # Draw tree for the columns if (!is_NA(tree_col) && treeheight_col != 0){ #vplayout(1, 2) vplayout('ctree') draw_dendrogram(tree_col, horizontal = T) upViewport() } # Draw tree for the rows if(!is_NA(tree_row) && treeheight_row !=0){ #vplayout(3, 1) vplayout('rtree') draw_dendrogram(tree_row, horizontal = F) upViewport() } # recompute margin fontsizes fontsize_row <- convertUnit(min(unit(fontsize_row, 'points'), unit(0.6*glo$cellheight, 'bigpts')), 'points') fontsize_col <- convertUnit(min(unit(fontsize_col, 'points'), unit(0.6*glo$cellwidth, 'bigpts')), 'points') # Draw matrix #vplayout(3, 2) vplayout('mat') draw_matrix(matrix, border_color, txt = txt, gp = gpar(fontsize = fontsize_row)) #d(matrix) #grid.rect() upViewport() # Draw colnames if(length(colnames(matrix)) != 0){ #vplayout(4, 2) vplayout('cnam') draw_colnames(colnames(matrix), gp = c_gpar(gp, fontsize = fontsize_col)) upViewport() } # Draw rownames if(length(rownames(matrix)) != 0){ #vplayout(3, 3) vplayout('rnam') draw_rownames(rownames(matrix), gp = c_gpar(gp, fontsize = fontsize_row)) upViewport() } # Draw annotation tracks if( !is_NA(annotation) ){ #vplayout(2, 2) vplayout('cann') draw_annotations(annotation, border_color) upViewport() } # add row annotations if necessary if ( !is_NA(row_annotation) ) { vplayout('rann') draw_annotations(row_annotation, border_color, horizontal=FALSE) upViewport() } # Draw annotation legend if( annotation_legend && !is_NA(annotation_colors) ){ #vplayout(3, 5) vplayout('aleg') draw_annotation_legend(annotation_colors, border_color, gp = c_gpar(gp, fontsize = fontsize)) upViewport() } # Draw legend if(!is_NA(legend)){ #vplayout(3, 4) vplayout('leg') draw_legend(color, breaks, legend, gp = c_gpar(gp, fontsize = fontsize)) upViewport() } # Draw main if(!is.null(mainGrob)){ vplayout('main') grid.draw(mainGrob) upViewport() } # Draw subtitle if(!is.null(subGrob)){ vplayout('sub') grid.draw(subGrob) upViewport() } # Draw info if(!is.null(infoGrob)){ vplayout('info') grid.draw(infoGrob) upViewport() } # return current vp tree #ct <- current.vpTree() #print(current.vpPath()) upViewport() #popViewport() # grab current grob and return # gr <- grid.grab() # grid.draw(gr) #ct NULL } generate_breaks = function(x, n, center=NA){ if( missing(center) || is_NA(center) ) seq(min(x, na.rm = T), max(x, na.rm = T), length.out = n + 1) else{ # center the breaks on the requested value n2 <- ceiling((n+0.5)/2) M <- max(abs(center - min(x, na.rm = TRUE)), abs(center - max(x, na.rm = TRUE))) lb <- seq(center-M, center, length.out = n2) rb <- seq(center, center+M, length.out = n2) c(lb, rb[-1]) } } scale_vec_colours = function(x, col = rainbow(10), breaks = NA){ return(col[as.numeric(cut(x, breaks = breaks, include.lowest = T))]) } scale_colours = function(mat, col = rainbow(10), breaks = NA){ mat = as.matrix(mat) return(matrix(scale_vec_colours(as.vector(mat), col = col, breaks = breaks), nrow(mat), ncol(mat), dimnames = list(rownames(mat), colnames(mat)))) } cutheight <- function(x, n){ # exit early if n <=1: nothing to do if( n <=1 ) return( attr(x, 'height') ) res <- NULL .heights <- function(subtree, n){ if( is.leaf(subtree) ) return() if (!(K <- length(subtree))) stop("non-leaf subtree of length 0") # extract heights from each subtree for( k in 1:K){ res <<- c(res, attr(subtree[[k]], 'height')) } # continue only if there is not yet enough subtrees if( length(res) < n ){ for( k in 1:K){ .heights(subtree[[k]], n) } } } # extract at least the top h heights .heights(x, n) # sort by decreasing order res <- sort(res, decreasing=TRUE) res[n-1] } #' Fade Out the Upper Branches from a Dendrogram #' #' @param x a dendrogram #' @param n the number of groups #' #' @import digest #' @keywords internal cutdendro <- function(x, n){ # exit early if n <=1: nothing to do if( n <= 1 ) return(x) # add node digest ids to x x <- dendrapply(x, function(n){ attr(n, 'id') <- digest(attributes(n)) n }) # cut x in n groups # find the height where to cut h <- cutheight(x, n) cfx <- cut(x, h) # get the ids of the upper nodes ids <- sapply(cfx$lower, function(sub) attr(sub, 'id')) # highlight the upper branches with dot lines dts <- c(lty=2, lwd=1.2, col=8) a <- dendrapply(x, function(node){ a <- attributes(node) if( a$id %in% ids || (!is.leaf(node) && any(c(attr(node[[1]], 'id'), attr(node[[2]], 'id')) %in% ids)) ) attr(node, 'edgePar') <- dts node }) } # internal class definition for as_treedef <- function(x, ...){ res <- if( is(x, 'hclust') ) list(dendrogram=as.dendrogram(x), dist.method=x$dist.method, method=x$method) else list(dendrogram=x, ...) class(res) <- "aheatmap_treedef" res } rev.aheatmap_treedef <- function(x){ x$dendrogram <- rev(x$dendrogram) x } is_treedef <- function(x) is(x, 'aheatmap_treedef') isLogical <- function(x) isTRUE(x) || identical(x, FALSE) # Convert an index vector usable on the subset data into one usable on the # original data subset2orginal_idx <- function(idx, subset){ if( is.null(subset) || is.null(idx) ) idx else{ res <- subset[idx] attr(res, 'subset') <- idx res } } #' Cluster Matrix Rows in Annotated Heatmaps #' #' @param mat original input matrix that has already been appropriately subset in #' the caller function (\code{aheatmap}) #' @param param clustering specifications #' @param distfun Default distance method/function #' @param hclustfun Default clustering (linkage) method/function #' @param reorderfun Default reordering function #' @param na.rm Logical that specifies if NA values should be removed #' @param subset index (integer) vector specifying the subset indexes used to #' subset mat. This is required to be able to return the original indexes. #' #' @keywords internal cluster_mat = function(mat, param, distfun, hclustfun, reorderfun, na.rm=TRUE, subset=NULL, verbose = FALSE){ # do nothing if an hclust object is passed parg <- deparse(substitute(param)) Rowv <- if( is(param, 'hclust') || is(param, 'dendrogram') ){ # hclust or dendrograms are honoured res <- as_treedef(param) # subset if requested: convert into an index vector # the actuval subsetting is done by first case (index vector) if( !is.null(subset) ){ warning("Could not directly subset dendrogram/hclust object `", parg ,"`: using subset of the dendrogram's order instead.") # use dendrogram order instead of dendrogram itself param <- order.dendrogram(res$dendrogram) }else # EXIT: return treedef return(res) }else if( is(param, 'silhouette') ){ # use silhouette order si <- sortSilhouette(param) param <- attr(si, 'iOrd') } # index vectors are honoured if( is.integer(param) && length(param) > 1 ){ # subset if requested: reorder the subset indexes as in param if( !is.null(subset) ) param <- order(match(subset, param)) param }else{ # will compute dendrogram (NB: mat was already subset before calling cluster_mat) param <- if( is.integer(param) ) param else if( is.null(param) || isLogical(param) ) # use default reordering by rowMeans rowMeans(mat, na.rm=na.rm) else if( is.numeric(param) ){ # numeric reordering weights # subset if necessary if( !is.null(subset) ) param <- param[subset] param }else if( is.character(param) || is.list(param) ){ if( length(param) == 0 ) stop("aheatmap - Invalid empty character argument `", parg, "`.") # set default names if no names were provided if( is.null(names(param)) ){ if( length(param) > 3 ){ warning("aheatmap - Only using the three first elements of `", parg, "` for distfun and hclustfun respectively.") param <- param[1:3] } n.allowed <- c('distfun', 'hclustfun', 'reorderfun') names(param) <- head(n.allowed, length(param)) } # use the distance passed in param if( 'distfun' %in% names(param) ) distfun <- param[['distfun']] # use the clustering function passed in param if( 'hclustfun' %in% names(param) ) hclustfun <- param[['hclustfun']] # use the reordering function passed in param if( 'reorderfun' %in% names(param) ) reorderfun <- param[['reorderfun']] TRUE }else stop("aheatmap - Invalid value for argument `", parg, "`. See ?aheatmap.") # compute distances d <- if( isString(distfun) ){ distfun <- distfun[1] corr.methods <- c("pearson", "kendall", "spearman") av <- c("correlation", corr.methods, "euclidean", "maximum", "manhattan", "canberra", "binary", "minkowski") i <- pmatch(distfun, av) if( is_NA(i) ) stop("aheatmap - Invalid dissimilarity method, must be one of: ", str_out(av, Inf)) distfun <- av[i] if(distfun == "correlation") distfun <- 'pearson' if(distfun %in% corr.methods){ # distance from correlation matrix if( verbose ) message("Using distance method: correlation (", distfun, ')') d <- dist(1 - cor(t(mat), method = distfun)) attr(d, 'method') <- distfun d }else{ if( verbose ) message("Using distance method: ", distfun) dist(mat, method = distfun) } }else if( is(distfun, "dist") ){ if( verbose ) message("Using dist object: ", distfun) distfun }else if( is.function(distfun) ){ if( verbose ) message("Using custom dist function") distfun(mat) }else stop("aheatmap - Invalid dissimilarity function: must be a character string, an object of class 'dist', or a function") # do hierarchical clustering hc <- if( is.character(hclustfun) ){ av <- c('ward', 'single', 'complete', 'average', 'mcquitty', 'median', 'centroid') i <- pmatch(hclustfun, av) if( is.na(i) ) stop("aheatmap - Invalid clustering method, must be one of: ", paste("'", av, "'", sep='', collapse=', ')) hclustfun <- av[i] if( verbose ) message("Using clustering method: ", hclustfun) hclust(d, method=hclustfun) }else if( is.function(hclustfun) ) hclustfun(d) else stop("aheatmap - Invalid clustering function: must be a character string or a function") #convert into a dendrogram dh <- as.dendrogram(hc) # customize the dendrogram plot: highlight clusters if( is.integer(param) ) dh <- cutdendro(dh, param) else if( is.numeric(param) && length(param)==nrow(mat) ) # reorder the dendrogram if necessary dh <- reorderfun(dh, param) # wrap up into a aheatmap_treedef object as_treedef(dh, dist.method=hc$dist.method, method=hc$method) } } #scale_rows = function(x){ # m = apply(x, 1, mean) # s = apply(x, 1, sd) # return((x - m) / s) #} scale_mat = function(x, scale, na.rm=TRUE){ av <- c("none", "row", "column", 'r1', 'c1') i <- pmatch(scale, av) if( is_NA(i) ) stop("scale argument shoud take values: 'none', 'row' or 'column'") scale <- av[i] switch(scale, none = x , row = { x <- sweep(x, 1L, rowMeans(x, na.rm = na.rm), check.margin = FALSE) sx <- apply(x, 1L, sd, na.rm = na.rm) sweep(x, 1L, sx, "/", check.margin = FALSE) } , column = { x <- sweep(x, 2L, colMeans(x, na.rm = na.rm), check.margin = FALSE) sx <- apply(x, 2L, sd, na.rm = na.rm) sweep(x, 2L, sx, "/", check.margin = FALSE) } , r1 = sweep(x, 1L, rowSums(x, na.rm = na.rm), '/', check.margin = FALSE) , c1 = sweep(x, 2L, colSums(x, na.rm = na.rm), '/', check.margin = FALSE) ) } .Rd.seed <- new.env() round.pretty <- function(x, min=2){ if( is.null(x) ) return(NULL) n <- 0 y <- round(sort(x), n) if( all(diff(y)==0) ) return( round(x, min) ) while( any(diff(y)==0) ){ n <- n+1 y <- round(sort(x), n) } dec <- max(min,n) round(x, dec) } generate_annotation_colours = function(annotation, annotation_colors, seed=TRUE){ if( is_NA(annotation_colors) ){ annotation_colors = list() } # use names from annotations if necessary/possible if( length(annotation_colors) > 0L && length(annotation_colors) <= length(annotation) && is.null(names(annotation_colors)) ){ names(annotation_colors) <- head(names(annotation), length(annotation_colors)) } count = 0 annotationLevels <- list() anames <- names(annotation) sapply(seq_along(annotation), function(i){ a <- annotation[[i]] if( class(annotation[[i]]) %in% c("character", "factor")){ # convert to character vector a <- if( is.factor(a) ) levels(a) else unique(a) count <<- count + nlevels(a) # merge if possible if( !is.null(anames) && anames[i]!='' ) annotationLevels[[anames[i]]] <<- unique(c(annotationLevels[[anames[i]]], a)) else annotationLevels <<- c(annotationLevels, list(a)) }else annotationLevels <<- c(annotationLevels, annotation[i]) }) annotation <- annotationLevels #str(annotationLevels) factor_colors = hcl(h = seq(1, 360, length.out = max(count+1,20)), 100, 70) # get random seeds to restore/update on exit rs <- RNGseed() on.exit({ # update local random seed on exit .Rd.seed$.Random.seed <- getRNG() # restore global random seed RNGseed(rs) }) # restore local random seed if it exists if( !is.null(.Rd.seed$.Random.seed) ) setRNG(.Rd.seed$.Random.seed) # set seed and restore on exit if( isTRUE(seed) ){ # reset .Random.seed to a dummy RNG in case the current kind is user-supplied: # we do not want to draw even once from the current RNG setRNG(c(401L, 0L, 0L)) set.seed(12345, 'default', 'default') } factor_colors <- sample(factor_colors) # pal(factor_colors); stop("sasa") res_colors <- list() for(i in 1:length(annotation)){ ann <- annotation[[i]] aname <- names(annotation)[i] # skip already generated colors acol_def <- res_colors[[aname]] if( !is.null(acol_def) ) next; acol <- annotation_colors[[aname]] if( is.null(acol) ){ res_colors[[aname]] <- if( class(annotation[[i]]) %in% c("character", "factor")){ lev <- ann ind = 1:length(lev) acol <- setNames(factor_colors[ind], lev) factor_colors = factor_colors[-ind] # conserve NA value acol[which(is.na(names(acol)))] <- NA acol } else{ h = round(runif(1) * 360) rg <- range(ann, na.rm=TRUE) if( rg[1] == rg[2] ) rg <- sort(c(0, rg[1])) setNames(rev(sequential_hcl(2, h, l = c(50, 95))), round.pretty(rg)) } }else{ acol <- if( length(acol) == 1 && grepl("^\\$", acol) ) # copy colors from other columns if the spec starts with '$' annotation_colors[[substr(acol, 2, nchar(acol))]] else if( !is.numeric(ann) ){ local({ #do this locally so that it does not affect `ann` # subset to the levels for which no colour has already been defined lev <- ann # subset to the levels for which no colour has already been defined # idx <- which(!lev %in% names(acol_def) & !is.na(lev)) # lev <- lev[idx] # #idx <- idx + length(acol_def) # # if( length(lev) == 0L ) acol_def # nothing to add # else { # convert to a palette of the number of levels if necessary nl <- length(lev) acol <- ccPalette(acol, nl) if( is.null(names(acol)) ) names(acol) <- lev c(acol_def, acol) } }) }else{ acol <- ccPalette(acol) if( is.null(names(acol)) ) names(acol) <- round.pretty(seq(min(ann, na.rm=TRUE), max(ann, na.rm=TRUE), length.out=length(acol))) acol } # update the colors if necessary if( !is.null(acol) ) res_colors[[aname]] <- acol } # store type information attr(res_colors[[aname]], 'afactor') <- !is.numeric(ann) } # return ordered colors as the annotations res_colors[names(annotation)[!duplicated(names(annotation))]] } # Create row/column names generate_dimnames <- function(x, n, ref){ if( is_NA(x) ) NULL else if( length(x) == n ) x else if( identical(x, 1) || identical(x, 1L) ) 1L:n else if( isString(x) ){ regexp <- "^/(.+)/([0-9]+)?$" if( grepl(regexp, x) ){ x <- str_match(x, regexp) p <- x[1,2] n <- if( x[1, 3] != '' ) as.numeric(x[1, 2]) else 2L s <- str_match(ref, p)[, n] ifelse(is.na(s), ref, s) } else paste(x, 1L:n, sep='') #print(str_match_all(x, "^/(([^%]*)(%[in])?)+/$")) } else stop("aheatmap - Invalid row/column label. Possible values are:" , " NA, a vector of correct length, value 1 (or 1L) or single character string.") } .make_annotation <- function(x, ord=NULL){ # convert into a data.frame if necessary if( !is.data.frame(x) ){ x <- if( is(x, 'ExpressionSet') ) Biobase::pData(x) else if( is.factor(x) || is.character(x) ) data.frame(Factor=x) else if( is.numeric(x) ) data.frame(Variable=x) else stop("aheatmap - Invalid annotation argument `", substitute(x), "`: must be a data.frame, a factor or a numeric vector") } # reorder if necessary if( !is.null(ord) ) x <- x[ord, , drop = F] # return modifed object x } renderAnnotations <- function(annCol, annRow, annotation_colors, verbose=getOption('verbose')){ # concatenate both col and row annotation annotation <- list() if( is_NA(annotation_colors) ) annotation_colors <- list() nc <- length(annCol) nr <- length(annRow) flag <- function(x, f){ if( missing(f) ) attr(x, 'flag') else{ attr(x, 'flag') <- f; x} } if( !is_NA(annCol) ) annotation <- c(annotation, sapply(as.list(annCol), flag, 'col', simplify=FALSE)) if( !is_NA(annRow) ) annotation <- c(annotation, sapply(as.list(annRow), flag, 'row', simplify=FALSE)) if( length(annotation) == 0 ) return( list(annCol=NA, annRow=NA, colors=NA) ) # generate the missing name n <- names(annotation) xnames <- paste('X', 1:length(annotation), sep='') if( is.null(n) ) names(annotation) <- xnames else names(annotation)[n==''] <- xnames[n==''] # preprocess the annotation color links if( !is.null(cnames <- names(annotation_colors)) ){ m <- str_match(cnames, "^@([^{]+)\\{([^}]+)\\}") apply(m, 1L, function(x){ # skip unmatched names if( is_NA(x[1]) ) return() acol <- annotation_colors[[x[1]]] # rename both annotation and annotation_colors if necessary if( x[2] != x[3] ){ annotation[[x[3]]] <<- annotation[[x[2]]] annotation[[x[2]]] <<- NULL if( !is_NA(acol) ) annotation_colors[[x[3]]] <<- acol annotation_colors[[x[1]]] <<- NULL } }) } # message("### ANNOTATION ###"); print(annotation) # message("### ANNOTATION COLORS ###"); print(annotation_colors) if( verbose ) message("Generate column annotation colours") annotation_colors <- generate_annotation_colours(annotation, annotation_colors) if( verbose > 2 ){ message("### Annotation colors ###") print(annotation_colors) message("#########################") } # bind each annotation with its respective color and regroup into column and row annotation res <- list() lapply(seq_along(annotation), function(i){ aname <- names(annotation)[i] acol <- annotation_colors[[aname]] if( is.null(acol) ) stop("aheatmap - No color was defined for annotation '", aname, "'.") attr(annotation[[i]], 'color') <- acol # put into the right annotation list if( flag(annotation[[i]]) == 'col' ) res$annCol <<- c(res$annCol, annotation[i]) else res$annRow <<- c(res$annRow, annotation[i]) }) res$annCol <- if( !is.null(res$annCol) ) convert_annotations(res$annCol, annotation_colors) else NA res$annRow <- if( !is.null(res$annRow) ) convert_annotations(res$annRow, annotation_colors) else NA res$colors <- annotation_colors # return result list res } # set/get special annotation handlers specialAnnotation <- local({ .empty <- list(list(), list()) .cache <- .empty function(margin, name, fun, clear=FALSE){ if( isTRUE(clear) ){ if( nargs() > 1L ) stop("Invalid call: no other argument can be passed when `clear=TRUE`") .cache <<- .empty return() } if( missing(name) && missing(fun) ){ return(.cache[[margin]]) }else if( is.list(name) ){ .cache[[margin]] <<- c(.cache[[margin]], name) }else if( missing(fun) ){ return(.cache[[margin]][[name]]) }else{ .cache[[margin]][[name]] <<- fun } } }) # Converts Subset Specification into Indexes subset_index <- function(x, margin, subset){ # if null then do nothing if( is.null(subset) ) return( NULL ) # get dimension n <- dim(x)[margin] dn <- dimnames(x)[[margin]] dt <- if( margin == 1L ) "rows" else "columns" so <- deparse(substitute(subset)) if( length(subset) == 0 ) stop("Invalid empty subset object `", so, "`") subIdx <- if( is.logical(subset) ){ if( length(subset) != n ){ if( n %% length(subset) == 0 ) subset <- rep(subset, n / length(subset)) else stop("Invalid length for logical subset argument `", so, "`: number of ", dt, " [" , n, "] is not a multiple of subset length [",length(subset),"].") } # convert into indexes which(subset) } else if( is.integer(subset) || is.character(subset) ){ if( length(subset) > n ) stop("Invalid too long integer/character subset argument `", so , "`: length must not exceed the number of ", dt, " [", n, "].") if( anyDuplicated(subset) ) warning("Duplicated index or name in subset argument `", so, "`.") # for character argument: match against dimname to convert into indexes if( is.character(subset) ){ if( is.null(dn) ) stop("Could not subset the ", dt, " with a character subset argument `", so, "`: no " , if( margin == 1L ) "rownames" else "colnames" , " are available.") msubset <- match(subset, dn) nas <- is.na(msubset) if( any(nas) ){ warning("Mismatch in character subset argument `", so ,"`: Could not find ", sum(nas), " out of ", length(subset), " names (" , paste("'", head(subset[nas], 5), "'", sep='', collapse=', ') , if( sum(nas) > 5 ) ", ... ", ").") msubset <- msubset[!nas] } subset <- msubset } subset }else stop("Invalid subset argument `", so, "`: should be a logical, integer or character vector.") # return the indexes sorted sort(subIdx) } #' Annotated Heatmaps #' #' The function \code{aheatmap} plots high-quality heatmaps, with a detailed legend #' and unlimited annotation tracks for both columns and rows. #' The annotations are coloured differently according to their type #' (factor or numeric covariate). #' Although it uses grid graphics, the generated plot is compatible with base #' layouts such as the ones defined with \code{'mfrow'} or \code{\link{layout}}, #' enabling the easy drawing of multiple heatmaps on a single a plot -- at last!. #' #' The development of this function started as a fork of the function #' \code{pheatmap} from the \pkg{pheatmap} package, and provides #' several enhancements such as: #' \itemize{ #' \item argument names match those used in the base function \code{\link{heatmap}}; #' \item unlimited number of annotation for \strong{both} columns and rows, #' with simplified and more flexible interface; #' \item easy specification of clustering methods and colors; #' \item return clustering data, as well as grid grob object. #' } #' #' Please read the associated vignette for more information and sample code. #' #' @section PDF graphic devices: if plotting on a PDF graphic device -- started with \code{\link{pdf}}, #' one may get generate a first blank page, due to internals of standard functions from #' the \pkg{grid} package that are called by \code{aheatmap}. #' The \pkg{NMF} package ships a custom patch that fixes this issue. #' However, in order to comply with CRAN policies, the patch is \strong{not} applied by default #' and the user must explicitly be enabled it. #' This can be achieved on runtime by either setting the NMF specific option 'grid.patch' #' via \code{nmf.options(grid.patch=TRUE)}, or on load time if the environment variable #' 'R_PACKAGE_NMF_GRID_PATCH' is defined and its value is something that is not equivalent #' to \code{FALSE} (i.e. not '', 'false' nor 0). #' #' @param x numeric matrix of the values to be plotted. #' An \emph{ExpressionSet} object can also be passed, in which case the expression values #' are plotted (\code{exprs(x)}). #' #' @param color colour specification for the heatmap. Default to palette #' '-RdYlBu2:100', i.e. reversed palette 'RdYlBu2' (a slight modification of #' RColorBrewer's palette 'RdYlBu') with 100 colors. #' Possible values are: #' \itemize{ #' \item a character/integer vector of length greater than 1 that is directly used #' and assumed to contain valid R color specifications. #' \item a single color/integer (between 0 and 8)/other numeric value #' that gives the dominant colors. Numeric values are converted into a pallete #' by \code{rev(sequential_hcl(2, h = x, l = c(50, 95)))}. Other values are #' concatenated with the grey colour '#F1F1F1'. #' \item one of RColorBrewer's palette name (see \code{\link[RColorBrewer]{display.brewer.all}}) #' , or one of 'RdYlBu2', 'rainbow', 'heat', 'topo', 'terrain', 'cm'. #' } #' When the coluor palette is specified with a single value, and is negative or #' preceded a minus ('-'), the reversed palette is used. #' The number of breaks can also be specified after a colon (':'). For example, #' the default colour palette is specified as '-RdYlBu2:100'. #' #' @param breaks a sequence of numbers that covers the range of values in \code{x} and is one #' element longer than color vector. Used for mapping values to colors. Useful, if needed #' to map certain values to certain colors. If value is NA then the #' breaks are calculated automatically. If \code{breaks} is a single value, #' then the colour palette is centered on this value. #' #' @param border_color color of cell borders on heatmap, use NA if no border should be #' drawn. #' #' @param cellwidth individual cell width in points. If left as NA, then the values #' depend on the size of plotting window. #' #' @param cellheight individual cell height in points. If left as NA, #' then the values depend on the size of plotting window. #' #' @param scale character indicating how the values should scaled in #' either the row direction or the column direction. Note that the scaling is #' performed after row/column clustering, so that it has no effect on the #' row/column ordering. #' Possible values are: #' \itemize{ #' \item \code{"row"}: center and standardize each row separately to row Z-scores #' \item \code{"column"}: center and standardize each column separately to column Z-scores #' \item \code{"r1"}: scale each row to sum up to one #' \item \code{"c1"}: scale each column to sum up to one #' \item \code{"none"}: no scaling #' } #' #' @param Rowv clustering specification(s) for the rows. It allows to specify #' the distance/clustering/ordering/display parameters to be used for the #' \emph{rows only}. #' Possible values are: #' \itemize{ #' \item \code{TRUE} or \code{NULL} (to be consistent with \code{\link{heatmap}}): #' compute a dendrogram from hierarchical clustering using the distance and #' clustering methods \code{distfun} and \code{hclustfun}. #' #' \item \code{NA}: disable any ordering. In this case, and if not otherwise #' specified with argument \code{revC=FALSE}, the heatmap shows the input matrix #' with the rows in their original order, with the first row on top to the last #' row at the bottom. Note that this differ from the behaviour or \code{\link{heatmap}}, #' but seemed to be a more sensible choice when vizualizing a matrix without #' reordering. #' #' \item an integer vector of length the number of rows of the input matrix #' (\code{nrow(x)}), that specifies the row order. As in the case \code{Rowv=NA}, #' the ordered matrix is shown first row on top, last row at the bottom. #' #' \item a character vector or a list specifying values to use instead of arguments #' \code{distfun}, \code{hclustfun} and \code{reorderfun} when clustering the #' rows (see the respective argument descriptions for a list of accepted #' values). #' If \code{Rowv} has no names, then the first element is used for \code{distfun}, #' the second (if present) is used for \code{hclustfun}, and the third #' (if present) is used for \code{reorderfun}. #' #' \item a numeric vector of weights, of length the number of rows of the input matrix, #' used to reorder the internally computed dendrogram \code{d} #' by \code{reorderfun(d, Rowv)}. #' #' \item \code{FALSE}: the dendrogram \emph{is} computed using methods \code{distfun}, #' \code{hclustfun}, and \code{reorderfun} but is not shown. #' #' \item a single integer that specifies how many subtrees (i.e. clusters) #' from the computed dendrogram should have their root faded out. #' This can be used to better highlight the different clusters. #' #' \item a single double that specifies how much space is used by the computed #' dendrogram. That is that this value is used in place of \code{treeheight}. #' } #' #' @param Colv clustering specification(s) for the columns. It accepts the same #' values as argument \code{Rowv} (modulo the expected length for vector specifications), #' and allow specifying the distance/clustering/ordering/display parameters to #' be used for the \emph{columns only}. #' \code{Colv} may also be set to \code{"Rowv"}, in which case the dendrogram #' or ordering specifications applied to the rows are also applied to the #' columns. Note that this is allowed only for square input matrices, #' and that the row ordering is in this case by default reversed #' (\code{revC=TRUE}) to obtain the diagonal in the standard way #' (from top-left to bottom-right). #' See argument \code{Rowv} for other possible values. #' #' @param revC a logical that specify if the \emph{row order} defined by #' \code{Rowv} should be reversed. This is mainly used to get the rows displayed #' from top to bottom, which is not the case by default. Its default value is #' computed at runtime, to suit common situations where natural ordering is a #' more sensible choice: no or fix ordering of the rows (\code{Rowv=NA} or an #' integer vector of indexes -- of length > 1), and when a symmetric ordering is #' requested -- so that the diagonal is shown as expected. #' An argument in favor of the "odd" default display (bottom to top) is that the #' row dendrogram is plotted from bottom to top, and reversing its reorder may #' take a not too long but non negligeable time. #' #' @param distfun default distance measure used in clustering rows and columns. #' Possible values are: #' \itemize{ #' \item all the distance methods supported by \code{\link{dist}} #' (e.g. "euclidean" or "maximum"). #' #' \item all correlation methods supported by \code{\link{cor}}, #' such as \code{"pearson"} or \code{"spearman"}. #' The pairwise distances between rows/columns are then computed as #' \code{d <- dist(1 - cor(..., method = distfun))}. #' #' One may as well use the string "correlation" which is an alias for "pearson". #' #' \item an object of class \code{dist} such as returned by \code{\link{dist}} or #' \code{\link{as.dist}}. #' } #' #' @param hclustfun default clustering method used to cluster rows and columns. #' Possible values are: #' \itemize{ #' \item a method name (a character string) supported by \code{\link{hclust}} #' (e.g. \code{'average'}). #' \item an object of class \code{hclust} such as returned by \code{\link{hclust}} #' \item a dendrogram #' } #' #' @param reorderfun default dendrogram reordering function, used to reorder the #' dendrogram, when either \code{Rowv} or \code{Colv} is a numeric weight vector, #' or provides or computes a dendrogram. It must take 2 parameters: a dendrogram, #' and a weight vector. #' #' @param subsetRow Specification of subsetting the rows before drawing the #' heatmap. #' Possible values are: #' \itemize{ #' \item an integer vector of length > 1 specifying the indexes of the rows to #' keep; #' \item a character vector of length > 1 specyfing the names of the rows to keep. #' These are the original rownames, not the names specified in \code{labRow}. #' \item a logical vector of length > 1, whose elements are recycled if the #' vector has not as many elements as rows in \code{x}. #' } #' Note that in the case \code{Rowv} is a dendrogram or hclust object, it is first #' converted into an ordering vector, and cannot be displayed -- and a warning is thrown. #' #' @param subsetCol Specification of subsetting the columns before drawing the #' heatmap. It accepts the similar values as \code{subsetRow}. See details above. #' #' @param txt character matrix of the same size as \code{x}, that contains text to #' display in each cell. #' \code{NA} values are allowed and are not displayed. #' See demo for an example. #' #' @param treeheight how much space (in points) should be used to display #' dendrograms. If specified as a single value, it is used for both dendrograms. #' A length-2 vector specifies separate values for the row and #' column dendrogram respectively. #' Default value: 50 points. #' #' @param legend boolean value that determines if a colour ramp for the heatmap's #' colour palette should be drawn or not. #' Default is \code{TRUE}. #' #' @param annCol specifications of column annotation tracks displayed as coloured #' rows on top of the heatmaps. The annotation tracks are drawn from bottom to top. #' A single annotation track can be specified as a single vector; multiple tracks #' are specified as a list, a data frame, or an \emph{ExpressionSet} object, in #' which case the phenotypic data is used (\code{pData(eset)}). #' Character or integer vectors are converted and displayed as factors. #' Unnamed tracks are internally renamed into \code{Xi}, with i being incremented for #' each unamed track, across both column and row annotation tracks. #' For each track, if no corresponding colour is specified in argument #' \code{annColors}, a palette or a ramp is automatically computed and named #' after the track's name. #' #' @param annRow specifications of row annotation tracks displayed as coloured #' columns on the left of the heatmaps. The annotation tracks are drawn from #' left to right. The same conversion, renaming and colouring rules as for argument #' \code{annCol} apply. #' #' @param annColors list for specifying annotation track colors manually. It is #' possible to define the colors for only some of the annotations. Check examples for #' details. #' #' @param annLegend boolean value specifying if the legend for the annotation tracks #' should be drawn or not. #' Default is \code{TRUE}. #' #' @param labRow labels for the rows. #' @param labCol labels for the columns. See description for argument \code{labRow} #' for a list of the possible values. #' #' @param fontsize base fontsize for the plot #' @param cexRow fontsize for the rownames, specified as a fraction of argument #' \code{fontsize}. #' @param cexCol fontsize for the colnames, specified as a fraction of argument #' \code{fontsize}. #' #' @param main Main title as a character string or a grob. #' @param sub Subtitle as a character string or a grob. #' @param info (experimental) Extra information as a character vector or a grob. #' If \code{info=TRUE}, information about the clustering methods is displayed #' at the bottom of the plot. #' #' @param filename file path ending where to save the picture. Currently following #' formats are supported: png, pdf, tiff, bmp, jpeg. Even if the plot does not fit into #' the plotting window, the file size is calculated so that the plot would fit there, #' unless specified otherwise. #' @param width manual option for determining the output file width in #' @param height manual option for determining the output file height in inches. #' #' @param verbose if \code{TRUE} then verbose messages are displayed and the #' borders of some viewports are highlighted. It is entended for debugging #' purposes. #' #' @param gp graphical parameters for the text used in plot. Parameters passed to #' \code{\link{grid.text}}, see \code{\link{gpar}}. #' #' @author #' Original version of \code{pheatmap}: Raivo Kolde #' #' Enhancement into \code{aheatmap}: Renaud Gaujoux #' #' @examples #' #' ## See the demo 'aheatmap' for more examples: #' \dontrun{ #' demo('aheatmap') #' } #' #' # Generate random data #' n <- 50; p <- 20 #' x <- abs(rmatrix(n, p, rnorm, mean=4, sd=1)) #' x[1:10, seq(1, 10, 2)] <- x[1:10, seq(1, 10, 2)] + 3 #' x[11:20, seq(2, 10, 2)] <- x[11:20, seq(2, 10, 2)] + 2 #' rownames(x) <- paste("ROW", 1:n) #' colnames(x) <- paste("COL", 1:p) #' #' ## Default heatmap #' aheatmap(x) #' #' ## Distance methods #' aheatmap(x, Rowv = "correlation") #' aheatmap(x, Rowv = "man") # partially matched to 'manhattan' #' aheatmap(x, Rowv = "man", Colv="binary") #' #' # Generate column annotations #' annotation = data.frame(Var1 = factor(1:p %% 2 == 0, labels = c("Class1", "Class2")), Var2 = 1:10) #' aheatmap(x, annCol = annotation) #' #' @demo Annotated heatmaps #' #' # Generate random data #' n <- 50; p <- 20 #' x <- abs(rmatrix(n, p, rnorm, mean=4, sd=1)) #' x[1:10, seq(1, 10, 2)] <- x[1:10, seq(1, 10, 2)] + 3 #' x[11:20, seq(2, 10, 2)] <- x[11:20, seq(2, 10, 2)] + 2 #' rownames(x) <- paste("ROW", 1:n) #' colnames(x) <- paste("COL", 1:p) #' #' ## Scaling #' aheatmap(x, scale = "row") #' aheatmap(x, scale = "col") # partially matched to 'column' #' aheatmap(x, scale = "r1") # each row sum up to 1 #' aheatmap(x, scale = "c1") # each colum sum up to 1 #' #' ## Heatmap colors #' aheatmap(x, color = colorRampPalette(c("navy", "white", "firebrick3"))(50)) #' # color specification as an integer: use R basic colors #' aheatmap(x, color = 1L) #' # color specification as a negative integer: use reverse basic palette #' aheatmap(x, color = -1L) #' # color specification as a numeric: use HCL color #' aheatmap(x, color = 1) #' # do not cluster the rows #' aheatmap(x, Rowv = NA) #' # no heatmap legend #' aheatmap(x, legend = FALSE) #' # cell and font size #' aheatmap(x, cellwidth = 10, cellheight = 5) #' #' # directly write into a file #' aheatmap(x, cellwidth = 15, cellheight = 12, fontsize = 8, filename = "aheatmap.pdf") #' unlink('aheatmap.pdf') #' #' # Generate column annotations #' annotation = data.frame(Var1 = factor(1:p %% 2 == 0, labels = c("Class1", "Class2")), Var2 = 1:10) #' #' aheatmap(x, annCol = annotation) #' aheatmap(x, annCol = annotation, annLegend = FALSE) #' #' #' # Specify colors #' Var1 = c("navy", "darkgreen") #' names(Var1) = c("Class1", "Class2") #' Var2 = c("lightgreen", "navy") #' #' ann_colors = list(Var1 = Var1, Var2 = Var2) #' #' aheatmap(x, annCol = annotation, annColors = ann_colors) #' #' # Specifying clustering from distance matrix #' drows = dist(x, method = "minkowski") #' dcols = dist(t(x), method = "minkowski") #' aheatmap(x, Rowv = drows, Colv = dcols) #' #' # Display text in each cells #' t <- outer(as.character(outer(letters, letters, paste0)), letters, paste0)[1:n, 1:p] #' aheatmap(x, txt = t) #' # NA values are shown as empty cells #' t.na <- t #' t.na[sample(length(t.na), 500)] <- NA # half of the cells #' aheatmap(x, txt = t.na) #' #' @export aheatmap = function(x , color = '-RdYlBu2:100' , breaks = NA, border_color=NA, cellwidth = NA, cellheight = NA, scale = "none" , Rowv=TRUE, Colv=TRUE , revC = identical(Colv, "Rowv") || is_NA(Rowv) || (is.integer(Rowv) && length(Rowv) > 1) || is(Rowv, 'silhouette') , distfun = "euclidean", hclustfun = "complete", reorderfun = function(d,w) reorder(d,w) , treeheight = 50 , legend = TRUE, annCol = NA, annRow = NA, annColors = NA, annLegend = TRUE , labRow = NULL, labCol = NULL , subsetRow = NULL, subsetCol = NULL , txt = NULL , fontsize=10, cexRow = min(0.2 + 1/log10(nr), 1.2), cexCol = min(0.2 + 1/log10(nc), 1.2) , filename = NA, width = NA, height = NA , main = NULL, sub = NULL, info = NULL , verbose=getOption('verbose'), gp = gpar()){ # set verbosity level ol <- lverbose(verbose) on.exit( lverbose(ol) ) # convert ExpressionSet into if( is(x, 'ExpressionSet') ){ requireNamespace('Biobase') #library(Biobase) if( isTRUE(annCol) ) annCol <- atrack(x) x <- Biobase::exprs(x) } # rename to old parameter name mat <- x if( !is.null(txt) ){ if( !all(dim(mat), dim(x)) ){ stop("Incompatible data and text dimensions: arguments x and txt must have the same size.") } } # init result list res <- list() # treeheight: use common or separate spec for rows and columns if( length(treeheight) == 1 ) treeheight <- c(treeheight, treeheight) treeheight_row <- treeheight[1] treeheight_col <- treeheight[2] ## SUBSET: process subset argument for rows/columsn if requested. # this has to be done before relabelling and clustering # but the actual subsetting has to be done after relabelling and before # clustering. # Here one convert a subset argument into an interger vector with the indexes if( !is.null(subsetRow) ){ if( verbose ) message("Compute row subset indexes") subsetRow <- subset_index(mat, 1L, subsetRow) } if( !is.null(subsetCol) ){ if( verbose ) message("Compute column subset indexes") subsetCol <- subset_index(mat, 2L, subsetCol) } ## LABELS: set the row/column labels # label row numerically if no rownames if( is.null(labRow) && is.null(rownames(mat)) ) labRow <- 1L if( !is.null(labRow) ){ if( verbose ) message("Process labRow") rownames(mat) <- generate_dimnames(labRow, nrow(mat), rownames(mat)) } # label columns numerically if no colnames if( is.null(labCol) && is.null(colnames(mat)) ) labCol <- 1L if( !is.null(labCol) ){ if( verbose ) message("Process labCol") colnames(mat) <- generate_dimnames(labCol, ncol(mat), colnames(mat)) } ## DO SUBSET if( !is.null(subsetRow) ){ mat <- mat[subsetRow, ] } if( !is.null(subsetCol) ){ mat <- mat[, subsetCol] } ## CLUSTERING # Do row clustering tree_row <- if( !is_NA(Rowv) ){ if( verbose ) message("Cluster rows") # single numeric Rowv means treeheight if( isReal(Rowv) ){ # treeheight treeheight_row <- Rowv # do cluster the rows Rowv <- TRUE } cluster_mat(mat, Rowv , distfun=distfun, hclustfun=hclustfun , reorderfun=reorderfun, subset=subsetRow , verbose = verbose) } else NA # do not show the tree if Rowv=FALSE or not a tree if( identical(Rowv, FALSE) || !is_treedef(tree_row) ) treeheight_row <- 0 # Do col clustering tree_col <- if( !is_NA(Colv) ){ if( identical(Colv,"Rowv") ){ # use row indexing if requested if( ncol(mat) != nrow(mat) ) stop("aheatmap - Colv='Rowv' but cannot treat columns and rows symmetrically: input matrix is not square.") treeheight_col <- treeheight_row tree_row }else{ # single numeric Colv means treeheight if( isReal(Colv) ){ # tree height treeheight_col <- Colv # do cluster the columns Colv <- TRUE } if( verbose ) message("Cluster columns") cluster_mat(t(mat), Colv , distfun=distfun, hclustfun=hclustfun , reorderfun=reorderfun, subset=subsetCol , verbose = verbose) } } else NA # do not show the tree if Colv=FALSE if( identical(Colv, FALSE) || !is_treedef(tree_col) ) treeheight_col <- 0 ## ORDER THE DATA if( !is_NA(tree_row) ){ # revert the row order if requested if( revC ){ if( verbose ) message("Reverse row clustering") tree_row <- rev(tree_row) } # store the order and row tree if possible if( is_treedef(tree_row) ){ res$Rowv <- tree_row$dendrogram res$rowInd <- order.dendrogram(tree_row$dendrogram) if( length(res$rowInd) != nrow(mat) ) stop("aheatmap - row dendrogram ordering gave index of wrong length (", length(res$rowInd), ")") } else{ res$rowInd <- tree_row tree_row <- NA } }else if( revC ){ # revert the row order if requested res$rowInd <- nrow(mat):1L } # possibly map the index to the original data index res$rowInd <- subset2orginal_idx(res$rowInd, subsetRow) # order the rows if necessary if( !is.null(res$rowInd) ){ # check validity of ordering if( !is.integer(res$rowInd) || length(res$rowInd) != nrow(mat) ) stop("aheatmap - Invalid row ordering: should be an integer vector of length nrow(mat)=", nrow(mat)) if( verbose ) message("Order rows") subInd <- attr(res$rowInd, 'subset') ri <- if( is.null(subInd) ) res$rowInd else subInd mat <- mat[ri, , drop=FALSE] # data if( !is.null(txt) ) txt <- txt[ri, , drop = FALSE] # text } if( !is_NA(tree_col) ){ # store the column order and tree if possible if( is_treedef(tree_col) ){ res$Colv <- tree_col$dendrogram res$colInd <- order.dendrogram(tree_col$dendrogram) if( length(res$colInd) != ncol(mat) ) stop("aheatmap - column dendrogram ordering gave index of wrong length (", length(res$colInd), ")") }else{ res$colInd <- tree_col tree_col <- NA } } # possibly map the index to the original data index res$colInd <- subset2orginal_idx(res$colInd, subsetCol) # order the columns if necessary if( !is.null(res$colInd) ){ # check validity of ordering if( !is.integer(res$colInd) || length(res$colInd) != ncol(mat) ) stop("aheatmap - Invalid column ordering: should be an integer vector of length ncol(mat)=", ncol(mat)) if( verbose ) message("Order columns") subInd <- attr(res$colInd, 'subset') ci <- if( is.null(subInd) ) res$colInd else subInd mat <- mat[, ci, drop=FALSE] # data if( !is.null(txt) ) txt <- txt[, ci, drop = FALSE] # text } # adding clustering info if( isTRUE(info) || is.character(info) ){ if( verbose ) message("Compute info") if( !is.character(info) ) info <- NULL linfo <- NULL if( is_treedef(tree_row) && !is.null(tree_row$dist.method) ) linfo <- paste("rows:", tree_row$dist.method, '/', tree_row$method) if( is_treedef(tree_col) && !is.null(tree_col$dist.method) ) linfo <- paste(linfo, paste(" - cols:", tree_col$dist.method, '/', tree_col$method)) info <- c(info, linfo) } # drop extra info except dendrograms for trees if( is_treedef(tree_col) ) tree_col <- tree_col$dendrogram if( is_treedef(tree_row) ) tree_row <- tree_row$dendrogram # Preprocess matrix if( verbose ) message("Scale matrix") mat = as.matrix(mat) mat = scale_mat(mat, scale) ## Colors and scales # load named palette if necessary color <- ccRamp(color) # generate breaks if necessary if( is_NA(breaks) || isNumber(breaks) ){ if( verbose ) message("Generate breaks") # if a single number: center the breaks on this value cbreaks <- if( isNumber(breaks) ) breaks else NA breaks = generate_breaks(as.vector(mat), length(color), center=cbreaks) } if( isTRUE(legend) ){ if( verbose ) message("Generate data legend breaks") legend = grid.pretty(range(as.vector(breaks))) } else { legend = NA } mat = scale_colours(mat, col = color, breaks = breaks) annotation_legend <- annLegend annotation_colors <- annColors # render annotation tracks for both rows and columns annCol_processed <- atrack(annCol, order=res$colInd, .SPECIAL=specialAnnotation(2L), .DATA=amargin(x,2L), .CACHE=annRow) annRow_processed <- atrack(annRow, order=res$rowInd, .SPECIAL=specialAnnotation(1L), .DATA=amargin(x,1L), .CACHE=annCol) specialAnnotation(clear=TRUE) annTracks <- renderAnnotations(annCol_processed, annRow_processed , annotation_colors = annotation_colors , verbose=verbose) # # retrieve dimension for computing cexRow and cexCol (evaluated from the arguments) nr <- nrow(mat); nc <- ncol(mat) # Draw heatmap res$vp <- heatmap_motor(mat, border_color = border_color, cellwidth = cellwidth, cellheight = cellheight , treeheight_col = treeheight_col, treeheight_row = treeheight_row, tree_col = tree_col, tree_row = tree_row , filename = filename, width = width, height = height, breaks = breaks, color = color, legend = legend , annTracks = annTracks, annotation_legend = annotation_legend , txt = txt , fontsize = fontsize, fontsize_row = cexRow * fontsize, fontsize_col = cexCol * fontsize , main = main, sub = sub, info = info , verbose = verbose , gp = gp) # return info about the plot invisible(res) } #' @import gridBase grid.base.mix <- function(opar, trace = getOption('verbose')){ if( !missing(opar) ){ if( !is.null(opar) ){ if( trace ) message("grid.base.mix - restore") upViewport(2) par(opar) } return(invisible()) } if( trace ) message("grid.base.mix - init") if( trace ) grid.rect(gp=gpar(lwd=40, col="blue")) opar <- par(xpd=NA) if( trace ) grid.rect(gp=gpar(lwd=30, col="green")) if( trace ) message("grid.base.mix - plot.new") plot.new() if( trace ) grid.rect(gp=gpar(lwd=20, col="black")) vps <- baseViewports() pushViewport(vps$inner) if( trace ) grid.rect(gp=gpar(lwd=10, col="red")) pushViewport(vps$figure) # if( trace ) grid.rect(gp=gpar(lwd=3, col="green")) # pushViewport(vps$plot) # upViewport(2) # if( trace ) grid.rect(gp=gpar(lwd=3, col="pink")) # pushViewport(viewport(x=unit(0.5, "npc"), y=unit(0, "npc"), width=unit(0.5, "npc"), height=unit(1, "npc"), just=c("left","bottom"))) if( trace ) grid.rect(gp=gpar(lwd=3, col="yellow")) opar } if( FALSE ){ testmix <- function(){ opar <- mixplot.start(FALSE) profplot(curated$data$model, curated$fits[[1]]) mixplot.add(TRUE) basismarker(curated$fits[[1]], curated$data$markers) mixplot.end() par(opar) } dd <- function(d, horizontal = TRUE, ...){ grid.rect(gp = gpar(col = "blue", lwd = 2)) opar <- par(plt = gridPLT(), new = TRUE) on.exit(par(opar)) plot(d, horiz=horizontal, xaxs="i", yaxs="i", axes=FALSE, leaflab="none", ...) } toto <- function(new=FALSE){ library(RGraphics) set.seed(123456) x <- matrix(runif(30*20), 30) x <- crossprod(x) d <- as.dendrogram(hclust(dist(x))) #grid.newpage() if( new ) plot.new() lo <- grid.layout(nrow=2, ncol=2) pushViewport(viewport(layout=lo)) pushViewport(viewport(layout.pos.row = 2, layout.pos.col = 1)) dd(d) upViewport() pushViewport(viewport(layout.pos.row = 1, layout.pos.col = 2)) dd(d, FALSE) upViewport() pushViewport(viewport(layout.pos.row = 2, layout.pos.col = 2)) grid.imageGrob(nrow(x), ncol(x), x) upViewport() popViewport() stop("END toto") } test <- function(){ pdf('aheatmap.pdf') #try(v <- aheatmap(consensus(res), color='grey:100', Colv=2L, verbose=TRUE)) try(v <- consensusmap(res, color='grey:100', Colv=2L, verbose=TRUE)) dev.off() } test2 <- function(){ op <- par(mfrow=c(1,2)) on.exit(par(op)) #try(v <- aheatmap(consensus(res), color='grey:100', Colv=2L, verbose=TRUE)) try(v <- consensusmap(res, verbose=TRUE)) try(v <- consensusmap(res, color='grey:100', Colv=2L, verbose=TRUE)) } testsw <- function(file=TRUE){ if(file ){ pdf('asweave.pdf', width=20, height=7) on.exit(dev.off()) } opar <- par(mfrow=c(1,2)) # removing all automatic annotation tracks coefmap(res, tracks=NA, verbose=TRUE) # customized plot coefmap(res, Colv = 'euclidean', Rowv='max', verbose=TRUE) # , main = "Metagene contributions in each sample", labCol = NULL # , tracks = c(Metagene='basis'), annCol = list(Class=a, Index=c) # , annColors = list(Metagene='Set2') # , info = TRUE) par(opar) } testvp <- function(file=TRUE){ if(file ){ pdf('avp.pdf', width=20, height=7) on.exit(dev.off()) } plot.new() lo <- grid.layout(nrow=1, ncol=2) pushViewport(viewport(layout=lo)) pushViewport(viewport(layout.pos.row = 1, layout.pos.col = 1)) basismap(res, Colv='eucl', verbose=TRUE) upViewport() pushViewport(viewport(layout.pos.row = 1, layout.pos.col = 2)) coefmap(res, tracks=NA, verbose=TRUE) upViewport() popViewport() } } NMF/R/algorithmic.R0000644000176200001440000001525713620502674013537 0ustar liggesusers# Definition of a generic interface for algorithms # # Author: Renaud Gaujoux ############################################################################### #' Generic Interface for Algorithms #' #' @description #' The functions documented here are S4 generics that define an general interface for #' -- optimisation -- algorithms. #' #' This interface builds upon the broad definition of an algorithm as a workhorse function #' to which is associated auxiliary objects such as an underlying model or an objective function #' that measures the adequation of the model with observed data. #' It aims at complementing the interface provided by the \code{\link{stats}} package. #' #' @section Interface fo NMF algorithms: #' This interface is implemented for NMF algorithms by the classes \code{\link{NMFfit}}, #' \code{\link{NMFfitX}} and \code{\link{NMFStrategy}}, and their respective sub-classes. #' The examples given in this documentation page are mainly based on this implementation. #' #' @param object an object computed using some algorithm, or that describes an algorithm #' itself. #' @param value replacement value #' @param ... extra arguments to allow extension #' #' @name algorithmic-NMF #' @rdname algorithmic NULL #' @details #' \code{algorithm} and \code{algorithm<-} get/set an object that describes the #' algorithm used to compute another object, or with which it is associated. #' It may be a simple character string that gives the algorithm's names, or an object that #' includes the algorithm's definition itself (e.g. an \code{\link{NMFStrategy}} object). #' #' @export #' @rdname algorithmic setGeneric('algorithm', function(object, ...) standardGeneric('algorithm') ) #' @export #' @rdname algorithmic setGeneric('algorithm<-', function(object, ..., value) standardGeneric('algorithm<-') ) #' @details #' \code{seeding} get/set the seeding method used to initialise the computation of an object, #' i.e. usually the function that sets the starting point of an algorithm. #' #' @export #' @rdname algorithmic setGeneric('seeding', function(object, ...) standardGeneric('seeding') ) #' @export #' @rdname algorithmic setGeneric('seeding<-', function(object, ..., value) standardGeneric('seeding<-') ) #' @details #' \code{niter} and \code{niter<-} get/set the number of iterations performed #' to compute an object. #' The function \code{niter<-} would usually be called just before returning the result #' of an algorithm, when putting together data about the fit. #' #' @export #' @rdname algorithmic setGeneric('niter', function(object, ...) standardGeneric('niter')) #' @rdname algorithmic #' @export setGeneric('niter<-', function(object, ..., value) standardGeneric('niter<-')) #' @details #' \code{nrun} returns the number of times the algorithm has been run to compute #' an object. #' Usually this will be 1, but may be be more if the algorithm involves multiple #' starting points. #' #' @export #' @rdname algorithmic setGeneric('nrun', function(object, ...) standardGeneric('nrun') ) #' Default method that returns the value of attribute \sQuote{nrun}. #' #' Such an attribute my be attached to objects to keep track of data about #' the parent fit object (e.g. by method \code{\link{consensus}}), which #' can be used by subsequent function calls such as plot functions #' (e.g. see \code{\link{consensusmap}}). #' This method returns \code{NULL} if no suitable data was found. setMethod('nrun', 'ANY', function(object){ attr(object, 'nrun') } ) #' @details #' \code{objective} and \code{objective<-} get/set the objective function associated #' with an object. #' Some methods for \code{objective} may also compute the objective value with respect to #' some target/observed data. #' #' @export #' @rdname algorithmic setGeneric('objective', function(object, ...) standardGeneric('objective')) #' @export #' @rdname algorithmic setGeneric('objective<-', function(object, ..., value) standardGeneric('objective<-')) #' @details #' \code{runtime} returns the CPU time required to compute an object. #' This would generally be an object of class \code{\link[=proc.time]{proc_time}}. #' #' @export #' @rdname algorithmic setGeneric('runtime', function(object, ...) standardGeneric('runtime') ) #' @details #' \code{runtime.all} returns the CPU time required to compute a collection of #' objects, e.g. a sequence of independent fits. #' #' @export #' @rdname algorithmic setGeneric('runtime.all', function(object, ...) standardGeneric('runtime.all') ) #' @details #' \code{seqtime} returns the sequential CPU time -- that would be -- required #' to compute a collection of objects. #' It would differ from \code{runtime.all} if the computations were performed #' in parallel. #' #' @export #' @rdname algorithmic setGeneric('seqtime', function(object, ...) standardGeneric('seqtime') ) #' @details #' \code{modelname} returns a the type of model associated with an object. #' #' @rdname algorithmic #' @export setGeneric('modelname', function(object, ...) standardGeneric('modelname')) #' Default method which returns the class name(s) of \code{object}. #' This should work for objects representing models on their own. #' #' For NMF objects, this is the type of NMF model, that corresponds to the #' name of the S4 sub-class of \code{\linkS4class{NMF}}, inherited by \code{object}. #' #' @examples #' # get the type of an NMF model #' modelname(nmfModel(3)) #' modelname(nmfModel(3, model='NMFns')) #' modelname(nmfModel(3, model='NMFOffset')) #' setMethod('modelname', 'ANY', function(object) { as.character(class(object)) } ) #' @details #' \code{run} calls the workhorse function that actually implements a strategy/algorithm, #' and run it on some data object. #' #' @param y data object, e.g. a target matrix #' @param x a model object used as a starting point by the algorithm, #' e.g. a non-empty NMF model. #' #' @export #' @rdname algorithmic setGeneric('run', function(object, y, x, ...) standardGeneric('run')) #' @details #' \code{logs} returns the log messages output during the computation of an #' object. #' @export #' @rdname algorithmic setGeneric('logs', function(object, ...) standardGeneric('logs')) #' Default method that returns the value of attribute/slot \code{'logs'} or, if this latter #' does not exists, the value of element \code{'logs'} if \code{object} is a \code{list}. #' It returns \code{NULL} if no logging data was found. setMethod('logs', 'ANY', function(object) { res <- attr(object, 'logs') if( !is.null(res) ) res else if( is.list(object) ) object$logs } ) #' @details #' \code{compare} compares objects obtained from running separate algorithms. #' #' @export #' @rdname algorithmic setGeneric('compare', function(object, ...) standardGeneric('compare') ) NMF/R/NMFStrategyIterative-class.R0000644000176200001440000010570113620502674016352 0ustar liggesusers#' @include NMFStrategy-class.R #' @include NMFfit-class.R NULL # Define union class for generalised function slots, e.g., slot 'NMFStrategyIterative::Stop' setClassUnion('.GfunctionSlotNULL', c('character', 'integer', 'numeric', 'function', 'NULL')) #' Interface for Algorithms: Implementation for Iterative NMF Algorithms #' #' @description #' This class provides a specific implementation for the generic function \code{run} #' -- concretising the virtual interface class \code{\linkS4class{NMFStrategy}}, #' for NMF algorithms that conform to the following iterative schema (starred numbers #' indicate mandatory steps): #' #' \itemize{ #' \item 1. Initialisation #' \item 2*. Update the model at each iteration #' \item 3. Stop if some criterion is satisfied #' \item 4. Wrap up #' } #' #' This schema could possibly apply to all NMF algorithms, since these are essentially optimisation algorithms, #' almost all of which use iterative methods to approximate a solution of the optimisation problem. #' The main advantage is that it allows to implement updates and stopping criterion separately, and combine them #' in different ways. #' In particular, many NMF algorithms are based on multiplicative updates, following the approach from #' \cite{Lee2001}, which are specially suitable to be cast into this simple schema. #' #' @slot onInit optional function that performs some initialisation or pre-processing on #' the model, before starting the iteration loop. #' @slot Update mandatory function that implement the update step, which computes new values for the model, based on its #' previous value. #' It is called at each iteration, until the stopping criterion is met or the maximum number of iteration is #' achieved. #' @slot Stop optional function that implements the stopping criterion. #' It is called \strong{before} each Update step. #' If not provided, the iterations are stopped after a fixed number of updates. #' @slot onReturn optional function that wraps up the result into an NMF object. #' It is called just before returning the #' setClass('NMFStrategyIterative' , representation( onInit = '.functionSlotNULL', Update = '.functionSlot', # update method Stop = '.GfunctionSlotNULL', # method called just after the update onReturn = '.functionSlotNULL' # method called just before returning the resulting NMF object ) , prototype=prototype( onInit = NULL , Update = '' , Stop = NULL , onReturn = NULL ) , contains = 'NMFStrategy' , validity = function(object){ if( is.character(object@Update) && object@Update == '' ) return("Slot 'Update' is required") # check the arguments of methods 'Update' and 'Stop' # (except for the 3 mandatory ones) n.update <- names(formals(object@Update)) # at least 3 arguments for 'Update' if( length(n.update) < 3 ){ return(str_c("Invalid 'Update' method - must have at least 3 arguments: ", "current iteration number [i], ", "target matrix [y], ", "current NMF model iterate [x]")) } n.update <- n.update[-seq(3)] # argument '...' must be present in method 'Update' if( !is.element('...', n.update) ) return("Invalid 'Update' method: must have argument '...' (even if not used)") # at least 3 arguments for 'Stop' if( !is.null(object@Stop) ){ # retrieve the stopping criterion and check its intrinsic validity .stop <- tryCatch( NMFStop(object@Stop, check=TRUE), error = function(e) return(message(e))) # Update and Stop methods cannot have overlapping arguments n.stop <- names(formals(.stop)) overlap <- intersect(n.update, n.stop) overlap <- overlap[which(overlap!='...')] if( length(overlap) > 0 ){ return(str_c("Invalid 'Update' and 'Stop' methods: conflicting arguments ", str_out(overlap, Inf))) } } TRUE } ) #' Show method for objects of class \code{NMFStrategyIterative} #' @export setMethod('show', 'NMFStrategyIterative', function(object){ #cat('') callNextMethod() cat(" \n") # go through the slots s.list <- names(getSlots('NMFStrategyIterative')) s.list <- setdiff(s.list, names(getSlots('NMFStrategy'))) #s.list <- s.list[s.list=='ANY'] # s.list <- c('Update', 'Stop', 'WrapNMF') out <- sapply(s.list, function(sname){ svalue <- slot(object,sname) svalue <- if( is.function(svalue) ) { str_args(svalue, exdent=12) } else if( is.null(svalue) ){ 'none' } else { paste("'", svalue,"'", sep='') } str_c(sname, ": ", svalue) }) cat(str_c(' ', out, collapse='\n'), "\n", sep='') return(invisible()) } ) ###% This class is an auxiliary class that defines the strategy's methods by directly callable functions. setClass('NMFStrategyIterativeX' , contains = 'NMFStrategyIterative' , representation = representation( workspace = 'environment' # workspace to use persistent variables accross methods ) ) ###% Creates a NMFStrategyIterativeX object from a NMFStrategyIterative object. xifyStrategy <- function(strategy, workspace=new.env(emptyenv())){ # do nothing if already executable if( is(strategy, 'NMFStrategyIterativeX') ) return(strategy) # first check the strategy's validity if( is.character(err <- validObject(strategy, test=TRUE)) ){ stop("Invalid strategy definition:\n\t- ", err) } # intanciate the NMFStrategyIterativeX, creating the strategy's workspace strategyX <- new('NMFStrategyIterativeX', strategy, workspace=workspace) # define auxiliary function to preload the 'function' slots in class NMFStrategyIterativeX preload.slot <- function(strategy, sname, default){ # get the content of the slot svalue <- slot(strategy,sname) # if the slot is valid (i.e. it's a non-empty character string), then process the name into a valid function fun <- if( is.null(svalue) && !missing(default) ) default else if( sname == 'Stop' ) NMFStop(svalue) else if( is.character(svalue) && nchar(svalue) > 0 ){ # set the slot with the executable version of the function getFunction(svalue) }else if( is.function(svalue) ) svalue else stop("NMFStrategyIterativeX - could not pre-load slot '", sname, "'") # return the loaded function fun } # preload the function slots slot(strategyX, 'Update') <- preload.slot(strategyX, 'Update') slot(strategyX, 'Stop') <- preload.slot(strategyX, 'Stop', function(strategy, i, target, data, ...){FALSE}) slot(strategyX, 'onReturn') <- preload.slot(strategyX, 'onReturn', identity) # load the objective function objective(strategyX) <- nmfDistance(objective(strategy)) # valid the preloaded object validObject(strategyX) # return the executable strategy strategyX } # #setGeneric('Update', function(object, v, ...) standardGeneric('Update') ) #setMethod('Update', signature(object='NMFStrategyIterative', v='matrix'), function(object, v, ...){ object@data <- object@Update(v, object@data, ...) }) # #setGeneric('Stop', function(object, i) standardGeneric('Stop') ) #setMethod('Stop', signature(object='NMFStrategyIterative', i='integer'), function(object, i){ object@Stop(i, object@data) }) # #setGeneric('WrapNMF', function(object) standardGeneric('WrapNMF') ) #setMethod('WrapNMF', signature(object='NMFStrategyIterative'), function(object){ object@WrapNMF(object@data) }) ###% Hook to initialize built-in iterative methods when the package is loaded ###% Hook to initialize old R version built-in iterative methods #' Get/Set a Static Variable in NMF Algorithms #' #' @description #' This function is used in iterative NMF algorithms to manage variables #' stored in a local workspace, that are accessible to all functions that #' define the iterative schema described in \code{\linkS4class{NMFStrategyIterative}}. #' #' It is specially useful for computing stopping criteria, which often require model data from #' different iterations. #' #' @param name Name of the static variable (as a single character string) #' @param value New value of the static variable #' @param init a logical used when a \code{value} is provided, that specifies #' if the variable should be set to the new value only if it does not exist yet #' (\code{init=TRUE}). #' @return The value of the static variable #' @export staticVar <- local({ .Workspace <- NULL function(name, value, init=FALSE){ # return last workspace if( missing(name) ) return(.Workspace) else if( is.null(name) ){ # reset workspace .Workspace <<- NULL return() } else if( is.environment(name) ){ # setup up static environment nmf.debug('Strategy Workspace', "initialize static workspace: ", capture.output(.Workspace), "=", capture.output(name)) .Workspace <<- name }else if( isString(name) && is.environment(.Workspace) ){ if( missing(value) ){ get(name, envir=.Workspace, inherits=FALSE) }else{ if( !init || !exists(name, envir=.Workspace, inherits=FALSE) ) { if( init ) nmf.debug('Strategy Workspace', "initialize variable '", name, "'") assign(name, value, envir=.Workspace) } # return current value get(name, envir=.Workspace, inherits=FALSE) } }else{ stop("Invalid NMF workspace query: .Workspace=", class(.Workspace), '| name=', name , if( !missing(value) ) paste0(' | value=', class(value))) } } }) #' Runs an NMF iterative algorithm on a target matrix \code{y}. #' #' @param .stop specification of a stopping criterion, that is used instead of the #' one associated to the NMF algorithm. #' It may be specified as: #' \itemize{ #' \item the access key of a registered stopping criterion; #' \item a single integer that specifies the exact number of iterations to perform, which will #' be honoured unless a lower value is explicitly passed in argument \code{maxIter}. #' \item a single numeric value that specifies the stationnarity threshold for the #' objective function, used in with \code{\link{nmf.stop.stationary}}; #' \item a function with signature \code{(object="NMFStrategy", i="integer", y="matrix", x="NMF", ...)}, #' where \code{object} is the \code{NMFStrategy} object that describes the algorithm being run, #' \code{i} is the current iteration, \code{y} is the target matrix and \code{x} is the current value of #' the NMF model. #' } #' @param maxIter maximum number of iterations to perform. #' #' @rdname NMFStrategy setMethod('run', signature(object='NMFStrategyIterative', y='matrix', x='NMFfit'), function(object, y, x, .stop=NULL, maxIter = nmf.getOption('maxIter') %||% 2000L, ...){ method <- object # override the stop method on runtime if( !is.null(.stop) ){ method@Stop <- NMFStop(.stop) # honour maxIter unless .stop is an integer and maxIter is not passed # either directly or from initial call # NB: maxIter may be not missing in the call to run() due to the application # of default arguments from the Strategy within nmf(), in which case one does not # want to honour it, since it is effectively missing in the original call. if( is.integer(.stop) && (missing(maxIter) || !('maxIter' %in% names(x@call))) ) maxIter <- .stop[1] } # debug object in debug mode if( nmf.getOption('debug') ) show(method) #Vc# Define local workspace for static variables # this function can be called in the methods to get/set/initialize # variables that are persistent within the strategy's workspace .Workspace <- new.env() staticVar(.Workspace) on.exit( staticVar(NULL) ) # runtime resolution of the strategy's functions by their names if necessary strategyX = xifyStrategy(method, .Workspace) run(strategyX, y, x, maxIter=maxIter, ...) }) #' @rdname NMFStrategy setMethod('run', signature(object='NMFStrategyIterativeX', y='matrix', x='NMFfit'), function(object, y, x, maxIter, ...){ strategy <- object v <- y seed <- x #V!# NMFStrategyIterativeX::run #Vc# Define workspace accessor function # this function can be called in the methods to get/set/initialize # variables that are persistent within the strategy's workspace # .Workspace <- strategy@workspace # assign('staticVar', function(name, value, init=FALSE){ # if( missing(value) ){ # get(name, envir=.Workspace, inherits=FALSE) # }else{ # if( !init || !exists(name, envir=.Workspace, inherits=FALSE) ) # { # if( init ) nmf.debug('Strategy Workspace', "initialize variable '", name, "'") # assign(name, value, envir=.Workspace) # } # } # } # , envir=.Workspace) #Vc# initialize the strategy # check validity of arguments if possible method.args <- nmfFormals(strategy, runtime=TRUE) internal.args <- method.args$internals expected.args <- method.args$defaults passed.args <- names(list(...)) forbidden.args <- is.element(passed.args, c(internal.args)) if( any(forbidden.args) ){ stop("NMF::run - Update/Stop method : formal argument(s) " , paste( paste("'", passed.args[forbidden.args],"'", sep=''), collapse=', ') , " already set internally.", call.=FALSE) } # !is.element('...', expected.args) && if( any(t <- !pmatch(passed.args, names(expected.args), nomatch=FALSE)) ){ stop("NMF::run - onInit/Update/Stop method for algorithm '", name(strategy),"': unused argument(s) " , paste( paste("'", passed.args[t],"'", sep=''), collapse=', '), call.=FALSE) } # check for required arguments required.args <- sapply(expected.args, function(x){ x <- as.character(x); length(x) == 1 && nchar(x) == 0 } ) required.args <- names(expected.args[required.args]) required.args <- required.args[required.args!='...'] if( any(t <- !pmatch(required.args, passed.args, nomatch=FALSE)) ) stop("NMF::run - Update/Stop method for algorithm '", name(strategy),"': missing required argument(s) " , paste( paste("'", required.args[t],"'", sep=''), collapse=', '), call.=FALSE) #Vc# Start iterations nmfData <- seed # cache verbose level verbose <- verbose(nmfData) # clone the object to allow the updates to work in place if( verbose > 1L ) message("# Cloning NMF model seed ... ", appendLF=FALSE) nmfFit <- clone(fit(nmfData)) if( verbose > 1L ) message("[", C.ptr(fit(nmfData)), " -> ", C.ptr(nmfFit), "]") ## onInit if( is.function(strategy@onInit) ){ if( verbose > 1L ) message("# Step 1 - onInit ... ", appendLF=TRUE) nmfFit <- strategy@onInit(strategy, v, nmfFit, ...) if( verbose > 1L ) message("OK") } ## # pre-load slots updateFun <- strategy@Update stopFun <- strategy@Stop showNIter.step <- 50L showNIter <- verbose && maxIter >= showNIter.step if( showNIter ){ ndIter <- nchar(as.character(maxIter)) itMsg <- paste0('Iterations: %', ndIter, 'i', "/", maxIter) cat(itMsgBck <- sprintf(itMsg, 0)) itMsgBck <- nchar(itMsgBck) } i <- 0L while( TRUE ){ #Vc# Stopping criteria # check convergence (generally do not stop for i=0L, but only initialise static variables stop.signal <- stopFun(strategy, i, v, nmfFit, ...) # if the strategy ask for stopping, then stop the iteration if( stop.signal || i >= maxIter ) break; # increment i i <- i+1L if( showNIter && (i==1L || i %% showNIter.step == 0L) ){ cat(paste0(rep("\r", itMsgBck), sprintf(itMsg, i))) } #Vc# update the matrices nmfFit <- updateFun(i, v, nmfFit, ...) # every now and then track the error if required nmfData <- trackError(nmfData, deviance(strategy, nmfFit, v, ...), niter=i) } if( showNIter ){ ended <- if( stop.signal ) 'converged' else 'stopped' cat("\nDONE (", ended, " at ",i,'/', maxIter," iterations)\n", sep='') } # force to compute last error if not already done nmfData <- trackError(nmfData, deviance(strategy, nmfFit, v, ...), niter=i, force=TRUE) # store the fitted model fit(nmfData) <- nmfFit #Vc# wrap up # let the strategy build the result nmfData <- strategy@onReturn(nmfData) if( !inherits(nmfData, 'NMFfit') ){ stop('NMFStrategyIterative[', name(strategy), ']::onReturn did not return a "NMF" instance [returned: "', class(nmfData), '"]') } # set the number of iterations performed niter(nmfData) <- i #return the result nmf.debug('NMFStrategyIterativeX::run', 'Done') invisible(nmfData) }) #' @export nmfFormals.NMFStrategyIterative <- function(x, runtime=FALSE, ...){ strategy <- xifyStrategy(x) # from run method m <- getMethod('run', signature(object='NMFStrategyIterative', y='matrix', x='NMFfit')) run.args <- allFormals(m)[-(1:3)] # onInit init.args <- if( is.function(strategy@onInit) ) formals(strategy@onInit) # Update update.args <- formals(strategy@Update) # Stop stop.args <- formals(strategy@Stop) # spplit internals and internal.args <- names(c(init.args[1:3], update.args[1:3], stop.args[1:4])) expected.args <- c(init.args[-(1:3)], update.args[-(1:3)], stop.args[-(1:4)]) if( runtime ){ # prepend registered default arguments expected.args <- expand_list(strategy@defaults, expected.args) list(internal=internal.args, defaults=expected.args) }else{ args <- c(run.args, expected.args) # prepend registered default arguments expand_list(strategy@defaults, args) } } ################################################################################################ # INITIALIZATION METHODS ################################################################################################ ################################################################################################ # UPDATE METHODS ################################################################################################ #' NMF Multiplicative Updates for Kullback-Leibler Divergence #' #' Multiplicative updates from \cite{Lee2001} for standard Nonnegative Matrix Factorization #' models \eqn{V \approx W H}, where the distance between the target matrix and its NMF #' estimate is measured by the Kullback-Leibler divergence. #' #' \code{nmf_update.KL.w} and \code{nmf_update.KL.h} compute the updated basis and coefficient #' matrices respectively. #' They use a \emph{C++} implementation which is optimised for speed and memory usage. #' #' @details #' The coefficient matrix (\code{H}) is updated as follows: #' \deqn{ #' H_{kj} \leftarrow H_{kj} \frac{\left( sum_i \frac{W_{ik} V_{ij}}{(WH)_{ij}} \right)}{ sum_i W_{ik} }. #' }{ #' H_kj <- H_kj ( sum_i [ W_ik V_ij / (WH)_ij ] ) / ( sum_i W_ik ) #' } #' #' These updates are used in built-in NMF algorithms \code{\link[=KL-nmf]{KL}} and #' \code{\link[=brunet-nmf]{brunet}}. #' #' @param v target matrix #' @param w current basis matrix #' @param h current coefficient matrix #' @param nbterms number of fixed basis terms #' @param ncterms number of fixed coefficient terms #' @param copy logical that indicates if the update should be made on the original #' matrix directly (\code{FALSE}) or on a copy (\code{TRUE} - default). #' With \code{copy=FALSE} the memory footprint is very small, and some speed-up may be #' achieved in the case of big matrices. #' However, greater care should be taken due the side effect. #' We recommend that only experienced users use \code{copy=TRUE}. #' #' @return a matrix of the same dimension as the input matrix to update #' (i.e. \code{w} or \code{h}). #' If \code{copy=FALSE}, the returned matrix uses the same memory as the input object. #' #' @author #' Update definitions by \cite{Lee2001}. #' #' C++ optimised implementation by Renaud Gaujoux. #' #' @rdname nmf_update_KL #' @aliases nmf_update.KL #' @export nmf_update.KL.h <- std.divergence.update.h <- function(v, w, h, nbterms=0L, ncterms=0L, copy=TRUE) { .Call("divergence_update_H", v, w, h, nbterms, ncterms, copy, PACKAGE='NMF') } #' \code{nmf_update.KL.w_R} and \code{nmf_update.KL.h_R} implement the same updates #' in \emph{plain R}. #' #' @param wh already computed NMF estimate used to compute the denominator term. #' #' @rdname nmf_update_KL #' @export nmf_update.KL.h_R <- R_std.divergence.update.h <- function(v, w, h, wh=NULL) { # compute WH if necessary if( is.null(wh) ) wh <- w %*% h # divergence-reducing NMF iterations # H_au = H_au ( sum_i [ W_ia V_iu / (WH)_iu ] ) / ( sum_k W_ka ) -> each row of H is divided by a the corresponding colSum of W h * crossprod(w, v / wh) / colSums(w) } #' @details #' The basis matrix (\code{W}) is updated as follows: #' \deqn{ #' W_{ik} \leftarrow W_{ik} \frac{ sum_j [\frac{H_{kj} A_{ij}}{(WH)_{ij}} ] }{sum_j H_{kj} } #' }{ #' W_ik <- W_ik ( sum_u [H_kl A_il / (WH)_il ] ) / ( sum_l H_kl ) #' } #' @rdname nmf_update_KL #' @export nmf_update.KL.w <- std.divergence.update.w <- function(v, w, h, nbterms=0L, ncterms=0L, copy=TRUE) { .Call("divergence_update_W", v, w, h, nbterms, ncterms, copy, PACKAGE='NMF') } #' @rdname nmf_update_KL #' @export nmf_update.KL.w_R <- R_std.divergence.update.w <- function(v, w, h, wh=NULL) { # compute WH if necessary if( is.null(wh) ) wh <- w %*% h # W_ia = W_ia ( sum_u [H_au A_iu / (WH)_iu ] ) / ( sum_v H_av ) -> each column of W is divided by a the corresponding rowSum of H #x2 <- matrix(rep(rowSums(h), nrow(w)), ncol=ncol(w), byrow=TRUE); #w * tcrossprod(v / wh, h) / x2; sweep(w * tcrossprod(v / wh, h), 2L, rowSums(h), "/", check.margin = FALSE) # optimize version? } #' NMF Multiplicative Updates for Euclidean Distance #' #' Multiplicative updates from \cite{Lee2001} for standard Nonnegative Matrix Factorization #' models \eqn{V \approx W H}, where the distance between the target matrix and its NMF #' estimate is measured by the -- euclidean -- Frobenius norm. #' #' \code{nmf_update.euclidean.w} and \code{nmf_update.euclidean.h} compute the updated basis and coefficient #' matrices respectively. #' They use a \emph{C++} implementation which is optimised for speed and memory usage. #' #' @details #' The coefficient matrix (\code{H}) is updated as follows: #' \deqn{ #' H_{kj} \leftarrow \frac{\max(H_{kj} W^T V)_{kj}, \varepsilon) }{(W^T W H)_{kj} + \varepsilon} #' }{ #' H_kj <- max(H_kj (W^T V)_kj, eps) / ( (W^T W H)_kj + eps ) #' } #' #' These updates are used by the built-in NMF algorithms \code{\link[=Frobenius-nmf]{Frobenius}} and #' \code{\link[=lee-nmf]{lee}}. #' #' @inheritParams nmf_update.KL.h #' @param eps small numeric value used to ensure numeric stability, by shifting up #' entries from zero to this fixed value. #' #' @return a matrix of the same dimension as the input matrix to update #' (i.e. \code{w} or \code{h}). #' If \code{copy=FALSE}, the returned matrix uses the same memory as the input object. #' #' @author #' Update definitions by \cite{Lee2001}. #' #' C++ optimised implementation by Renaud Gaujoux. #' #' @rdname nmf_update_euclidean #' @aliases nmf_update.euclidean #' @export nmf_update.euclidean.h <- std.euclidean.update.h <- function(v, w, h, eps=10^-9, nbterms=0L, ncterms=0L, copy=TRUE){ .Call("euclidean_update_H", v, w, h, eps, nbterms, ncterms, copy, PACKAGE='NMF') } #' \code{nmf_update.euclidean.w_R} and \code{nmf_update.euclidean.h_R} implement the same updates #' in \emph{plain R}. #' #' @param wh already computed NMF estimate used to compute the denominator term. #' #' @rdname nmf_update_euclidean #' @export nmf_update.euclidean.h_R <- R_std.euclidean.update.h <- function(v, w, h, wh=NULL, eps=10^-9){ # compute WH if necessary den <- if( is.null(wh) ) crossprod(w) %*% h else{ t(w) %*% wh} # H_au = H_au (W^T V)_au / (W^T W H)_au pmax(h * crossprod(w,v),eps) / (den + eps); } #' @details #' The basis matrix (\code{W}) is updated as follows: #' \deqn{ #' W_ik \leftarrow \frac{\max(W_ik (V H^T)_ik, \varepsilon) }{ (W H H^T)_ik + \varepsilon} #' }{ #' W_ik <- max(W_ik (V H^T)_ik, eps) / ( (W H H^T)_ik + eps ) #' } #' #' @param weight numeric vector of sample weights, e.g., used to normalise samples #' coming from multiple datasets. #' It must be of the same length as the number of samples/columns in \code{v} #' -- and \code{h}. #' #' @rdname nmf_update_euclidean #' @export nmf_update.euclidean.w <- std.euclidean.update.w <- function(v, w, h, eps=10^-9, nbterms=0L, ncterms=0L, weight=NULL, copy=TRUE){ .Call("euclidean_update_W", v, w, h, eps, weight, nbterms, ncterms, copy, PACKAGE='NMF') } #' @rdname nmf_update_euclidean #' @export nmf_update.euclidean.w_R <- R_std.euclidean.update.w <- function(v, w, h, wh=NULL, eps=10^-9){ # compute WH if necessary den <- if( is.null(wh) ) w %*% tcrossprod(h) else{ wh %*% t(h)} # W_ia = W_ia (V H^T)_ia / (W H H^T)_ia and columns are rescaled after each iteration pmax(w * tcrossprod(v, h), eps) / (den + eps); } ################################################################################################ # AFTER-UPDATE METHODS ################################################################################################ #' Stopping Criteria for NMF Iterative Strategies #' #' The function documented here implement stopping/convergence criteria #' commonly used in NMF algorithms. #' #' \code{NMFStop} acts as a factory method that creates stopping criterion functions #' from different types of values, which are subsequently used by #' \code{\linkS4class{NMFStrategyIterative}} objects to determine when to stop their #' iterative process. #' #' @details #' \code{NMFStop} can take the following values: #' \describe{ #' \item{function}{ is returned unchanged, except when it has no arguments, #' in which case it assumed to be a generator, which is immediately called and should return #' a function that implements the actual stopping criterion;} #' \item{integer}{ the value is used to create a stopping criterion that stops at #' that exact number of iterations via \code{nmf.stop.iteration};} #' \item{numeric}{ the value is used to create a stopping criterion that stops when #' at that stationary threshold via \code{nmf.stop.threshold};} #' \item{character}{ must be a single string which must be an access key #' for registered criteria (currently available: \dQuote{connectivity} and \dQuote{stationary}), #' or the name of a function in the global environment or the namespace of the loading package.} #' } #' #' @param s specification of the stopping criterion. #' See section \emph{Details} for the supported formats and how they are processed. #' @param check logical that indicates if the validity of the stopping criterion #' function should be checked before returning it. #' #' @return a function that can be passed to argument \code{.stop} of function #' \code{\link{nmf}}, which is typically used when the algorith is implemented as #' an iterative strategy. #' #' @aliases stop-NMF #' @rdname stop-NMF #' @export NMFStop <- function(s, check=TRUE){ key <- s fun <- if( is.integer(key) ) nmf.stop.iteration(key) else if( is.numeric(key) ) nmf.stop.threshold(key) else if( is.function(key) ) key else if( is.character(key) ){ # update .stop for back compatibility: if( key == 'nmf.stop.consensus') key <- 'connectivity' # first lookup for a `nmf.stop.*` function key2 <- paste('nmf.stop.', key, sep='') e <- pkgmaker::packageEnv() sfun <- getFunction(key2, mustFind=FALSE, where = e) if( is.null(sfun) ) # lookup for the function as such sfun <- getFunction(key, mustFind = FALSE, where = e) if( is.null(sfun) ) stop("Invalid key ['", key,"']: could not find functions '",key2, "' or '", key, "'") sfun }else if( identical(key, FALSE) ) # create a function that does not stop function(strategy, i, target, data, ...){FALSE} else stop("Invalid key: should be a function, a character string or a single integer/numeric value. See ?NMFStop.") # execute if generator (i.e. no arguments) if( length(formals(fun)) == 0L ) fun <- fun() # check validity if requested if( check ){ n.stop <- names(formals(fun)) if( length(n.stop) < 4 ){ stop("Invalid 'Stop' method - must have at least 4 arguments: ", "NMF strategy object [strategy], ", "current iteration number [i], ", "target matrix [y], ", "current NMF model iterate [x]") } n.stop <- n.stop[-seq(4)] # argument '...' must be present in method 'Stop' if( !is.element('...', n.stop) ) stop("Invalid 'Stop' method: must have argument '...' (even if not used)") } # return function fun } #' \code{nmf.stop.iteration} generates a function that implements the stopping #' criterion that limits the number of iterations to a maximum of \code{n}), #' i.e. that returns \code{TRUE} if \code{i>=n}, \code{FALSE} otherwise. #' #' @param n maximum number of iteration to perform. #' #' @return a function that can be used as a stopping criterion for NMF algorithms #' defined as \code{\linkS4class{NMFStrategyIterative}} objects. #' That is a function with arguments \code{(strategy, i, target, data, ...)} #' that returns \code{TRUE} if the stopping criterion is satisfied -- which in #' turn stops the iterative process, and \code{FALSE} otherwise. #' #' @export #' @family NMFStrategyIterative #' @rdname stop-NMF nmf.stop.iteration <- function(n){ nmf.debug("Using stopping criterion - Fixed number of iterations: ", n) if( !is.numeric(n) ) stop("Invalid argument `n`: must be an integer value") if( length(n) > 1 ) warning("NMF::nmf - Argument `n` [", deparse(substitute(n)), "] has length > 1: only using the first element.") .max <- n[1] function(object, i, y, x, ...) i >= .max } #' \code{nmf.stop.threshold} generates a function that implements the stopping #' criterion that stops when a given stationarity threshold is achieved by #' successive iterations. #' The returned function is identical to \code{nmf.stop.stationary}, but with #' the default threshold set to \code{threshold}. #' #' @param threshold default stationarity threshold #' #' @export #' @rdname stop-NMF nmf.stop.threshold <- function(threshold){ nmf.debug("Using stopping criterion - Stationarity threshold: ", threshold) if( !is.numeric(threshold) ) stop("Invalid argument `threshold`: must be a numeric value") if( length(threshold) > 1 ) warning("NMF::nmf - Argument `threshold` [", deparse(substitute(threshold)), "] has length > 1: only using the first element.") eval(parse(text=paste("function(strategy, i, target, data, stationary.th=", threshold, ", ...){ nmf.stop.stationary(strategy, i, target, data, stationary.th=stationary.th, ...) }"))) } #' \code{nmf.stop.stationary} implements the stopping criterion of stationarity #' of the objective value, which stops when the gradient of the objective function #' is uniformly small over a certain number of iterations. #' #' More precisely, the objective function is computed over \eqn{n} successive iterations (specified #' in argument \code{check.niter}), every \code{check.interval} iterations. #' The criterion stops when the absolute difference between the maximum and the minimum #' objective values over these iterations is lower than a given threshold \eqn{\alpha} #' (specified in \code{stationary.th}): #' #' \deqn{ #' \left| \frac{\max_{i- N_s + 1 \leq k \leq i} D_k - \min_{i - N_s +1 \leq k \leq i} D_k}{n} \right| \leq \alpha, #' }{ #' | [max( D(i- N_s + 1), ..., D(i) ) - min( D(i- N_s + 1), ..., D(i) )] / n | <= alpha #' } #' #' @param object an NMF strategy object #' @param i the current iteration #' @param y the target matrix #' @param x the current NMF model #' @param stationary.th maximum absolute value of the gradient, for the objective #' function to be considered stationary. #' @param check.interval interval (in number of iterations) on which the stopping #' criterion is computed. #' @param check.niter number of successive iteration used to compute the stationnary #' criterion. #' @param ... extra arguments passed to the function \code{\link{objective}}, #' which computes the objective value between \code{x} and \code{y}. #' #' @export #' @rdname stop-NMF nmf.stop.stationary <- local({ # static variable .last.objective.value <- c(-Inf, Inf) .niter <- 0L .store_value <- function(value){ .niter <<- .niter + 1L .last.objective.value <<- c(max(.last.objective.value[1L], value) , min(.last.objective.value[2L], value)) } .reset_value <- function(){ .last.objective.value <<- c(-Inf, Inf) .niter <<- 0L } function(object, i, y, x, stationary.th=.Machine$double.eps, check.interval=5*check.niter, check.niter=10L, ...){ # check validity if( check.interval < check.niter ){ stop("Invalid argument values: `check.interval` must always be greater than `check.niter`") } # initialisation call: compute initial objective value if( i == 0L || (i == 1L && is.null(.last.objective.value)) ){ .reset_value() # give the chance to update once and estimate from a partial model if( is.partial.nmf(x) ) return( FALSE ) # compute initial deviance current.value <- deviance(object, x, y, ...) # check for NaN, i.e. probably infinitely small value (cf. bug reported by Nadine POUKEN SIEWE) if( is.nan(current.value) ) return(TRUE) # store value in static variable for next calls .store_value(current.value) return(FALSE) } # test convergence only every 10 iterations if( .niter==0L && i %% check.interval != 0 ) return( FALSE ); # get last objective value from static variable current.value <- deviance(object, x, y, ...) # check for NaN, i.e. probably infinitely small value (cf. bug reported by Nadine POUKEN SIEWE) if( is.nan(current.value) ) return(TRUE) # update static variables .store_value(current.value) # once values have been computed for check.niter iterations: # check if the difference in the extreme objective values is small enough if( .niter == check.niter+1 ){ crit <- abs(.last.objective.value[1L] - .last.objective.value[2L]) / check.niter if( crit <= stationary.th ){ if( nmf.getOption('verbose') ){ message(crit) } return( TRUE ) } .reset_value() } # do NOT stop FALSE } }) #' \code{nmf.stop.connectivity} implements the stopping criterion that is based #' on the stationarity of the connectivity matrix. #' #' @inheritParams nmf.stop.stationary #' @param stopconv number of iterations intervals over which the connectivity #' matrix must not change for stationarity to be achieved. #' #' @export #' @rdname stop-NMF nmf.stop.connectivity <- local({ # static variables .consold <- NULL .inc <- NULL function(object, i, y, x, stopconv=40, check.interval=10, ...){ if( i == 0L ){ # initialisation call # Initialize consensus variables # => they are static variables within the strategy's workspace so that # they are persistent and available throughout across the calls p <- ncol(x) .consold <<- matrix(0, p, p) .inc <<- 0 return(FALSE) } # test convergence only every 10 iterations if( i %% check.interval != 0 ) return( FALSE ); # retrieve metaprofiles h <- coef(x, all=FALSE) # construct connectivity matrix index <- apply(h, 2, function(x) which.max(x) ) cons <- outer(index, index, function(x,y) ifelse(x==y, 1,0)); changes <- cons != .consold if( !any(changes) ) .inc <<- .inc + 1 # connectivity matrix has not changed: increment the count else{ .consold <<- cons; .inc <<- 0; # else restart counting } # prints number of changing elements #if( verbose(x) ) cat( sprintf('%d ', sum(changes)) ) #cat( sprintf('%d ', sum(changes)) ) # assume convergence is connectivity stops changing if( .inc > stopconv ) return( TRUE ); # do NOT stop FALSE } }) ################################################################################################ # WRAP-UP METHODS ################################################################################################ NMF/R/heatmaps.R0000644000176200001440000004403113620502674013027 0ustar liggesusers# Heatmap functions # # Author: Renaud Gaujoux ############################################################################### #' @include NMF-class.R #' @include aheatmap.R NULL #' @param object an R object #' @param ... other arguments #' #' @export #' @inline #' @rdname NMF-defunct setGeneric('metaHeatmap', function(object, ...) standardGeneric('metaHeatmap') ) #' Defunct method substituted by \code{\link{aheatmap}}. setMethod('metaHeatmap', signature(object='matrix'), function(object, ...){ local <- function(object, type=c('plain', 'consensus'), class , unit.scaling=c('none', 'row', 'column'), palette="YlOrRd" , rev.palette=FALSE, show.prediction=TRUE, ...){ .Defunct('metaHeatmap', 'NMF', "The S4 method 'metaHeatmap,matrix' is defunct, use 'aheatmap' instead.") # # load libary RColorBrewer # library(RColorBrewer) # # # retreive the graphical parameters and match them to the sub-sequent call to 'heatmap.plus.2' # graphical.params <- list(...) # names(graphical.params) <- .match.call.args(names(graphical.params), 'heatmap.plus.2', in.fun='metaHeatmap', call='NMF::metaHeatmap') # # type <- match.arg(type) # if( type == 'consensus' ){ # # set default graphical parameters for type 'consensus' # graphical.params <- .set.list.defaults(graphical.params # , distfun = function(x){ as.dist(1-x) } # , main='Consensus matrix' # , symm=TRUE # , Rowv=TRUE # , revC=TRUE # ) # # if( missing(palette) ) palette <- 'RdYlBu' # if( missing(rev.palette) ) rev.palette <- TRUE # if( missing(unit.scaling) ) unit.scaling <- 'none' # show.prediction <- FALSE # not used for consensus matrices # } # # # apply unit scaling if necessary # unit.scaling <- match.arg(unit.scaling) # if( unit.scaling == 'column' ) # object <- apply(object, 2, function(x) x/sum(x)) # else if ( unit.scaling == 'row' ) # object <- t(apply(object, 1, function(x) x/sum(x))) # # # check validity of palette # col.palette <- brewer.pal(brewer.pal.info[palette,'maxcolors'],palette) # if( rev.palette ) col.palette <- rev(col.palette) # # # set default graphical parameters (if those are not already set) # graphical.params <- .set.list.defaults(graphical.params # , cexRow=0.8, cexCol=0.8 # , hclustfun = function(m) hclust(m,method="average") # , dendrogram='none' # , col=col.palette # , scale='none', trace="none" # , keysize=1, margins=c(5,10) # ) # # # if a known class is provided, add a side color over the top row # if( !missing(class) ){ # if( !is.factor(class) ) class <- as.factor(class) # class.num <- as.numeric(class) # legend.pal <- palette(rainbow(max(2,nlevels(class))))[1:nlevels(class)] # col.matrix <- matrix(legend.pal[class.num], ncol(object), 1) # # # show association with metagenes # if( show.prediction ){ # # only if there is less than 9 metagenes # # cf. limitation of brewer color palette # if( nrow(object) <= 9 ){ # prediction <- .predict.nmf(object) # prediction.num <- as.numeric(prediction) # pal.pred <- brewer.pal(max(3,nrow(object)),'Set2')[1:nrow(object)] # col.matrix <- cbind(pal.pred[prediction.num], col.matrix) # graphical.params <- .set.list.defaults(graphical.params # , RowSideColors=pal.pred # ) # } # else warning("NMF::metaHeatmap - cannot not show prediction for more than 9 metagenes.") # } # # do that otherwise heatmap.plus complains # if( ncol(col.matrix) < 2 ) # col.matrix <- cbind(col.matrix, col.matrix) # # # add the ColSideColors # graphical.params <- .set.list.defaults(graphical.params # , ColSideColors=col.matrix # ) # } # # # res.heatmap <- do.call('heatmap.plus.2', c(list(object), graphical.params)) # # if( !missing(class) ){ # # order properly the legend boxes # class.num <- as.numeric(class[res.heatmap$colInd]) # # occ <- NA # will store the current number of occurences # class.max.occ <- rep(0, nlevels(class)) # will store the current maximum number of occurences per class # class.start <- rep(NA, nlevels(class)) # will store the current start of the longer stretch per class # last.l <- '' # sapply( seq(length(class.num), 1, -1), # function(i){ # l <- class.num[i] # if(l==last.l){ # occ <<- occ + 1 # }else{ # occ <<- 1 # } # if(occ > class.max.occ[l]){ # class.max.occ[l] <<- occ # class.start[l] <<- i # } # last.l <<- l # } # ) # # class.ord <- order(class.start) # l.names <- levels(class)[class.ord] # l.color <- legend.pal[class.ord] # legend('top', title='Classes' # , legend=l.names, fill=l.color # , horiz=TRUE, bty='n') # } # # # return invisible # invisible(res.heatmap) } local(object, ...) } ) #' Deprecated method that is substituted by \code{\link{coefmap}} and \code{\link{basismap}}. setMethod('metaHeatmap', signature(object='NMF'), function(object, ...){ local <- function(object, what=c('samples', 'features'), filter=FALSE, ...){ what <- match.arg(what) if( what == 'samples' ){ # send deprecated warning .Defunct('coefmap', 'NMF', "Direct use of the S4-Method 'metaHeatmap' for 'NMF' objects is defunct, use 'coefmap' instead.") # call the new function 'coefmap' return( coefmap(object, ...) ) }else if( what == 'features' ){ # send deprecated warning .Defunct('basismap', 'NMF', "Direct use of the S4-Method 'metaHeatmap' for 'NMF' objects is defunct, use 'basismap' instead.") # call the new function 'basismap' return( basismap(object, subsetRow=filter, ...) ) } } local(object, ...) } ) # match an annotation track against list of supported tracks match_named_track <- function(annotation, tracks, msg, optional=FALSE){ idx <- if( is.character(annotation) ){ i <- match(annotation, tracks, nomatch=if(optional) 0L else NA ) if( any(!is.na(i)) ){ if( !optional && any(is.na(i)) ){ stop(msg, "invalid track(s) [", str_out(annotation[is.na(i)]) , "]: should be one of ", str_out(tracks)) } } i }else if( is.list(annotation) ){ sapply(annotation, function(x){ if( isString(x) ) match(x, tracks, nomatch=if(optional) 0L else NA ) else NA }) } if( is.null(idx) ) return() ok <- !is.na(idx) # result # remaining annotations ann <- annotation[!ok] if( length(ann) == 0L ) ann <- NULL # track annotations tr <- unlist(annotation[which(ok)]) idx <- idx[which(ok)] if( is.null(names(annotation)) ) names(tr) <- tr else{ mn <- names(tr) == '' names(tr)[mn] <- tr[mn] } others <- tr[idx==0L] # # list(ann=ann, tracks=tr[idx>0L], others=if(length(others)) others else NULL) list(ann=as.list(ann), tracks=tr) } #' Heatmaps of NMF Factors #' #' The NMF package ships an advanced heatmap engine implemented by the function #' \code{\link{aheatmap}}. #' Some convenience heatmap functions have been implemented for NMF models, #' which redefine default values for some of the arguments of \code{\link{aheatmap}}, #' hence tuning the output specifically for NMF models. #' #' \strong{IMPORTANT:} although they essentially have the same set of arguments, #' their order sometimes differ between them, as well as from \code{\link{aheatmap}}. #' We therefore strongly recommend to use fully named arguments when calling these functions. #' #' @rdname heatmaps #' @name heatmap-NMF #' #' @examples #' #' ## More examples are provided in demo `heatmaps` #' \dontrun{ #' demo(heatmaps) #' } #' ## #' #' # random data with underlying NMF model #' v <- syntheticNMF(20, 3, 10) #' # estimate a model #' x <- nmf(v, 3) #' #' @demo Heatmaps of NMF objects #' #' #' # random data with underlying NMF model #' v <- syntheticNMF(20, 3, 10) #' # estimate a model #' x <- nmf(v, 3) #' NULL #' \code{basimap} draws an annotated heatmap of the basis matrix. #' #' @details #' \code{basimap} default values for the following arguments of \code{\link{aheatmap}}: #' \itemize{ #' \item the color palette; #' \item the scaling specification, which by default scales each #' row separately so that they sum up to one (\code{scale='r1'}); #' \item the column ordering which is disabled; #' \item allowing for passing feature extraction methods in argument #' \code{subsetRow}, that are passed to \code{\link{extractFeatures}}. #' See argument description here and therein. #' \item the addition of a default named annotation track, that shows #' the dominant basis component for each row (i.e. each feature). #' #' This track is specified in argument \code{tracks} (see its argument description). #' By default, a matching column annotation track is also displayed, but may be #' disabled using \code{tracks=':basis'}. #' #' \item a suitable title and extra information like the fitting algorithm, #' when \code{object} is a fitted NMF model. #' } #' #' @param object an object from which is extracted NMF factors or a consensus #' matrix #' @param ... extra arguments passed to \code{\link{aheatmap}}. #' #' @rdname heatmaps #' @inline #' @export #' #' @examples #' #' # show basis matrix #' basismap(x) #' \dontrun{ #' # without the default annotation tracks #' basismap(x, tracks=NA) #' } #' #' @demo #' #' # highligh row only (using custom colors) #' basismap(x, tracks=':basis', annColor=list(basis=1:3)) #' #' ## character annotation vector: ok if it does not contain 'basis' #' # annotate first and second row + automatic special track #' basismap(x, annRow=c('alpha', 'beta')) #' # no special track here #' basismap(x, annRow=c('alpha', 'beta', ':basis'), tracks=NA) #' # with special track `basis` #' basismap(x, annRow=list(c('alpha', 'beta'), ':basis'), tracks=NA) #' # highligh columns only (using custom colors) #' basismap(x, tracks='basis:') #' #' # changing the name of the basis annotation track #' basismap(x, annRow=list(new_name=':basis')) #' setGeneric('basismap', function(object, ...) standardGeneric('basismap') ) #' Plots a heatmap of the basis matrix of the NMF model \code{object}. #' This method also works for fitted NMF models (i.e. \code{NMFfit} objects). #' #' @inheritParams aheatmap #' @param subsetRow Argument that specifies how to filter the rows that #' will appear in the heatmap. #' When \code{FALSE} (default), all rows are used. #' Besides the values supported by argument \code{subsetRow} of #' \code{\link{aheatmap}}, other possible values are: #' #' \itemize{ #' \item \code{TRUE}: only the rows that are basis-specific are used. #' The default selection method is from \cite{KimH2007}. #' This is equivalent to \code{subsetRow='kim'}. #' #' \item a single \code{character} string or numeric value that specifies #' the method to use to select the basis-specific rows, that should appear in the #' heatmap (cf. argument \code{method} for function \code{\link{extractFeatures}}). #' #' Note \code{\link{extractFeatures}} is called with argument \code{nodups=TRUE}, #' so that features that are selected for multiple components only appear once. #' } #' @param tracks Special additional annotation tracks to highlight associations between #' basis components and sample clusters: #' \describe{ #' \item{basis}{matches each row (resp. column) to the most contributing basis component #' in \code{basismap} (resp. \code{coefmap}). #' In \code{basismap} (resp. \code{coefmap}), adding a track \code{':basis'} to #' \code{annCol} (resp. \code{annRow}) makes the column (resp. row) corresponding to #' the component being also highlited using the mathcing colours.} #' } #' @param info if \code{TRUE} then the name of the algorithm that fitted the NMF #' model is displayed at the bottom of the plot, if available. #' Other wise it is passed as is to \code{aheatmap}. #' #' setMethod('basismap', signature(object='NMF'), function(object, color = 'YlOrRd:50' , scale = 'r1' , Rowv=TRUE, Colv=NA, subsetRow=FALSE , annRow=NA, annCol=NA, tracks = 'basis' , main="Basis components", info = FALSE , ...){ # resolve subsetRow if its a single value if( is.atomic(subsetRow) && length(subsetRow) == 1 ){ subsetRow <- if( isFALSE(subsetRow) ) NULL else if( isTRUE(subsetRow) ) # use Kim and Park scoring scheme for filtering extractFeatures(object, format='combine') else if( is.character(subsetRow) || is.numeric(subsetRow) ) # use subsetRow as a filtering method extractFeatures(object, method=subsetRow, format='combine') else stop("NMF::basismap - invalid single value for argument 'subsetRow' [logical, numeric or character expected]") } # extract the basis vector matrix x <- basis(object) # add side information if requested info <- if( isTRUE(info) && isNMFfit(object) ) paste("Method:", algorithm(object)) else if( isFALSE(info) ) NULL else info # process annotation tracks ptracks <- process_tracks(x, tracks, annRow, annCol) annRow <- ptracks$row annCol <- ptracks$col # set special annotation handler specialAnnotation(1L, 'basis', function() predict(object, what='features')) specialAnnotation(2L, 'basis', function() as.factor(1:nbasis(object))) # # call aheatmap on matrix aheatmap(x, color = color, ... , scale = scale, Rowv=Rowv, Colv = Colv, subsetRow = subsetRow , annRow = annRow, annCol = annCol , main = main, info = info) } ) # check if an object contains some value anyValue <- function(x){ length(x) > 0L && !is_NA(x) } grep_track <- function(x){ list( both = grepl("^[^:].*[^:]$", x) | grepl("^:.*:$", x) , row = grepl("^:.*[^:]$", x) , col = grepl("^[^:].*:$", x) ) } # process extra annotation tracks process_tracks <- function(data, tracks, annRow=NA, annCol=NA){ if( anyValue(tracks) ){ # extract choices from caller function formal.args <- formals(sys.function(sys.parent())) choices <- eval(formal.args[[deparse(substitute(tracks))]]) if( isTRUE(tracks) ) tracks <- choices else{ if( !is.character(tracks) ) stop("Special annotation tracks must be specified either as NA, TRUE or a character vector [", class(tracks), "].") # check validity pattern <- "^(:)?([^:]*)(:)?$" basech <- str_match(choices, pattern) basetr <- str_match(tracks, pattern) tr <- basetr[, 3L] # print(basetr) # print(basech) # extend base track name i <- charmatch(tr, basech[,3L]) tr[!is.na(i)] <- basech[i[!is.na(i)],3L] tracks_long <- str_c(basetr[,2L], tr, basetr[,4L]) # extend choices tty_choice <- grep_track(choices) if( any(tty_choice$both) ) choices <- c(choices, str_c(':', choices[tty_choice$both]), str_c(choices[tty_choice$both], ':')) # look for exact match itr <- charmatch(tracks_long, choices) if( length(err <- which(is.na(itr))) ){ stop("Invalid special annotation track name [", str_out(tracks[err], Inf) ,"]. Should partially match one of ", str_out(choices, Inf), '.') } tracks[!is.na(itr)] <- choices[itr] } # print(tracks) } # tty <- grep_track(tracks) # create result object build <- function(x, ann, data, margin){ t <- if( anyValue(x) ) as.list(setNames(str_c(':', sub("(^:)|(:$)","",x)), names(x))) else NA # build annotations atrack(ann, t, .DATA=amargin(data,margin)) } res <- list() res$row <- build(tracks[tty$both | tty$row], annRow, data, 1L) res$col <- build(tracks[tty$both | tty$col], annCol, data, 2L) #str(res) res } #' \code{coefmap} draws an annotated heatmap of the coefficient matrix. #' #' @details #' \code{coefmap} redefines default values for the following arguments of #' \code{\link{aheatmap}}: #' \itemize{ #' \item the color palette; #' \item the scaling specification, which by default scales each #' column separately so that they sum up to one (\code{scale='c1'}); #' \item the row ordering which is disabled; #' \item the addition of a default annotation track, that shows the most #' contributing basis component for each column (i.e. each sample). #' #' This track is specified in argument \code{tracks} (see its argument description). #' By default, a matching row annotation track is also displayed, but can be disabled #' using \code{tracks='basis:'}. #' \item a suitable title and extra information like the fitting algorithm, #' when \code{object} is a fitted NMF model. #' } #' #' @rdname heatmaps #' @inline #' @export #' #' @examples #' #' # coefficient matrix #' coefmap(x) #' \dontrun{ #' # without the default annotation tracks #' coefmap(x, tracks=NA) #' } #' #' @demo #' #' # coefficient matrix #' coefmap(x, annCol=c('alpha', 'beta')) # annotate first and second sample #' coefmap(x, annCol=list('basis', Greek=c('alpha', 'beta'))) # annotate first and second sample + basis annotation #' coefmap(x, annCol=c(new_name='basis')) #' setGeneric('coefmap', function(object, ...) standardGeneric('coefmap') ) #' The default method for NMF objects has special default values for #' some arguments of \code{\link{aheatmap}} (see argument description). setMethod('coefmap', signature(object='NMF'), function(object, color = 'YlOrRd:50' , scale = 'c1' , Rowv = NA, Colv = TRUE , annRow = NA, annCol = NA, tracks='basis' , main="Mixture coefficients", info = FALSE , ...){ # use the mixture coefficient matrix x <- coef(object) # add side information if requested info <- if( isTRUE(info) && isNMFfit(object) ) paste("Method: ", algorithm(object)) else if( isFALSE(info) ) NULL else info # process annotation tracks ptracks <- process_tracks(x, tracks, annRow, annCol) annRow <- ptracks$row annCol <- ptracks$col # set special annotation handler specialAnnotation(1L, 'basis', function() as.factor(1:nbasis(object))) specialAnnotation(2L, 'basis', function() predict(object)) # ## process ordering if( isString(Colv) ){ if( Colv == 'basis' ) Colv <- 'samples' if( Colv == 'samples' ) Colv <- order(as.numeric(predict(object, Colv))) } ## # call aheatmap on matrix aheatmap(x, ..., color = color , scale = scale, Rowv = Rowv, Colv=Colv , annRow=annRow, annCol = annCol , main=main, info = info) } ) NMF/R/utils.R0000644000176200001440000007463313620502674012400 0ustar liggesusers#' @include rmatrix.R #' @include nmf-package.R NULL #' Utility Function in the NMF Package #' #' @name utils-NMF #' @rdname utils NULL #' Internal verbosity option #' @param val logical that sets the verbosity level. #' @return the old verbose level #' @keywords internal lverbose <- local({ .val <- 0 function(val){ if( missing(val) ) return(.val) oval <- .val .val <<- val invisible(oval) } }) vmessage <- function(..., appendLF=TRUE) if( lverbose() ) cat(..., if(appendLF) "\n", sep='') nmf_stop <- function(name, ...){ stop("NMF::", name , ' - ', ..., call.=FALSE) } nmf_warning <- function(name, ...){ warning("NMF::", name , ' - ', ..., call.=FALSE) } # or-NULL operator '%||%' <- function(x, y) if( !is.null(x) ) x else y # cat object or class for nice cat/message quick_str <- function(x) if( is.atomic(x) ) x else class(x)[1] # remove all attributes from an object rmAttributes <- function(x){ attributes(x) <- NULL x } #' \code{str_args} formats the arguments of a function using \code{\link{args}}, #' but returns the output as a string. #' #' @param x a function #' @param exdent indentation for extra lines if the output takes more than one line. #' #' @export #' @rdname utils #' #' @examples #' #' args(library) #' str_args(library) #' str_args <- function(x, exdent=10L){ s <- capture.output(print(args(x))) paste(str_trim(s[-length(s)]), collapse=str_c('\n', paste(rep(' ', exdent), collapse=''))) } #' Simple Progress Bar #' #' Creates a simple progress bar with title. #' This function is identical to \code{utils::txtProgressBar} but allow adding #' a title to the progress bar, and can be shared by multiple processes, #' e.g., in multicore or multi-hosts computations. #' #' @inheritParams utils::txtProgressBar #' @param shared specification of a shared directory to use when the progress #' bar is to be used by multiple processes. #' #' @author R Core Team #' @keywords internal txtProgressBar <- function (min = 0, max = 1, initial = 0, char = "=", width = NA, title= if( style == 3 ) ' ', label, style = 1, file = "" , shared = NULL) { if (!identical(file, "") && !(inherits(file, "connection") && isOpen(file))) stop("'file' must be \"\" or an open connection object") if (!style %in% 1L:3L) style <- 1 .val <- initial .killed <- FALSE .nb <- 0L .pc <- -1L nw <- nchar(char, "w") if (is.na(width)) { width <- getOption("width") if (style == 3L) width <- width - 10L width <- trunc(width/nw) } if (max <= min) stop("must have max > min") # setup shared directory .shared <- NULL if( isTRUE(shared) ) shared <- tempdir() if( is.character(shared) ){ .shared <- tempfile('progressbar_', tmpdir=shared[1L]) dir.create(.shared) } # getval <- function(value){ if( value >= max || value <= min || is.null(.shared) || !file.exists(.shared) ){ value }else{ cat('', file=file.path(.shared, paste('_', value, sep=''))) length(list.files(.shared)) } } up1 <- function(value) { if (!is.finite(value) || value < min || value > max) return() # get actual value value <- getval(value) .val <<- value nb <- round(width * (value - min)/(max - min)) if (.nb < nb) { cat(paste(rep.int(char, nb - .nb), collapse = ""), file = file) flush.console() } else if (.nb > nb) { cat("\r", title, paste(rep.int(" ", .nb * nw), collapse = ""), "\r", title, paste(rep.int(char, nb), collapse = ""), sep = "", file = file) flush.console() } .nb <<- nb } up2 <- function(value) { if (!is.finite(value) || value < min || value > max) return() # get actual value value <- getval(value) .val <<- value nb <- round(width * (value - min)/(max - min)) if (.nb <= nb) { cat("\r", title, paste(rep.int(char, nb), collapse = ""), sep = "", file = file) flush.console() } else { cat("\r", title, paste(rep.int(" ", .nb * nw), collapse = ""), "\r", paste(rep.int(char, nb), collapse = ""), sep = "", file = file) flush.console() } .nb <<- nb } up3 <- function(value) { if (!is.finite(value) || value < min || value > max) return() # get actual value value <- getval(value) .val <<- value nb <- round(width * (value - min)/(max - min)) pc <- round(100 * (value - min)/(max - min)) if (nb == .nb && pc == .pc) return() cat(paste(c("\r",title," |", rep.int(" ", nw * width + 6)), collapse = ""), file = file) cat(paste(c("\r",title," |", rep.int(char, nb), rep.int(" ", nw * (width - nb)), sprintf("| %3d%%", pc)), collapse = ""), file = file) flush.console() .nb <<- nb .pc <<- pc } getVal <- function() .val kill <- function(cleanup=TRUE) if (!.killed) { cat("\n", file = file) flush.console() .killed <<- TRUE # do some cleanup if( cleanup ){ # delete shared directory if( !is.null(.shared) && file.exists(.shared) ) unlink(.shared, recursive=TRUE) # } invisible(TRUE) } up <- switch(style, up1, up2, up3) up(initial) structure(list(getVal = getVal, up = up, kill = kill), class = "txtProgressBar") } ###% apply a function to each entry in a matrix matapply <- function(x, FUN, ...){ res <- sapply(x, FUN, ...) matrix(res, nrow(x)) } ###% try to convert a character string into a numeric toNumeric <- function(x){ suppressWarnings( as.numeric(x) ) } ###% Tells one is running in Sweave isSweave <- function() !is.null(sweaveLabel()) sweaveLabel <- function(){ if ((n.parents <- length(sys.parents())) >= 3) { for (i in seq_len(n.parents) - 1) { if ("chunkopts" %in% ls(envir = sys.frame(i))) { chunkopts = get("chunkopts", envir = sys.frame(i)) if (all(c("prefix.string", "label") %in% names(chunkopts))) { img.name = paste(chunkopts$prefix.string, chunkopts$label, sep = "-") return(img.name) break } } } } } sweaveFile <- function(){ label <- sweaveLabel() if( !is.null(label) ) paste(label, '.pdf', sep='') } fixSweaveFigure <- function(filename){ if( missing(filename) ){ filename <- sweaveLabel() if( is.null(filename) ) return() filename <- paste(filename, '.pdf', sep='') } filepath <- normalizePath(filename) tf <- tempfile() system(paste("pdftk", filepath, "cat 2-end output", tf, "; mv -f", tf, filepath)) } ###% 'more' functionality to read data progressively more <- function(x, step.size=10, width=20, header=FALSE, pattern=NULL){ if( !(is.matrix(x) || is.data.frame(x) || is.vector(x) || is.list(x)) ) stop("NMF::more - invalid argument 'x': only 'matrix', 'data.frame', 'vector' and 'list' objects are handled.") one.dim <- is.null(dim(x)) single.char <- FALSE n <- if( is.character(x) && length(x) == 1 ){ cat("\n") single.char <- TRUE nchar(x) } else if( one.dim ){ cat("<", class(x),":", length(x), ">\n") # limit to matching terms if necessary if( !is.null(pattern) ) x[grep(pattern, x)] length(x) }else{ cat("<", class(x),":", nrow(x), "x", ncol(x), ">\n") head.init <- colnames(x) head.on <- TRUE # limit to matching terms if necessary if( !is.null(pattern) ){ idx <- apply(x, 2, grep, pattern=pattern) print(idx) idx <- unique(if( is.list(idx) ) unlist(idx) else as.vector(idx)) x <- x[idx,, drop=FALSE] } nrow(x) } i <- 0 while( i < n ){ # reduce 'step.size' if necessary step.size <- min(step.size, n-i) what2show <- if( single.char ) substr(x, i+1, i+step.size) else if( one.dim ) if( !is.na(width) ) sapply(x[seq(i+1, i+step.size)], function(s) substr(s, 1, width) ) else x[seq(i+1, i+step.size)] else{ w <- x[seq(i+1, i+step.size), , drop=FALSE] if( !is.na(width) ){ w <- apply(w, 2, function(s){ ns <- toNumeric(s) if( !is.na(ns[1]) ) # keep numerical value as is ns else # limit output if required substr(s, 1, width) }) rownames(w) <- rownames(x)[seq(i+1, i+step.size)] } # remove header if not required if( !header && head.on ){ colnames(x) <- sapply(colnames(x), function(c) paste(rep(' ', nchar(c)), collapse='')) head.on <- FALSE } # return the content w } cat( show(what2show) ) i <- i + step.size # early break if necessary if( i >= n ) break # ask user what to to next ans <- scan(what='character', quiet=TRUE, n=1, multi.line=FALSE) # process user command if any (otherwise carry on) if( length(ans) > 0 ){ if( !is.na(s <- toNumeric(ans)) ) # change step size step.size <- s else if( !header && ans %in% c('h', 'head') ){ colnames(x) <- head.init head.on <- TRUE } else if( ans %in% c('q', 'quit') ) # quit break } } invisible() } #' Randomizing Data #' #' \code{randomize} permutates independently the entries in each column #' of a matrix-like object, to produce random data that can be used #' in permutation tests or bootstrap analysis. #' #' In the context of NMF, it may be used to generate random data, whose #' factorization serves as a reference for selecting a factorization rank, #' that does not overfit the data. #' #' @param x data to be permutated. It must be an object suitable to be #' passed to the function \code{\link{apply}}. #' @param ... extra arguments passed to the function \code{\link{sample}}. #' #' @return a matrix #' #' @export #' @examples #' x <- matrix(1:32, 4, 8) #' randomize(x) #' randomize(x) #' randomize <- function(x, ...){ if( is(x, 'ExpressionSet') ) x <- Biobase::exprs(x) # resample the columns apply(x, 2, function(c, ...) sample(c, size=length(c), ...), ...) } ###% Returns the rank-k truncated SVD approximation of x tsvd <- function(x, r, ...){ stopifnot( r > 0 && r <= min(dim(x))) s <- svd(x, nu=r, nv=r, ...) s$d <- s$d[1:r] # return results s } ###% Subset a list leaving only the arguments from a given function .extract.args <- function(x, fun, ...){ fdef <- if( is.character(fun) ) getFunction(fun, ...) else if( is.function(fun) ) fun else stop("invalid argument 'fun': expected function name or definition") if( length(x) == 0 ) return(x) x.ind <- charmatch(if( is.list(x) ) names(x) else x, args <- formalArgs(fdef)) x[!is.na(x.ind)] } ###% Returns the version of the package nmfInfo <- function(command){ pkg <- 'NMF' curWarn <- getOption("warn") on.exit(options(warn = curWarn), add = TRUE) options(warn = -1) desc <- packageDescription(pkg, fields="Version") if (is.na(desc)) stop(paste("Package", pkg, "not found")) desc } ###% Returns TRUE if running under Mac OS X + GUI is.Mac <- function(check.gui=FALSE){ is.mac <- (length(grep("darwin", R.version$platform)) > 0) # return TRUE is running on Mac (adn optionally through GUI) is.mac && (!check.gui || .Platform$GUI == 'AQUA') } ###% Hash a function body (using digest) #' @import digest hash_function <- function(f){ b <- body(f) attributes(b) <- NULL fdef <- paste(c(capture.output(args(f))[1], capture.output(print(b))), collapse="\n") # print(fdef) digest(b) } ###% compare function with copy and with no copy cmp.cp <- function(...){ res <- nmf(..., copy=F) resc <- nmf(..., copy=T) cat("identical: ", identical(fit(res), fit(resc)) , " - all.equal: ", all.equal(fit(res), fit(resc)) , " - diff: ", all.equal(fit(res), fit(resc), tol=0) , "\n" ) invisible(res) } # return the internal pointer address C.ptr <- function(x, rec=FALSE) { attribs <- attributes(x) if( !rec || is.null(attribs) ) .Call("ptr_address", x, PACKAGE='NMF') else c( C.ptr(x), sapply(attribs, C.ptr, rec=TRUE)) } is.same <- function(x, y){ C.ptr(x) == C.ptr(y) } is.eset <- function(x) is(x, 'ExpressionSet') # clone an object clone <- function(x){ .Call('clone_object', x, PACKAGE='NMF') } # deep-clone an object clone2 <- function(x){ if( is.environment(x) ){ y <- Biobase::copyEnv(x) eapply(ls(x, all.names=TRUE), function(n){ if( is.environment(x[[n]]) ){ y[[n]] <<- clone(x[[n]]) if( identical(parent.env(x[[n]]), x) ) parent.env(y[[n]]) <<- y } }) }else{ y <- .Call('clone_object', x, PACKAGE='NMF') if( isS4(x) ){ ## deep copy R object lapply(slotNames(class(y)), function(n){ slot(y, n) <<- clone(slot(x, n)) }) }else if( is.list(x) ){ ## copy list or vector sapply(seq_along(x), function(i){ y[[i]] <<- clone(x[[i]]) }) } } y } #compute RSS with C function .rss <- function(x, y) { .Call("Euclidean_rss", x, y, PACKAGE='NMF') } #compute KL divergence with C function .KL <- function(x, y) { .Call("KL_divergence", x, y, PACKAGE='NMF') } #' Updating Objects In Place #' #' These functions modify objects (mainly matrix objects) in place, i.e. they #' act directly on the C pointer. #' Due to their side-effect, they are not meant to be called by the end-user. #' #' \code{pmax.inplace} is a version of \code{\link{pmax}} that updates its first #' argument. #' #' @param x an object to update in place. #' @param lim lower threshold value #' @param skip indexes to skip #' #' @export #' @rdname inplace #' @keywords internal pmax.inplace <- function(x, lim, skip=NULL){ .Call('ptr_pmax', x, lim, as.integer(skip), PACKAGE='NMF') } # colMin colMin <- function(x){ .Call('colMin', x, PACKAGE='NMF') } # colMax colMax <- function(x){ .Call('colMax', x, PACKAGE='NMF') } #' \code{neq.constraints.inplace} apply unequality constraints in place. #' #' @param constraints constraint specification. #' @param ratio fixed ratio on which the constraint applies. #' @param value fixed value to enforce. #' @param copy a logical that indicates if \code{x} should be updated in place #' or not. #' #' @export #' @rdname inplace neq.constraints.inplace <- function(x, constraints, ratio=NULL, value=NULL, copy=FALSE){ # if requested: clone data as neq.constrains.inplace modify the input data in place if( copy ) x <- clone(x) .Call('ptr_neq_constraints', x, constraints, ratio, value, PACKAGE='NMF') } # Test if an external pointer is nil # Taken from package bigmemory ptr_isnil <- function (address) { if (class(address) != "externalptr") stop("address is not an externalptr.") .Call("ptr_isnil", address, PACKAGE='NMF') } ###% Draw the palette of colors ###% ###% Taken from the examples of colorspace::rainbow_hcl ###% pal <- function(col, h=1, border = "light gray") { n <- length(col) plot(0, 0, type="n", xlim = c(0, 1), ylim = c(0, h), axes = FALSE, xlab = "", ylab = "") rect(0:(n-1)/n, 0, 1:n/n, h, col = col, border = border) } ###% Draw the Palette of Colors as a Wheel ###% ###% Taken from the examples of colorspace::rainbow_hcl ###% wheel <- function(col, radius = 1, ...) pie(rep(1, length(col)), col = col, radius = radius, ...) # Define a S4 class to handle function slots given as either a function definition # or a character string that gives the function's name. setClassUnion('.functionSlot', c('character', 'function')) # Define a S4 class to handle function slots given as either a function definition # or a character string that gives the function's name or NULL. setClassUnion('.functionSlotNULL', c('character', 'function', 'NULL')) .validFunctionSlot <- function(slot, allow.empty=FALSE, allow.null=TRUE){ if( is.null(slot) ){ if( !allow.null ) return('NULL value is not allowed') return(TRUE) } if( is.character(slot) ){ if( !allow.empty && slot == '' ) return('character string cannot be empty') if( length(slot) != 1 ) return(paste('character string must be a single value [length =', length(slot), ']', sep='')) } return(TRUE) } ####% Utility function needed in heatmap.plus.2 #invalid <- function (x) #{ # if (missing(x) || is.null(x) || length(x) == 0) # return(TRUE) # if (is.list(x)) # return(all(sapply(x, invalid))) # else if (is.vector(x)) # return(all(is.na(x))) # else return(FALSE) #} # ####% Modification of the function heatmap.2 including a small part of function ####% heatmap.plus to allow extra annotation rows #heatmap.plus.2 <- # function (x, Rowv = TRUE, Colv = if (symm) "Rowv" else TRUE, # distfun = dist, hclustfun = hclust, dendrogram = c("both", # "row", "column", "none"), symm = FALSE, scale = c("none", # "row", "column"), na.rm = TRUE, revC = identical(Colv, # "Rowv"), add.expr, breaks, symbreaks = min(x < 0, na.rm = TRUE) || # scale != "none", col = "heat.colors", colsep, rowsep, # sepcolor = "white", sepwidth = c(0.05, 0.05), cellnote, notecex = 1, # notecol = "cyan", na.color = par("bg"), trace = c("column", # "row", "both", "none"), tracecol = "cyan", hline = median(breaks), # vline = median(breaks), linecol = tracecol, margins = c(5, # 5), ColSideColors, RowSideColors, cexRow = 0.2 + 1/log10(nr), # cexCol = 0.2 + 1/log10(nc), labRow = NULL, labCol = NULL, # key = TRUE, keysize = 1.5, density.info = c("histogram", # "density", "none"), denscol = tracecol, symkey = min(x < # 0, na.rm = TRUE) || symbreaks, densadj = 0.25, main = NULL, # xlab = NULL, ylab = NULL, lmat = NULL, lhei = NULL, lwid = NULL, # ...) #{ # scale01 <- function(x, low = min(x), high = max(x)) { # x <- (x - low)/(high - low) # x # } # retval <- list() # scale <- if (symm && missing(scale)) # "none" # else match.arg(scale) # dendrogram <- match.arg(dendrogram) # trace <- match.arg(trace) # density.info <- match.arg(density.info) # if (length(col) == 1 && is.character(col)) # col <- get(col, mode = "function") # if (!missing(breaks) && (scale != "none")) # warning("Using scale=\"row\" or scale=\"column\" when breaks are", # "specified can produce unpredictable results.", "Please consider using only one or the other.") # if (is.null(Rowv) || is.na(Rowv)) # Rowv <- FALSE # if (is.null(Colv) || is.na(Colv)) # Colv <- FALSE # else if (Colv == "Rowv" && !isTRUE(Rowv)) # Colv <- FALSE # if (length(di <- dim(x)) != 2 || !is.numeric(x)) # stop("`x' must be a numeric matrix") # nr <- di[1] # nc <- di[2] # if (nr <= 1 || nc <= 1) # stop("`x' must have at least 2 rows and 2 columns") # if (!is.numeric(margins) || length(margins) != 2) # stop("`margins' must be a numeric vector of length 2") # if (missing(cellnote)) # cellnote <- matrix("", ncol = ncol(x), nrow = nrow(x)) # if (!inherits(Rowv, "dendrogram")) { # if (((!isTRUE(Rowv)) || (is.null(Rowv))) && (dendrogram %in% # c("both", "row"))) { # if (is.logical(Colv) && (Colv)) # dendrogram <- "column" # else dedrogram <- "none" # warning("Discrepancy: Rowv is FALSE, while dendrogram is `", # dendrogram, "'. Omitting row dendogram.") # } # } # if (!inherits(Colv, "dendrogram")) { # if (((!isTRUE(Colv)) || (is.null(Colv))) && (dendrogram %in% # c("both", "column"))) { # if (is.logical(Rowv) && (Rowv)) # dendrogram <- "row" # else dendrogram <- "none" # warning("Discrepancy: Colv is FALSE, while dendrogram is `", # dendrogram, "'. Omitting column dendogram.") # } # } # if (inherits(Rowv, "dendrogram")) { # ddr <- Rowv # rowInd <- order.dendrogram(ddr) # } # else if (is.integer(Rowv)) { # hcr <- hclustfun(distfun(x)) # ddr <- as.dendrogram(hcr) # ddr <- reorder(ddr, Rowv) # rowInd <- order.dendrogram(ddr) # if (nr != length(rowInd)) # stop("row dendrogram ordering gave index of wrong length") # } # else if (isTRUE(Rowv)) { # Rowv <- rowMeans(x, na.rm = na.rm) # hcr <- hclustfun(distfun(x)) # ddr <- as.dendrogram(hcr) # ddr <- reorder(ddr, Rowv) # rowInd <- order.dendrogram(ddr) # if (nr != length(rowInd)) # stop("row dendrogram ordering gave index of wrong length") # } # else { # rowInd <- nr:1 # } # if (inherits(Colv, "dendrogram")) { # ddc <- Colv # colInd <- order.dendrogram(ddc) # } # else if (identical(Colv, "Rowv")) { # if (nr != nc) # stop("Colv = \"Rowv\" but nrow(x) != ncol(x)") # if (exists("ddr")) { # ddc <- ddr # colInd <- order.dendrogram(ddc) # } # else colInd <- rowInd # } # else if (is.integer(Colv)) { # hcc <- hclustfun(distfun(if (symm) # x # else t(x))) # ddc <- as.dendrogram(hcc) # ddc <- reorder(ddc, Colv) # colInd <- order.dendrogram(ddc) # if (nc != length(colInd)) # stop("column dendrogram ordering gave index of wrong length") # } # else if (isTRUE(Colv)) { # Colv <- colMeans(x, na.rm = na.rm) # hcc <- hclustfun(distfun(if (symm) # x # else t(x))) # ddc <- as.dendrogram(hcc) # ddc <- reorder(ddc, Colv) # colInd <- order.dendrogram(ddc) # if (nc != length(colInd)) # stop("column dendrogram ordering gave index of wrong length") # } # else { # colInd <- 1:nc # } # retval$rowInd <- rowInd # retval$colInd <- colInd # retval$call <- match.call() # x <- x[rowInd, colInd] # x.unscaled <- x # cellnote <- cellnote[rowInd, colInd] # if (is.null(labRow)) # labRow <- if (is.null(rownames(x))) # (1:nr)[rowInd] # else rownames(x) # else labRow <- labRow[rowInd] # if (is.null(labCol)) # labCol <- if (is.null(colnames(x))) # (1:nc)[colInd] # else colnames(x) # else labCol <- labCol[colInd] # if (scale == "row") { # retval$rowMeans <- rm <- rowMeans(x, na.rm = na.rm) # x <- sweep(x, 1, rm) # retval$rowSDs <- sx <- apply(x, 1, sd, na.rm = na.rm) # x <- sweep(x, 1, sx, "/") # } # else if (scale == "column") { # retval$colMeans <- rm <- colMeans(x, na.rm = na.rm) # x <- sweep(x, 2, rm) # retval$colSDs <- sx <- apply(x, 2, sd, na.rm = na.rm) # x <- sweep(x, 2, sx, "/") # } # if (missing(breaks) || is.null(breaks) || length(breaks) < # 1) { # if (missing(col) || is.function(col)) # breaks <- 16 # else breaks <- length(col) + 1 # } # if (length(breaks) == 1) { # if (!symbreaks) # breaks <- seq(min(x, na.rm = na.rm), max(x, na.rm = na.rm), # length = breaks) # else { # extreme <- max(abs(x), na.rm = TRUE) # breaks <- seq(-extreme, extreme, length = breaks) # } # } # nbr <- length(breaks) # ncol <- length(breaks) - 1 # if (class(col) == "function") # col <- col(ncol) # min.breaks <- min(breaks) # max.breaks <- max(breaks) # x[x < min.breaks] <- min.breaks # x[x > max.breaks] <- max.breaks # if (missing(lhei) || is.null(lhei)) # lhei <- c(keysize, 4) # if (missing(lwid) || is.null(lwid)) # lwid <- c(keysize, 4) # if (missing(lmat) || is.null(lmat)) { # lmat <- rbind(4:3, 2:1) # # # hack for adding extra annotations # if (!missing(ColSideColors)) { # if (!is.matrix(ColSideColors)) # stop("'ColSideColors' must be a matrix") # # if (!is.character(ColSideColors) || dim(ColSideColors)[1] != # nc) # stop("'ColSideColors' must be a character vector/matrix with length/ncol = ncol(x)") # lmat <- rbind(lmat[1, ] + 1, c(NA, 1), lmat[2, ] + 1) # lhei <- c(lhei[1], 0.2, lhei[2]) # } # if (!missing(RowSideColors)) { # if (!is.character(RowSideColors) || length(RowSideColors) != # nr) # stop("'RowSideColors' must be a character vector of length nrow(x)") # lmat <- cbind(lmat[, 1] + 1, c(rep(NA, nrow(lmat) - # 1), 1), lmat[, 2] + 1) # lwid <- c(lwid[1], 0.2, lwid[2]) # } # lmat[is.na(lmat)] <- 0 # } # if (length(lhei) != nrow(lmat)) # stop("lhei must have length = nrow(lmat) = ", nrow(lmat)) # if (length(lwid) != ncol(lmat)) # stop("lwid must have length = ncol(lmat) =", ncol(lmat)) # op <- par(no.readonly = TRUE) # on.exit(par(op)) # layout(lmat, widths = lwid, heights = lhei, respect = FALSE) # if (!missing(RowSideColors)) { # par(mar = c(margins[1], 0, 0, 0.5)) # image(rbind(1:nr), col = RowSideColors[rowInd], axes = FALSE) # } # if (!missing(ColSideColors)) { # par(mar = c(0.5, 0, 0, margins[2])) # csc = ColSideColors[colInd, ] # csc.colors = matrix() # csc.names = names(table(csc)) # csc.i = 1 # for (csc.name in csc.names) { # csc.colors[csc.i] = csc.name # csc[csc == csc.name] = csc.i # csc.i = csc.i + 1 # } # csc = matrix(as.numeric(csc), nrow = dim(csc)[1]) # image(csc, col = as.vector(csc.colors), axes = FALSE) # if (length(colnames(ColSideColors)) > 0) { # axis(2, 0:(dim(csc)[2] - 1)/(dim(csc)[2] - 1), colnames(ColSideColors), # las = 2, tick = FALSE, cex.axis= cexRow) # } # } # par(mar = c(margins[1], 0, 0, margins[2])) # if (!symm || scale != "none") { # x <- t(x) # cellnote <- t(cellnote) # } # if (revC) { # iy <- nr:1 # if (exists("ddr")) # ddr <- rev(ddr) # x <- x[, iy] # cellnote <- cellnote[, iy] # } # else iy <- 1:nr # image(1:nc, 1:nr, x, xlim = 0.5 + c(0, nc), ylim = 0.5 + # c(0, nr), axes = FALSE, xlab = "", ylab = "", col = col, # breaks = breaks, ...) # retval$carpet <- x # if (exists("ddr")) # retval$rowDendrogram <- ddr # if (exists("ddc")) # retval$colDendrogram <- ddc # retval$breaks <- breaks # retval$col <- col # if (!invalid(na.color) & any(is.na(x))) { # mmat <- ifelse(is.na(x), 1, NA) # image(1:nc, 1:nr, mmat, axes = FALSE, xlab = "", ylab = "", # col = na.color, add = TRUE) # } # axis(1, 1:nc, labels = labCol, las = 2, line = -0.5, tick = 0, # cex.axis = cexCol) # if (!is.null(xlab)) # mtext(xlab, side = 1, line = margins[1] - 1.25) # axis(4, iy, labels = labRow, las = 2, line = -0.5, tick = 0, # cex.axis = cexRow) # if (!is.null(ylab)) # mtext(ylab, side = 4, line = margins[2] - 1.25) # if (!missing(add.expr)) # eval(substitute(add.expr)) # if (!missing(colsep)) # for (csep in colsep) rect(xleft = csep + 0.5, ybottom = rep(0, # length(csep)), xright = csep + 0.5 + sepwidth[1], # ytop = rep(ncol(x) + 1, csep), lty = 1, lwd = 1, # col = sepcolor, border = sepcolor) # if (!missing(rowsep)) # for (rsep in rowsep) rect(xleft = 0, ybottom = (ncol(x) + # 1 - rsep) - 0.5, xright = nrow(x) + 1, ytop = (ncol(x) + # 1 - rsep) - 0.5 - sepwidth[2], lty = 1, lwd = 1, # col = sepcolor, border = sepcolor) # min.scale <- min(breaks) # max.scale <- max(breaks) # x.scaled <- scale01(t(x), min.scale, max.scale) # if (trace %in% c("both", "column")) { # retval$vline <- vline # vline.vals <- scale01(vline, min.scale, max.scale) # for (i in colInd) { # if (!is.null(vline)) { # abline(v = i - 0.5 + vline.vals, col = linecol, # lty = 2) # } # xv <- rep(i, nrow(x.scaled)) + x.scaled[, i] - 0.5 # xv <- c(xv[1], xv) # yv <- 1:length(xv) - 0.5 # lines(x = xv, y = yv, lwd = 1, col = tracecol, type = "s") # } # } # if (trace %in% c("both", "row")) { # retval$hline <- hline # hline.vals <- scale01(hline, min.scale, max.scale) # for (i in rowInd) { # if (!is.null(hline)) { # abline(h = i + hline, col = linecol, lty = 2) # } # yv <- rep(i, ncol(x.scaled)) + x.scaled[i, ] - 0.5 # yv <- rev(c(yv[1], yv)) # xv <- length(yv):1 - 0.5 # lines(x = xv, y = yv, lwd = 1, col = tracecol, type = "s") # } # } # if (!missing(cellnote)) # text(x = c(row(cellnote)), y = c(col(cellnote)), labels = c(cellnote), # col = notecol, cex = notecex) # par(mar = c(margins[1], 0, 0, 0)) # if (dendrogram %in% c("both", "row")) { # plot(ddr, horiz = TRUE, axes = FALSE, yaxs = "i", leaflab = "none") # } # else plot.new() # par(mar = c(0, 0, if (!is.null(main)) 5 else 0, margins[2])) # if (dendrogram %in% c("both", "column")) { # plot(ddc, axes = FALSE, xaxs = "i", leaflab = "none") # } # else plot.new() # if (!is.null(main)) # title(main, cex.main = 1.5 * op[["cex.main"]]) # if (key) { # par(mar = c(5, 4, 2, 1), cex = 0.75) # tmpbreaks <- breaks # if (symkey) { # max.raw <- max(abs(c(x, breaks)), na.rm = TRUE) # min.raw <- -max.raw # tmpbreaks[1] <- -max(abs(x)) # tmpbreaks[length(tmpbreaks)] <- max(abs(x)) # } # else { # min.raw <- min(x, na.rm = TRUE) # max.raw <- max(x, na.rm = TRUE) # } # z <- seq(min.raw, max.raw, length = length(col)) # image(z = matrix(z, ncol = 1), col = col, breaks = tmpbreaks, # xaxt = "n", yaxt = "n") # par(usr = c(0, 1, 0, 1)) # lv <- pretty(breaks) # xv <- scale01(as.numeric(lv), min.raw, max.raw) # axis(1, at = xv, labels = lv) # if (scale == "row") # mtext(side = 1, "Row Z-Score", line = 2) # else if (scale == "column") # mtext(side = 1, "Column Z-Score", line = 2) # else mtext(side = 1, "Value", line = 2) # if (density.info == "density") { # dens <- density(x, adjust = densadj, na.rm = TRUE) # omit <- dens$x < min(breaks) | dens$x > max(breaks) # dens$x <- dens$x[-omit] # dens$y <- dens$y[-omit] # dens$x <- scale01(dens$x, min.raw, max.raw) # lines(dens$x, dens$y/max(dens$y) * 0.95, col = denscol, # lwd = 1) # axis(2, at = pretty(dens$y)/max(dens$y) * 0.95, pretty(dens$y)) # title("Color Key\nand Density Plot") # par(cex = 0.5) # mtext(side = 2, "Density", line = 2) # } # else if (density.info == "histogram") { # h <- hist(x, plot = FALSE, breaks = breaks) # hx <- scale01(breaks, min.raw, max.raw) # hy <- c(h$counts, h$counts[length(h$counts)]) # lines(hx, hy/max(hy) * 0.95, lwd = 1, type = "s", # col = denscol) # axis(2, at = pretty(hy)/max(hy) * 0.95, pretty(hy)) # title("Color Key\nand Histogram") # par(cex = 0.5) # mtext(side = 2, "Count", line = 2) # } # else title("Color Key") # } # else plot.new() # retval$colorTable <- data.frame(low = retval$breaks[-length(retval$breaks)], # high = retval$breaks[-1], color = retval$col) # invisible(retval) #} # Extracted from the psychometric package (0.1.0) # Copyright Thomas D. Fletcher # Under Gnu GPL2 CI.Rsqlm <- function (obj, level = 0.95) { l <- level rsq <- summary(obj)$r.squared k <- summary(obj)$df[1] - 1 n <- obj$df + k + 1 mat <- CI.Rsq(rsq, n, k, level = l) return(mat) } # Extracted from the psychometric package (0.1.0) # Copyright Thomas D. Fletcher # Under Gnu GPL2 CI.Rsq <- function (rsq, n, k, level = 0.95) { noma <- 1 - level sersq <- sqrt((4 * rsq * (1 - rsq)^2 * (n - k - 1)^2)/((n^2 - 1) * (n + 3))) zs <- -qnorm(noma/2) mez <- zs * sersq lcl <- rsq - mez ucl <- rsq + mez mat <- data.frame(Rsq = rsq, SErsq = sersq, LCL = lcl, UCL = ucl) return(mat) } str_dim <- function(x, dims=dim(x)){ if( !is.null(dims) ) paste0(dims, collapse = ' x ') else length(x) } # Internal override stringr function str_match # # This is to get the previous behaviour on optional groups, because # in stringr >= 1.0.0 absent optional groups get an NA value instead # of an empty string, which in turn breaks some downstream processing. str_match <- function(...){ res <- stringr::str_match(...) # replace NAs by "" for globally matched strings if( length(w <- which(!is.na(res[, 1L]))) ){ res[w, ][is.na(res[w, ])] <- "" } res } NMF/R/NMFSeed-class.R0000644000176200001440000000570013620502674013551 0ustar liggesusers#' @include registry.R #' @include NMFStrategy-class.R NULL #' Base class that defines the interface for NMF seeding methods. #' #' This class implements a simple wrapper strategy object that defines a unified #' interface to seeding methods, that are used to initialise NMF models before #' fitting them with any NMF algorithm. #' #' @slot name character string giving the name of the seeding strategy #' @slot method workhorse function that implements the seeding strategy. #' It must have signature \code{(object="NMF", x="matrix", ...)} and initialise #' the NMF model \code{object} with suitable values for fitting the target #' matrix \code{x}. #' setClass('NMFSeed' , representation( method = 'function' # the method actual definition ) , contains = 'Strategy' ) #' Show method for objects of class \code{NMFSeed} setMethod('show', 'NMFSeed', function(object){ cat('\n") cat("name:\t", name(object), "\n") svalue <- algorithm(object) svalue <- if( is.function(svalue) ) '' else paste("'", svalue,"'", sep='') cat("method:\t", svalue, "\n") return(invisible()) } ) #' Returns the workhorse function of the seeding method described by \code{object}. setMethod('algorithm', signature(object='NMFSeed'), function(object){ slot(object, 'method') } ) #' Sets the workhorse function of the seeding method described by \code{object}. setReplaceMethod('algorithm', signature(object='NMFSeed', value='function'), function(object, value){ slot(object, 'method') <- value validObject(object) object } ) #' \code{NMFSeed} is a constructor method that instantiate #' \code{\linkS4class{NMFSeed}} objects. #' #' @param key access key as a single character string #' @param method specification of the seeding method, as a function that takes #' at least the following arguments: #' \describe{ #' \item{object}{uninitialised/empty NMF model, i.e. that it has 0 rows and #' columns, but has already the rank requested in the call to \code{\link{nmf}} #' or \code{\link{seed}}.} #' \item{x}{target matrix} #' \item{...}{extra arguments} #' } #' #' @export #' @rdname setNMFSeed #' @inline setGeneric('NMFSeed', function(key, method, ...) standardGeneric('NMFSeed') ) #' Default method simply calls \code{\link{new}} with the same arguments. setMethod('NMFSeed', signature(key='character', method='ANY'), function(key, method, ...){ # wrap function method into a new NMFSeed object new('NMFSeed', name=key, method=method, ..., package=topns_name()) } ) #' Creates an \code{NMFSeed} based on a template object (Constructor-Copy), #' in particular it uses the \strong{same} name. setMethod('NMFSeed', signature(key='NMFSeed', method='ANY'), function(key, method, ...){ # do not change the object if single argument if( nargs() == 1L ) return(key) # build an object based on template object new(class(method), key, method=method, ..., package=topns_name()) } ) NMF/R/algorithms-lsnmf.R0000644000176200001440000000606713620502674014522 0ustar liggesusers# Implementations of LS-NMF # # Reference: # LS-NMF: a modified non-negative matrix factorization algorithm utilizing uncertainty estimates. # Wang, Guoli, Andrew V Kossenkov, and Michael F Ochs. # BMC bioinformatics 7 (January 2006): 175. http://www.ncbi.nlm.nih.gov/pubmed/16569230. # # Author: Renaud Gaujoux # Creation: 09 Nov 2011 ############################################################################### #' @include registry-algorithms.R NULL #' Multiplicative Updates for LS-NMF #' #' Implementation of the updates for the LS-NMF algorithm from \cite{Wang2006}. #' #' @param i current iteration #' @param X target matrix #' @param object current NMF model #' @param weight value for \eqn{\Sigma}{S}, i.e. the weights that are applied to each #' entry in \code{X} by \code{X * weight} (= entry wise product). #' Weights are usually specified as a matrix of the same dimension as \code{X} #' (e.g. uncertainty estimates for each measurement), but may also be passed as a vector, #' in which case the standard rules for entry wise product between matrices and vectors apply #' (e.g. recylcing elements). #' @param eps small number passed to the standard euclidean-based NMF updates #' (see \code{\link{nmf_update.euclidean}}). #' @param ... extra arguments (not used) #' #' @return updated object \code{object} #' @rdname lsNMF-nmf nmf_update.lsnmf <- function(i, X, object, weight, eps=10^-9, ...) { if( i == 1 ){# pre-compute weighted target matrix staticVar('wX', X * weight, init=TRUE) } # retrieve weighted target matrix wX <- staticVar('wX') # retrieve each factor w <- .basis(object); h <- .coef(object); # compute the estimate WH wh <- fitted(object) * weight # euclidean-reducing NMF iterations # H_au = H_au (W^T V/sigma)_au / (W^T (W H)/sigma)_au h <- nmf_update.euclidean.h_R(wX, w, h, wh=wh, eps=eps) # update H and recompute the estimate WH .coef(object) <- h; wh <- fitted(object) * weight # W_ia = W_ia (V/sigma H^T)_ia / ((W H)/sigma H^T)_ia and columns are rescaled after each iteration w <- nmf_update.euclidean.w_R(wX, w, h, wh=wh, eps=eps) #return the modified data .basis(object) <- w return(object) } #' \code{wrss} implements the objective function used by the LS-NMF algorithm. #' #' @rdname lsNMF-nmf wrss <- function(object, X, weight){ sum( ((X - fitted(object)) * weight)^2 )/2 } # Registration of LS-NMF #' @inheritParams run,NMFStrategyIterative,matrix,NMFfit-method #' @inheritParams nmf.stop.stationary #' #' @aliases lsNMF-nmf #' @rdname lsNMF-nmf nmfAlgorithm.lsNMF <- setNMFMethod('ls-nmf', objective=wrss , Update=nmf_update.lsnmf , Stop='stationary') # Unit test for the LS-NMF algorithm runit.lsnmf <- function(){ requireNamespace('RUnit') set.seed(12345) X <- rmatrix(100,20) res <- nmf(X, 3, 'ls-nmf', weight=1, seed=1) res2 <- nmf(X, 3, '.R#lee', rescale=FALSE, seed=1, .stop=nmf.stop.stationary) tol <- 10^-14 RUnit::checkTrue( nmf.equal(res, res2, identical=FALSE, tol=tol ), paste("LS-NMF with weight = 1 and .R#Lee (no scale + stationary) give identical results at tolerance=", tol)) } NMF/R/NMFSet-class.R0000644000176200001440000017222413710764470013436 0ustar liggesusers#' @include NMFfit-class.R #' @include heatmaps.R NULL #' \code{isNMFfit} tells if an object results from an NMF fit. #' #' @details \emph{isNMFfit} checks if \code{object} inherits from class #' \code{\linkS4class{NMFfit}} or \code{\linkS4class{NMFfitX}}, which are #' the two types of objects returned by the function \code{\link{nmf}}. #' If \code{object} is a plain \code{list} and \code{recursive=TRUE}, then #' the test is performed on each element of the list, and the return value #' is a logical vector (or a list if \code{object} is a list of list) of #' the same length as \code{object}. #' #' @export #' @rdname types #' @param object any R object. #' @param recursive if \code{TRUE} and \code{object} is a plain list then #' \code{isNMFfit} tests each element of the list. #' Note that the recursive test only applies in the case of lists that are #' not themselves NMFfit objects, like \code{NMFfitXn} objects for which #' the result of \code{isNMFfit} will always be \code{TRUE}, although they are #' list objects (a single logical value). #' #' @return \code{isNMFfit} returns a \code{logical} vector (or a list if #' \code{object} is a list of list) of the same length as \code{object}. #' #' @seealso \code{\linkS4class{NMFfit}}, \code{\linkS4class{NMFfitX}}, #' \code{\linkS4class{NMFfitXn}} #' @examples #' #' ## Testing results of fits #' # generate a random #' V <- rmatrix(20, 10) #' #' # single run -- using very low value for maxIter to speed up the example #' res <- nmf(V, 3, maxIter=3L) #' isNMFfit(res) #' #' # multiple runs - keeping single fit #' resm <- nmf(V, 3, nrun=2, maxIter=3L) #' isNMFfit(resm) #' #' # with a list of results #' isNMFfit(list(res, resm, 'not a result')) #' isNMFfit(list(res, resm, 'not a result'), recursive=FALSE) #' isNMFfit <- function(object, recursive=TRUE){ res <- is(object, 'NMFfit') || is(object, 'NMFfitX') # if the object is not a NMF result: apply to each element if a list (only in recursive mode) if( !res && recursive && is.list(object) ) sapply(object, isNMFfit) else res } #' Class for Storing Heterogeneous NMF fits #' #' @description #' This class wraps a list of NMF fit objects, which may come from different #' runs of the function \code{\link{nmf}}, using different parameters, methods, etc.. #' These can be either from a single run (NMFfit) or multiple runs (NMFfitX). #' #' Note that its definition/interface is very likely to change in the future. #' @export #' setClass('NMFList' , representation( runtime='proc_time' ) , contains='namedList' , validity=function(object){ # the list must only contains NMFfit objects of the same dimensions ok <- isNMFfit(object) if( !is.logical(ok) ) return("Could not validate elements in list: input is probably a complex structure of lists.") pb <- which(!ok) if( length(pb) ){ return(paste("invalid class for element(s)" , str_out(i) , "of input list [all elements must be fitted NMF models]")) } } ) #' Show method for objects of class \code{NMFList} #' @export setMethod('show', 'NMFList', function(object) { cat("\n") cat("Length:", length(object), "\n") if( length(object) > 0 ) cat("Method(s):", algorithm(object, string=TRUE), "\n") # show totaltime if present tt <- runtime(object) if( length(tt) > 0 ){ cat("Total timing:\n"); show(tt); } } ) #' Returns the method names used to compute the NMF fits in the list. #' It returns \code{NULL} if the list is empty. #' #' @param string a logical that indicate whether the names should be collapsed #' into a comma-separated string. #' @param unique a logical that indicates whether the result should contain the #' set of method names, removing duplicated names. #' This argument is forced to \code{TRUE} when \code{string=TRUE}. #' setMethod('algorithm', 'NMFList', function(object, string=FALSE, unique=TRUE){ l <- length(object) if( string ) unique <- TRUE if( l == 0 ) NULL else if( l == 1 ) algorithm(object[[1]]) else{ # build the vector of the algorithm names (with no repeat) m <- sapply(object, algorithm) if( unique ) m <- unique(m) if( string ) m <- paste(m, collapse=', ') m } } ) .seqtime <- function(object){ if( length(object) == 0 ) return(NULL) # sum up the time across the runs t.mat <- sapply(object, function(x){ if( is(x, 'NMFfitXn') ) runtime.all(x) else runtime(x) }) res <- rowSums(t.mat) class(res) <- 'proc_time' res } #' Returns the CPU time that would be required to sequentially compute all NMF #' fits stored in \code{object}. #' #' This method calls the function \code{runtime} on each fit and sum up the #' results. #' It returns \code{NULL} on an empty object. setMethod('seqtime', 'NMFList', function(object){ if( length(object) == 0 ) return(NULL) # sum up the time across the runs .seqtime(object) } ) #' Returns the CPU time required to compute all NMF fits in the list. #' It returns \code{NULL} if the list is empty. #' If no timing data are available, the sequential time is returned. #' #' @param all logical that indicates if the CPU time of each fit should be #' returned (\code{TRUE}) or only the total CPU time used to compute all #' the fits in \code{object}. setMethod('runtime', 'NMFList', function(object, all=FALSE){ if( !all ){ t <- slot(object, 'runtime') if( length(t)==0 ) seqtime(object) else t }else sapply(object, runtime) } ) as.NMFList <- function(..., unlist=FALSE){ arg.l <- list(...) if( length(arg.l) == 1L && is.list(arg.l[[1]]) && !is(arg.l[[1]], 'NMFfitX') ) arg.l <- arg.l[[1]] # unlist if required if( unlist ) arg.l <- unlist(arg.l) # create a NMFList object from the input list new('NMFList', arg.l) } #' Virtual Class to Handle Results from Multiple Runs of NMF Algorithms #' #' This class defines a common interface to handle the results from multiple #' runs of a single NMF algorithm, performed with the \code{\link{nmf}} method. #' #' Currently, this interface is implemented by two classes, #' \code{\linkS4class{NMFfitX1}} and \code{\linkS4class{NMFfitXn}}, which #' respectively handle the case where only the best fit is kept, and the case #' where the list of all the fits is returned. #' #' See \code{\link{nmf}} for more details on the method arguments. #' #' @slot runtime.all Object of class \code{\link[=proc.time]{proc_time}} that #' contains CPU times required to perform all the runs. #' #' @export #' @family multipleNMF #' @examples #' #' # generate a synthetic dataset with known classes #' n <- 20; counts <- c(5, 2, 3); #' V <- syntheticNMF(n, counts) #' #' # perform multiple runs of one algorithm (default is to keep only best fit) #' res <- nmf(V, 3, nrun=3) #' res #' #' # plot a heatmap of the consensus matrix #' \dontrun{ consensusmap(res) } #' setClass('NMFfitX' , representation( runtime.all = 'proc_time' # running time to perform all the NMF runs ) , contains='VIRTUAL' ) #' Returns the CPU time required to compute all the NMF runs. #' It returns \code{NULL} if no CPU data is available. setMethod('runtime.all', 'NMFfitX', function(object){ t <- slot(object, 'runtime.all') if( length(t) > 0 ) t else NULL } ) #' Returns the number of NMF runs performed to create \code{object}. #' #' It is a pure virtual method defined to ensure \code{nrun} is defined #' for sub-classes of \code{NMFfitX}, which throws an error if called. #' #' Note that because the \code{\link{nmf}} function allows to run the NMF #' computation keeping only the best fit, \code{nrun} may return a value #' greater than one, while only the result of the best run is stored in #' the object (cf. option \code{'k'} in method \code{\link{nmf}}). setMethod('nrun', 'NMFfitX', function(object){ stop("NMF::NMFfitX - missing definition for pure virtual method 'nrun' in class '", class(object), "'") } ) #' This method always returns 1, since an \code{NMFfit} object is obtained #' from a single NMF run. setMethod('nrun', 'NMFfit', function(object){ 1L } ) #' \code{consensus} is an S4 generic that computes/returns the consensus matrix #' from a model object, which is the mean connectivity matrix of all the runs. #' #' The consensus matrix has been proposed by \cite{Brunet2004} to help #' visualising and measuring the stability of the clusters obtained by #' NMF approaches. #' For objects of class \code{NMF} (e.g. results of a single NMF run, or NMF #' models), the consensus matrix reduces to the connectivity matrix. #' #' @rdname connectivity #' @export setGeneric('consensus', function(object, ...) standardGeneric('consensus') ) #' Pure virtual method defined to ensure \code{consensus} is defined for sub-classes of \code{NMFfitX}. #' It throws an error if called. setMethod('consensus', 'NMFfitX', function(object, ...){ stop("NMF::NMFfitX - missing definition for pure virtual method 'consensus' in class '", class(object), "'") } ) #' This method is provided for completeness and is identical to #' \code{\link{connectivity}}, and returns the connectivity matrix, #' which, in the case of a single NMF model, is also the consensus matrix. setMethod('consensus', 'NMF', function(object, ...){ connectivity(object, ...) } ) #' Hierarchical Clustering of a Consensus Matrix #' #' The function \code{consensushc} computes the hierarchical clustering of #' a consensus matrix, using the matrix itself as a similarity matrix and #' average linkage. #' It is #' #' @param object a matrix or an \code{NMFfitX} object, as returned by multiple #' NMF runs. #' @param ... extra arguments passed to next method calls #' #' @return an object of class \code{dendrogram} or \code{hclust} depending on the #' value of argument \code{dendrogram}. #' #' @inline #' @export setGeneric('consensushc', function(object, ...) standardGeneric('consensushc')) #' Workhorse method for matrices. #' #' @param method linkage method passed to \code{\link{hclust}}. #' @param dendrogram a logical that specifies if the result of the hierarchical #' clustering (en \code{hclust} object) should be converted into a dendrogram. #' Default value is \code{TRUE}. setMethod('consensushc', 'matrix', function(object, method='average', dendrogram=TRUE){ # hierachical clustering based on the connectivity matrix hc <- hclust(as.dist(1-object), method=method) # convert into a dendrogram if requested if( dendrogram ) as.dendrogram(hc) else hc } ) #' Compute the hierarchical clustering on the connectivity matrix of \code{object}. setMethod('consensushc', 'NMF', function(object, ...){ # hierachical clustering based on the connectivity matrix consensushc(connectivity(object), ...) } ) #' Compute the hierarchical clustering on the consensus matrix of \code{object}, #' or on the connectivity matrix of the best fit in \code{object}. #' #' @param what character string that indicates which matrix to use in the #' computation. #' setMethod('consensushc', 'NMFfitX', function(object, what=c('consensus', 'fit'), ...){ what <- match.arg(what) if( what == 'consensus' ){ # hierachical clustering on the consensus matrix consensushc(consensus(object), ...) }else if( what == 'fit' ) consensushc(fit(object), ...) } ) #' Returns the cluster membership index from an NMF model fitted with multiple #' runs. #' #' Besides the type of clustering available for any NMF models #' (\code{'columns', 'rows', 'samples', 'features'}), this method can return #' the cluster membership index based on the consensus matrix, computed from #' the multiple NMF runs. #' #' Argument \code{what} accepts the following extra types: #' \describe{ #' \item{\code{'chc'}}{ returns the cluster membership based on the #' hierarchical clustering of the consensus matrix, as performed by #' \code{\link{consensushc}}.} #' \item{\code{'consensus'}}{ same as \code{'chc'} but the levels of the membership #' index are re-labeled to match the order of the clusters as they would be displayed on the #' associated dendrogram, as re-ordered on the default annotation track in consensus #' heatmap produced by \code{\link{consensusmap}}.} #' } #' setMethod('predict', signature(object='NMFfitX'), function(object, what=c('columns', 'rows', 'samples', 'features', 'consensus', 'chc'), dmatrix = FALSE, ...){ # determine which prediction to do what <- match.arg(what) res <- if( what %in% c('consensus', 'chc') ){ # build the tree from consensus matrix h <- consensushc(object, what='consensus', dendrogram=FALSE) # extract membership from the tree cl <- cutree(h, k=nbasis(object)) # rename the cluster ids in the case of a consensus map if( what != 'chc' ){ dr <- as.dendrogram(h) o <- order.dendrogram(reorder(dr, rowMeans(consensus(object), na.rm=TRUE))) cl <- setNames(match(cl, unique(cl[o])), names(cl)) } res <- as.factor(cl) # add dissimilarity matrix if requested if( dmatrix ){ attr(res, 'dmatrix') <- 1 - consensus(object) } if( what != 'chc' ) attr(res, 'iOrd') <- o # return res } else predict(fit(object), what=what, ..., dmatrix = dmatrix) attr(res, 'what') <- what res } ) #' Returns the model object that achieves the lowest residual approximation #' error across all the runs. #' #' It is a pure virtual method defined to ensure \code{fit} is defined #' for sub-classes of \code{NMFfitX}, which throws an error if called. setMethod('fit', 'NMFfitX', function(object){ stop("NMF::NMFfitX - missing definition for pure virtual method 'fit' in class '", class(object), "'") } ) #' Returns the fit object that achieves the lowest residual approximation #' error across all the runs. #' #' It is a pure virtual method defined to ensure \code{minfit} is defined #' for sub-classes of \code{NMFfitX}, which throws an error if called. setMethod('minfit', 'NMFfitX', function(object){ stop("NMF::NMFfitX - missing definition for pure virtual method 'minfit' in class '", class(object), "'") } ) #' Show method for objects of class \code{NMFfitX} #' @export setMethod('show', 'NMFfitX', function(object){ cat("\n") # name of the algorithm cat(" Method:", algorithm(object), "\n") # number of runs cat(" Runs: ", nrun(object),"\n"); # initial state cat(" RNG:\n ", RNGstr(getRNG1(object)),"\n"); if( nrun(object) > 0 ){ # show total timing cat(" Total timing:\n"); show(runtime.all(object)); } } ) #' Extracting RNG Data from NMF Objects #' #' The \code{\link{nmf}} function returns objects that contain embedded RNG data, #' that can be used to exactly reproduce any computation. #' These data can be extracted using dedicated methods for the S4 generics #' \code{\link[rngtools]{getRNG}} and \code{\link[rngtools]{getRNG1}}. #' #' @inheritParams rngtools::getRNG #' @inheritParams rngtools::getRNG1 #' #' @inline #' @rdname RNG #' @export setGeneric('getRNG1', package='rngtools') #' Returns the RNG settings used for the first NMF run of multiple NMF runs. #' #' @examples #' # For multiple NMF runs, the RNG settings used for the first run is also stored #' V <- rmatrix(20,10) #' res <- nmf(V, 3, nrun=3) #' # RNG used for the best fit #' getRNG(res) #' # RNG used for the first of all fits #' getRNG1(res) #' # they may differ if the best fit is not the first one #' rng.equal(res, getRNG1(res)) #' setMethod('getRNG1', signature(object='NMFfitX'), function(object){ stop("NMF::getRNG1(", class(object), ") - Unimplemented pure virtual method: could not extract initial RNG settings.") } ) #' Compares two NMF models when at least one comes from multiple NMF runs. setMethod('nmf.equal', signature(x='NMFfitX', y='NMF'), function(x, y, ...){ nmf.equal(fit(x), y, ...) } ) #' Compares two NMF models when at least one comes from multiple NMF runs. setMethod('nmf.equal', signature(x='NMF', y='NMFfitX'), function(x, y, ...){ nmf.equal(x, fit(y), ...) } ) #' Returns the residuals achieved by the best fit object, i.e. the lowest #' residual approximation error achieved across all NMF runs. setMethod('residuals', signature(object='NMFfitX'), function(object, ...){ residuals(minfit(object), ...) } ) #' Returns the deviance achieved by the best fit object, i.e. the lowest #' deviance achieved across all NMF runs. setMethod('deviance', signature(object='NMFfitX'), function(object, ...){ deviance(minfit(object), ...) } ) ######################################################### # END_NMFfitX ######################################################### #' Structure for Storing the Best Fit Amongst Multiple NMF Runs #' #' This class is used to return the result from a multiple run of a single NMF #' algorithm performed with function \code{nmf} with the -- default -- option #' \code{keep.all=FALSE} (cf. \code{\link{nmf}}). #' #' It extends both classes \code{\linkS4class{NMFfitX}} and #' \code{\linkS4class{NMFfit}}, and stores a the result of the best fit in its #' \code{NMFfit} structure. #' #' Beside the best fit, this class allows to hold data about the computation of #' the multiple runs, such as the number of runs, the CPU time used to perform #' all the runs, as well as the consensus matrix. #' #' Due to the inheritance from class \code{NMFfit}, objects of class #' \code{NMFfitX1} can be handled exactly as the results of single NMF run -- #' as if only the best run had been performed. #' #' #' @slot consensus object of class \code{matrix} used to store the #' consensus matrix based on all the runs. #' #' @slot nrun an \code{integer} that contains the number of runs #' performed to compute the object. #' #' @slot rng1 an object that contains RNG settings used for the first #' run. See \code{\link{getRNG1}}. #' #' @export #' @family multipleNMF #' @examples #' #' # generate a synthetic dataset with known classes #' n <- 15; counts <- c(5, 2, 3); #' V <- syntheticNMF(n, counts) #' #' # get the class factor #' groups <- V$pData$Group #' #' # perform multiple runs of one algorithm, keeping only the best fit (default) #' #i.e.: the implicit nmf options are .options=list(keep.all=FALSE) or .options='-k' #' res <- nmf(V, 3, nrun=2) #' res #' #' # compute summary measures #' summary(res) #' # get more info #' summary(res, target=V, class=groups) #' #' # show computational time #' runtime.all(res) #' #' # plot the consensus matrix, as stored (pre-computed) in the object #' \dontrun{ consensusmap(res, annCol=groups) } #' setClass('NMFfitX1' , representation( #fit = 'NMFfit' # holds the best fit from all the runs consensus = 'matrix' # average connectivity matrix of all the NMF runs , nrun = 'integer' , rng1 = 'ANY' ) , contains=c('NMFfitX', 'NMFfit') , prototype=prototype( consensus = matrix(as.numeric(NA),0,0) , nrun = as.integer(0) ) ) #' Show method for objects of class \code{NMFfitX1} #' @export setMethod('show', 'NMFfitX1', function(object){ callNextMethod(object) # show details of the best fit #cat(" # Best fit:\n ") #s <- capture.output(show(fit(object))) #cat(s, sep="\n |") } ) #' Returns the number of NMF runs performed, amongst which \code{object} was #' selected as the best fit. setMethod('nrun', 'NMFfitX1', function(object){ slot(object,'nrun') } ) #' Returns the consensus matrix computed while performing all NMF runs, #' amongst which \code{object} was selected as the best fit. #' #' The result is the matrix stored in slot \sQuote{consensus}. #' This method returns \code{NULL} if the consensus matrix is empty. setMethod('consensus', signature(object='NMFfitX1'), function(object, no.attrib = FALSE){ C <- slot(object, 'consensus') if( length(C) > 0 ){ if( !no.attrib ){ class(C) <- c(class(C), 'NMF.consensus') attr(C, 'nrun') <- nrun(object) attr(C, 'nbasis') <- nbasis(object) } C }else NULL } ) #' Returns the fit object associated with the best fit, amongst all the #' runs performed when fitting \code{object}. #' #' Since \code{NMFfitX1} objects only hold the best fit, this method simply #' returns \code{object} coerced into an \code{NMFfit} object. setMethod('minfit', 'NMFfitX1', function(object){ # coerce the object into a NMFfit object as(object, 'NMFfit') } ) #' Returns the model object associated with the best fit, amongst all the #' runs performed when fitting \code{object}. #' #' Since \code{NMFfitX1} objects only hold the best fit, this method simply #' returns the NMF model fitted by \code{object} -- that is stored in slot #' \sQuote{fit}. setMethod('fit', signature(object='NMFfitX1'), function(object){ slot(object, 'fit') } ) #' Returns the RNG settings used to compute the first of all NMF runs, amongst #' which \code{object} was selected as the best fit. setMethod('getRNG1', signature(object='NMFfitX1'), function(object){ object@rng1 } ) #' Compares the NMF models fitted by multiple runs, that only kept the best fits. setMethod('nmf.equal', signature(x='NMFfitX1', y='NMFfitX1'), function(x, y, ...){ nmf.equal(fit(x), fit(y), ...) } ) ######################################################### # END_NMFfitX1 ######################################################### #' Structure for Storing All Fits from Multiple NMF Runs #' #' This class is used to return the result from a multiple run of a single NMF #' algorithm performed with function \code{nmf} with option #' \code{keep.all=TRUE} (cf. \code{\link{nmf}}). #' #' It extends both classes \code{\linkS4class{NMFfitX}} and \code{list}, and #' stores the result of each run (i.e. a \code{NMFfit} object) in its #' \code{list} structure. #' #' IMPORTANT NOTE: This class is designed to be \strong{read-only}, even though #' all the \code{list}-methods can be used on its instances. Adding or removing #' elements would most probably lead to incorrect results in subsequent calls. #' Capability for concatenating and merging NMF results is for the moment only #' used internally, and should be included and supported in the next release of #' the package. #' #' #' @slot .Data standard slot that contains the S3 \code{list} object data. #' See R documentation on S3/S4 classes for more details (e.g., \code{\link{setOldClass}}). #' #' @export #' @family multipleNMF #' @examples #' #' # generate a synthetic dataset with known classes #' n <- 15; counts <- c(5, 2, 3); #' V <- syntheticNMF(n, counts) #' #' # get the class factor #' groups <- V$pData$Group #' #' # perform multiple runs of one algorithm, keeping all the fits #' res <- nmf(V, 3, nrun=2, .options='k') # .options=list(keep.all=TRUE) also works #' res #' #' summary(res) #' # get more info #' summary(res, target=V, class=groups) #' #' # compute/show computational times #' runtime.all(res) #' seqtime(res) #' #' # plot the consensus matrix, computed on the fly #' \dontrun{ consensusmap(res, annCol=groups) } #' setClass('NMFfitXn' , contains=c('NMFfitX', 'list') , validity=function(object){ # the list must only contains NMFfit objects of the same dimensions ref.dim <- NULL ref.algo <- NULL for(i in seq_along(object)){ # check class of the element item <- object[[i]] if( !(is(item, 'NMFfit') && !is(item, 'NMFfitX')) ) return(paste("invalid class for element", i, "of input list [all elements must be a NMFfit object]")) # check dimensions if( is.null(ref.dim) ) ref.dim <- dim(item) if( !identical(ref.dim, dim(item)) ) return(paste("invalid dimension for element", i, "of input list [all elements must have the same dimensions]")) # check algorithm names if( is.null(ref.algo) ) ref.algo <- algorithm(item) if( !identical(ref.algo, algorithm(item)) ) return(paste("invalid algorithm for element", i, "of input list [all elements must result from the same algorithm]")) } } ) # Updater for slot .Data #objectUpdater('NMFfitXn', '0.5.06' # , vfun=function(object){ !.hasSlot(object, 'rng1') } # , function(x, y){ # y@.Data <- lapply(x@.Data, nmfObject) # } #) #' Show method for objects of class \code{NMFfitXn} #' @export setMethod('show', 'NMFfitXn', function(object){ callNextMethod(object) # if the object is not empty and slot runtime.all is not null then show # the sequential time, as it might be different from runtime.all if( length(object) > 0 && !is.null(runtime.all(object, null=TRUE)) ){ # show total sequential timing cat(" Sequential timing:\n"); show(seqtime(object)); } } ) #' Returns the number of basis components common to all fits. #' #' Since all fits have been computed using the same rank, it returns the #' factorization rank of the first fit. #' This method returns \code{NULL} if the object is empty. setMethod('nbasis', signature(x='NMFfitXn'), function(x, ...){ if( length(x) == 0 ) return(NULL) return( nbasis(x[[1]]) ) } ) #' Returns the dimension common to all fits. #' #' Since all fits have the same dimensions, it returns the dimension of the #' first fit. #' This method returns \code{NULL} if the object is empty. #' #' @rdname dims setMethod('dim', signature(x='NMFfitXn'), function(x){ if( length(x) == 0 ) return(NULL) return( dim(x[[1L]]) ) } ) #' Returns the coefficient matrix of the best fit amongst all the fits stored in #' \code{object}. #' It is a shortcut for \code{coef(fit(object))}. setMethod('coef', signature(object='NMFfitXn'), function(object, ...){ coef(fit(object), ...) } ) #' Returns the basis matrix of the best fit amongst all the fits stored in #' \code{object}. #' It is a shortcut for \code{basis(fit(object))}. setMethod('basis', signature(object='NMFfitXn'), function(object, ...){ basis(fit(object), ...) } ) #' Method for multiple NMF fit objects, which returns the indexes of fixed basis #' terms from the best fitted model. setMethod('ibterms', 'NMFfitX', function(object){ ibterms(fit(object)) } ) #' Method for multiple NMF fit objects, which returns the indexes of fixed #' coefficient terms from the best fitted model. setMethod('icterms', 'NMFfit', function(object){ icterms(fit(object)) } ) #' Returns the number of runs performed to compute the fits stored in the list #' (i.e. the length of the list itself). setMethod('nrun', 'NMFfitXn', function(object){ length(object) } ) #' Returns the name of the common NMF algorithm used to compute all fits #' stored in \code{object} #' #' Since all fits are computed with the same algorithm, this method returns the #' name of algorithm that computed the first fit. #' It returns \code{NULL} if the object is empty. setMethod('algorithm', 'NMFfitXn', function(object){ if( length(object) == 0 ) return(NULL) return( algorithm(object[[1]]) ) } ) #' Returns the name of the common seeding method used the computation of all fits #' stored in \code{object} #' #' Since all fits are seeded using the same method, this method returns the #' name of the seeding method used for the first fit. #' It returns \code{NULL} if the object is empty. setMethod('seeding', 'NMFfitXn', function(object){ if( length(object) == 0 ) return(NULL) return( seeding(object[[1]]) ) } ) #' Returns the common type NMF model of all fits stored in \code{object} #' #' Since all fits are from the same NMF model, this method returns the #' model type of the first fit. #' It returns \code{NULL} if the object is empty. setMethod('modelname', signature(object='NMFfitXn'), function(object){ if( length(object) == 0 ) return(NULL) return( modelname(object[[1]]) ) } ) #' Returns the CPU time that would be required to sequentially compute all NMF #' fits stored in \code{object}. #' #' This method calls the function \code{runtime} on each fit and sum up the #' results. #' It returns \code{NULL} on an empty object. setMethod('seqtime', 'NMFfitXn', function(object){ if( length(object) == 0 ) return(NULL) # sum up the time across the runs .seqtime(object) } ) #' Returns the CPU time used to perform all the NMF fits stored in \code{object}. #' #' If no time data is available from in slot \sQuote{runtime.all} and argument #' \code{null=TRUE}, then the sequential time as computed by #' \code{\link{seqtime}} is returned, and a warning is thrown unless \code{warning=FALSE}. #' #' @param null a logical that indicates if the sequential time should be returned #' if no time data is available in slot \sQuote{runtime.all}. #' @param warning a logical that indicates if a warning should be thrown if the #' sequential time is returned instead of the real CPU time. #' setMethod('runtime.all', 'NMFfitXn', function(object, null=FALSE, warning=TRUE){ if( length(object) == 0 ) return(NULL) stored.time <- slot(object, 'runtime.all') # if there is some time stored, return it if( length(stored.time) > 0 ) stored.time else if( null ) NULL else{ if( warning ) warning("NMFfitXn::runtime.all - computation time data not available [sequential time was used instead]") seqtime(object) # otherwise total sequential time } } ) #' Returns the best NMF model in the list, i.e. the run that achieved the lower #' estimation residuals. #' #' The model is selected based on its \code{deviance} value. #' setMethod('minfit', 'NMFfitXn', function(object){ b <- which.best(object, deviance) # test for length 0 if( length(b) == 0 ) return(NULL) # return the run with the lower object[[ b ]] } ) #' \code{which.best} returns the index of the best fit in a list of NMF fit, #' according to some quantitative measure. #' The index of the fit with the lowest measure is returned. #' #' @param object an NMF model fitted by multiple runs. #' @param FUN the function that computes the quantitative measure. #' @param ... extra arguments passed to \code{FUN}. #' #' @export #' @rdname advanced which.best <- function(object, FUN=deviance, ...){ # test for length 0 if( length(object) == 0 ) return(integer()) # retrieve the measure for each run e <- sapply(object, FUN, ...) # return the run with the lower which.min(e) } #' Returns the RNG settings used for the first run. #' #' This method throws an error if the object is empty. setMethod('getRNG1', signature(object='NMFfitXn'), function(object){ if( length(object) == 0 ) stop("NMF::getRNG1 - Could not extract RNG data from empty object [class:", class(object), "]") getRNG(object[[1]]) } ) #' @inline #' @rdname RNG #' @export setGeneric('.getRNG', package='rngtools') #' Returns the RNG settings used for the best fit. #' #' This method throws an error if the object is empty. setMethod('.getRNG', signature(object='NMFfitXn'), function(object, ...){ if( length(object) == 0 ) stop("NMF::getRNG - Could not extract RNG data from empty object [class:", class(object), "]") getRNG(minfit(object), ...) } ) #' Returns the best NMF fit object amongst all the fits stored in \code{object}, #' i.e. the fit that achieves the lowest estimation residuals. setMethod('fit', signature(object='NMFfitXn'), function(object){ fit( minfit(object) ) } ) #' Compares the results of multiple NMF runs. #' #' This method either compare the two best fit, or all fits separately. #' All extra arguments in \code{...} are passed to each internal call to #' \code{nmf.equal}. #' #' @param all a logical that indicates if all fits should be compared separately #' or only the best fits #' @param vector a logical, only used when \code{all=TRUE}, that indicates if #' all fits must be equal for \code{x} and \code{y} to be declared equal, or #' if one wants to return the result of each comparison in a vector. #' #' @inline setMethod('nmf.equal', signature(x='list', y='list'), function(x, y, ..., all=FALSE, vector=FALSE){ if( !all ) nmf.equal(x[[ which.best(x) ]], y[[ which.best(y) ]], ...) else{ if( length(x) != length(y) ) FALSE else res <- mapply(function(a,b,...) isTRUE(nmf.equal(a,b,...)), x, y, MoreArgs=list(...)) if( !vector ) res <- all( res ) res } } ) #' Compare all elements in \code{x} to \code{x[[1]]}. setMethod('nmf.equal', signature(x='list', y='missing'), function(x, y, ...){ if( length(x) == 0L ){ warning("Empty list argument `x`: returning NA") return(NA) } if( length(x) == 1L ){ warning("Only one element in list argument `x`: returning TRUE") return(TRUE) } for( a in x ){ if( !nmf.equal(x[[1]], a, ...) ) return(FALSE) } return(TRUE) } ) #' Computes the consensus matrix of the set of fits stored in \code{object}, as #' the mean connectivity matrix across runs. #' #' This method returns \code{NULL} on an empty object. #' The result is a matrix with several attributes attached, that are used by #' plotting functions such as \code{\link{consensusmap}} to annotate the plots. #' #' @aliases plot.NMF.consensus setMethod('consensus', signature(object='NMFfitXn'), function(object, ..., no.attrib = FALSE){ if( length(object) == 0 ) return(NULL) # init empty consensus matrix con <- matrix(0, ncol(object), ncol(object)) # name the rows and columns appropriately: use the sample names of the first fit dimnames(con) <- list(colnames(object[[1]]), colnames(object[[1]])) # compute mean connectivity matrix sapply(object , function(x, ...){ con <<- con + connectivity(x, ..., no.attrib = TRUE) NULL } , ... ) con <- con / nrun(object) # return result if( !no.attrib ){ class(con) <- c(class(con), 'NMF.consensus') attr(con, 'nrun') <- nrun(object) attr(con, 'nbasis') <- nbasis(object) } con } ) #' @method plot NMF.consensus #' @export plot.NMF.consensus <- function(x, ...){ consensusmap(x, ...) } #' Dispersion of a Matrix #' #' Computes the dispersion coefficient of a -- consensus -- matrix #' \code{object}, generally obtained from multiple NMF runs. #' #' The dispersion coefficient is based on the consensus matrix (i.e. the #' average of connectivity matrices) and was proposed by \cite{KimH2007} to #' measure the reproducibility of the clusters obtained from NMF. #' #' It is defined as: #' \deqn{\rho = \sum_{i,j=1}^n 4 (C_{ij} - \frac{1}{2})^2 , } #' where \eqn{n} is the total number of samples. #' #' By construction, \eqn{0 \leq \rho \leq 1} and \eqn{\rho = 1} only for a perfect #' consensus matrix, where all entries 0 or 1. #' A perfect consensus matrix is obtained only when all the connectivity matrices #' are the same, meaning that the algorithm gave the same clusters at each run. #' See \cite{KimH2007}. #' #' @param object an object from which the dispersion is computed #' @param ... extra arguments to allow extension #' #' @export setGeneric('dispersion', function(object, ...) standardGeneric('dispersion') ) #' Workhorse method that computes the dispersion on a given matrix. setMethod('dispersion', 'matrix', function(object, ...){ stopifnot( nrow(object) == ncol(object) ) sum( 4 * (object-1/2)^2 ) / nrow(object)^2 } ) #' Computes the dispersion on the consensus matrix obtained from multiple NMF #' runs. setMethod('dispersion', 'NMFfitX', function(object, ...){ dispersion(consensus(object), ...) } ) #' Factory Method for Multiple NMF Run Objects #' #' @param object an object from which is created an \code{NMFfitX} object #' @param ... extra arguments used to pass values for slots #' #' @inline #' @keywords internal setGeneric('NMFfitX', function(object, ...) standardGeneric('NMFfitX') ) #' Create an \code{NMFfitX} object from a list of fits. #' #' @param .merge a logical that indicates if the fits should be aggregated, only #' keeping the best fit, and return an \code{NMFfitX1} object. #' If \code{FALSE}, an \code{NMFfitXn} object containing the data of all the fits #' is returned. #' setMethod('NMFfitX', 'list', function(object, ..., .merge=FALSE){ if( length(object) == 0 ) return(new('NMFfitXn')) else if( is(object, 'NMFfitXn') && !.merge) return(object) # retrieve the extra arguments extra <- list(...) # if runtime.all is provided: be sure it's of the right class tt <- extra$runtime.all compute.tt <- TRUE if( !is.null(tt) ){ if( !is(tt, 'proc_time') ){ if( !is.numeric(tt) || length(tt) != 5 ) stop("NMF::NMFfitX - invalid value for 'runtime.all' [5-length numeric expected]") class(extra$runtime.all) <- 'proc_time' } compute.tt <- FALSE }else{ extra$runtime.all <- rep(0,5) class(extra$runtime.all) <- 'proc_time' } # check validity and aggregate if required ref.algo <- NULL ref.class <- NULL nrun <- 0 lapply( seq_along(object) , function(i){ item <- object[[i]] # check the type of each element if( !(is(item, 'NMFfitX') || is(item, 'NMFfit')) ) stop("NMF::NMFfitX - invalid class for element ", i, " of input list [all elements must be NMFfit or NMFfitX objects]") # check that all elements result from the same algorithm if( is.null(ref.algo) ) ref.algo <<- algorithm(item) if( !identical(algorithm(item), ref.algo) ) stop("NMF::NMFfitX - invalid algorithm for element ", i, " of input list [cannot join results from different algorithms]") # check if simple join is possible: only Ok if all elements are from the same class (NMFfit or NMFfitXn) if( length(ref.class) <= 1 ) ref.class <<- unique(c(ref.class, class(item))) # sum up the number of runs nrun <<- nrun + nrun(item) # compute total running time if necessary if( compute.tt ) extra$runtime.all <<- extra$runtime.all + runtime.all(item) } ) # force merging if the input list is hetergeneous or if it only contains NMFfitX1 objects if( length(ref.class) > 1 || ref.class == 'NMFfitX1' ){ nmf.debug('NMFfitX', ".merge is forced to TRUE") .merge <- TRUE } # unpack all the NMFfit objects object.list <- unlist(object) nmf.debug('NMFfitX', "Number of fits to join = ", length(object.list)) # one wants to keep only the best result if( .merge ){ warning("NMF::NMFfitX - The method for merging lists is still in development") # set the total number of runs extra$nrun <- as.integer(nrun) # consensus matrix if( !is.null(extra$consensus) ) warning("NMF::NMFfitX - the value of 'consensus' was discarded as slot 'consensus' is computed internally") extra$consensus <- NULL consensus <- matrix(as.numeric(NA), 0, 0) best.res <- Inf best.fit <- NULL sapply(object.list, function(x){ if( !is(x, 'NMFfit') ) stop("NMF::NMFfitX - all inner-elements of '",substitute(object),"' must inherit from class 'NMFfit'") # merge consensus matrices consensus <<- if( sum(dim(consensus)) == 0 ) nrun(x) * consensus(x) else consensus + nrun(x) * consensus(x) temp.res <- residuals(x) if( temp.res < best.res ){ # keep best result best.fit <<- minfit(x) best.res <<- temp.res } }) # finalize consensus matrix consensus <- consensus/extra$nrun extra$consensus <- consensus # return merged result return( do.call(NMFfitX, c(list(best.fit), extra)) ) } else{ # create a NMFfitXn object that holds the whole list do.call('new', c(list('NMFfitXn', object.list), extra)) } } ) #' Creates an \code{NMFfitX1} object from a single fit. #' This is used in \code{\link{nmf}} when only the best fit is kept in memory or #' on disk. #' setMethod('NMFfitX', 'NMFfit', function(object, ...){ extra <- list(...) # default value for nrun is 1 if( is.null(extra$nrun) ) extra$nrun = as.integer(1) # a consensus matrix is required (unless nrun is 1) if( is.null(extra$consensus) ){ if( extra$nrun == 1 ) extra$consensus <- connectivity(object) else stop("Slot 'consensus' is required to create a 'NMFfitX1' object where nrun > 1") } # slot runtime.all is inferred if missing and nrun is 1 if( is.null(extra$runtime.all) && extra$nrun == 1 ) extra$runtime.all <- runtime(object) # create the NMFfitX1 object do.call('new', c(list('NMFfitX1', object), extra)) } ) #' Provides a way to aggregate \code{NMFfitXn} objects into an \code{NMFfitX1} #' object. setMethod('NMFfitX', 'NMFfitX', function(object, ...){ # nothing to do in the case of NMFfitX1 objects if( is(object, 'NMFfitX1') ) return(object) # retrieve extra arguments extra <- list(...) # take runtime.all from the object itself if( !is.null(extra$runtime.all) ) warning("NMF::NMFfitX - argument 'runtime.all' was discarded as it is computed from argument 'object'") extra$runtime.all <- runtime.all(object) # create the NMFfitX1 object f <- selectMethod(NMFfitX, 'list') do.call(f, c(list(object), extra)) } ) #' Computes the best or mean purity across all NMF fits stored in \code{x}. #' #' @param method a character string that specifies how the value is computed. #' It may be either \code{'best'} or \code{'mean'} to compute the best or mean #' purity respectively. #' #' @inline setMethod('purity', signature(x='NMFfitXn', y='ANY'), function(x, y, method='best', ...){ c <- sapply(x, purity, y=y, ...) # aggregate the results if a method is provided if( is.null(method) ) c else aggregate.measure(c, method, decreasing=TRUE) } ) #' Computes the best or mean entropy across all NMF fits stored in \code{x}. #' #' @inline setMethod('entropy', signature(x='NMFfitXn', y='ANY'), function(x, y, method='best', ...){ c <- sapply(x, entropy, y=y, ...) # aggregate the results if a method is provided if( is.null(method) ) c else aggregate.measure(c, method) } ) ###% Utility function to aggregate numerical quality measures from \code{NMFfitXn} objects. ###% ###% Given a numerical vector, this function computes an aggregated value using one of the following methods: ###% - mean: the mean of the measures ###% - best: the best measure according to the specified sorting order (decreasing or not) ###% aggregate.measure <- function(measure, method=c('best', 'mean'), decreasing=FALSE){ # aggregate the results method <- match.arg(method) res <- switch(method , mean = mean(measure) , best = if( decreasing ) max(measure) else min(measure) ) # set the name to names(res) <- method # return result res } #' Computes a set of measures to help evaluate the quality of the \emph{best #' fit} of the set. #' The result is similar to the result from the \code{summary} method of #' \code{NMFfit} objects. #' See \code{\linkS4class{NMF}} for details on the computed measures. #' In addition, the cophenetic correlation (\code{\link{cophcor}}) and #' \code{\link{dispersion}} coefficients of the consensus matrix are returned, #' as well as the total CPU time (\code{\link{runtime.all}}). #' setMethod('summary', signature(object='NMFfitX'), function(object, ...){ # compute summary measures for the best fit best.fit <- minfit(object) s <- summary(best.fit, ...) # get totaltime t <- runtime.all(object) # replace cpu.all and nrun in the result (as these are set by the summary method of class NMFfit) s[c('cpu.all', 'nrun')] <- c(as.numeric(t['user.self']+t['user.child']), nrun(object)) # compute cophenetic correlation coeff and dispersion C <- consensus(object) s <- c(s, cophenetic=cophcor(C), dispersion=dispersion(C)) # compute mean consensus silhouette width si <- silhouette(object, what = 'consensus') s <- c(s, silhouette.consensus = if( !is_NA(si) ) summary(si)$avg.width else NA) # return result s } ) #' Comparing Results from Different NMF Runs #' #' The functions documented here allow to compare the fits computed in #' different NMF runs. #' The fits do not need to be from the same algorithm, nor have the same #' dimension. #' #' The methods \code{compare} enables to compare multiple NMF fits either #' passed as arguments or as a list of fits. #' These methods eventually call the method \code{summary,NMFList}, so that #' all its arguments can be passed \strong{named} in \code{...}. #' #' @param ... extra arguments passed by \code{compare} to \code{summary,NMFList} #' or to the \code{summary} method of each fit. #' #' @name compare-NMF #' @rdname nmf-compare NULL .compare_NMF <- function(...){ args <- list(...) iargs <- if( is.null(names(args)) ){ names(args) <- rep("", length(args)) seq(args) }else{ iargs <- which(names(args)=='') if( length(iargs) != length(args) ) iargs <- iargs[ iargs < which(names(args)!='')[1L] ] iargs } lfit <- args[iargs] lfit <- unlist(lfit, recursive=FALSE) # wrap up into an NMFList object object <- as.NMFList(lfit) do.call('summary', c(list(object), args[-iargs])) } #' Compare multiple NMF fits passed as arguments. #' #' @rdname nmf-compare #' #' @examples #' #' x <- rmatrix(20,10) #' res <- nmf(x, 3) #' res2 <- nmf(x, 2, 'lee') #' #' # compare arguments #' compare(res, res2, target=x) #' setMethod('compare', signature(object='NMFfit'), function(object, ...){ .compare_NMF(object, ...) } ) #' Compares the fits obtained by separate runs of NMF, in a single #' call to \code{\link{nmf}}. #' #' @rdname nmf-compare #' #' # compare each fits in a multiple runs #' res3 <- nmf(x, 2, nrun=3, .opt='k') #' compare(res3) #' compare(res3, res, res2) #' compare(list(res3), res, res2, target=x) #' setMethod('compare', signature(object='NMFfitXn'), function(object, ...){ do.call(.compare_NMF, c(unlist(object), list(...))) } ) #' Compares multiple NMF fits passed as a standard list. #' #' @rdname nmf-compare #' #' @examples #' # compare elements of a list #' compare(list(res, res2), target=x) setMethod('compare', signature(object='list'), function(object, ...){ do.call(.compare_NMF, c(list(object), list(...))) } ) #' @details #' \code{summary,NMFList} computes summary measures for each NMF result in the list #' and return them in rows in a \code{data.frame}. #' By default all the measures are included in the result, and \code{NA} values #' are used where no data is available or the measure does not apply to the #' result object (e.g. the dispersion for single' NMF runs is not meaningful). #' This method is very useful to compare and evaluate the performance of #' different algorithms. #' #' @param select the columns to be output in the result \code{data.frame}. The #' column are given by their names (partially matched). The column names are #' the names of the summary measures returned by the \code{summary} methods of #' the corresponding NMF results. #' @param sort.by the sorting criteria, i.e. a partial match of a column name, #' by which the result \code{data.frame} is sorted. The sorting direction #' (increasing or decreasing) is computed internally depending on the chosen #' criteria (e.g. decreasing for the cophenetic coefficient, increasing for the #' residuals). #' #' @rdname nmf-compare setMethod('summary', signature(object='NMFList'), function(object, sort.by=NULL, select=NULL, ...){ if( length(object) == 0L ) return() # define the sorting schema for each criteria (TRUE for decreasing, FALSE for increasing) sorting.schema <- list(method=FALSE, seed=FALSE, rng=FALSE, metric=FALSE , residuals=FALSE, cpu=FALSE, purity=TRUE, nrun=FALSE, cpu.all=FALSE , cophenetic=TRUE, dispersion=TRUE #NMFfitX only , entropy=FALSE, sparseness.basis=TRUE, sparseness.coef=TRUE, rank=FALSE, rss=FALSE , niter=FALSE, evar=TRUE , silhouette.coef = TRUE, silhouette.basis = TRUE , silhouette.consensus = TRUE) # for each result compute the summary measures measure.matrix <- sapply(object, summary, ...) # the results from 'summary' might not have the same length => generate NA where necessary if( is.list(measure.matrix) ){ name.all <- unique(unlist(sapply(measure.matrix, names))) measure.matrix <- sapply(seq_along(measure.matrix), function(i){ m <- measure.matrix[[i]][name.all] names(m) <- name.all m } ) } # transpose the results so that methods are in lines, measures are in columns measure.matrix <- t(measure.matrix) # set up the resulting data.frame methods <- sapply(object, function(x, ...){ x <- minfit(x) m <- algorithm(x) s <- seeding(x) svalue <- objective(x) svalue <- if( is.function(svalue) ) '' else svalue c(method=m, seed=s, rng=RNGdigest(x), metric=svalue) } ) methods <- t(methods) res <- as.data.frame(methods, stringsAsFactors=FALSE) # add the measures to the result res <- cbind(res, measure.matrix) res$rng <- as.numeric(factor(res$rng)) # sort according to the user's preference # ASSERT FOR DEV: all columns measure must have a defined sorting schema #if( !all( no.schema <- is.element(colnames(res), names(sorting.schema))) ) # warning("ASSERT: missing sorting schema for criteria(e): ", paste(paste("'", colnames(res)[!no.schema], "'", sep=''), collapse=', ')) if( !is.null(sort.by) ){ sorting.criteria <- intersect(colnames(res), names(sorting.schema)) sort.by.ind <- pmatch(sort.by, sorting.criteria) if( is.na(sort.by.ind) ) stop("NMF::summary[NMFList] : argument 'sort.by' must be NULL or partially match one of " , paste( paste("'", names(sorting.schema), "'", sep=''), collapse=', ') , call.=FALSE) sort.by <- sorting.criteria[sort.by.ind] res <- res[order(res[[sort.by]], decreasing=sorting.schema[[sort.by]]) , ] # add an attribute to the result to show the sorting criteria that was used attr(res, 'sort.by') <- sort.by } # limit the output to the required measures if( !is.null(select) || !missing(select) ){ select.full <- match.arg(select, colnames(res), several.ok=TRUE) if( length(select.full) < length(select) ) stop("NMF::summary[NMFList] - the elements of argument 'select' must partially match one of " , paste(paste("'", colnames(res),"'", sep=''), collapse=', ') , call.=FALSE) res <- subset(res, select=select.full) } # return result res } ) #' @details #' \code{plot} plot on a single graph the residuals tracks for each fit in \code{x}. #' See function \code{\link{nmf}} for details on how to enable the tracking of residuals. #' #' @param x an \code{NMFList} object that contains fits from separate NMF runs. #' @param y missing #' @inheritParams plot,NMFfit,missing-method #' #' @rdname nmf-compare setMethod('plot', signature(x='NMFList', y='missing'), function(x, y, skip=-1L, ...){ # retrieve normalized residuals tracks max.iter <- 0 tracks <- lapply( x, function(res){ res <- minfit(res) t <- residuals(res, track=TRUE) # skip some residuals(s) if requested if( skip == -1L && !is.null(names(t)) ) t <- t[names(t)!='0'] # remove initial residual else if( skip > 0 ) t <- t[-(1:skip)] #print(t) # update max iteration max.iter <<- max(max.iter, as.numeric(names(t))) # return normalized track t/t[1] } ) minT <- min(sapply(tracks, min)) maxT <- max(sapply(tracks, max)) #print(tracks) # create an empty plot # set default graphical parameters (those can be overriden by the user) params <- .set.list.defaults(list(...) , xlab='Iterations', ylab='Normalised objective values' , main='NMF Residuals') # setup the plot do.call('plot', c(list(0, xlim=c(0,max.iter+100), ylim=c(minT, maxT)), col='#00000000' , params) ) # add legend cols <- seq_along(tracks) legend('topright', legend=names(tracks), fill=cols , title='Algorithm') # plot each tracks lapply( seq_along(tracks), function(i){ t <- tracks[[i]] points(names(t), t, col=cols[i], type='p', cex=0.5) points(names(t), t, col=cols[i], type='l', lwd=1.4) }) # return invisible return(invisible()) } ) #' Deprecated method subsituted by \code{\link{consensusmap}}. setMethod('metaHeatmap', signature(object='NMFfitX'), function(object, ...){ # send deprecated warning .Deprecated('metaHeatmap', 'NMF', "Direct use of the S4-Method 'metaHeatmap' for 'NMFfitX' objects is deprecated, use 'consensusmap' instead.") # call the new function 'consmap' return( consensusmap(object, ...) ) } ) #' \code{consensusmap} plots heatmaps of consensus matrices. #' #' @details #' \code{consensusmap} redefines default values for the following arguments of #' \code{\link{aheatmap}}: #' \itemize{ #' \item the colour palette; #' \item the column ordering which is set equal to the row ordering, since #' a consensus matrix is symmetric; #' \item the distance and linkage methods used to order the rows (and columns). #' The default is to use 1 minus the consensus matrix itself as distance, and #' average linkage. #' \item the addition of two special named annotation tracks, \code{'basis:'} and #' \code{'consensus:'}, that show, for each column (i.e. each sample), #' the dominant basis component in the best fit and the hierarchical clustering #' of the consensus matrix respectively (using 1-consensus as distance and average #' linkage). #' #' These tracks are specified in argument \code{tracks}, which behaves as in #' \code{\link{basismap}}. #' #' \item a suitable title and extra information like the type of NMF model or the #' fitting algorithm, when \code{object} is a fitted NMF model. #' } #' #' @rdname heatmaps #' #' @examples #' #' \dontrun{ #' res <- nmf(x, 3, nrun=3) #' consensusmap(res) #' } #' #' @inline #' @export setGeneric('consensusmap', function(object, ...) standardGeneric('consensusmap') ) #' Plots a heatmap of the consensus matrix obtained when fitting an NMF model with multiple runs. setMethod('consensusmap', 'NMFfitX', function(object, annRow=NA, annCol=NA , tracks=c('basis:', 'consensus:', 'silhouette:') , main = 'Consensus matrix', info = FALSE , ...){ # add side information if requested info <- if( isTRUE(info) ){ paste("NMF model: '", modelname(object) , "'\nAlgorithm: '", algorithm(object) , "'\nbasis: ", nbasis(object) ,"\nnrun: ", nrun(object), sep='') }else if( isFALSE(info) ) NULL else info x <- consensus(object) # process annotation tracks ptracks <- process_tracks(x, tracks, annRow, annCol) annRow <- ptracks$row annCol <- ptracks$col # set special annotation handler ahandlers <- list( basis = function() predict(object) , consensus = function() predict(object, what='consensus') , silhouette = function(){ si <- silhouette(object, what='consensus', order = NA) if( is_NA(si) ) NA else si[, 'sil_width'] } ) specialAnnotation(1L, ahandlers) specialAnnotation(2L, ahandlers) # consensusmap(x, ..., annRow=annRow, annCol=annCol, main = main, info = info) } ) #' Plots a heatmap of the connectivity matrix of an NMF model. setMethod('consensusmap', 'NMF', function(object, ...){ consensusmap(connectivity(object), ...) } ) #' Main method that redefines default values for arguments of \code{\link{aheatmap}}. setMethod('consensusmap', 'matrix', function(object, color='-RdYlBu' , distfun = function(x) as.dist(1-x), hclustfun = 'average' , Rowv = TRUE, Colv = "Rowv" , main = if( is.null(nr) || nr > 1 ) 'Consensus matrix' else 'Connectiviy matrix' , info = FALSE , ...){ nr <- nrun(object) nb <- nbasis(object) info <- if( isTRUE(info) ){ info <- NULL if( !is.null(nr) ) info <- c(info, paste("nrun:", nr)) if( !is.null(nb) ) info <- c(info, paste("nbasis:", nb)) info <- c(info, paste("cophcor:", round(cophcor(object), 3))) }else if( isFALSE(info) ) NULL else info aheatmap(object, color = color, ... , distfun = distfun, hclustfun = hclustfun , Rowv = Rowv, Colv = Colv , main = main , info = info) } ) setOldClass('NMF.rank') #' Draw a single plot with a heatmap of the consensus matrix obtained for each value of the rank, #' in the range tested with \code{\link{nmfEstimateRank}}. #' #' @rdname nmf-compare setMethod('consensusmap', 'NMF.rank', function(object, ...){ # plot the list of consensus matrix (set names to be used as default main titles) consensusmap(setNames(object$fit, paste("rank = ", lapply(object$fit, nbasis))), ...) } ) #' Draw a single plot with a heatmap of the consensus matrix of each element in the list \code{object}. #' #' @param layout specification of the layout. #' It may be a single numeric or a numeric couple, to indicate a square or rectangular layout #' respectively, that is filled row by row. #' It may also be a matrix that is directly passed to the function \code{\link[graphics]{layout}} #' from the package \code{graphics}. #' #' @rdname nmf-compare setMethod('consensusmap', 'list', function(object, layout , Rowv = FALSE, main = names(object) , ...){ opar <- par(no.readonly=TRUE) on.exit(par(opar)) # define default layout if (missing(layout) ){ n <- length(object) nr <- nc <- floor(sqrt(n)) if( nr^2 != n ){ nc <- nr + 1 if( nr == 1 && nr*nc < n ) nr <- nr + 1 } layout <- c(nr, nc) } if( !is.matrix(layout) ){ if( !is.numeric(layout) ) stop("invalid layout specification: must be a matrix or a numeric") if( length(layout) == 1 ) layout <- c(layout, layout) layout <- matrix(1:(layout[1]*layout[2]), layout[1], byrow=TRUE) } graphics::layout(layout) res <- sapply(seq_along(object), function(i, ...){ x <- object[[i]] # set main title main <- if( !is.null(main) && length(main) > 1 ){ if( length(main) != length(object) ) stop("consensusmap - Invalid length for argument `main`: should be either a single character string, or a list or vector of same length as ", deparse(substitute(object))) main[[i]] } # call method for the fit consensusmap(x, ..., Rowv=Rowv, main=main) }, ...) invisible(res) } ) #' Plots a heatmap of the basis matrix of the best fit in \code{object}. setMethod('basismap', signature(object='NMFfitX'), function(object, ...){ # call the method on the best fit basismap(minfit(object), ...) } ) #' Plots a heatmap of the coefficient matrix of the best fit in \code{object}. #' #' This method adds: #' \itemize{ #' \item an extra special column annotation track for multi-run NMF fits, #' \code{'consensus:'}, that shows the consensus cluster associated to each sample. #' \item a column sorting schema \code{'consensus'} that can be passed #' to argument \code{Colv} and orders the columns using the hierarchical clustering of the #' consensus matrix with average linkage, as returned by \code{\link{consensushc}(object)}. #' This is also the ordering that is used by default for the heatmap of the consensus matrix #' as ploted by \code{\link{consensusmap}}. #' } setMethod('coefmap', signature(object='NMFfitX'), function(object , Colv=TRUE , annRow=NA, annCol=NA , tracks=c('basis', 'consensus:') , ...){ x <- minfit(object) # process annotation tracks ptracks <- process_tracks(x, tracks, annRow, annCol) annRow <- ptracks$row annCol <- ptracks$col # set special annotation handler specialAnnotation(2L, 'consensus', function() predict(object, what='consensus')) # row track handler is added in coefmap,NMF # ## process ordering if( isString(Colv) ){ if( Colv %in% c('consensus', 'cmap') ) Colv <- consensushc(object, 'consensus') } ## # call the method on the best fit coefmap(x, ..., Colv=Colv, annRow=annRow, annCol=annCol, tracks=NA) } ) #' Cophenetic Correlation Coefficient #' #' The function \code{cophcor} computes the cophenetic correlation coefficient #' from consensus matrix \code{object}, e.g. as obtained from multiple NMF runs. #' #' The cophenetic correlation coeffificient is based on the consensus matrix #' (i.e. the average of connectivity matrices) and was proposed by #' \cite{Brunet2004} to measure the stability of the clusters obtained from NMF. #' #' It is defined as the Pearson correlation between the samples' distances #' induced by the consensus matrix (seen as a similarity matrix) and their #' cophenetic distances from a hierachical clustering based on these very #' distances (by default an average linkage is used). #' See \cite{Brunet2004}. #' #' @param object an object from which is extracted a consensus matrix. #' @param ... extra arguments to allow extension and passed to subsequent calls. #' #' @inline #' @seealso \code{\link{cophenetic}} #' @export setGeneric('cophcor', function(object, ...) standardGeneric('cophcor') ) #' Workhorse method for matrices. #' #' @param linkage linkage method used in the hierarchical clustering. #' It is passed to \code{\link{hclust}}. #' setMethod('cophcor', signature(object='matrix'), function(object, linkage='average'){ # check for empty matrix if( nrow(object)==0 || ncol(object)==0 ) { warning("NMF::cophcor - NA produced [input matrix is of dimension ", nrow(object), "x", ncol(object), "]" , call.=FALSE) return(NA) } # safe-guard for diagonal matrix: to prevent error in 'cor' if( all(object[upper.tri(object)]==0) && all(diag(object)==object[1,1]) ) return(1) # convert consensus matrix into dissimilarities d.consensus <- as.dist(1 - object) # compute cophenetic distance based on these dissimilarities hc <- hclust(d.consensus, method=linkage) d.coph <- cophenetic(hc) # return correlation between the two distances res <- cor(d.consensus, d.coph, method='pearson') return(res) } ) #' Computes the cophenetic correlation coefficient on the consensus matrix #' of \code{object}. #' All arguments in \code{...} are passed to the method \code{cophcor,matrix}. setMethod('cophcor', signature(object='NMFfitX'), function(object, ...){ # compute the consensus matrix C <- consensus(object) return( cophcor(C, ...)) } ) # TODO: uncomment this and make it compute the mean or best rss #setMethod('rss', 'NMFfitXn', # function(object, target, ...){ # rss(fit(object, ...), target) # } #) NMF/R/NMFfit-class.R0000644000176200001440000006301513620502674013456 0ustar liggesusers# Implementation of class NMFfit # # This class manages the result of a single run of a NMF algorithm. # # Author: Renaud Gaujoux ############################################################################### #' @include fixed-terms.R #' @include nmfModel.R NULL #' Base Class for to store Nonnegative Matrix Factorisation results #' #' Base class to handle the results of general \strong{Nonnegative Matrix #' Factorisation} algorithms (NMF). #' #' It provides a general structure and generic functions to manage the results #' of NMF algorithms. It contains a slot with the fitted NMF model (see slot #' \code{fit}) as well as data about the methods and parameters used to compute #' the factorization. #' #' The purpose of this class is to handle in a generic way the results of NMF #' algorithms. Its slot \code{fit} contains the fitted NMF model as an object #' of class \code{\linkS4class{NMF}}. #' #' Other slots contains data about how the factorization has been computed, #' such as the algorithm and seeding method, the computation time, the final #' residuals, etc\dots{} #' #' Class \code{NMFfit} acts as a wrapper class for its slot \code{fit}. It #' inherits from interface class \code{\linkS4class{NMF}} defined for generic #' NMF models. Therefore, all the methods defined by this interface can be #' called directly on objects of class \code{NMFfit}. The calls are simply #' dispatched on slot \code{fit}, i.e. the results are the same as if calling #' the methods directly on slot \code{fit}. #' #' @slot fit An object that inherits from class \code{\linkS4class{NMF}}, and #' contains the fitted NMF model. #' #' NB: class \code{NMF} is a virtual class. The default class for this #' slot is \code{NMFstd}, that implements the standard NMF model. #' #' @slot residuals A \code{numeric} vector that contains the final #' residuals or the residuals track between the target matrix and its NMF #' estimate(s). Default value is \code{numeric()}. #' #' See method \code{\link{residuals}} for details on accessor methods and main #' interface \code{\link{nmf}} for details on how to compute NMF with residuals #' tracking. #' #' @slot method a single \code{character} string that contains the #' name of the algorithm used to fit the model. #' Default value is \code{''}. #' #' @slot seed a single \code{character} string that contains the #' name of the seeding method used to seed the algorithm that fitted the NMF #' model. #' Default value is \code{''}. See \code{\link{nmf}} for more details. #' #' @slot rng an object that contains the RNG settings used for the #' fit. #' Currently the settings are stored as an integer vector, the value of #' \code{\link{.Random.seed}} at the time the object is created. #' It is initialized by the \code{initialized} method. #' See \code{\link{getRNG}} for more details. #' #' @slot distance either a single \code{"character"} string that #' contains the name of the built-in objective function, or a \code{function} #' that measures the residuals between the target matrix and its NMF estimate. #' See \code{\link{objective}} and \code{\link{deviance,NMF-method}}. #' #' @slot parameters a \code{list} that contains the extra parameters #' -- usually specific to the algorithm -- that were used to fit the model. #' #' @slot runtime object of class \code{"proc_time"} that contains #' various measures of the time spent to fit the model. #' See \code{\link[base]{system.time}} #' #' @slot options a \code{list} that contains the options used to #' compute the object. #' #' @slot extra a \code{list} that contains extra miscellaneous data #' for internal usage only. #' For example it can be used to store extra parameters or temporary data, #' without the need to explicitly extend the \code{NMFfit} class. #' Currently built-in algorithms only use this slot to #' store the number of iterations performed to fit the object. #' #' Data that need to be easily accessible by the end-user should rather be set #' using the methods \code{$<-} that sets elements in the \code{list} slot #' \code{misc} -- that is inherited from class \code{\linkS4class{NMF}}. #' #' @slot call stored call to the last \code{nmf} method that generated the #' object. #' #' @export #' @examples #' # run default NMF algorithm on a random matrix #' n <- 50; r <- 3; p <- 20 #' V <- rmatrix(n, p) #' res <- nmf(V, r) #' #' # result class is NMFfit #' class(res) #' isNMFfit(res) #' #' # show result #' res #' #' # compute summary measures #' summary(res, target=V) #' setClass('NMFfit' , representation( fit = 'NMF', # NMF model residuals = 'numeric', # residuals from the target matrix method = 'character', # method used to compute the factorization seed = 'character', # seeding method used to compute the factorization rng = 'ANY', # numerical random seed distance = '.functionSlotNULL', # method used to compute the distance between the target matrix and its NMF estimate parameters = 'list', # method used to compute the factorization runtime = 'proc_time', # running time to perform the NMF options = 'list', # run options extra = 'list' # extra list of results output by the method , call = 'call' # store last call to nmf() ) , prototype = prototype( residuals = numeric(), method = '', seed = '', parameters = list(), extra = list() ) , validity = function(object){ # slot 'objective' must either be a non-empty character string or a function obj <- objective(object) if( is.character(obj) && obj == '') return(paste("Slot 'objective' must either be a non-empty character string or a function definition", sep='')) # everything went fine: return TRUE TRUE } , contains = 'NMF' ) #' The function \code{NMFfit} is a factory method for NMFfit objects, that should #' not need to be called by the user. #' It is used internally by the functions \code{\link{nmf}} and \code{seed} to #' instantiate the starting point of NMF algorithms. #' #' @param fit an NMF model #' @param ... extra argument used to initialise slots in the instantiating #' \code{NMFfit} object. #' @param rng RNG settings specification (typically a suitable value for #' \code{\link{.Random.seed}}). #' #' @rdname NMFfit-class NMFfit <- function(fit=nmfModel(), ..., rng=NULL){ # use current RNG settings if not otherwise provided if( is.null(rng) ) rng <- getRNG() new('NMFfit', fit=fit, ..., rng=rng) } #' Computes and return the estimated target matrix from an NMF model fitted with #' function \code{\link{nmf}}. #' #' It is a shortcut for \code{fitted(fit(object), ...)}, dispatching the call to #' the \code{fitted} method of the actual NMF model. setMethod('fitted', signature(object='NMFfit'), function(object, ...){ fitted(fit(object), ...) } ) #' Returns the basis matrix from an NMF model fitted with #' function \code{\link{nmf}}. #' #' It is a shortcut for \code{.basis(fit(object), ...)}, dispatching the call to #' the \code{.basis} method of the actual NMF model. setMethod('.basis', signature(object='NMFfit'), function(object, ...){ .basis(fit(object), ...) } ) #' Sets the the basis matrix of an NMF model fitted with #' function \code{\link{nmf}}. #' #' It is a shortcut for \code{.basis(fit(object)) <- value}, dispatching the call to #' the \code{.basis<-} method of the actual NMF model. #' It is not meant to be used by the user, except when developing #' NMF algorithms, to update the basis matrix of the seed object before #' returning it. #' setReplaceMethod('.basis', signature(object='NMFfit', value='matrix'), function(object, value){ .basis(fit(object)) <- value object } ) #' Returns the the coefficient matrix from an NMF model fitted with #' function \code{\link{nmf}}. #' #' It is a shortcut for \code{.coef(fit(object), ...)}, dispatching the call to #' the \code{.coef} method of the actual NMF model. setMethod('.coef', signature(object='NMFfit'), function(object, ...){ .coef(fit(object), ...) } ) #' Sets the the coefficient matrix of an NMF model fitted with #' function \code{\link{nmf}}. #' #' It is a shortcut for \code{.coef(fit(object)) <- value}, dispatching the call to #' the \code{.coef<-} method of the actual NMF model. #' It is not meant to be used by the user, except when developing #' NMF algorithms, to update the coefficient matrix in the seed object before #' returning it. #' setReplaceMethod('.coef', signature(object='NMFfit', value='matrix'), function(object, value){ .coef(fit(object)) <- value object } ) #' Method for single NMF fit objects, which returns the indexes of fixed #' basis terms from the fitted model. setMethod('ibterms', 'NMFfit', function(object){ ibterms(fit(object)) } ) #' Method for single NMF fit objects, which returns the indexes of fixed #' coefficient terms from the fitted model. setMethod('icterms', 'NMFfit', function(object){ icterms(fit(object)) } ) #' Returns the offset from the fitted model. setMethod('offset', signature(object='NMFfit'), function(object){ offset(fit(object)) } ) #' Returns the number of iteration performed to fit an NMF model, typically #' with function \code{\link{nmf}}. #' #' Currently this data is stored in slot \code{'extra'}, but this might change #' in the future. setMethod('niter', signature(object='NMFfit'), function(object, ...){ object@extra$iteration } ) #' Sets the number of iteration performed to fit an NMF model. #' #' This function is used internally by the function \code{\link{nmf}}. #' It is not meant to be called by the user, except when developing #' new NMF algorithms implemented as single function, to set the number #' of iterations performed by the algorithm on the seed, before returning it #' (see \code{\linkS4class{NMFStrategyFunction}}). #' setReplaceMethod('niter', signature(object='NMFfit', value='numeric'), function(object, value){ if( (length(value) != 1) || value < 0 ) stop("NMF::niter - invalid value for 'niter': single non-negative value is required.", call.=FALSE) object@extra$iteration <- value object } ) #' Show method for objects of class \code{NMFfit} setMethod('show', 'NMFfit', function(object) { cat("\n", sep='') cat(" # Model:\n ") s <- capture.output(show(fit(object))) cat(s, sep="\n ") cat(" # Details:\n ") .local <- function(){ if( algorithm(object) != '' ) cat("algorithm: ", algorithm(object), "\n") if( seeding(object) != '' ) cat("seed: ", seeding(object), "\n") # initial RNG stream cat("RNG: ", RNGstr(object), "\n", sep='') # distance/objective function svalue <- objective(object) svalue <- if( is.function(svalue) ) '' else paste("'", svalue,"'", sep='') cat("distance metric: ", svalue, "\n") if( length(residuals(object)) !=0 ) cat("residuals: ", residuals(object), "\n"); # show the miscellaneous result values if( length(object@misc) > 0L ) cat("miscellaneous:", str_desc(object@misc, exdent=12L), ". (use 'misc(object)')\n") # show the parameters specific to the method if( length(object@parameters) > 0 ){ cat("parameters:", str_desc(object@parameters, exdent=12L), "\n") # p <- sapply(object@parameters, function(x){ # if( is.vector(x) && length(x) == 1L ) x # else paste("<", class(x), ">", sep='') # }) # cat(str_wrap(str_out(p, NA, use.names=TRUE, quote=FALSE), exdent=12), "\n") } # show number of iterations if present if( !is.null(i <- niter(object)) ) cat("Iterations:", i, "\n") # show elapsed time if present if( length(runtime(object)) > 0 ){ cat("Timing:\n"); show(runtime(object));} } s <- capture.output(.local()) cat(s, sep="\n ") } ) #' Extracting Fitted Models #' #' The functions \code{fit} and \code{minfit} are S4 genetics that extract #' the best model object and the best fit object respectively, from a collection #' of models or from a wrapper object. #' #' @details #' A fit object differs from a model object in that it contains data about the #' fit, such as the initial RNG settings, the CPU time used, etc\ldots, while #' a model object only contains the actual modelling data such as regression #' coefficients, loadings, etc\ldots #' #' That best model is generally defined as the one that achieves the #' maximum/minimum some quantitative measure, amongst all models in a collection. #' #' In the case of NMF models, the best model is the one that achieves the best #' approximation error, according to the objective function associated with the #' algorithm that performed the fit(s). #' #' @param object an object fitted by some algorithm, e.g. as returned by the #' function \code{\link{nmf}}. #' @param value replacement value #' @param ... extra arguments to allow extension #' #' @rdname fit #' @export setGeneric('fit', function(object, ...) standardGeneric('fit')) #' Returns the NMF model object stored in slot \code{'fit'}. setMethod('fit', 'NMFfit', function(object) slot(object, 'fit')) #' \code{fit<-} sets the fitted model in a fit object. #' It is meant to be called only when developing new NMF algorithms, e.g. to update #' the value of the model stored in the starting point. #' #' @rdname fit #' @export setGeneric('fit<-', function(object, value) standardGeneric('fit<-')) #' Updates the NMF model object stored in slot \code{'fit'} with a new value. setReplaceMethod('fit', signature(object='NMFfit', value='NMF'), function(object, value){ slot(object, 'fit') <- value object # TODO: valid object before returning it (+param check=TRUE or FALSE) } ) #' @rdname fit #' @export setGeneric('minfit', function(object, ...) standardGeneric('minfit') ) #' Returns the object its self, since there it is the result of a single NMF run. setMethod('minfit', 'NMFfit', function(object) object) #' Returns the type of a fitted NMF model. #' It is a shortcut for \code{modelname(fit(object)}. setMethod('modelname', signature(object='NMFfit'), function(object) { modelname(fit(object)) } ) #' Residuals in NMF Models #' #' The package NMF defines methods for the function \code{\link[stats]{residuals}} #' that returns the final residuals of an NMF fit or the track of the residuals #' along the fit process, computed according to the objective function #' associated with the algorithm that fitted the model. #' #' When called with \code{track=TRUE}, the whole residuals track is returned, #' if available. #' Note that method \code{\link{nmf}} does not compute the residuals track, #' unless explicitly required. #' #' It is a S4 methods defined for the associated generic functions from package #' \code{stats} (See \link[stats]{residuals}). #' #' @note Stricly speaking, the method \code{residuals,NMFfit} does not fulfill #' its contract as defined by the package \code{stats}, but rather acts as function #' \code{deviance}. #' The might be changed in a later release to make it behave as it should. #' #' @param object an \code{NMFfit} object as fitted by function \code{\link{nmf}}, #' in single run mode. #' @param ... extra parameters (not used) #' #' @return \code{residuals} returns a single numeric value if \code{track=FALSE} #' or a numeric vector containing the residual values at some iterations. #' The names correspond to the iterations at which the residuals were computed. #' #' @family stats #' @inline #' @rdname residuals #' @export #' setGeneric('residuals', package='stats') #' Returns the residuals -- track -- between the target matrix and the NMF #' fit \code{object}. #' #' @param track a logical that indicates if the complete track of residuals #' should be returned (if it has been computed during the fit), or only the last #' value. #' #' @param niter specifies the iteration number for which one wants #' to get/set/test a residual value. This argument is used only if not \code{NULL} #' setMethod('residuals', 'NMFfit', function(object, track=FALSE, niter=NULL, ...){ ## IMPORTANT: keep this '...' and do not add a 'method' argument as this ## one is passed by NMFfitX::fit (see bug #159) and is not supposed to be ## used res <- slot(object, 'residuals') if( track ) res else if( is.null(niter) ) tail(res, n=1) else res[as.character(niter)] } ) #' \code{residuals<-} sets the value of the last residuals, or, optionally, #' of the complete residual track. #' #' @param value residual value #' #' @export #' @inline #' @rdname residuals setGeneric('residuals<-', function(object, ..., value) standardGeneric('residuals<-') ) #' @inline setReplaceMethod('residuals', 'NMFfit', function(object, ..., niter=NULL, track=FALSE, value){ if( track ) slot(object, 'residuals') <- value else{ if( !is.null(niter) ) value <- setNames(value, niter) slot(object, 'residuals') <- c(slot(object, 'residuals'), value) } object } ) #' Tells if an \code{NMFfit} object contains a recorded residual track. #' #' @export #' @rdname residuals hasTrack <- function(object, niter=NULL){ if( is.null(niter) ) length( slot(object, 'residuals') ) > 1 else !is.na(slot(object, 'residuals')[as.character(niter)]) } #' \code{trackError} adds a residual value to the track of residuals. #' #' @param force logical that indicates if the value should be added to the track #' even if there already is a value for this iteration number or if the iteration #' does not conform to the tracking interval \code{nmf.getOption('track.interval')}. #' #' @rdname residuals #' @export trackError <- function(object, value, niter, force=FALSE){ track <- run.options(object, 'error.track') track.interval <- run.options(object, 'track.interval') if( force || (track && niter %% track.interval == 0) ){ # add the new value to the error track last.iter <- names(residuals(object)) duplicate <- if( !is.null(last.iter) ) niter == last.iter else FALSE if( !duplicate ){ iter <- if( niter >= 0 ) niter residuals(object, niter=iter) <- value } } object } #' Returns the deviance of a fitted NMF model. #' #' This method returns the final residual value if the target matrix \code{y} is #' not supplied, or the approximation error between the fitted NMF model stored #' in \code{object} and \code{y}. #' In this case, the computation is performed using the objective function #' \code{method} if not missing, or the objective of the algorithm that #' fitted the model (stored in slot \code{'distance'}). #' #' If not computed by the NMF algorithm itself, the value is automatically #' computed at the end of the fitting process by the function \code{\link{nmf}}, #' using the objective function associated with the NMF algorithm, so that it #' should always be available. #' #' @inline setMethod('deviance', 'NMFfit', function(object, y, method, ...){ if( missing(y) ) setNames(residuals(object), NULL) else{ # if missing retrieve the actual distance measure from the NMF object if( missing(method) ) method = object@distance # compute the distance between the target and the fitted NMF model deviance(fit(object), y, method=method, ...) } } ) #' Returns the name of the algorithm that fitted the NMF model \code{object}. setMethod('algorithm', 'NMFfit', function(object){ object@method } ) #' @inline setReplaceMethod('algorithm', 'NMFfit', function(object, value){ object@method <- value object } ) #' Returns the name of the seeding method that generated the starting point #' for the NMF algorithm that fitted the NMF model \code{object}. setMethod('seeding', 'NMFfit', function(object){ object@seed } ) #' @inline setReplaceMethod('seeding', 'NMFfit', function(object, value){ object@seed <- value object } ) #' Returns the objective function associated with the algorithm that computed the #' fitted NMF model \code{object}, or the objective value with respect to a given #' target matrix \code{y} if it is supplied. #' #' @param y optional target matrix used to compute the objective value. #' setMethod('objective', signature(object='NMFfit'), function(object, y){ # when both x and y are missing then returns slot objective if( missing(y) ) return(slot(object, 'distance')) # return the distance computed using the strategy's objective function deviance(fit(object), y, method=slot(object, 'distance')) } ) #' @inline setReplaceMethod('objective', signature(object='NMFfit', value='ANY'), function(object, value){ slot(object, 'distance') <- value validObject(object) object } ) #' Returns the CPU time required to compute a single NMF fit. setMethod('runtime', 'NMFfit', function(object, ...){ object@runtime } ) #' Identical to \code{runtime}, since their is a single fit. setMethod('runtime.all', 'NMFfit', getMethod('runtime', 'NMFfit')) ###% Access methods to run options. setGeneric('run.options', function(object, ...) standardGeneric('run.options') ) setMethod('run.options', 'NMFfit', function(object, name){ if( missing(name) ) object@options else object@options[[name]] } ) setGeneric('run.options<-', function(object, ..., value) standardGeneric('run.options<-') ) setReplaceMethod('run.options', 'NMFfit', function(object, ..., value){ params <- list(...) baseError <- 'Setting NMF runtime options: ' if ( length(params) == 0 ){ if( !is.list(value) ) stop(baseError, 'options must be given as a list') object@options <- value return(object) } else if ( length(params) > 1 ) stop(baseError, 'options cannot set more than one option at a time') name <- params[[1]] if( !is.character(name) ) stop(baseError, 'option name must be given as a character string') # check if the option exists #if( !is.element(name, names(nmf.options.runtime())) ) stop(baseError, "option '", name, "' is not defined.") object@options[[name]] <- value object } ) setGeneric('verbose', function(object, ...) standardGeneric('verbose') ) setMethod('verbose', 'NMFfit', function(object){ return(run.options(object, 'verbose') || nmf.getOption('debug')) } ) setGeneric('plot', package='graphics' ) #' Plots the residual track computed at regular interval during the fit of #' the NMF model \code{x}. #' #' @param skip an integer that indicates the number of points to skip/remove from the beginning #' of the curve. #' If \code{skip=1L} (default) only the initial residual -- that is computed before any iteration, is #' skipped, if present in the track (it associated with iteration 0). #' #' @export setMethod('plot', signature(x='NMFfit', y='missing'), function(x, y, skip=-1L, ...){ # retrieve the residuals track track <- residuals(x, track=TRUE) if( length(track) <= 1 ){ warning(class(x), ' object has no residuals track') return(invisible()) } # skip part of the track if( skip == -1L && !is.null(names(track)) ) track <- track[names(track)!='0'] # remove initial residual else if( skip > 0 ) track <- track[-(1:skip)] # set default graphical parameters (those can be overriden by the user) params <- .set.list.defaults(list(...) , xlab='Iterations' , ylab=paste('Objective value (' , if( is.character(x@distance) ) x@distance else algorithm(x), ')' , sep='' ) , main=paste("NMF Residuals\nMethod: ", algorithm(x), " - Rank: ", nbasis(x), sep='') , cex.main = 1 , col='#5555ff', lwd=1.4, type='l', cex=0.5) do.call('plot', c(list(names(track), track), params)) points(names(track), track, type='p', cex=0.6, col=params$col) } ) #' Computes summary measures for a single fit from \code{\link{nmf}}. #' #' This method adds the following measures to the measures computed by the method #' \code{summary,NMF}: #' #' \describe{ #' \item{residuals}{Residual error as measured by the objective function associated #' to the algorithm used to fit the model.} #' \item{niter}{Number of iterations performed to achieve convergence of the algorithm.} #' \item{cpu}{Total CPU time required for the fit.} #' \item{cpu.all}{Total CPU time required for the fit. For \code{NMFfit} objects, this element is #' always equal to the value in \dQuote{cpu}, but will be different for multiple-run fits.} #' \item{nrun}{Number of runs performed to fit the model. This is always equal to 1 for #' \code{NMFfit} objects, but will vary for multiple-run fits.} #' } #' #' @inline #' #' @examples #' # generate a synthetic dataset with known classes: 50 features, 18 samples (5+5+8) #' n <- 50; counts <- c(5, 5, 8); #' V <- syntheticNMF(n, counts) #' cl <- unlist(mapply(rep, 1:3, counts)) #' #' # perform default NMF with rank=2 #' x2 <- nmf(V, 2) #' summary(x2, cl, V) #' # perform default NMF with rank=2 #' x3 <- nmf(V, 3) #' summary(x2, cl, V) #' setMethod('summary', signature(object='NMFfit'), function(object, ...){ res <- summary(fit(object), ...) ## IMPORTANT: if adding a summary measure also add it in the sorting ## schema of method NMFfitX::compare to allow ordering on it # retreive final residuals res <- c(res, residuals=as.numeric(residuals(object))) # nb of iterations res <- c(res, niter=as.integer(niter(object)) ) # runtime t <- runtime(object) utime <- as.numeric(t['user.self'] + t['user.child']) res <- c(res, cpu=utime, cpu.all=utime, nrun=1) # return result return(res) } ) #' Compares two NMF models when at least one comes from a NMFfit object, #' i.e. an object returned by a single run of \code{\link{nmf}}. setMethod('nmf.equal', signature(x='NMFfit', y='NMF'), function(x, y, ...){ nmf.equal(fit(x), y, ...) } ) #' Compares two NMF models when at least one comes from a NMFfit object, #' i.e. an object returned by a single run of \code{\link{nmf}}. setMethod('nmf.equal', signature(x='NMF', y='NMFfit'), function(x, y, ...){ nmf.equal(x, fit(y), ...) } ) #' Compares two fitted NMF models, i.e. objects returned by single runs of #' \code{\link{nmf}}. setMethod('nmf.equal', signature(x='NMFfit', y='NMFfit'), function(x, y, ...){ nmf.equal(fit(x), fit(y), ...) } ) NMF/R/nmfModel.R0000644000176200001440000006777513620533556013015 0ustar liggesusers# Factory/Constructor Methods for NMF models # # Author: Renaud Gaujoux # Creation: 03 Jul 2012 ############################################################################### #' @include NMFstd-class.R #' @include NMFns-class.R #' @include NMFOffset-class.R NULL #' Factory Methods NMF Models #' #' \code{nmfModel} is a S4 generic function which provides a convenient way to #' build NMF models. #' It implements a unified interface for creating \code{NMF} objects from any #' NMF models, which is designed to resolve potential dimensions inconsistencies. #' #' All \code{nmfModel} methods return an object that inherits from class \code{NMF}, #' that is suitable for seeding NMF algorithms via arguments \code{rank} or #' \code{seed} of the \code{\link{nmf}} method, in which case the factorisation #' rank is implicitly set by the number of basis components in the seeding #' model (see \code{\link{nmf}}). #' #' For convenience, shortcut methods and internal conversions for working on #' \code{data.frame} objects directly are implemented. #' However, note that conversion of a \code{data.frame} into a \code{matrix} #' object may take some non-negligible time, for large datasets. #' If using this method or other NMF-related methods several times, consider #' converting your data \code{data.frame} object into a matrix once for good, #' when first loaded. #' #' @param rank specification of the target factorization rank #' (i.e. the number of components). #' @param target an object that specifies the dimension of the estimated target matrix. #' @param ... extra arguments to allow extension, that are passed down to the #' workhorse method \code{nmfModel,numeric.numeric}, where they are used to #' initialise slots specific to the instantiating NMF model class. #' #' @return an object that inherits from class \code{\linkS4class{NMF}}. #' @family NMF-interface #' @export #' @inline setGeneric('nmfModel', function(rank, target=0L, ...) standardGeneric('nmfModel')) #' Main factory method for NMF models #' #' This method is the workhorse method that is eventually called by all other methods. #' See section \emph{Main factory method} for more details. #' #' @param ncol a numeric value that specifies the number #' of columns of the target matrix, fitted the NMF model. #' It is used only if not missing and when argument \code{target} is a single #' numeric value. #' @param model the class of the object to be created. #' It must be a valid class name that inherits from class \code{NMF}. #' Default is the standard NMF model \code{\linkS4class{NMFstd}}. #' @param W value for the basis matrix. #' \code{data.frame} objects are converted into matrices with \code{\link{as.matrix}}. #' @param H value for the mixture coefficient matrix #' \code{data.frame} objects are converted into matrices with \code{\link{as.matrix}}. #' @param force.dim logical that indicates whether the method should try #' lowering the rank or shrinking dimensions of the input matrices to #' make them compatible #' @param order.basis logical that indicates whether the basis components should #' reorder the rows of the mixture coefficient matrix to match the order of the #' basis components, based on their respective names. It is only used if the #' basis and coefficient matrices have common unique column and row names #' respectively. #' #' @section Main factory method: #' The main factory engine of NMF models is implemented by the method with #' signature \code{numeric, numeric}. #' Other factory methods provide convenient ways of creating NMF models from e.g. a #' given target matrix or known basis/coef matrices (see section \emph{Other Factory Methods}). #' #' This method creates an object of class \code{model}, using the extra #' arguments in \code{...} to initialise slots that are specific to the given model. #' #' All NMF models implement get/set methods to access the matrix factors #' (see \code{\link{basis}}), which are called to initialise them from arguments #' \code{W} and \code{H}. #' These argument names derive from the definition of all built-in models that #' inherit derive from class \code{\linkS4class{NMFstd}}, which has two slots, #' \var{W} and \var{H}, to hold the two factors -- following the notations used #' in \cite{Lee1999}. #' #' If argument \code{target} is missing, the method creates a standard NMF #' model of dimension 0x\code{rank}x0. #' That is that the basis and mixture coefficient matrices, \var{W} and \var{H}, #' have dimension 0x\code{rank} and \code{rank}x0 respectively. #' #' If target dimensions are also provided in argument \code{target} as a #' 2-length vector, then the method creates an \code{NMF} object compatible to #' fit a target matrix of dimension \code{target[1]}x\code{target[2]}. #' That is that the basis and mixture coefficient matrices, \var{W} and \var{H}, #' have dimension \code{target[1]}x\code{rank} and \code{rank}x\code{target[2]} #' respectively. #' The target dimensions can also be specified using both arguments \code{target} #' and \code{ncol} to define the number of rows and the number of columns of the #' target matrix respectively. #' If no other argument is provided, these matrices are filled with NAs. #' #' If arguments \code{W} and/or \code{H} are provided, the method creates a NMF #' model where the basis and mixture coefficient matrices, \var{W} and \var{H}, #' are initialised using the values of \code{W} and/or \code{H}. #' #' The dimensions given by \code{target}, \code{W} and \code{H}, must be compatible. #' However if \code{force.dim=TRUE}, the method will reduce the dimensions to the achieve #' dimension compatibility whenever possible. #' #' When \code{W} and \code{H} are both provided, the \code{NMF} object created is #' suitable to seed a NMF algorithm in a call to the \code{\link{nmf}} method. #' Note that in this case the factorisation rank is implicitly set by the number #' of basis components in the seed. #' #' @examples #' #' # data #' n <- 20; r <- 3; p <- 10 #' V <- rmatrix(n, p) # some target matrix #' #' # create a r-ranked NMF model with a given target dimensions n x p as a 2-length vector #' nmfModel(r, c(n,p)) # directly #' nmfModel(r, dim(V)) # or from an existing matrix <=> nmfModel(r, V) #' # or alternatively passing each dimension separately #' nmfModel(r, n, p) #' #' # trying to create a NMF object based on incompatible matrices generates an error #' w <- rmatrix(n, r) #' h <- rmatrix(r+1, p) #' try( new('NMFstd', W=w, H=h) ) #' try( nmfModel(w, h) ) #' try( nmfModel(r+1, W=w, H=h) ) #' # The factory method can be force the model to match some target dimensions #' # but warnings are thrown #' nmfModel(r, W=w, H=h) #' nmfModel(r, n-1, W=w, H=h) #' setMethod('nmfModel', signature(rank='numeric', target='numeric'), function(rank, target, ncol=NULL, model='NMFstd', W, H, ..., force.dim=TRUE, order.basis=TRUE){ if( is.null(model) ) model <- 'NMFstd' # check validity of the provided class if( !isClass(model) ) stop("nmfModel - Invalid model name: class '", model,"' is not defined.") if( !extends(model, 'NMF') ) stop("nmfModel - Invalid model name: class '", model,"' does not extend class 'NMF'.") # check the validity of the target if( length(target) == 0 ) stop('nmfModel - Invalid dimensions: `target` must be at least of length 1') if( length(target) > 2 ) stop('nmfModel - Invalid dimensions: `target` must be at most of length 2') if( !missing(ncol) && !is.null(ncol) && (!is.vector(ncol) || length(ncol) > 1 || !is.numeric(ncol) || ncol<0 ) ) stop('nmfModel - Invalid dimensions: `ncol` must be a single nonnegative integer') # compute the target dimension target <- as.integer(target) n <- target[1] m <- if( length(target) == 2 ) target[2] else if( !missing(ncol) && !is.null(ncol) ) ncol else if( !missing(H) ) ncol(H) else n if( n < 0 ) stop("nmfModel - Invalid target number of rows: nonnegative value expected") if( m < 0 ) stop("nmfModel - Invalid target number of columns: nonnegative value expected") # force rank to be an integer r <- as.integer(rank) # check the validity of the rank if( length(r) != 1 ) stop("Invalid argument 'rank': single numeric expected") if( r < 0 ) stop("nmfModel - Invalid argument 'rank': nonnegative value expected") # do not allow dimension incompatibility if required if( !force.dim && !missing(W) && !missing(H) && ncol(W) != nrow(H) ){ stop('nmfModel - Invalid number of columns in the basis matrix [', ncol(W), ']: ' , 'it should match the number of rows in the mixture coefficient matrix [', nrow(H), ']') } # build dummy compatible W and H if necessary W.was.missing <- FALSE if( missing(W) ){ W <- matrix(as.numeric(NA), n, r) W.was.missing <- TRUE } else{ if( is.vector(W) ) # convert numerical vectors into a matrix W <- matrix(W, n, r) else if( is.data.frame(W) ) # convert data.frame into matrix W <- as.matrix(W) if( r == 0 ) r <- ncol(W) else if( r < ncol(W) ){ if( !force.dim ){ stop('nmfModel - Invalid number of columns in the basis matrix [', ncol(W), ']: ', 'it should match the factorization rank [', r, ']') } warning("Objective rank is [",r,"] lower than the number of columns in W [",ncol(W),"]: " , "only the first ", r," columns of W will be used") W <- W[,1:r, drop=FALSE] } else if( r > ncol(W) ){ stop("nmfModel - Objective rank [",r,"] is greater than the number of columns in W [",ncol(W),"]") } # resolve consistency with target if( n == 0 ) n <- nrow(W) else if( n < nrow(W) ){ if( !force.dim ){ stop('nmfModel - Invalid number of rows in the basis matrix [', nrow(W), ']: ' , 'it should match the target number of rows [', n, ']') } warning("nmfModel - Number of rows in target is lower than the number of rows in W [",nrow(W),"]: ", "only the first ", n," rows of W will be used") W <- W[1:n, , drop=FALSE] } else if( n > nrow(W) ){ stop("nmfModel - Number of rows in target [",n,"] is greater than the number of rows in W [",nrow(W),"]") } } if( missing(H) ) H <- matrix(as.numeric(NA), ncol(W), m) else{ # convert numerical vectors into a matrix if( is.vector(H) ) H <- matrix(H, r, m) else if( is.data.frame(H) ) # convert data.frame into matrix H <- as.matrix(H) if( r == 0 ) r <- nrow(H) else if( r < nrow(H) ){ if( !force.dim ){ stop('nmfModel - Invalid number of rows in the mixture coefficient matrix [', nrow(H), ']: ' , 'it should match the factorization rank [', r, ']') } warning("nmfModel - Objective rank [",r,"] is lower than the number of rows in H [",nrow(H),"]: " , "only the first ", r," rows of H will be used") H <- H[1:r,, drop=FALSE] } else if( r > nrow(H) ) stop("nmfModel - Objective rank [",r,"] is greater than the number of rows in H [",nrow(H),"]") # force dummy W to be at least compatible with H if( W.was.missing ) W <- matrix(as.numeric(NA), n, r) # resolve consistency with target if( m == 0 ) m <- ncol(H) else if( m < ncol(H) ){ if( !force.dim ){ stop('nmfModel - Invalid number of columns in the mixture coefficient matrix [', ncol(H), ']:' , ' it should match the target number of columns [', m, ']') } warning("nmfModel - Number of columns in target is lower than the number of columns in H [",ncol(H),"]:" , " only the first ", m," columns of H will be used") H <- H[, 1:m, drop=FALSE] } else if( m > ncol(H) ){ stop("nmfModel - Number of columns in target [",m,"]" ," is greater than the number of columns in H [",ncol(H),"]") } } # check validity of matrices W and H (only if one of the target dimension is not null) if( n + m > 0 ){ if( nrow(W) != n ) stop('nmfModel - Invalid number of rows for W: should match number of rows in target [', n, ']') if( ncol(W) != r ) stop('nmfModel - Invalid number of columns for W: should match factorization rank [', r, ']') if( nrow(H) != r ) stop('nmfModel - Invalid number of rows for H: should match factorization rank [', r, ']') if( ncol(H) != m ) stop('nmfModel - Invalid number of columns for H: should match number of columns in target [', m, ']') } # build and return a dummy NMF object nmf.debug('nmfModel', "Instantiate NMF model:", model) res <- new(model, ...) nmf.debug('nmfModel', "Set factors in model:", model) # set the dimnames if possible cW <- !is.null(colnames(W)) rH <- !is.null(rownames(H)) if( cW && !rH )# use colnames of W as basisnames rownames(H) <- colnames(W) else if( !cW && rH )# use rownames of H as basisnames colnames(W) <- rownames(H) else if( cW && rH ){# try to match names or use colnames of W (with a warning) # reorder as in the basis matrix if it makes sense, i.e. if the names are the same if( order.basis && !anyDuplicated(rownames(H)) && length(setdiff(rownames(H), colnames(W)))==0 ){ H <- H[match(rownames(H), colnames(W)),] } else{ rownames(H) <- colnames(W) warning("nmfModel - The rownames of the mixture matrix were set to match the colnames of the basis matrix") } } # set the basis and coef matrices .basis(res) <- W; .coef(res) <- H # check validity validObject(res) # return the model res } ) #' Creates an empty NMF model of a given rank. #' #' This call is equivalent to \code{nmfModel(rank, 0L, ...)}, which #' creates \emph{empty} \code{NMF} object with a basis and mixture coefficient matrix #' of dimension 0 x \code{rank} and \code{rank} x 0 respectively. #' #' @seealso \code{\link{is.empty.nmf}} #' @examples #' ## Empty model of given rank #' nmfModel(3) #' setMethod('nmfModel', signature(rank='numeric', target='missing'), function(rank, target, ...){ nmfModel(rank, 0L, ...) } ) #' Creates an empty NMF model of null rank and a given dimension. #' #' This call is equivalent to \code{nmfModel(0, target, ...)}. #' #' @examples #' nmfModel(target=10) #square #' nmfModel(target=c(10, 5)) #' setMethod('nmfModel', signature(rank='missing', target='ANY'), function(rank, target, ...){ nmfModel(0L, target, ...) } ) #' Creates an empty NMF model of null rank and given dimension. #' #' This call is equivalent to \code{nmfModel(0, target, ...)}, and is meant for #' internal usage only. setMethod('nmfModel', signature(rank='NULL', target='ANY'), function(rank, target, ...){ nmfModel(0L, target, ...) } ) #' Creates an empty NMF model or from existing factors #' #' This method is equivalent to \code{nmfModel(0, 0, ..., force.dim=FALSE)}. #' This means that the dimensions of the NMF model will be taken from the optional #' basis and mixture coefficient arguments \code{W} and \code{H}. #' An error is thrown if their dimensions are not compatible. #' #' Hence, this method may be used to generate an NMF model from existing factor #' matrices, by providing the named arguments \code{W} and/or \code{H}: #' #' \code{nmfModel(W=w)} or \code{nmfModel(H=h)} or \code{nmfModel(W=w, H=h)} #' #' Note that this may be achieved using the more convenient interface is #' provided by the method \code{nmfModel,matrix,matrix} (see its dedicated description). #' #' See the description of the appropriate method below. #' #' @examples #' #' # Build an empty NMF model #' nmfModel() #' #' # create a NMF object based on one random matrix: the missing matrix is deduced #' # Note this only works when using factory method NMF #' n <- 50; r <- 3; #' w <- rmatrix(n, r) #' nmfModel(W=w) #' #' # create a NMF object based on random (compatible) matrices #' p <- 20 #' h <- rmatrix(r, p) #' nmfModel(H=h) #' #' # specifies two compatible matrices #' nmfModel(W=w, H=h) #' # error if not compatible #' try( nmfModel(W=w, H=h[-1,]) ) #' setMethod('nmfModel', signature(rank='missing', target='missing'), function(rank, target, ...){ # build an a priori empty model (extra args may provide the true dimension) # NB: do not allow dimension incompatibilities nmfModel(0L, 0L, ..., force.dim=FALSE) } ) #' Creates an NMF model compatible with a target matrix. #' #' This call is equivalent to \code{nmfModel(rank, dim(target), ...)}. #' That is that the returned NMF object fits a target matrix of the same #' dimension as \code{target}. #' #' Only the dimensions of \code{target} are used to construct the \code{NMF} object. #' The matrix slots are filled with \code{NA} values if these are not specified #' in arguments \code{W} and/or \code{H}. #' However, dimension names are set on the return NMF model if present in #' \code{target} and argument \code{use.names=TRUE}. #' #' @param use.names a logical that indicates whether the dimension names of the #' target matrix should be set on the returned NMF model. #' #' @inline #' @examples #' #' # create a r-ranked NMF model compatible with a given target matrix #' obj <- nmfModel(r, V) #' all(is.na(basis(obj))) #' setMethod('nmfModel', signature(rank='numeric', target='matrix'), function(rank, target, ..., use.names=TRUE){ # build an object compatible with the target's dimensions res <- nmfModel(rank, dim(target), ...) # try to set dimnames if it makes sense: # set on target and not somehow already set on the result if( use.names && !is.null(dimnames(target)) ){ dn <- dimnames(res) if( is.null(dn) ) dn <- list(NULL, NULL, NULL) if( is.null(rownames(res)) && !is.null(rownames(target)) ) dimnames(res) <- c(dimnames(target)[1], dn[2:3]) if( is.null(colnames(res)) && !is.null(colnames(target)) ) dimnames(res) <- c(dimnames(res)[1], dimnames(target)[2], dimnames(res)[3]) } res } ) #' Creates an NMF model based on two existing factors. #' #' This method is equivalent to \code{nmfModel(0, 0, W=rank, H=target..., force.dim=FALSE)}. #' This allows for a natural shortcut for wrapping existing \strong{compatible} #' matrices into NMF models: #' \samp{nmfModel(w, h)} #' #' Note that an error is thrown if their dimensions are not compatible. #' #' @examples #' ## From two existing factors #' #' # allows a convenient call without argument names #' w <- rmatrix(n, 3); h <- rmatrix(3, p) #' nmfModel(w, h) #' #' # Specify the type of NMF model (e.g. 'NMFns' for non-smooth NMF) #' mod <- nmfModel(w, h, model='NMFns') #' mod #' #' # One can use such an NMF model as a seed when fitting a target matrix with nmf() #' V <- rmatrix(mod) #' res <- nmf(V, mod) #' nmf.equal(res, nmf(V, mod)) #' #' # NB: when called only with such a seed, the rank and the NMF algorithm #' # are selected based on the input NMF model. #' # e.g. here rank was 3 and the algorithm "nsNMF" is used, because it is the default #' # algorithm to fit "NMFns" models (See ?nmf). #' setMethod('nmfModel', signature(rank='matrix', target='matrix'), function(rank, target, ...){ # use rank and target as W and H respectively # NB: do not allow dimension incompatibilities nmfModel(0L, 0L, W=rank, H=target, ..., force.dim=FALSE) } ) #' Same as \code{nmfModel('matrix', 'matrix')} but for \code{data.frame} objects, #' which are generally produced by \code{\link{read.delim}}-like functions. #' #' The input \code{data.frame} objects are converted into matrices with #' \code{\link{as.matrix}}. setMethod('nmfModel', signature(rank='data.frame', target='data.frame'), function(rank, target, ...){ nmfModel(as.matrix(rank), as.matrix(target), ...) } ) #' Creates an NMF model with arguments \code{rank} and \code{target} swapped. #' #' This call is equivalent to \code{nmfModel(rank=target, target=rank, ...)}. #' This allows to call the \code{nmfModel} function with arguments \code{rank} #' and \code{target} swapped. #' It exists for convenience: #' \itemize{ #' \item allows typing \code{nmfModel(V)} instead of \code{nmfModel(target=V)} to create #' a model compatible with a given matrix \code{V} (i.e. of dimension \code{nrow(V), 0, ncol(V)}) #' \item one can pass the arguments in any order (the one that comes to the user's mind first) #' and it still works as expected. #' } #' #' @examples #' ## swapped arguments `rank` and `target` #' V <- rmatrix(20, 10) #' nmfModel(V) # equivalent to nmfModel(target=V) #' nmfModel(V, 3) # equivalent to nmfModel(3, V) #' setMethod('nmfModel', signature(rank='matrix', target='ANY'), function(rank, target, ...){ if( missing(target) ) target <- NULL # call nmfModel with swapping the arguments nmfModel(target, rank, ...) } ) #' Simple Parsing of Formula #' #' Formula parser for formula-based NMF models. #' #' @param x formula to parse #' @return a list with the following elements: #' \item{response}{ logical that indicates if the formula has a response term.} #' \item{y}{ name of the response variable.} #' \item{x}{ list of regressor variable names.} #' \item{n}{ number of regressor variables.} #' #' @keywords internal parse_formula <- function(x){ res <- list() # parse formula f <- as.character(x) hasResponse <- length(f) == 3L # response res$response <- hasResponse res$y <- if( hasResponse ) f[2L] # regressors reg <- if( hasResponse ) f[3L] else f[2L] res$x <- strsplit(reg, ' ')[[1]] res$n <- length(res$reg) # as a tring res$string <- paste(res$y, '~', reg, collapse='') res } #' Build a formula-based NMF model, that can incorporate fixed basis or #' coefficient terms. #' #' @param data Optional argument where to look for the variables used in the #' formula. #' @param no.attrib logical that indicate if attributes containing data related #' to the formula should be attached as attributes. #' If \code{FALSE} attributes \code{'target'} and \code{'formula'} contain the #' target matrix, and a list describing each formula part (response, regressors, #' etc.). #' #' @inline #' #' @examples #' #' # empty 3-rank model #' nmfModel(~ 3) #' #' # 3-rank model that fits a given data matrix #' x <- rmatrix(20,10) #' nmfModel(x ~ 3) #' #' # add fixed coefficient term defined by a factor #' gr <- gl(2, 5) #' nmfModel(x ~ 3 + gr) #' #' # add fixed coefficient term defined by a numeric covariate #' nmfModel(x ~ 3 + gr + b, data=list(b=runif(10))) #' #' # 3-rank model that fits a given ExpressionSet (with fixed coef terms) #' e <- Biobase::ExpressionSet(x) #' pData(e) <- data.frame(a=runif(10)) #' nmfModel(e ~ 3 + gr + a) # `a` is looked up in the phenotypic data of x pData(x) #' setMethod('nmfModel', signature(rank='formula', target='ANY'), function(rank, target, ..., data=NULL, no.attrib=FALSE){ # missing target is NULL if( missing(target) ) target <- NULL # data is a model class name (passed from nmf) if( is.character(data) ){ model <- data data <- NULL }else model <- NULL # parse formula f <- parse_formula(rank) enclos <- environment(rank) rank <- 0L if( is.vector(target) && is.numeric(target) ){ rank <- target target <- NULL } # utility function to merge data and pData merge_pdata <- function(x, data){ pd <- pData(x) if( length(pd) ){ if( is.null(data) ) pd else{ cbind(data, pd) } }else data } # determine formula data if( is.null(data) ){ # target data.frame taken as data if a response variable if defined if( is.data.frame(target) && f$response ){ data <- target target <- NULL }else if( is.environment(target) ){ # use target as enclosure enclos <- target target <- NULL } } # determine target matrix: X <- 0L # if a response term is present, lookup target data in other arguments if( f$response ){ X <- eval(parse(text=f$y), enclos) if( is.eset(target) && !identical(X, target) ){ warning("Conflicting response term and target: the ExpressionSet in `target` will only be used for covariates.") data <- merge_pdata(target, data) } } else if( is.null(target) ){ # no response, no target: try ExpressionSet in data if( is.eset(data) ){ X <- exprs(data) } }else{ X <- target } # merge data and pData from ExpressionSet target if( is.eset(X) ){ data <- merge_pdata(X, data) X <- exprs(X) } r <- rank cterms <- bterms <- list() # dimensions are also inferred from the formula n <- if( identical(X, 0L) ) 0L else nrow(X) p <- if( identical(X, 0L) ) 0L else ncol(X) for( v in f$x ){ if( grepl("^[0-9]+$", v) ){ if( rank == 0L ){ # rank not specified in target r <- as.numeric(v) }else{ warning("NMF::nmfModel - Discarding rank specified in the formula [", v,"]:" , " using value specified in target rank instead [", rank, "].") } }else if( grepl("^[+-]$", v) ) next else { val <- eval(parse(text=v), data, enclos) .add_term <- function(v, val, type = NULL){ if( p==0L || length(val) == p || identical(type, 'coef') ){ cterms[[v]] <<- val if( p==0L ) p <<- length(val) }else if( n==0L || length(val) == n || identical(type, 'basis') ){ bterms[[v]] <<- val if( n==0L ) n <<- length(val) }else stop("Invalid", type," term '", v, "' length [", length(val), "]:" , " length must either be the number of target columns [", p, "]" , " or rows [", n, "]") } if( is.null(dim(val)) ) .add_term(v, val) else if( n == 0L || nrow(val) == n ){ lapply(1:ncol(val), function(i){ if( !is.null(cname <- colnames(val)[i]) && nzchar(cname) ) vname <- cname else vname <- paste0(v, i) .add_term(vname, val[, i], type = 'basis') }) }else{ # special handling of data.frames: # -> coef terms are passed as column variables if( is.data.frame(val) && (p == 0L || nrow(val) == p)){ val <- t(val) } if( p == 0L || ncol(val) == p ){ lapply(1:nrow(val), function(i){ if( !is.null(cname <- rownames(val)[i]) && nzchar(cname) ) vname <- cname else vname <- paste0(v, i) .add_term(vname, val[i, ], type = 'coef') }) }else{ stop("Incompatible matrix-like term '", v, "' dimensions [", str_dim(val), "]:" , " number of rows or columns must match the ones of the target matrix [", str_dim(X, dims = c(n, p)) ,"]") } } } } # try to fixup X if possible if( identical(X, 0L) ) X <- c(n, p) # call nmfModel with cterms if( hasArg(model) || is.null(model) ) object <- nmfModel(r, X, ...) else object <- nmfModel(r, X, ..., model=model) # set fixed basis terms if( length(bterms) ){ bterms(object) <- as.data.frame(bterms) } # set fixed coef terms if( length(cterms) ){ cterms(object) <- as.data.frame(cterms) } # valid object validObject(object) # attach formula data if( !no.attrib ){ attr(object, 'target') <- X attr(object, 'formula') <- f } # return object object } ) #' Listing NMF Models #' #' \code{nmfModels} lists all available NMF models currently defined that can be #' used to create NMF objects, i.e. -- more or less -- all S4 classes that #' inherit from class \code{\linkS4class{NMF}}. #' #' @param builtin.only logical that indicates whether only built-in NMF models, #' i.e. defined within the NMF package, should be listed. #' #' @return a list #' #' @export #' @family NMF-interface #' @rdname nmfModel #' @examples #' #' # show all the NMF models available (i.e. the classes that inherit from class NMF) #' nmfModels() #' # show all the built-in NMF models available #' nmfModels(builtin.only=TRUE) #' nmfModels <- function(builtin.only=FALSE){ if( builtin.only ) return( .nmf.Models.Builtin ) # return all subclasses of class 'NMF' (minus class 'NMFfit' and its subclasses) models <- names(methods::getClass('NMF')@subclasses) models.wraps <- c('NMFfit', names(methods::getClass('NMFfit')@subclasses)) return( models[!is.element(models, models.wraps)] ) } ###% Initialization function for NMF models .nmf.Models.Builtin <- NULL .init.nmf.models <- function(){ .nmf.Models.Builtin <<- nmfModels() } NMF/R/parallel.R0000644000176200001440000011457313620502674013032 0ustar liggesusers# Definitions used in the parallel computations of NMF # # - reproducible backend # - reproducible %dopar% operator: %dorng% # # Author: Renaud Gaujoux # Creation: 08-Feb-2011 ############################################################################### #' @include utils.R #' @import foreach #' @import doParallel NULL # returns the number of cores to use in all NMF computation when no number is # specified by the user getMaxCores <- function(limit=TRUE){ #ceiling(parallel::detectCores()/2) nt <- n <- parallel::detectCores() # limit to number of cores specified in options if asked for if( limit ){ if( !is.null(nc <- getOption('cores')) ) n <- nc # global option else if( !is.null(nc <- nmf.getOption('cores')) ) n <- nc # NMF-specific option else if( n > 2 ) n <- n - 1L # leave one core free if possible } # forces limiting maximum number of cores to 2 during CRAN checks if( n > 2 && isCHECK() ){ message("# NOTE - CRAN check detected: limiting maximum number of cores [2/", nt, "]") n <- 2L } n } #' Utilities and Extensions for Foreach Loops #' #' \code{registerDoBackend} is a unified register function for foreach backends. #' #' @param object specification of a foreach backend, e.g. \sQuote{SEQ}, #' \sQuote{PAR} (for doParallel), \sQuote{MPI}, etc\ldots #' @param ... extra arguments passed to the backend own registration function. #' #' @keywords internal #' @rdname foreach registerDoBackend <- function(object, ...){ # restore old backend data in case of an error old <- getDoBackend() on.exit( setDoBackend(old) ) # get old foreach backend object ob <- ForeachBackend() # register new backend: call the register method b <- ForeachBackend(object, ...) res <- register(b) # cancel backend restoration on.exit() # call old backend cleanup method doBackendCleanup(ob) # return old backend invisible(ob) } #' \code{getDoBackend} returns the internal data of the currently registered foreach \%dopar\% backend. #' @rdname foreach #' @export getDoBackend <- function(){ fe_ns <- asNamespace('foreach') fe <- ns_get('.foreachGlobals', fe_ns) if( !exists("fun", where = fe, inherits = FALSE) ) return(NULL) getDoPar <- ns_get('getDoPar', fe_ns) c(getDoPar() # this returns the registered %dopar% function + associated data # -> add info function from foreach internal environment , info= if( exists("info", where = fe, inherits = FALSE) ){ get('info', fe, inherits=FALSE) }else{ function(data, item) NULL } , cleanup = if( exists("cleanup", where = fe, inherits = FALSE) ){ get('cleanup', fe, inherits=FALSE) } ) } getDoBackendInfo <- function(x, item){ if( is.function(x$info) ) x$info(x$data, item) } getDoBackendName <- function(x){ getDoBackendInfo(x, 'name') } #' \code{setDoBackend} is identical to \code{\link[foreach]{setDoPar}}, but #' returns the internal of the previously registered backend. #' #' @param data internal data of a foreach \%dopar\% backend. #' @param cleanup logical that indicates if the previous #' backend's cleanup procedure should be run, \strong{before} #' setting the new backend. #' #' @export #' @rdname foreach setDoBackend <- function(data, cleanup=FALSE){ # get old backend data ob <- getDoBackend() ofb <- ForeachBackend() # cleanup old backend if requested if( cleanup ){ doBackendCleanup(ofb) } if( !is.null(data) ){ bdata <- data if( is.backend(data) ) data <- data[!names(data) %in% c('name', 'cleanup')] do.call('setDoPar', data) setBackendCleanup(bdata) }else{ do.call('setDoPar', list(NULL)) fe <- ns_get('.foreachGlobals', 'foreach') if (exists("fun", envir = fe, inherits = FALSE)) remove("fun", envir = fe) setBackendCleanup(NULL) } # return old backend invisible(ob) } # setup cleanup procedure for the current backend setBackendCleanup <- function(object, fun, verbose=FALSE){ fe <- ns_get('.foreachGlobals', 'foreach') name <- getDoParName() if( !is.null(fun <- object$cleanup) ){ if( verbose ) message("# Registering cleaning up function for '", name, "'... ", appendLF=FALSE) assign('cleanup', fun, fe) if( verbose ) message("OK") }else if (exists("cleanup", envir = fe, inherits = FALSE)){ if( verbose ) message("# Removing cleaning up function for '", name, "'... ", appendLF=FALSE) remove("cleanup", envir = fe) if( verbose ) message("OK") } invisible(object) } # run cleanup procedure for a given backend object doBackendCleanup <- function(object, ..., run=TRUE, verbose=FALSE){ name <- object$name if( !is.null(fun <- object$cleanup) ){ if( verbose ) message("# Cleaning up '", name, "'... ", appendLF=FALSE) res <- try(fun(), silent=TRUE) if( verbose ) message(if( is(res, 'try-error') ) 'ERROR' else 'OK') if( isTRUE(res) ) object$cleanup <- NULL if( verbose ) message('OK', if( !is.null(res) ) str_c(' [', res,']')) } invisible(object) } #' \code{register} is a generic function that register objects. #' It is used to as a unified interface to register foreach backends. #' #' @param x specification of a foreach backend #' #' @rdname foreach #' @export register <- function(x, ...){ UseMethod('register', x) } #' @export register.foreach_backend <- function(x, ...){ be <- x$name # For everything except doSEQ: # require definition package (it is safer to re-check) if( be != 'doSEQ' ){ if( !require.quiet(be, character.only=TRUE) ) stop("Package '", be, "' is required to use foreach backend '", be, "'") } regfun <- .foreach_regfun(x$name) res <- if( length(formals(regfun)) > 0L ) do.call(regfun, c(x$data, ...)) else regfun() # throw an error if not successful (foreach::setDoPar do not throw errors!!) if( is(res, 'simpleError') ) stop(res) # set cleanup procedure if any setBackendCleanup(x) # return result invisible(res) } #' \code{ForeachBackend} is a factory method for foreach backend objects. #' #' @export #' @inline #' @rdname foreach setGeneric('ForeachBackend', function(object, ...) standardGeneric('ForeachBackend')) #' Default method defined to throw an informative error message, when no other #' method was found. setMethod('ForeachBackend', 'ANY', function(object, ...){ if( is.backend(object) ){ # update arg list if necessary if( nargs() > 1L ) object$data <- list(...) object }else if( is(object, 'cluster') ) selectMethod('ForeachBackend', 'cluster')(object, ...) else stop("Could not create foreach backend object with a specification of class '", class(object)[1L], "'") } ) formatDoName <- function(x){ # numeric values are resolved as doParallel if( is.numeric(x) ) x <- 'PAR' if( is.character(x) ){ # use upper case if not already specified as 'do*' if( !grepl("^do", x) ){ x <- toupper(x) # special treatment for doParallel if( x %in% c('PAR', 'PARALLEL') ) x <- 'Parallel' } # stick prefix 'do' (removing leading 'do' if necessary) str_c('do', sub('^do', '', x)) }else '' } #' Creates a foreach backend object based on its name. setMethod('ForeachBackend', 'character', function(object, ...){ object <- formatDoName(object) # build S3 class name s3class <- str_c(object, "_backend") # create empty S3 object obj <- structure(list(name=object, data=list(...)) , class=c(s3class, 'foreach_backend')) # give a chance to a backend-specific ForeachBackend factory method # => this will generally fill the object with the elements suitable # to be used in a call to foreach::setDoPar: fun, data, info # and possibly change the name or the object class, e.g. to allow # subsequent argument-dependent dispatch. obj <- ForeachBackend(obj, ...) # check the registration routine is available .foreach_regfun(obj$name) # set data slot if not already set by the backend-specific method if( is.null(obj$data) || (length(obj$data) == 0L && nargs()>1L) ) obj$data <- list(...) # return object obj } ) #' Creates a foreach backend object for the currently registered backend. setMethod('ForeachBackend', 'missing', function(object, ...){ be <- getDoParName() data <- getDoBackend() bdata <- data$data res <- if( !is.null(bdata) ) do.call(ForeachBackend, c(list(be, bdata), ...)) else ForeachBackend(be, ...) if( !is.null(data$cleanup) ) res$cleanup <- data$cleanup res } ) #' Dummy method that returns \code{NULL}, defined for correct dispatch. setMethod('ForeachBackend', 'NULL', function(object, ...){ NULL }) setOldClass('cluster') #' Creates a doParallel foreach backend that uses the cluster described in #' \code{object}. setMethod('ForeachBackend', 'cluster', function(object, ...){ ForeachBackend('doParallel', cl=object) } ) #' Creates a doParallel foreach backend with \code{object} processes. setMethod('ForeachBackend', 'numeric', function(object, ...){ # check numeric specification if( length(object) == 0L ) stop("invalid number of cores specified as a backend [empty]") object <- object[1] if( object <= 0 ) stop("invalid negative number of cores [", object, "] specified for backend 'doParallel'") ForeachBackend('doParallel', cl=object, ...) } ) ############### # doParallel ############### setOldClass('doParallel_backend') #' doParallel-specific backend factory #' #' @param cl cluster specification: a cluster object or a numeric that indicates the #' number of nodes to use. #' @param type type of cluster, See \code{\link[parallel]{makeCluster}}. setMethod('ForeachBackend', 'doParallel_backend', function(object, cl, type=NULL){ # set type of cluster if explicitly provided if( !is.null(type) ) object$data$type <- type # required registration data # NB: a function doParallel:::doParallel should exist and do the same # thing as parallel::registerDoParallel without registering the backend #object$fun <- doParallel:::doParallel # object$info <- doParallel:::info # doParallel:::info has been removed from doParallel since version 1.0.7 # Reported in Issue #7 object$info <- getDoParallelInfo(object) # return object object } ) setOldClass('doParallelMC_backend') #' doParallel-specific backend factory for multicore (fork) clusters #' #' This method is needed since version 1.0.7 of \pkg{doParallel}, which removed #' internal function \code{info} and defined separate backend names for mc and snow clusters. setMethod('ForeachBackend', 'doParallelMC_backend', function(object, ...){ object$info <- getDoParallelInfo('mc') object$name <- 'doParallel' # return object object } ) setOldClass('doParallelSNOW_backend') #' doParallel-specific backend factory for SNOW clusters. #' #' This method is needed since version 1.0.7 of \pkg{doParallel}, which removed #' internal function \code{info} and defined separate backend names for mc and snow clusters. setMethod('ForeachBackend', 'doParallelSNOW_backend', function(object, ...){ object$info <- getDoParallelInfo('snow') object$name <- 'doParallel' # return object object } ) getDoParallelType <- function(x){ cl <- x$data[['cl']] if( is.null(cl) && length(x$data) && (is.null(names(x$data)) || names(x$data)[[1L]] == '') ) cl <- x$data[[1L]] if ( is.null(cl) || is.numeric(cl) ) { if (.Platform$OS.type == "windows" || (!is.null(x$data$type) && !identical(x$data$type, 'FORK')) ) 'snow' else 'mc' } else 'snow' } getDoParallelInfo <- function(x, ...){ t <- if( isString(x) ) x else getDoParallelType(x, ...) # str(t) ns <- asNamespace('doParallel') if( t == 'mc' ) get('mcinfo', ns) else get('snowinfo', ns) } ###################################################### # doPSOCK # Default snow-like cluster from parallel on Windows # but works on Unix as well ###################################################### setOldClass('doPSOCK_backend') #' doSNOW-specific backend factory setMethod('ForeachBackend', 'doPSOCK_backend', function(object, cl){ # use all available cores if not otherwise specified if( missing(cl) ) cl <- getMaxCores() # return equivalent doParallel object ForeachBackend('doParallel', cl, type='PSOCK') } ) .cl_cleanup <- function(gvar, envir=.GlobalEnv){ if( !exists(gvar, envir = envir) ) return() cl <- get(gvar, envir = envir) try( parallel::stopCluster(cl), silent=TRUE) rm(list=gvar, envir = envir) TRUE } cleanupCluster <- function(x, cl, stopFun=NULL){ function(){ if( is(x, 'doParallel_backend') ){ # On non-Windows machines registerDoParallel(numeric) will use # parallel::mclapply with `object` cores (no cleanup required). # On Windows doParallel::registerDoParallel(numeric) will create a # SOCKcluster with `object` cores. # => Windows needs a cleanup function that will stop the cluster # when another backend is registered. # Fortunately doParallel::registerDoParallel assign the cluster object # to the global variable `.revoDoParCluster` if( .Platform$OS.type == "windows" ){ .cl_cleanup(".revoDoParCluster") } } if( is.null(stopFun) ) stopFun <- parallel::stopCluster # stop cluster stopFun(cl) TRUE } } #' @export register.doParallel_backend <- function(x, ...){ # start cluster if numeric specification and type is defined cl <- x$data[[1]] if( is.numeric(cl) && (.Platform$OS.type == 'windows' || !is.null(x$data$type)) ){ names(x$data)[1L] <- 'spec' # start cluster clObj <- do.call(parallel::makeCluster, x$data) x$data <- list(clObj) # setup cleanup procedure x$cleanup <- cleanupCluster(x, clObj) } # register register.foreach_backend(x, ...) } ############### # doMPI ############### isMPIBackend <- function(x, ...){ b <- if( missing(x) ) ForeachBackend(...) else ForeachBackend(object=x, ...) if( is.null(b) ) FALSE else if( identical(b$name, 'doMPI') ) TRUE else if( length(b$data) ){ is(b$data[[1]], 'MPIcluster') || is(b$data[[1]], 'mpicluster') }else FALSE } #' @export register.doMPI_backend <- function(x, ...){ if( length(x$data) && isNumber(cl <- x$data[[1]]) ){ clObj <- doMPI::startMPIcluster(cl) x$data[[1]] <- clObj # setup cleanup procedure x$cleanup <- cleanupCluster(x, clObj, doMPI::closeCluster) } # register register.foreach_backend(x, ...) } setOldClass('mpicluster') #' Creates a doMPI foreach backend that uses the MPI cluster described in #' \code{object}. setMethod('ForeachBackend', 'mpicluster', function(object, ...){ ForeachBackend('doMPI', cl=object) } ) setOldClass('doMPI_backend') #' doMPI-specific backend factory setMethod('ForeachBackend', 'doMPI_backend', function(object, cl){ # use all available cores if not otherwise specified if( missing(cl) ) cl <- getMaxCores() # required registration data object$fun <- doMPI:::doMPI object$info <- doMPI:::info # return object object } ) #as.foreach_backend <- function(x, ...){ # # args <- list(...) # if( is.backend(x) ){ # # update arg list if necessary # if( length(args) > 0L ) x$args <- args # return(x) # } # # be <- # if( is.null(x) ){ # getDoParName() # } else if( is(x, 'cluster') || is.numeric(x) ){ # # check numeric specification # if( is.numeric(x) ){ # if( length(x) == 0L ) # stop("invalid number of cores specified as a backend [empty]") # x <- x[1] # if( x <= 0 ) # stop("invalid negative number of cores [", x, "] specified for backend 'doParallel'") # } # # args$spec <- x # 'Parallel' # } else if( is(x, 'mpicluster') ){ # args$spec <- x # 'MPI' # } else if( is.character(x) ){ # toupper(x) # } else # stop("invalid backend specification: must be NULL, a valid backend name, a numeric value or a cluster object [", class(x)[1L], "]") # # if( be %in% c('PAR', 'PARALLEL') ) be <- 'Parallel' # # remove leading 'do' # be <- str_c('do', sub('^do', '', be)) # # build S3 class name # s3class <- str_c(be, "_backend") # # # check the registration routine is available # regfun <- .foreach_regfun(be) # # structure(list(name=be, args=args), class=c(s3class, 'foreach_backend')) #} is.backend <- function(x) is(x, 'foreach_backend') #' @export print.foreach_backend <- function(x, ...){ cat("\n", sep='') if( length(x$data) ){ cat("Specifications:\n") str(x$data) } } .foreach_regfun <- function(name){ # early exit for doSEQ if( name == 'doSEQ' ) return( registerDoSEQ ) # build name of registration function s <- str_c(toupper(substring(name, 1,1)), substring(name, 2)) funname <- str_c('register', s) s3class <- str_c(name, "_backend") # require definition package if( !require.quiet(name, character.only=TRUE) ) stop("could not find package for foreach backend '", name, "'") # check for registering function or generic if( is.null(regfun <- getFunction(funname, mustFind=FALSE, where=asNamespace(name))) ){ if( is.null(regfun <- getS3method('register', s3class, optional=TRUE)) ) stop("could not find registration routine for foreach backend '", name, "'") # stop("backend '", name,"' is not supported: function " # ,"`", regfun, "` and S3 method `register.", s3class, "` not found.") } regfun } #' \code{getDoParHosts} is a generic function that returns the hostname of the worker nodes used by a backend. #' #' @export #' @rdname foreach #' @inline setGeneric('getDoParHosts', function(object, ...) standardGeneric('getDoParHosts')) setOldClass('foreach_backend') #' Default method that tries to heuristaically infer the number of hosts and in last #' resort temporarly register the backend and performs a foreach loop, to retrieve the #' nodename from each worker. setMethod('getDoParHosts', 'ANY', function(object, ...){ be <- if( missing(object) ) ForeachBackend(...) else ForeachBackend(object, ...) if( existsMethod('getDoParHosts', class(be)[1L]) ) return( callGeneric(object) ) # default behaviour nodename <- setNames(Sys.info()['nodename'], NULL) if( is.null(be) || is.null(be$data) ) return( NULL ) # doSEQ if( be$name == 'doSEQ' ) return( nodename ) if( isNumber(be$data) ) return( rep(nodename, be$data) ) if( length(be$data) && isNumber(be$data[[1]]) ) return( rep(nodename, be$data[[1]]) ) if( length(be$data) && be$name == 'doParallel' ) return( sapply(be$data[[1L]], '[[', 'host') ) if( !missing(object) ){ # backend passed: register temporarly ob <- getDoBackend() on.exit( setDoBackend(ob) ) registerDoBackend(be) } setNames(unlist(times(getDoParWorkers()) %dopar% { Sys.info()['nodename'] }), NULL) } ) #' \code{getDoParNHosts} returns the number of hosts used by a backend. #' #' @export #' @rdname foreach getDoParNHosts <- function(object){ if( missing(object) ) foreach::getDoParWorkers() else{ length(getDoParHosts(object)) } } # add new option: limit.cores indicates if the number of cores used in parallel # computation can exceed the detected number of CPUs on the host. #.OPTIONS$newOptions(limit.cores=TRUE) #' Computational Setup Functions #' #' @description #' Functions used internally to setup the computational environment. #' #' \code{setupBackend} sets up a foreach backend given some specifications. #' #' @param spec target parallel specification: either \code{TRUE} or \code{FALSE}, #' or a single numeric value that specifies the number of cores to setup. #' @param backend value from argument \code{.pbackend} of \code{nmf}. #' @param optional a logical that indicates if the specification must be fully #' satisfied, throwing an error if it is not, or if one can switch back to #' sequential, only outputting a verbose message. #' @param verbose logical or integer level of verbosity for message outputs. #' #' @return Returns \code{FALSE} if no foreach backend is to be used, \code{NA} if the currently #' registered backend is to be used, or, if this function call registered a new backend, #' the previously registered backend as a \code{foreach} object, so that it can be restored #' after the computation is over. #' @keywords internals #' @rdname setup setupBackend <- function(spec, backend, optional=FALSE, verbose=FALSE){ pbackend <- backend str_backend <- quick_str(pbackend) # early exit: FALSE specification or NA backend means not using foreach at all if( isFALSE(spec) || is_NA(pbackend) ) return(FALSE) # use doParallel with number of cores if specified in backend if( is.numeric(pbackend) ){ spec <- pbackend pbackend <- 'PAR' } # identify doSEQ calls doSEQ <- formatDoName(pbackend) == 'doSEQ' # custom error function pcomp <- is.numeric(spec) && !identical(spec[1], 1) errorFun <- function(value=FALSE, stop=FALSE, level=1){ function(e, ...){ if( !is(e, 'error') ) e <- list(message=str_c(e, ...)) pref <- if( pcomp ) "Parallel" else "Foreach" if( !optional || stop ){ if( verbose >= level ) message('ERROR') stop(pref, " computation aborted: ", e$message, call.=FALSE) }else if( verbose >= level ){ message('NOTE') message("# NOTE: ", pref, " computation disabled: ", e$message) } value } } # check current backend if backend is NULL if( is.null(pbackend) ){ if( verbose > 1 ){ message("# Using current backend ... ", appendLF=FALSE) } ok <- tryCatch({ if( is.null(parname <- getDoParName()) ) stop("argument '.pbackend' is NULL but there is no registered backend") if( verbose > 1 ) message('OK [', parname, ']') TRUE }, error = errorFun()) if( !ok ) return(FALSE) # exit now since there is nothing to setup, nothing should change # return NULL so that the backend is not restored on.exit of the parent call. return(NA) } ## # test if requested number of cores is actually available NCORES <- getMaxCores(limit=FALSE) if( verbose > 2 ) message("# Check available cores ... [", NCORES, ']') if( verbose > 2 ) message("# Check requested cores ... ", appendLF=FALSE) ncores <- if( doSEQ ) 1L else{ ncores <- tryCatch({ if( is.numeric(spec) ){ if( length(spec) == 0L ) stop("no number of cores specified for backend '", str_backend, "'") spec <- spec[1] if( spec <= 0L ) stop("invalid negative number of cores [", spec, "] specified for backend '", str_backend, "'") spec }else # by default use the 'cores' option or half the number of cores getMaxCores() #getOption('cores', ceiling(NCORES/2)) }, error = errorFun(stop=TRUE)) if( isFALSE(ncores) ) return(FALSE) ncores } if( verbose > 2 ) message('[', ncores, ']') # create backend object if( verbose > 2 ) message("# Loading backend for specification `", str_backend, "` ... ", appendLF=FALSE) newBackend <- tryCatch({ # NB: limit to the number of cores available on the host if( !doSEQ ) ForeachBackend(pbackend, min(ncores, NCORES)) else ForeachBackend(pbackend) }, error = errorFun(level=3)) if( isFALSE(newBackend) ) return(FALSE) if( verbose > 2 ) message('OK') if( verbose > 1 ) message("# Check host compatibility ... ", appendLF=FALSE) ok <- tryCatch({ # check if we're not running on MAC from GUI if( is.Mac(check.gui=TRUE) && (newBackend$name == 'doMC' || (newBackend$name == 'doParallel' && is.numeric(newBackend$data[[1]]))) ){ # error only if the parallel computation was explicitly asked by the user stop("multicore parallel computations are not safe from R.app on Mac OS X." , "\n -> Use a terminal session, starting R from the command line.") } TRUE }, error = errorFun()) if( !ok ) return(FALSE) if( verbose > 1 ) message('OK') if( verbose > 1 ) message("# Registering backend `", newBackend$name, "` ... ", appendLF=FALSE) # try registering the backend oldBackend <- getDoBackend() # setup retoration of backend in case of an error # NB: the new backend cleanup will happens only # if regsitration succeeds, since the cleanup routine is # setup after the registration by the suitable register S3 method. on.exit( setDoBackend(oldBackend, cleanup=TRUE) ) ov <- lverbose(verbose) ok <- tryCatch({ registerDoBackend(newBackend) TRUE } , error ={ lverbose(ov) errorFun() }) lverbose(ov) if( !ok ) return(FALSE) if( verbose > 1 ) message('OK') # check allocated cores if not doSEQ backend if( newBackend$name != 'doSEQ' ){ # test allocated number of cores if( verbose > 2 ) message("# Check allocated cores ... ", appendLF=FALSE) wcores <- getDoParWorkers() if( ncores > 0L && wcores < ncores ){ if( !optional ){ errorFun(level=3)("only ", wcores, " core(s) available [requested ", ncores ," core(s)]") }else if( verbose > 2 ){ message('NOTE [', wcores, '/', ncores, ']') message("# NOTE: using only ", wcores, " core(s) [requested ", ncores ," core(s)]") } } else if( verbose > 2 ){ message('OK [', wcores, '/', ncores , if(ncores != NCORES ) str_c(' out of ', NCORES) , ']') } } # cancel backend restoration on.exit() # return old backend oldBackend } # add extra package bigmemory and synchronicity on Unix platforms if( .Platform$OS.type != 'windows' ){ setPackageExtra('install.packages', 'bigmemory', pkgs='bigmemory') setPackageExtra('install.packages', 'synchronicity', pkgs='synchronicity') } # add new option: shared.memory that indicates if one should try using shared memory # to speed-up parallel computations. .OPTIONS$newOptions(shared.memory = (.Platform$OS.type != 'windows' && !is.Mac())) #' \code{setupSharedMemory} checks if one can use the packages \emph{bigmemory} and \emph{sychronicity} #' to speed-up parallel computations when not keeping all the fits. #' When both these packages are available, only one result per host is written on disk, #' with its achieved deviance stored in shared memory, that is accessible to all cores on #' a same host. #' It returns \code{TRUE} if both packages are available and NMF option \code{'shared'} is #' toggled on. #' #' @rdname setup setupSharedMemory <- function(verbose){ if( verbose > 1 ) message("# Check shared memory capability ... ", appendLF=FALSE) # early exit if option shared is off if( !nmf.getOption('shared.memory') ){ if( verbose > 1 ) message('SKIP [disabled]') return(FALSE) } # early exit if foreach backend is doMPI: it is not working, not sure why if( isMPIBackend() ){ if( verbose > 1 ) message('SKIP [MPI cluster]') return(FALSE) } # not on Windows if( .Platform$OS.type == 'windows' ){ if( verbose > 1 ) message('SKIP [Windows OS]') return(FALSE) } if( !require.quiet('bigmemory', character.only=TRUE) ){ if( verbose > 1 ){ message('NO', if( verbose > 2 ) ' [Package `bigmemory` required]') } return(FALSE) } if( !require.quiet('synchronicity', character.only=TRUE) ){ if( verbose > 1 ){ message('NO', if( verbose > 2 ) ' [Package `synchronicity` required]') } return(FALSE) } if( verbose > 1 ) message('YES', if( verbose > 2 ) ' [synchronicity]') TRUE } is.doSEQ <- function(){ dn <- getDoParName() is.null(dn) || dn == 'doSEQ' } #' \code{setupTempDirectory} creates a temporary directory to store the best fits computed on each host. #' It ensures each worker process has access to it. #' #' @rdname setup setupTempDirectory <- function(verbose){ # - Create a temporary directory to store the best fits computed on each host NMF_TMPDIR <- tempfile('NMF_', getwd()) if( verbose > 2 ) message("# Setup temporary directory: '", NMF_TMPDIR, "' ... ", appendLF=FALSE) dir.create(NMF_TMPDIR) if( !is.dir(NMF_TMPDIR) ){ if( verbose > 2 ) message('ERROR') nmf_stop('nmf', "could not create temporary result directory '", NMF_TMPDIR, "'") } on.exit( unlink(NMF_TMPDIR, recursive=TRUE) ) # ensure that all workers can see the temporary directory wd <- times(getDoParWorkers()) %dopar% { if( !file_test('-d', NMF_TMPDIR) ) dir.create(NMF_TMPDIR, recursive=TRUE) file_test('-d', NMF_TMPDIR) } # check it worked if( any(!wd) ){ if( verbose > 2 ) message('ERROR') nmf_stop('nmf', "could not create/see temporary result directory '", NMF_TMPDIR, "' on worker nodes ", str_out(which(!wd), Inf)) } if( verbose > 2 ) message('OK') on.exit() NMF_TMPDIR } #' Utilities for Parallel Computations #' #' #' @rdname parallel #' @name parallel-NMF NULL #' \code{ts_eval} generates a thread safe version of \code{\link{eval}}. #' It uses boost mutexes provided by the \code{\link[synchronicity]{synchronicity}} #' package. #' The generated function has arguments \code{expr} and \code{envir}, which are passed #' to \code{\link{eval}}. #' #' @param mutex a mutex or a mutex descriptor. #' If missing, a new mutex is created via the function \code{\link[synchronicity]{boost.mutex}}. #' @param verbose a logical that indicates if messages should be printed when #' locking and unlocking the mutex. #' #' @rdname parallel #' @export ts_eval <- function(mutex = synchronicity::boost.mutex(), verbose=FALSE){ requireNamespace('bigmemory') #library(bigmemory) requireNamespace('synchronicity') #library(synchronicity) # describe mutex if necessary .MUTEX_DESC <- if( is(mutex, 'boost.mutex') ) synchronicity::describe(mutex) else mutex loadpkg <- TRUE function(expr, envir=parent.frame()){ # load packages once if( loadpkg ){ requireNamespace('bigmemory') #library(bigmemory) requireNamespace('synchronicity') #library(synchronicity) loadpkg <<- FALSE } MUTEX <- synchronicity::attach.mutex(.MUTEX_DESC) synchronicity::lock(MUTEX) if( verbose ) message('#', Sys.getpid(), " - START mutex: ", .MUTEX_DESC@description$shared.name) ERROR <- "### ###\n" on.exit({ if( verbose ){ message(ERROR, '#', Sys.getpid(), " - END mutex: ", .MUTEX_DESC@description$shared.name) } synchronicity::unlock(MUTEX) }) eval(expr, envir=envir) ERROR <- NULL } } #' \code{ts_tempfile} generates a \emph{unique} temporary filename #' that includes the name of the host machine and/or the caller's process id, #' so that it is thread safe. #' #' @inheritParams base::tempfile #' @param ... extra arguments passed to \code{\link[base]{tempfile}}. #' @param host logical that indicates if the host machine name should #' be appear in the filename. #' @param pid logical that indicates if the current process id #' be appear in the filename. #' #' @rdname parallel #' @export ts_tempfile <- function(pattern = "file", ..., host=TRUE, pid=TRUE){ if( host ) pattern <- c(pattern, Sys.info()['nodename']) if( pid ) pattern <- c(pattern, Sys.getpid()) tempfile(paste(pattern, collapse='_'), ...) } #' \code{hostfile} generates a temporary filename composed with #' the name of the host machine and/or the current process id. #' #' @inheritParams base::tempfile #' @inheritParams ts_tempfile #' #' @rdname parallel #' @export hostfile <- function(pattern = "file", tmpdir=tempdir(), fileext='', host=TRUE, pid=TRUE){ if( host ) pattern <- c(pattern, Sys.info()['nodename']) if( pid ) pattern <- c(pattern, Sys.getpid()) file.path(tmpdir, str_c(paste(pattern, collapse='.'), fileext)) } #' \code{gVariable} generates a function that access a global static variable, #' possibly in shared memory (only for numeric matrix-coercible data in this case). #' It is used primarily in parallel computations, to preserve data accross #' computations that are performed by the same process. #' #' @param init initial value #' @param shared a logical that indicates if the variable should be stored in shared #' memory or in a local environment. #' #' @rdname parallel #' @export gVariable <- function(init, shared=FALSE){ if( shared ){ # use bigmemory shared matrices if( !is.matrix(init) ) init <- as.matrix(init) requireNamespace('bigmemory') #library(bigmemory) DATA <- bigmemory::as.big.matrix(init, type='double', shared=TRUE) DATA_DESC <- bigmemory::describe(DATA) }else{ # use variables assigned to .GlobalEnv DATA_DESC <- basename(tempfile('.gVariable_')) } .VALUE <- NULL .loadpkg <- TRUE function(value){ # load packages once if( shared && .loadpkg ){ requireNamespace('bigmemory') #library(bigmemory) .loadpkg <<- FALSE } # if shared: attach bigmemory matrix from its descriptor object if( shared ){ DATA <- bigmemory::attach.big.matrix(DATA_DESC) } if( missing(value) ){# READ ACCESS if( !shared ){ # initialise on first call if necessary if( is.null(.VALUE) ) .VALUE <<- init # return variable .VALUE }else DATA[] }else{# WRITE ACCESS if( !shared ) .VALUE <<- value else DATA[] <- value } } } #' \code{setupLibPaths} add the path to the NMF package to each workers' libPaths. #' #' @param pkg package name whose path should be exported the workers. #' #' @rdname setup setupLibPaths <- function(pkg='NMF', verbose=FALSE){ # do nothing in sequential mode if( is.doSEQ() ) return( character() ) if( verbose ){ message("# Setting up libpath on workers for package(s) " , str_out(pkg, Inf), ' ... ', appendLF=FALSE) } p <- path.package(pkg) if( is.null(p) ) return() if( !isDevNamespace(pkg) ){ # not a dev package plibs <- dirname(p) libs <- times(getDoParWorkers()) %dopar% { .libPaths(c(.libPaths(), plibs)) } libs <- unique(unlist(libs)) if( verbose ){ message("OK\n# libPaths:\n", paste(' ', libs, collapse="\n")) } libs pkg }else if( getDoParName() != 'doParallel' || !isNumber(getDoBackend()$data) ){ # devmode: load the package + depends if( verbose ){ message("[devtools::load_all] ", appendLF=FALSE) } times(getDoParWorkers()) %dopar% { capture.output({ suppressMessages({ requireNamespace('devtools') #library(devtools) requireNamespace('bigmemory') #library(bigmemory) devtools::load_all(p) }) }) } if( verbose ){ message("OK") } c('bigmemory', 'rngtools') } else if( verbose ){ message("OK") } } #StaticWorkspace <- function(..., .SHARED=FALSE){ # # # create environment # e <- new.env(parent=.GlobalEnv) # # fill with initial data # vars <- list(...) # if( .SHARED ){ # lapply(names(vars), function(x){ # bm <- bigmemory::as.big.matrix(vars[[x]], type='double', shared=TRUE) # e[[x]] <- bigmemory::describe(bm) # }) # }else # list2env(vars, envir=e) # # structure(e, shared=.SHARED, class=c("static_wsp", 'environment')) #} # #`[[.static_wsp` <- function(x, ..., exact = TRUE){ # if( attr(x, 'shared') ){ # var <- bigmemory::attach.big.matrix(NextMethod()) # var[] # }else # NextMethod() #} # #`[[.static_wsp<-` <- function(x, i, value){ # # if( attr(x, 'shared') ){ # var <- bigmemory::attach.big.matrix(x[[i]]) # var[] <- value # }else # x[[i]] <- value # x #} isRNGseed <- function(x){ is.numeric(x) || ( is.list(x) && is.null(names(x)) && all(sapply(x, is.numeric)) ) } #' \code{setupRNG} sets the RNG for use by the function nmf. #' It returns the old RNG as an rstream object or the result of set.seed #' if the RNG is not changed due to one of the following reason: #' - the settings are not compatible with rstream #' #' @param seed initial RNG seed specification #' @param n number of RNG seeds to generate #' #' @rdname setup setupRNG <- function(seed, n, verbose=FALSE){ if( verbose == 2 ){ message("# Setting up RNG ... ", appendLF=FALSE) on.exit( if( verbose == 2 ) message("OK") ) }else if( verbose > 2 ) message("# Setting up RNG ... ") if( verbose > 3 ){ message("# ** Original RNG settings:") showRNG() } # for multiple runs one always uses RNGstreams if( n > 1 ){ # seeding with numeric values only if( is.list(seed) && isRNGseed(seed) ){ if( length(seed) != n ) stop("Invalid list of RNG seeds: must be of length ", n) if( verbose > 2 ) message("# Using supplied list of RNG seeds") return(seed) }else if( is.numeric(seed) ){ if( verbose > 2 ){ message("# Generate RNGStream sequence using seed (" , RNGstr(seed), ") ... " , appendLF=FALSE) } res <- RNGseq(n, seed) if( verbose > 2 ) message("OK") return(res) }else{ # create a sequence of RNGstream using a random seed if( verbose > 2 ){ message("# Generate RNGStream sequence using a random seed ... " , appendLF=FALSE) } res <- RNGseq(n, NULL) if( verbose > 2 ) message("OK") return(res) } }else if( is.numeric(seed) ){ # for single runs: 1-length seeds are used to set the current RNG # 6-length seeds are used to set RNGstream if( !is.vector(seed) ){ message('ERROR') stop("NMF::nmf - Invalid numeric seed: expects a numeric vector.") } # convert to an integer vector seed <- as.integer(seed) # immediately setup the RNG in the standard way if( length(seed) == 1L ){ if( verbose > 2 ){ message("# RNG setup: standard [seeding current RNG]") message("# Seeding current RNG with seed (", seed, ") ... " , appendLF=FALSE) } set.seed(seed) if( verbose > 2 ) message("OK") return( getRNG() ) }else if( length(seed) == 6L ){ if( verbose > 2 ){ message("# RNG setup: reproducible [using RNGstream]") message("# Generate RNGStream sequence using seed (" , RNGstr(seed), ") ... " , appendLF=FALSE) } res <- RNGseq(1, seed) setRNG(res) if( verbose > 2 ) message("OK") return( res ) }else{ if( verbose > 2 ){ message("# RNG setup: directly setting RNG") message("# Setting RNG with .Random.seed= (" , RNGstr(seed), ") ... " , appendLF=FALSE) } setRNG(seed, verbose > 2) if( verbose > 2 ) message("OK") return( getRNG() ) } stop("NMF::nmf - Invalid numeric seed: unexpected error.") }else{ if( verbose > 2 ) message("# RNG setup: standard [using current RNG]") NULL } } ################################################################## ## END ################################################################## NMF/R/simulation.R0000644000176200001440000001211213620502674013404 0ustar liggesusers# Functions to simulate NMF data # # Author: Renaud Gaujoux ############################################################################### #' @include utils.R NULL #' Simulating Datasets #' #' The function \code{syntheticNMF} generates random target matrices that follow #' some defined NMF model, and may be used to test NMF algorithms. #' It is designed to designed to produce data with known or clear classes of #' samples. #' #' @param n number of rows of the target matrix. #' @param r specification of the factorization rank. #' It may be a single \code{numeric}, in which case argument \code{p} is required #' and \code{r} groups of samples are generated from a draw from a multinomial #' distribution with equal probabilities, that provides their sizes. #' #' It may also be a numerical vector, which contains the number of samples in #' each class (i.e integers). In this case argument \code{p} is discarded #' and forced to be the sum of \code{r}. #' @param p number of columns of the synthetic target matrix. #' Not used if parameter \code{r} is a vector (see description of argument \code{r}). #' @param offset specification of a common offset to be added to the synthetic target #' matrix, before noisification. #' Its may be a numeric vector of length \code{n}, or a single numeric value that #' is used as the standard deviation of a centred normal distribution from which #' the actual offset values are drawn. #' @param noise a logical that indicate if noise should be added to the #' matrix. #' @param factors a logical that indicates if the NMF factors should be return #' together with the matrix. #' @param seed a single numeric value used to seed the random number generator #' before generating the matrix. #' The state of the RNG is restored on exit. #' #' @return a matrix, or a list if argument \code{factors=TRUE}. #' #' When \code{factors=FALSE}, the result is a matrix object, with the following attributes set: #' \describe{ #' \item{coefficients}{the true underlying coefficient matrix (i.e. \code{H});} #' \item{basis}{the true underlying coefficient matrix (i.e. \code{H});} #' \item{offset}{the offset if any;} #' \item{pData}{a \code{list} with one element \code{'Group'} that contains a factor #' that indicates the true groups of samples, i.e. the most contributing basis component for each sample;} #' \item{fData}{a \code{list} with one element \code{'Group'} that contains a factor #' that indicates the true groups of features, i.e. the basis component #' to which each feature contributes the most.} #' } #' #' Moreover, the result object is an \code{\link{ExposeAttribute}} object, which means that #' relevant attributes are accessible via \code{$}, e.g., \code{res$coefficients}. #' In particular, methods \code{\link{coef}} and \code{\link{basis}} will work as expected #' and return the true underlying coefficient and basis matrices respectively. #' #' @export #' @examples #' #' # generate a synthetic dataset with known classes: 50 features, 18 samples (5+5+8) #' n <- 50 #' counts <- c(5, 5, 8) #' #' # no noise #' V <- syntheticNMF(n, counts, noise=FALSE) #' \dontrun{aheatmap(V)} #' #' # with noise #' V <- syntheticNMF(n, counts) #' \dontrun{aheatmap(V)} #' syntheticNMF <- function(n, r, p, offset=NULL, noise=TRUE, factors=FALSE, seed=NULL){ # set seed if necessary if( !is.null(seed) ){ os <- RNGseed() on.exit( RNGseed(os) ) set.seed(seed) } # internal parameters mu.W <- 1; sd.W <- 1 if( isTRUE(noise) ){ noise <- list(mean=0, sd=1) }else if( isNumber(noise) ){ noise <- list(mean=0, sd=noise) }else if( is.list(noise) ){ stopifnot( length(noise) == 2L ) noise <- setNames(noise, c('mean', 'sd')) }else noise <- FALSE if( length(r) == 1 ){ g <- rmultinom(1, p, rep(1, r)) }else{ # elements of r are the number of samples in each class g <- r p <- sum(r) # total number of samples r <- length(r) # number of class } # generate H H <- matrix(0, r, p) tmp <- 0 for( i in 1:r ){ H[i,(tmp+1):(tmp+g[i])] <- 1 tmp <- tmp+g[i] } if( length(n) == 1 ){ b <- rmultinom(1, n, rep(1, r)) }else{ # elements of n are the number of genes in each class b <- n n <- sum(n) } # generate W W <- matrix(0, n, r) tmp <- 0 for( i in 1:r ){ W[(tmp+1):(tmp+b[i]),i] <- abs(rnorm(b[i], mu.W, sd.W)) tmp <- tmp + b[i] } # build the composite matrix res <- W %*% H # add the offset if necessary if( !is.null(offset) ){ if( length(offset) == 1L ) offset <- rnorm(n, mean=0, sd=offset) stopifnot(length(offset)==n) res <- res + offset } # add some noise if required if( !isFALSE(noise) ) res <- pmax(res + rmatrix(res, dist=rnorm, mean=noise$mean, sd=noise$sd), 0) # return the factors if required pData <- list(Group=factor(unlist(mapply(rep, 1:r, g, SIMPLIFY=FALSE)))) fData <- list(Group=factor(unlist(mapply(rep, 1:r, b, SIMPLIFY=FALSE)))) if( factors ) res <- list(res, W=W, H=H, offset=offset, pData=pData, fData=fData) # wrap results and expose relevant attributes ExposeAttribute(res, coefficients=H, basis=W, offset=offset , pData = pData, fData = fData , .VALUE=TRUE, .MODE='r') } NMF/R/colorcode.R0000644000176200001440000001333113620502674013175 0ustar liggesusers# Functions to define/extract compact colour specifications # # Author: "Renaud Gaujoux" # Creation: 19 Sep 2011 ############################################################################### #' @import RColorBrewer #' @importFrom colorspace sequential_hcl #' @import grDevices NULL col2hex <- function (cname) { colMat <- col2rgb(cname) rgb(red = colMat[1, ]/255, green = colMat[2, ]/255, blue = colMat[3,]/255) } #cc <- function(x, cval=80, lval=30){ # # sapply(x, function(co){ # if( is.integer(co) ) col2hex(if( co <= 8 ) co else colors()[co]) # else if( is.numeric(co) ) hcl(co, c=cval, l=lval) # else if( !grepl("^#") ) # else co # }) # #} #' Flags a Color Palette Specification for Reversion #' @keywords internal revPalette <- function(x){ attr(x, 'revPalette') <- TRUE x } #' Builds a Color Palette from Compact Color Specification #' @keywords internal ccPalette <- function(x, n=NA, verbose=FALSE){ if( length(x)==1 ){ # shortcut for single colors if( (is_NA(n) || n==1) && length(x) > 1L && all(grepl("^#", x)) ) return(x) sp <- ccSpec(x) x <- sp$palette if( is_NA(n) ) n <- sp$n a <- attributes(x) if( is.integer(x) ) # integer code between 1 and 8: R basic colour x <- c("#F1F1F1", col2hex(x)) else if( is.numeric(x) ) # numeric: hcl colour x <- rev(sequential_hcl(2, h = x, l = c(50, 95))) else if( is.character(x) ){ # Palette name: if( require.quiet('RColorBrewer') && x %in% rownames(brewer.pal.info) ){ if( verbose ) message("Load and generate ramp from RColorBrewer colour palette '", x, "'") x <- brewer.pal(brewer.pal.info[x, 'maxcolors'], x) }else{ cpal <- c('RdYlBu2', 'rainbow', 'heat', 'topo', 'terrain', 'cm', 'gray', 'grey') i <- pmatch(x, cpal) if( is.na(i) && (x %in% colours() || grepl("^#[0-9a-fA-F]+$", x)) ){ x <- c("#F1F1F1", x) }else{ if( is.na(i) ){ stop("Invalid palette name '", x, "': should be an RColorBrewer palette or one of " , paste("'", cpal ,"'", sep='', collapse=', ') , ".\n Available RColorBrewer palettes: ", str_out(rownames(brewer.pal.info), Inf), '.') } x <- cpal[i] # use default value of 10 for n if not specified np <- if( is_NA(n) ) 10 else n x <- switch(x , RdYlBu2 = c("#D73027", "#FC8D59", "#FEE090", "#FFFFBF", "#E0F3F8", "#91BFDB", "#4575B4") , rainbow = rainbow(np) , gray = rev(gray.colors(np)) , grey = rev(grey.colors(np)) , heat = heat.colors(np) , topo = topo.colors(np) , terrain = terrain.colors(np) , cm = cm.colors(np) , stop("Unknown colour palette name: '", x, "'") ) } } } else stop("Invalid colour palette specification :", x) attributes(x) <- a } # revert the palette if requested if( !is.null(attr(x, 'revPalette')) ){ x <- rev(x) attr(x, 'revPalette') <- NULL } # limit to the requested length if( !is_NA(n) ) x <- x[1:n] # return converted palette x } #' Generate Break Intervals from Numeric Variables #' #' Implementation is borrowed from the R core function \code{\link{cut.default}}. #' #' @keywords internal ccBreaks <- function(x, breaks){ if (!is.numeric(x)) stop("'x' must be numeric") if (length(breaks) == 1L) { if (is.na(breaks) | breaks < 2L) stop("Invalid number of intervals: should be >= 2") nb <- as.integer(breaks + 1) dx <- diff(rx <- range(x, na.rm = TRUE)) if (dx == 0) dx <- abs(rx[1L]) breaks <- seq.int(rx[1L] - dx/1000, rx[2L] + dx/1000, length.out = nb) } else nb <- length(breaks <- sort.int(as.double(breaks))) if (anyDuplicated(breaks)) stop("'breaks' are not unique") breaks } #' Extract Colour Palette Specification #' #' @param a character string that specify a colour palette. #' @return a list with elements: palette, n and rev #' #' @keywords internal ccSpec <- function(x){ n <- NA rv <- FALSE if( length(x) == 1 ){ if( is.character(x) ){ # flag as reversed if it starts with a '-' if( grepl('^-', x) ){ x <- substr(x, 2, nchar(x)) rv <- TRUE } # extract length for string sm <- str_match(x, "([^:]+):([0-9]+).*")[1,] if( !is_NA(sm[1]) ){ n <- as.integer(sm[3]) x <- sm[2] } # convert to a colour code if possible # use maximum colour number of brewer sets if( is_NA(n) && isString(x) && require.quiet('RColorBrewer') && x %in% rownames(brewer.pal.info) ){ n <- brewer.pal.info[x,'maxcolors'] }else if( grepl("^[1-8]$", x) ){# integer code between 1 and 8: R basic colour x <- palette()[as.integer(x)] }else if( grepl("^[0-9.]+$", x) ) # numeric: hcl colour x <- as.numeric(x) }else if( is.numeric(x) ){ if( x < 0 ){ x <- -x rv<- TRUE } # single integer specification: use R default colours if( isInteger(x) ){ if( x <= 8 ) x <- palette()[x] else x <- colours()[x] } } } if( rv ) x <- revPalette(x) res <- list(palette=x, n=n, rev=rv) # print(res) res } #' Builds a Color Ramp from Compact Color Specification #' #' @keywords internal ccRamp <- function(x, n=NA, ...){ #breaks, data, ...){ # generate random color specification if necessary if( missing(x) ) x <- round(runif(1) * 360) # extract specifications sp <- ccSpec(x) x <- sp$palette if( missing(n) ){ n <- sp$n if( is_NA(n) ) n <- 50 } # create a palette from specification x x <- ccPalette(x, ...) # # compute breaks # breaks <- # if( !missing(breaks) ){ # breaks <- ccBreaks(x, breaks) # if( missing(n) ) # n <- length(breaks) # breaks # } # else if( !missing(data) ){ # if( missing(n) ) # n <- length(x) # ccBreaks(data, n) # } if( is_NA(n) ) n <- length(x) # return ramp from palette colorRampPalette(x)(n+1) } NMF/R/algorithms-lnmf.R0000644000176200001440000000506213620502674014331 0ustar liggesusers# Algorithm for Nonnegative Matrix Factorization: Local NMF (LNMF) # # @author Renaud Gaujoux # @created 21 Jul 2009 #' @include registry-algorithms.R NULL ###% \item{\sQuote{lnmf}}{ Local Nonnegative Matrix Factorization. Based on a ###% regularized Kullback-Leibler divergence, it uses a modified version of ###% Lee and Seung's multiplicative updates. ###% See \emph{Li et al. (2001)}.} ###% Algorithm for Nonnegative Matrix Factorization: Local NMF (LNMF). ###% ###% The local NMF algorithm is minimizes use the following Kullback-Leibler divergence based objective function: ###% $$ ###% \sum_{i=1}^m\sum_{j=1}^n\left(X_{ij} \log\frac{X_{ij}}{(WH)_{ij}} - X_{ij} + (WH)_{ij} + \alpha U_{ij}\right) - \beta \sum_i V_{ij}, ###% $$ ###% where $\alpha, \beta > 0$ are some constants, $U = W^TW$ and $V = HH^T$. ###% ###% TODO: add explaination for each terms (see Wild 2002) ###% ###% @references Learning spatially localized, parts-based representation ###% , S.Z. Li, X.W. Hou, and H.J. Zhang. ###% , In Proceedings of IEEE International Conference on Computer Vision and Pattern Recognition ###% , December 2001 nmf_update_R.lnmf <- function(i, v, data, ...){ # retrieve each factor w <- .basis(data); h <- .coef(data); # update H h <- sqrt( h * crossprod(w, v / (w %*% h)) ) # update W using the standard divergence based update w <- R_std.divergence.update.w(v, w, h, w %*% h) # scale columns of W w <- sweep(w, 2L, colSums(w), "/", check.margin=FALSE) #every 10 iterations: adjust small values to avoid underflow if( i %% 10 == 0 ){ #precision threshold for numerical stability eps <- .Machine$double.eps h[h this ensures the methods use the same stochastic environment orng <- RNGseed() if( k < n ) on.exit( RNGseed(orng), add = TRUE) # look for method-specific arguments i.param <- 0L if( length(.parameters) ){ i.param <- charmatch(names(.parameters), methname) if( !length(i.param <- seq_along(.parameters)[!is.na(i.param)]) ) i.param <- 0L else if( length(i.param) > 1L ){ stop("Method name '", methname, "' matches multiple method-specific parameters " , "[", str_out(names(.parameters)[i.param], Inf), "]") } } #o <- capture.output( if( !i.param ){ res <- try( nmf(x, rank, meth, ...) , silent=TRUE) }else{ if( is.null(ARGS) ) ARGS <<- list(x, rank, ...) .used.parameters <<- c(.used.parameters, names(.parameters)[i.param]) res <- try( do.call(nmf, c(ARGS, method = meth, .parameters[[i.param]])) , silent=TRUE) } #) if( is(res, 'try-error') ) cat("ERROR\n") else cat("OK\n") return(res) } , ...) }) # filter out bad results ok <- sapply(res, function(x){ if( is(x, 'NMF.rank') ) all(sapply(x$fit, isNMFfit)) else isNMFfit(x) }) if( any(!ok) ){ # throw warning if some methods raised an error err <- lapply(which(!ok), function(i){ paste("'", method[[i]],"': ", res[[i]], sep='')}) warning("NMF::nmf - Incomplete results due to ", sum(!ok), " errors: \n- ", paste(err, collapse="- "), call.=FALSE) } res <- res[ok] # TODO error if ok is empty # not-used parameters if( length(.used.parameters) != length(.parameters) ){ warning("NMF::nmf - Did not use methods-specific parameters ", str_out(setdiff(names(.parameters), .used.parameters), Inf)) } # add names to the result list names(res) <- sapply(res, function(x){ if( is(x, 'NMF.rank') ) x <- x$fit[[1]] algorithm(x) }) # return list as is if surveying multiple ranks if( length(rank) > 1 ) return(res) # wrap the result in a NMFList object # DO NOT WRAP anymore here: NMFfitX objects are used only for results of multiple runs (single method) # the user can still join on the result if he wants to #res <- join(res, runtime=t) res <- new('NMFList', res, runtime=t) # return result return(res) } ) #' Fits an NMF model on \code{x} using an algorithm registered with access key #' \code{method}. #' #' Argument \code{method} is partially match against the access keys of all #' registered algorithms (case insensitive). #' Available algorithms are listed in section \emph{Algorithms} below or the #' introduction vignette. #' A vector of their names may be retrieved via \code{nmfAlgorithm()}. #' #' @section Algorithms: #' All algorithms are accessible by their respective access key as listed below. #' The following algorithms are available: #' \describe{ #' #' \item{\sQuote{brunet}}{ Standard NMF, based on the Kullback-Leibler divergence, #' from \cite{Brunet2004}. #' It uses simple multiplicative updates from \cite{Lee2001}, enhanced to avoid #' numerical underflow. #' #' Default stopping criterion: invariance of the connectivity matrix #' (see \code{\link{nmf.stop.connectivity}}). #' } #' #' \item{\sQuote{lee}}{ Standard NMF based on the Euclidean distance from \cite{Lee2001}. #' It uses simple multiplicative updates. #' #' Default stopping criterion: invariance of the connectivity matrix #' (see \code{\link{nmf.stop.connectivity}}). #' } #' #' \item{ls-nmf}{ Least-Square NMF from \cite{Wang2006}. #' It uses modified versions of Lee and Seung's multiplicative updates for the #' Euclidean distance, which incorporates weights on each entry of the target #' matrix, e.g. to reflect measurement uncertainty. #' #' Default stopping criterion: stationarity of the objective function #' (see \code{\link{nmf.stop.stationary}}). #' } #' #' \item{\sQuote{nsNMF}}{ Nonsmooth NMF from \cite{Pascual-Montano2006}. #' It uses a modified version of Lee and Seung's multiplicative updates for the #' Kullback-Leibler divergence \cite{Lee2001}, to fit a extension of the standard #' NMF model, that includes an intermediate smoothing matrix, meant meant to produce #' sparser factors. #' #' Default stopping criterion: invariance of the connectivity matrix #' (see \code{\link{nmf.stop.connectivity}}). #' } #' #' \item{\sQuote{offset}}{ NMF with offset from \cite{Badea2008}. #' It uses a modified version of Lee and Seung's multiplicative #' updates for Euclidean distance \cite{Lee2001}, to fit an NMF model that includes #' an intercept, meant to capture a common baseline and shared patterns, in #' order to produce cleaner basis components. #' #' Default stopping criterion: invariance of the connectivity matrix #' (see \code{\link{nmf.stop.connectivity}}). #' } #' #' \item{\sQuote{pe-nmf}}{ Pattern-Expression NMF from \emph{Zhang2008}. #' It uses multiplicative updates to minimize an objective function based on the #' Euclidean distance, that is regularized for effective expression of patterns #' with basis vectors. #' #' Default stopping criterion: stationarity of the objective function #' (see \code{\link{nmf.stop.stationary}}). #' } #' #' \item{\sQuote{snmf/r}, \sQuote{snmf/l}}{ Alternating Least Square (ALS) approach #' from \cite{KimH2007}. #' It applies the nonnegative least-squares algorithm from \cite{VanBenthem2004} #' (i.e. fast combinatorial nonnegative least-squares for multiple right-hand), #' to estimate the basis and coefficient matrices alternatively #' (see \code{\link{fcnnls}}). #' It minimises an Euclidean-based objective function, that is regularized to #' favour sparse basis matrices (for \sQuote{snmf/l}) or sparse coefficient matrices #' (for \sQuote{snmf/r}). #' #' Stopping criterion: built-in within the internal workhorse function \code{nmf_snmf}, #' based on the KKT optimality conditions. #' } #' #' } #' #' @section Seeding methods: #' The purpose of seeding methods is to compute initial values for the factor #' matrices in a given NMF model. #' This initial guess will be used as a starting point by the chosen NMF algorithm. #' #' The seeding method to use in combination with the algorithm can be passed #' to interface \code{nmf} through argument \code{seed}. #' The seeding seeding methods available in registry are listed by the function #' \code{\link{nmfSeed}} (see list therein). #' #' Detailed examples of how to specify the seeding method and its parameters can #' be found in the \emph{Examples} section of this man page and in the package's #' vignette. #' #' @seealso \code{\link{nmfAlgorithm}} #' #' @demo #' #' # specify algorithm by its name #' res <- nmf(x, 3, 'nsNMF', seed=123) # nonsmooth NMF #' # names are partially matched so this also works #' identical(res, nmf(x, 3, 'ns', seed=123)) #' #' res <- nmf(x, 3, 'offset') # NMF with offset #' #' setMethod('nmf', signature(x='matrix', rank='numeric', method='character'), function(x, rank, method, ...) { # if there is more than one methods then treat the vector as a list if( length(method) > 1 ){ return( nmf(x, rank, as.list(method), ...) ) } # create the NMFStrategy from its name strategy <- nmfAlgorithm(method) # apply nmf using the retrieved strategy nmf(x, rank, method=strategy, ...) } ) #' Fits an NMF model on \code{x} using a custom algorithm defined the function #' \code{method}. #' #' The supplied function must have signature \code{(x=matrix, start=NMF, ...)} #' and return an object that inherits from class \code{\linkS4class{NMF}}. #' It will be called internally by the workhorse \code{nmf} method, with an NMF model #' to be used as a starting point passed in its argument \code{start}. #' #' Extra arguments in \code{...} are passed to \code{method} from the top #' \code{nmf} call. #' Extra arguments that have no default value in the definition of the function #' \code{method} are required to run the algorithm (e.g. see argument \code{alpha} #' of \code{myfun} in the examples). #' #' If the algorithm requires a specific type of NMF model, this can be specified #' in argument \code{model} that is handled as in the workhorse \code{nmf} #' method (see description for this argument). #' #' @param name name associated with the NMF algorithm implemented by the function #' \code{method} [only used when \code{method} is a function]. #' @param objective specification of the objective function associated with the #' algorithm implemented by the function \code{method} #' [only used when \code{method} is a function]. #' #' It may be either \code{'euclidean'} or \code{'KL'} for specifying the euclidean #' distance (Frobenius norm) or the Kullback-Leibler divergence respectively, #' or a function with signature \code{(x="NMF", y="matrix", ...)} that computes #' the objective value for an NMF model \code{x} on a target matrix \code{y}, #' i.e. the residuals between the target matrix and its NMF estimate. #' Any extra argument may be specified, e.g. \code{function(x, y, alpha, beta=2, ...)}. #' #' @param mixed a logical that indicates if the algorithm implemented by the function #' \code{method} support mixed-sign target matrices, i.e. that may contain negative #' values [only used when \code{method} is a function]. #' #' @demo #' #' # run a custom algorithm defined as a standard function #' myfun <- function(x, start, alpha){ #' # update starting point #' # ... #' basis(start) <- 3 * basis(start) #' # return updated point #' start #' } #' #' res <- nmf(x, 2, myfun, alpha=3) #' algorithm(res) #' # error: alpha missing #' try( nmf(x, 2, myfun) ) #' #' # possibly the algorithm fits a non-standard NMF model, e.g. NMFns model #' res <- nmf(x, 2, myfun, alpha=3, model='NMFns') #' modelname(res) #' setMethod('nmf', signature(x='matrix', rank='numeric', method='function'), function(x, rank, method, seed, model='NMFstd', ..., name, objective='euclidean', mixed=FALSE){ model_was_a_list <- is.list(model) if( is.character(model) ) model <- list(model=model) if( !is.list(model) ){ stop("nmf - Invalid argument `model`: must be NULL or a named list of initial values for slots in an NMF model.") } # arguments passed to the call to NMFStrategyFunction strat <- list('NMFStrategyFunction' , algorithm = method , objective = objective , mixed = mixed[1] ) ## Determine type of NMF model associated with the NMFStrategy # All elements of `model` (except the model class) will be passed to # argument `model` of the workhorse `nmf` method, which will use them # to create the NMF model in a call to `nmfModel` if( length(model) > 0L ){ if( !is.null(model$model) ){ strat$model <- model$model model$model <- NULL }else if( isNMFclass(model[[1]]) ){ strat$model <- model[[1]] # use the remaining elements to instanciate the NMF model model <- model[-1] } # all elements must be named if( !hasNames(model, all=TRUE) ){ stop("NMF::nmf - Invalid argument `model`: all elements must be named, except the first one which must then be an NMF model class name") } } ## # if name is missing: generate a temporary unique name if( missing(name) ) name <- basename(tempfile("nmf_")) # check that the name is not a registered name if( existsNMFMethod(name) ) stop("Invalid name for custom NMF algorithm: '",name,"' is already a registered NMF algorithm") strat$name <- name # create NMFStrategy strategy <- do.call('new', strat) # full validation of the strategy validObject(strategy, complete=TRUE) if( missing(seed) ) seed <- NULL if( !model_was_a_list && length(model) == 0L ) model <- NULL # call method 'nmf' with the new object nmf(x, rank, strategy, seed=seed, model=model, ...) } ) #' Fits an NMF model using the NMF model \code{rank} to seed the computation, #' i.e. as a starting point. #' #' This method is provided for convenience as a shortcut for #' \code{nmf(x, nbasis(object), method, seed=object, ...)} #' It discards any value passed in argument \code{seed} and uses the NMF model passed #' in \code{rank} instead. #' It throws a warning if argument \code{seed} not missing. #' #' If \code{method} is missing, this method will call the method #' \code{nmf,matrix,numeric,NULL}, which will infer an algorithm suitable for fitting an #' NMF model of the class of \code{rank}. #' #' @demo #' #' # assume a known NMF model compatible with the matrix `x` #' y <- rnmf(3, x) #' # fits an NMF model (with default method) on some data using y as a starting point #' res <- nmf(x, y) #' # the fit can be reproduced using the same starting point #' nmf.equal(nmf(x, y), res) #' setMethod('nmf', signature(x='matrix', rank='NMF', method='ANY'), function(x, rank, method, seed, ...){ if( !missing(seed) ){ if( isNumber(seed) ){ set.seed(seed) }else if( !is.null(seed) ){ warning("NMF::nmf - Discarding value of argument `seed`: directly using NMF model supplied in `rank` instead.\n" , " If seeding is necessary, please use argument `model` pass initial model slots, which will be filled by the seeding method.") } # # pass the model via a one-off global variable # .nmf_InitModel(rank) } # replace missing method by NULL for correct dispatch if( missing(method) ) method <- NULL nmf(x, nbasis(rank), method, seed=rank, ...) } ) .nmf_InitModel <- oneoffVariable() #' Fits an NMF model using the NMF model supplied in \code{seed}, to seed the computation, #' i.e. as a starting point. #' #' This method is provided for completeness and is equivalent to #' \code{nmf(x, seed, method, ...)}. #' setMethod('nmf', signature(x='matrix', rank='NULL', method='ANY'), function(x, rank, method, seed, ...){ if( missing(seed) || !is.nmf(seed) ) stop("NMF::nmf - Argument `seed` must be an NMF model when argument `rank` is missing.") # replace missing method by NULL for correct dispatch if( missing(method) ) method <- NULL nmf(x, nbasis(seed), method, seed=seed, ...) } ) #' Method defined to ensure the correct dispatch to workhorse methods in case #' of argument \code{rank} is missing. setMethod('nmf', signature(x='matrix', rank='missing', method='ANY'), function(x, rank, method, ...){ # replace missing method by NULL for correct dispatch if( missing(method) ) method <- NULL nmf(x, NULL, method, ...) } ) #' Method defined to ensure the correct dispatch to workhorse methods in case #' of argument \code{method} is missing. #' #' @demo #' # missing method: use default algorithm #' res <- nmf(x, 3) #' setMethod('nmf', signature(x='matrix', rank='numeric', method='missing'), function(x, rank, method, ...){ nmf(x, rank, NULL, ...) } ) #' Fits an NMF model partially seeding the computation with a given matrix passed #' in \code{rank}. #' #' The matrix \code{rank} is used either as initial value for the basis or mixture #' coefficient matrix, depending on its dimension. #' #' Currently, such partial NMF model is directly used as a seed, meaning that #' the remaining part is left uninitialised, which is not accepted by all NMF algorithm. #' This should change in the future, where the missing part of the model will be #' drawn from some random distribution. #' #' Amongst built-in algorithms, only \sQuote{snmf/l} and \sQuote{snmf/r} support #' partial seeds, with only the coefficient or basis matrix initialised #' respectively. #' #' @demo #' #' # Fit a 3-rank model providing an initial value for the basis matrix #' nmf(x, rmatrix(nrow(x), 3), 'snmf/r') #' #' # Fit a 3-rank model providing an initial value for the mixture coefficient matrix #' nmf(x, rmatrix(3, ncol(x)), 'snmf/l') #' setMethod('nmf', signature(x='matrix', rank='matrix', method='ANY'), function(x, rank, method, seed, model=list(), ...) { if( is.character(model) ) model <- list(model=model) if( !is.list(model) ) stop("nmf - Invalid argument `model`: must be NULL or a named list of initial values for slots in an NMF object.") if( !hasNames(model, all=TRUE) ) stop("nmf - Invalid argument `model`: all elements must be named") # remove rank specification if necessary if( !is.null(model$rank) ){ warning("nmf - Discarding rank specification in argument `model`: use value inferred from matrix supplied in argument `rank`") model$rank <- NULL } # check compatibility of dimensions newseed <- if( nrow(rank) == nrow(x) ){ # rank is the initial value for the basis vectors if( length(model)==0L ) nmfModel(W=rank) else{ model$W <- rank do.call('nmfModel', model) } }else if( ncol(rank) == ncol(x) ){ # rank is the initial value for the mixture coefficients if( length(model)==0L ) nmfModel(H=rank) else{ model$H <- rank do.call('nmfModel', model) } }else stop("nmf - Invalid argument `rank`: matrix dimensions [",str_out(dim(x),sep=' x '),"]" , " are incompatible with the target matrix [", str_out(dim(x),sep=' x '),"].\n" , " When `rank` is a matrix it must have the same number of rows or columns as the target matrix `x`.") # replace missing values by NULL values for correct dispatch if( missing(method) ) method <- NULL if( missing(seed) ) seed <- NULL #nmf(x, nbasis(newseed), method, seed=seed, model=newseed, ...) nmf(x, newseed, method, seed=seed, ...) } ) #' Shortcut for \code{nmf(x, as.matrix(rank), method, ...)}. setMethod('nmf', signature(x='matrix', rank='data.frame', method='ANY'), function(x, rank, method, ...){ # replace missing values by NULL values for correct dispatch if( missing(method) ) method <- NULL nmf(x, as.matrix(rank), method, ...) } ) #' This method implements the interface for fitting formula-based NMF models. #' See \code{\link{nmfModel}}. #' #' Argument \code{rank} target matrix or formula environment. #' If not missing, \code{model} must be a \code{list}, a \code{data.frame} or #' an \code{environment} in which formula variables are searched for. #' setMethod('nmf', signature(x='formula', rank='ANY', method='ANY'), function(x, rank, method, ..., model=NULL){ # replace missing values by NULL values for correct dispatch if( missing(method) ) method <- NULL if( missing(rank) ) rank <- NULL # if multiple numeric rank: use nmfRestimateRank if( is.vector(rank) && is.numeric(rank) ){ if( length(rank) > 1L ){ return( nmfEstimateRank(x, rank, method, ..., model=model) ) } } # build formula based model model <- nmfModel(x, rank, data=model) nmf(attr(model, 'target'), nbasis(model), method, ..., model=model) } ) .as.numeric <- function(x){ suppressWarnings( as.numeric(x) ) } .translate.string <- function(string, dict){ res <- list() dict <- as.list(dict) if( nchar(string) == 0 ) return(res) opt.val <- TRUE last.key <- NULL buffer <- '' lapply(strsplit(string, '')[[1]], function(c){ if( c=='-' ) opt.val <<- FALSE else if( c=='+' ) opt.val <<- TRUE else if( opt.val && !is.na(.as.numeric(c)) ) buffer <<- paste(buffer, c, sep='') else if( !is.null(dict[[c]]) ){ # flush the buffer into the last key if necessary if( nchar(buffer) > 0 && !is.null(last.key) && !is.na(buffer <- .as.numeric(buffer)) ){ res[[dict[[last.key]]]] <<- buffer buffer <<- '' } res[[dict[[c]]]] <<- opt.val last.key <<- c } } ) # flush the buffer into the last key if( nchar(buffer) > 0 && !is.null(last.key) && !is.na(buffer <- .as.numeric(buffer)) ) res[[dict[[last.key]]]] <- buffer # return result return(res) } #' Error Checks in NMF Runs #' #' Auxiliary function for internal error checks in nmf results. #' #' @param object a list of lists #' @param element name of an element of the inner lists #' #' @keywords internal checkErrors <- function(object, element=NULL){ # extract error messages errors <- if( is.null(element) ){ lapply(seq_along(object), function(i){ x <- object[[i]] if( is(x, 'error') ) c(i, x) else NA }) }else{ lapply(seq_along(object), function(i){ x <- object[[i]][[element, exact=TRUE]] if( is(x, 'error') ) c(i, x) else NA }) } errors <- errors[!is.na(errors)] nerrors <- length(errors) res <- list(n = nerrors) # format messages if( nerrors ){ ierrors <- sapply(errors, '[[', 1L) msg <- sapply(errors, '[[', 2L) ierrors_unique <- ierrors[!duplicated(msg)] res$msg <- str_c(" - ", str_c("run #", ierrors_unique, ': ', msg[ierrors_unique], collapse="\n - ")) } # return error data res } ###% Performs NMF on a matrix using a given NMF method. ###% ###% This method is the entry point for NMF. It is eventually called by any definition of the \code{nmf} function. #' @param seed specification of the starting point or seeding method, which will #' compute a starting point, usually using data from the target matrix in order to #' provide a good guess. #' #' The seeding method may be specified in the following way: #' #' \describe{ #' #' \item{a \code{character} string:}{ giving the name of a \emph{registered} #' seeding method. The corresponding method will be called to compute #' the starting point. #' #' Available methods can be listed via \code{nmfSeed()}. #' See its dedicated documentation for details on each available registered methods #' (\code{\link{nmfSeed}}). #' } #' #' \item{a \code{list}:}{ giving the name of a \emph{registered} #' seeding method and, optionally, extra parameters to pass to it.} #' #' \item{a single \code{numeric}:}{ that is used to seed the random number #' generator, before generating a random starting point. #' #' Note that when performing multiple runs, the L'Ecuyer's RNG is used in order to #' produce a sequence of random streams, that is used in way that ensures #' that parallel computation are fully reproducible. #' } #' #' \item{an object that inherits from \code{\linkS4class{NMF}}:}{ it should #' contain the data of an initialised NMF model, i.e. it must contain valid #' basis and mixture coefficient matrices, directly usable by the algorithm's #' workhorse function.} #' #' \item{a \code{function}:}{ that computes the starting point. It must have #' signature \code{(object="NMF", target="matrix", ...)} and return an object that #' inherits from class \code{NMF}. #' It is recommended to use argument \code{object} as a template for the returned object, #' by only updating the basis and coefficient matrices, using \code{\link{basis<-}} and #' \code{\link{coef<-}} respectively. #' } #' #' } #' #' @param rng rng specification for the run(s). #' This argument should be used to set the the RNG seed, while still specifying the seeding #' method argument \var{seed}. #' #' @param model specification of the type of NMF model to use. #' #' It is used to instantiate the object that inherits from class \code{\linkS4class{NMF}}, #' that will be passed to the seeding method. #' The following values are supported: #' \itemize{ #' #' \item \code{NULL}, the default model associated to the NMF algorithm is #' instantiated and \code{...} is looked-up for arguments with names that #' correspond to slots in the model class, which are passed to the function #' \code{\link{nmfModel}} to instantiate the model. #' Arguments in \code{...} that do not correspond to slots are passed to the #' algorithm. #' #' \item a single \code{character} string, that is the name of the NMF model #' class to be instantiate. #' In this case, arguments in \code{...} are handled in the same way as #' when \code{model} is \code{NULL}. #' #' \item a \code{list} that contains named values that are passed to the #' function \code{\link{nmfModel}} to instantiate the model. #' In this case, \code{...} is not looked-up at all, and passed entirely to #' the algorithm. #' This means that all necessary model parameters must be specified in #' \code{model}. #' #' } #' #' \strong{Argument/slot conflicts:} #' In the case a parameter of the algorithm has the same name as a model slot, #' then \code{model} MUST be a list -- possibly empty --, if one wants this #' parameter to be effectively passed to the algorithm. #' #' If a variable appears in both arguments \code{model} and \code{\dots}, #' the former will be used to initialise the NMF model, the latter will be #' passed to the NMF algorithm. #' See code examples for an illustration of this situation. #' #' @param nrun number of runs to perform. #' It specifies the number of runs to perform. #' By default only one run is performed, except if \code{rank} is a numeric vector #' with more than one element, in which case a default of 30 runs per value of the #' rank are performed, allowing the computation of a consensus matrix that is used #' in selecting the appropriate rank (see \code{\link{consensus}}). #' #' When using a random seeding method, multiple runs are generally required to #' achieve stability and avoid \emph{bad} local minima. #' #' @param .options this argument is used to set runtime options. #' #' It can be a \code{list} containing named options with their values, or, in #' the case only boolean/integer options need to be set, a character string #' that specifies which options are turned on/off or their value, in a unix-like #' command line argument way. #' #' The string must be composed of characters that correspond to a given option #' (see mapping below), and modifiers '+' and '-' that toggle options on and off respectively. #' E.g. \code{.options='tv'} will toggle on options \code{track} and \code{verbose}, #' while \code{.options='t-v'} will toggle on option \code{track} and toggle off #' option \code{verbose}. #' #' Modifiers '+' and '-' apply to all option character found after them: #' \code{t-vp+k} means \code{track=TRUE}, \code{verbose=parallel=FALSE}, #' and \code{keep.all=TRUE}. #' The default behaviour is to assume that \code{.options} starts with a '+'. #' #' for options that accept integer values, the value may be appended to the #' option's character e.g. \code{'p4'} for asking for 4 processors or \code{'v3'} #' for showing verbosity message up to level 3. #' #' The following options are available (the characters after \dQuote{-} are those #' to use to encode \code{.options} as a string): #' \describe{ #' #' \item{debug - d}{ Toggle debug mode (default: \code{FALSE}). #' Like option \code{verbose} but with more information displayed.} #' #' \item{keep.all - k}{ used when performing multiple runs (\code{nrun}>1): if #' \code{TRUE}, all factorizations are saved and returned (default: \code{FALSE}). #' Otherwise only the factorization achieving the minimum residuals is returned.} #' #' \item{parallel - p}{ this option is useful on multicore *nix or Mac machine #' only, when performing multiple runs (\code{nrun} > 1) (default: \code{TRUE}). #' If \code{TRUE}, the runs are performed using the parallel foreach backend #' defined in argument \code{.pbackend}. #' If this is set to \code{'mc'} or \code{'par'} then \code{nmf} tries to #' perform the runs using multiple cores with package #' \code{link[doParallel]{doParallel}} -- which therefore needs to be installed. #' #' If equal to an integer, then \code{nmf} tries to perform the computation on #' the specified number of processors. #' When passing options as a string the number is appended to the option's character #' e.g. \code{'p4'} for asking for 4 processors. #' #' If \code{FALSE}, then the computation is performed sequentially using the base #' function \code{\link{sapply}}. #' #' Unlike option 'P' (capital 'P'), if the computation cannot be performed in #' parallel, then it will still be carried on sequentially. #' #' \strong{IMPORTANT NOTE FOR MAC OS X USERS:} The parallel computation is #' based on the \code{doMC} and \code{multicore} packages, so the same care #' should be taken as stated in the vignette of \code{doMC}: \emph{\dQuote{it #' is not safe to use doMC from R.app on Mac OS X. Instead, you should use doMC #' from a terminal session, starting R from the command line.}} } #' #' \item{parallel.required - P}{ Same as \code{p}, but an error is thrown if #' the computation cannot be performed in parallel or with the specified number #' of processors.} #' #' \item{shared.memory - m}{ toggle usage of shared memory (requires the #' \pkg{synchronicity} package). #' Default is as defined by \code{nmf.getOption('shared.memory')}.} #' #' \item{restore.seed - r}{ deprecated option since version 0.5.99. #' Will throw a warning if used.} #' #' \item{simplifyCB - S}{ toggle simplification of the callback results. #' Default is \code{TRUE}} #' #' \item{track - t}{ enables error tracking (default: FALSE). #' If \code{TRUE}, the returned object's slot \code{residuals} contains the #' trajectory of the objective values, which can be retrieved via #' \code{residuals(res, track=TRUE)} #' This tracking functionality is available for all built-in algorithms. #' } #' #' \item{verbose - v}{ Toggle verbosity (default: \code{FALSE}). #' If \code{TRUE}, messages about the configuration and the state of the #' current run(s) are displayed. #' The level of verbosity may be specified with an integer value, the greater #' the level the more messages are displayed. #' Value \code{FALSE} means no messages are displayed, while value \code{TRUE} #' is equivalent to verbosity level 1. #' } #' #' } #' #' @param .pbackend specification of the \code{\link{foreach}} parallel backend #' to register and/or use when running in parallel mode. #' See options \code{p} and \code{P} in argument \code{.options} for how to #' enable this mode. #' Note that any backend that is internally registered is cleaned-up on exit, #' so that the calling foreach environment should not be affected by a call to #' \code{nmf} -- except when \code{.pbackend=NULL}. #' #' Currently it accepts the following values: #' \describe{ #' #' \item{\sQuote{par}}{ use the backend(s) defined by the package #' \code{\link{doParallel}};} #' \item{a numeric value}{ use the specified number of cores with \code{doParallel} #' backend;} #' \item{\sQuote{seq}}{ use the foreach sequential backend \code{doSEQ};} #' \item{\code{NULL}}{ use currently registered backend;} #' \item{\code{NA}}{ do not compute using a foreach loop -- and therefore not in #' parallel -- but rather use a call to standard \code{\link{sapply}}. #' This is useful for when developing/debugging NMF algorithms, as foreach loop #' handling may sometime get in the way. #' #' Note that this is equivalent to using \code{.options='-p'} or \code{.options='p0'}, #' but takes precedence over any option specified in \code{.options}: #' e.g. \code{nmf(..., .options='P10', .pbackend=NA)} performs all runs sequentially #' using \code{sapply}. #' Use \code{nmf.options(pbackend=NA)} to completely disable foreach/parallel computations #' for all subsequent \code{nmf} calls.} #' #' \item{\sQuote{mc}}{ identical to \sQuote{par} and defined to ensure backward #' compatibility.} #' } #' #' @param .callback Used when option \code{keep.all=FALSE} (default). It #' allows to pass a callback function that is called after each run when #' performing multiple runs (i.e. with \code{nrun>1}). #' This is useful for example if one is also interested in saving summary #' measures or process the result of each NMF fit before it gets discarded. #' After each run, the callback function is called with two arguments, the #' \code{\linkS4class{NMFfit}} object that as just been fitted and the run #' number: \code{.callback(res, i)}. #' For convenience, a function that takes only one argument or has #' signature \code{(x, ...)} can still be passed in \code{.callback}. #' It is wrapped internally into a dummy function with two arguments, #' only the first of which is passed to the actual callback function (see example #' with \code{summary}). #' #' The call is wrapped into a tryCatch so that callback errors do not stop the #' whole computation (see below). #' #' The results of the different calls to the callback function are stored in a #' miscellaneous slot accessible using the method \code{$} for \code{NMFfit} #' objects: \code{res$.callback}. #' By default \code{nmf} tries to simplify the list of callback result using #' \code{sapply}, unless option \code{'simplifyCB'} is \code{FASE}. #' #' If no error occurs \code{res$.callback} contains the list of values that #' resulted from the calling the callback function --, ordered as the fits. #' If any error occurs in one of the callback calls, then the whole computation is #' \strong{not} stopped, but the error message is stored in \code{res$.callback}, #' in place of the result. #' #' See the examples for sample code. #' #' @return The returned value depends on the run mode: #' #' \item{Single run:}{An object of class \code{\linkS4class{NMFfit}}.} #' #' \item{Multiple runs, single method:}{When \code{nrun > 1} and \code{method} #' is not \code{list}, this method returns an object of class \code{\linkS4class{NMFfitX}}.} #' #' \item{Multiple runs, multiple methods:}{When \code{nrun > 1} and \code{method} #' is a \code{list}, this method returns an object of class \code{\linkS4class{NMFList}}.} #' #' @demo #' #' # default fit #' res <- nmf(x, 2) #' summary(res, class=groups) #' #' # run default algorithm multiple times (only keep the best fit) #' res <- nmf(x, 3, nrun=10) #' res #' summary(res, class=groups) #' #' # run default algorithm multiple times keeping all the fits #' res <- nmf(x, 3, nrun=10, .options='k') #' res #' summary(res, class=groups) #' #' ## Note: one could have equivalently done #' # res <- nmf(V, 3, nrun=10, .options=list(keep.all=TRUE)) #' #' # use a method that fit different model #' res <- nmf(x, 2, 'nsNMF') #' fit(res) #' #' # pass parameter theta to the model via `...` #' res <- nmf(x, 2, 'nsNMF', theta=0.2) #' fit(res) #' #' ## handling arguments in `...` and model parameters #' myfun <- function(x, start, theta=100){ cat("theta in myfun=", theta, "\n\n"); start } #' # no conflict: default theta #' fit( nmf(x, 2, myfun) ) #' # no conlfict: theta is passed to the algorithm #' fit( nmf(x, 2, myfun, theta=1) ) #' # conflict: theta is used as model parameter #' fit( nmf(x, 2, myfun, model='NMFns', theta=0.1) ) #' # conflict solved: can pass different theta to model and algorithm #' fit( nmf(x, 2, myfun, model=list('NMFns', theta=0.1), theta=5) ) #' #' ## USING SEEDING METHODS #' #' # run default algorithm with the Non-negative Double SVD seeding method ('nndsvd') #' res <- nmf(x, 3, seed='nndsvd') #' #' ## Note: partial match also works #' identical(res, nmf(x, 3, seed='nn')) #' #' # run nsNMF algorithm, fixing the seed of the random number generator #' res <- nmf(x, 3, 'nsNMF', seed=123456) #' nmf.equal(nmf(x, 3, 'nsNMF', seed=123456), res) #' #' # run default algorithm specifying the starting point following the NMF standard model #' start.std <- nmfModel(W=matrix(0.5, n, 3), H=matrix(0.2, 3, p)) #' nmf(x, start.std) #' #' # to run nsNMF algorithm with an explicit starting point, this one #' # needs to follow the 'NMFns' model: #' start.ns <- nmfModel(model='NMFns', W=matrix(0.5, n, 3), H=matrix(0.2, 3, p)) #' nmf(x, start.ns) #' # Note: the method name does not need to be specified as it is infered from the #' # when there is only one algorithm defined for the model. #' #' # if the model is not appropriate (as defined by the algorihtm) an error is thrown #' # [cf. the standard model doesn't include a smoothing parameter used in nsNMF] #' try( nmf(x, start.std, method='nsNMF') ) #' #' ## Callback functions #' # Pass a callback function to only save summary measure of each run #' res <- nmf(x, 3, nrun=3, .callback=summary) #' # the callback results are simplified into a matrix #' res$.callback #' res <- nmf(x, 3, nrun=3, .callback=summary, .opt='-S') #' # the callback results are simplified into a matrix #' res$.callback #' #' # Pass a custom callback function #' cb <- function(obj, i){ if( i %% 2 ) sparseness(obj) >= 0.5 } #' res <- nmf(x, 3, nrun=3, .callback=cb) #' res$.callback #' #' # Passs a callback function which throws an error #' cb <- function(){ i<-0; function(object){ i <<- i+1; if( i == 1 ) stop('SOME BIG ERROR'); summary(object) }} #' res <- nmf(x, 3, nrun=3, .callback=cb()) #' #' ## PARALLEL COMPUTATIONS #' # try using 3 cores, but use sequential if not possible #' res <- nmf(x, 3, nrun=3, .options='p3') #' #' # force using 3 cores, error if not possible #' res <- nmf(x, 3, nrun=3, .options='P3') #' #' # use externally defined cluster #' library(parallel) #' cl <- makeCluster(6) #' res <- nmf(x, 3, nrun=3, .pbackend=cl) #' #' # use externally registered backend #' registerDoParallel(cl) #' res <- nmf(x, 3, nrun=3, .pbackend=NULL) #' setMethod('nmf', signature(x='matrix', rank='numeric', method='NMFStrategy'), #function(x, rank, method, seed='random', nrun=1, keep.all=FALSE, optimized=TRUE, init='NMF', track, verbose, ...) function(x, rank, method , seed=nmf.getOption('default.seed'), rng = NULL , nrun=if( length(rank) > 1L ) 30 else 1, model=NULL, .options=list() , .pbackend=nmf.getOption('pbackend') , .callback=NULL #callback function called after a run , ...) { fwarning <- function(...) nmf_warning('nmf', ...) fstop <- function(...) nmf_stop('nmf', ...) n <- NULL RNGobj <- NULL # if options are given as a character string, translate it into a list of booleans if( is.character(.options) ){ .options <- .translate.string(.options, c(t='track', v='verbose', d='debug' , p='parallel', P='parallel.required' , k='keep.all', r='restore.seed', f='dry.run' , g='garbage.collect' , c='cleanup', S='simplifyCB' , R='RNGstream', m='shared.memory')) } # get seeding method from the strategy's defaults if needed seed <- defaultArgument(seed, method, nmf.getOption('default.seed'), force=is.null(seed)) .method_defaults <- method@defaults .method_defaults$seed <- NULL # # RNG specification if( isRNGseed(seed) ){ if( !is.null(rng) ) warning("Discarding RNG specification in argument `rng`: using those passed in argument `seed`.") rng <- seed seed <- 'random' } # # setup verbosity options debug <- if( !is.null(.options$debug) ) .options$debug else nmf.getOption('debug') verbose <- if( debug ) Inf else if( !is.null(.options$verbose) ) .options$verbose else nmf.getOption('verbose') # show call in debug mode if( debug ){ .ca <- match.call() message('# NMF call: ', paste(capture.output(print(.ca)), collapse="\n ")) } # nmf over a range of values: pass the call to nmfEstimateRank if( length(rank) > 1 ){ if( verbose <= 1 ) .options$verbose <- FALSE return( nmfEstimateRank(x, range = rank, method = method, nrun = nrun , seed = seed, rng = rng, model = model , .pbackend = .pbackend, .callback = .callback , verbose=verbose, .options=.options, ...) ) } .OPTIONS <- list() # cleanup on exit .CLEANUP <- .options$cleanup %||% TRUE # tracking of objective value .OPTIONS$track <- if( !is.null(.options$track) ) .options$track else nmf.getOption('track') # dry run dry.run <- .options$dry.run %||% FALSE # call the garbage collector regularly opt.gc <- if( !is.null(.options$garbage.collect) ) .options$garbage.collect else nmf.getOption('gc') if( is.logical(opt.gc) && opt.gc ) opt.gc <- ceiling(max(nrun,50) / 3) .options$garbage.collect <- opt.gc # keep results from all runs? keep.all <- .options$keep.all %||% FALSE # shared memory? shared.memory <- if( !is.null(.options$shared.memory) ) .options$shared.memory else nmf.getOption('shared.memory') # use RNG stream .options$RNGstream <- .options$RNGstream %||% TRUE # discard .callback when not used if( is.function(.callback) ){ w <- if( nrun==1 ) "discarding argument `.callback`: not used when `nrun=1`." else if( keep.all ) "discarding argument `.callback`: not used when option `keep.all=TRUE`." if( !is.null(w) ){ .callback <- NULL fwarning(w, immediate.=TRUE) } # wrap into another function if necessary if( is.function(.callback) ){ # default is to simplify .options$simplifyCB <- .options$simplifyCB %||% TRUE args <- formals(.callback) if( length(args) <= 2L ){ if( length(args) < 2L || '...' %in% names(args) ){ .CALLBACK <- .callback .callback <- function(object, i) .CALLBACK(object) } } # define post-processing function processCallback <- function(res){ # check errors errors <- checkErrors(res, '.callback') if( errors$n > 0 ){ fwarning("All NMF fits were successful but ", errors$n, "/", nrun, " callback call(s) threw an error.\n" ,"# ", if(errors$n>10) "First 10 c" else "C", "allback error(s) thrown:\n" , errors$msg ) } # add callback values to result list sapply(res, '[[', '.callback' , simplify=.options$simplifyCB && errors$n == 0L) } } } ## ROLLBACK PROCEDURE exitSuccess <- exitCheck() on.exit({ if( verbose > 1 ) message("# NMF computation exit status ... ", if( exitSuccess() ) 'OK' else 'ERROR') if( verbose > 2 ){ if( exitSuccess() ){ message('\n## Running normal exit clean up ... ') }else{ message('\n## Running rollback clean up ... ') } } }, add=TRUE) # RNG restoration on error .RNG_ORIGIN <- getRNG() on.exit({ if( !exitSuccess() ){ if( verbose > 2 ) message("# Restoring RNG settings ... ", appendLF=verbose>3) setRNG(.RNG_ORIGIN) if( verbose > 3 ) showRNG(indent=' #') if( verbose > 2 ) message("OK") } }, add=TRUE) # Set debug/verbosity option just for the time of the run old.opt <- nmf.options(debug=debug, verbose=verbose, shared.memory = shared.memory); on.exit({ if( verbose > 2 ) message("# Restoring NMF options ... ", appendLF=FALSE) nmf.options(old.opt) if( verbose > 2 ) message("OK") }, add=TRUE) # make sure rank is an integer rank <- as.integer(rank) if( length(rank) != 1 ) fstop("invalid argument 'rank': must be a single numeric value") if( rank < 1 ) fstop("invalid argument 'rank': must be greater than 0") # option 'restore.seed' is deprecated if( !is.null(.options$restore.seed) ) fwarning("Option 'restore.seed' is deprecated and discarded since version 0.5.99.") if( verbose ){ if( dry.run ) message("*** fake/dry-run ***") message("NMF algorithm: '", name(method), "'") } ##START_MULTI_RUN # if the number of run is more than 1, then call itself recursively if( nrun > 1 ) { if( verbose ) message("Multiple runs: ", nrun) if( verbose > 3 ){ cat("## OPTIONS:\n") sapply(seq_along(.options) , function(i){ r <- i %% 4 cat(if(r!=1) '\t| ' else "# ", names(.options)[i],': ', .options[[i]], sep='') if(r==0) cat("\n# ") }) if( length(.options) %% 4 != 0 )cat("\n") } ## OPTIONS: parallel computations # option require-parallel: parallel computation is required if TRUE or numeric != 0 opt.parallel.required <- !is.null(.options$parallel.required) && .options$parallel.required # determine specification for parallel computations opt.parallel.spec <- if( opt.parallel.required ){ # priority over try-parallel # option require-parallel implies and takes precedence over option try-parallel .options$parallel.required }else if( !is.null(.options$parallel) ) .options$parallel # priority over .pbackend else !is_NA(.pbackend) # required only if backend is not trivial # determine if one should run in parallel at all: TRUE or numeric != 0, .pbackend not NA opt.parallel <- !is_NA(.pbackend) && (isTRUE(opt.parallel.spec) || opt.parallel.spec) ## if( opt.parallel ){ if( verbose > 1 ) message("# Setting up requested `foreach` environment: " , if( opt.parallel.required ) 'require-parallel' else 'try-parallel' , ' [', quick_str(.pbackend) , ']') # switch doMC backend to doParallel if( isString(.pbackend, 'MC', ignore.case=TRUE) ){ .pbackend <- 'par' } # try setting up parallel foreach backend oldBackend <- setupBackend(opt.parallel.spec, .pbackend, !opt.parallel.required, verbose=verbose) opt.parallel <- !isFALSE(oldBackend) # setup backend restoration if using one different from the current one if( opt.parallel && !is_NA(oldBackend) ){ on.exit({ if( verbose > 2 ){ message("# Restoring previous foreach backend '", getDoBackendName(oldBackend) ,"' ... ", appendLF=FALSE) } setDoBackend(oldBackend, cleanup=TRUE) if( verbose > 2 ) message('OK') }, add=TRUE) }# # From this point, the backend is registered # => one knows if we'll run a sequential or parallel foreach loop .MODE_SEQ <- is.doSEQ() MODE_PAR <- .MODE_PAR <- !.MODE_SEQ } # check seed method: fixed values are not sensible -> warning .checkRandomness <- FALSE if( is.nmf(seed) && !is.empty.nmf(seed) ){ .checkRandomness <- TRUE } # start_RNG_all # if the seed is numerical or a rstream object, then use it to set the # initial state of the random number generator: # build a sequence of RNGstreams: if no suitable seed is provided # then the sequence use a random seed generated with a single draw # of the current active RNG. If the seed is valid, then the # # setup the RNG sequence # override with standard RNG if .options$RNGstream=FALSE resetRNG <- NULL if( !.options$RNGstream && (!opt.parallel || .MODE_SEQ) ){ .RNG.seed <- rep(list(NULL), nrun) if( isNumber(rng) ){ resetRNG <- getRNG() if( verbose > 2 ) message("# Force using current RNG settings seeded with: ", rng) set.seed(rng) }else if( verbose > 2 ) message("# Force using current RNG settings") }else{ .RNG.seed <- setupRNG(rng, n = nrun, verbose=verbose) # restore the RNG state on exit as after RNGseq: # - if no seeding occured then the RNG has still been drawn once in RNGseq # which must be reflected so that different unseeded calls use different RNG states # - one needs to restore the RNG because it switched to L'Ecuyer-CMRG. resetRNG <- getRNG() } stopifnot( length(.RNG.seed) == nrun ) # update RNG settings on exit if necessary # and only if no error occured if( !is.null(resetRNG) ){ on.exit({ if( exitSuccess() ){ if( verbose > 2 ) message("# Updating RNG settings ... ", appendLF=FALSE) setRNG(resetRNG) if( verbose > 2 ) message("OK") if( verbose > 3 ) showRNG() } }, add=TRUE) } #end_RNG_all ####FOREACH_NMF if( opt.parallel ){ if( verbose ){ if( verbose > 1 ) message("# Using foreach backend: ", getDoParName() ," [version ", getDoParVersion(),"]") # show number of processes if( getDoParWorkers() == 1 ) message("Mode: sequential [foreach:",getDoParName(),"]") else message("Mode: parallel ", str_c("(", getDoParWorkers(), '/', parallel::detectCores()," core(s))")) } # check shared memory capability .MODE_SHARED <- !keep.all && setupSharedMemory(verbose) # setup temporary directory when not keeping all fits if( !keep.all || verbose ){ NMF_TMPDIR <- setupTempDirectory(verbose) # delete on exit if( .CLEANUP ){ on.exit({ if( verbose > 2 ) message("# Deleting temporary directory '", NMF_TMPDIR, "' ... ", appendLF=FALSE) unlink(NMF_TMPDIR, recursive=TRUE) if( verbose > 2 ) message('OK') }, add=TRUE) } } run.all <- function(x, rank, method, seed, model, .options, ...){ ## 1. SETUP # load some variables from parent environment to ensure they # are exported in the foreach loop MODE_SEQ <- .MODE_SEQ MODE_SHARED <- .MODE_SHARED verbose <- verbose keep.all <- keep.all opt.gc <- .options$garbage.collect CALLBACK <- .callback .checkRandomness <- .checkRandomness # check if single or multiple host(s) hosts <- unique(getDoParHosts()) if( verbose > 2 ) message("# Running on ", length(hosts), " host(s): ", str_out(hosts)) SINGLE_HOST <- length(hosts) <= 1L MODE_SHARED <- MODE_SHARED && SINGLE_HOST if( verbose > 2 ) message("# Using shared memory ... ", MODE_SHARED) # setup mutex evaluation function mutex_eval <- if( MODE_SHARED ) ts_eval(verbose = verbose > 4) else force # Specific thing only if one wants only the best result if( !keep.all ){ NMF_TMPDIR <- NMF_TMPDIR # - Define the shared memory objects vOBJECTIVE <- gVariable(as.numeric(NA), MODE_SHARED) # the consensus matrix is computed only if not all the results are kept vCONSENSUS <- gVariable(matrix(0, ncol(x), ncol(x)), MODE_SHARED) } ## 2. RUN # ensure that the package NMF is in each worker's search path .packages <- setupLibPaths('NMF', verbose>3) # export all packages that contribute to NMF registries, # e.g., algorithms or seeding methods. # This is important so that these can be found in worker nodes # for non-fork clusters. if( !is.null(contribs <- registryContributors(package = 'NMF')) ){ .packages <- c(.packages, contribs) } # export dev environment if in dev mode # .export <- if( isDevNamespace('NMF') && !is.doSEQ() ) ls(asNamespace('NMF')) # in parallel mode: verbose message from each run are only shown in debug mode .options$verbose <- FALSE if( verbose ){ if( debug || (.MODE_SEQ && verbose > 1) ) .options$verbose <- verbose if( (!.MODE_SEQ && !debug) || (.MODE_SEQ && verbose == 1) ){ if( verbose == 1 ){ # create progress bar pbar <- txtProgressBar(0, nrun+1, width=50, style=3, title='Runs:' , shared=NMF_TMPDIR) }else{ cat("Runs: ") } } } # get options from master process to pass to workers nmf.opts <- nmf.options() # load extra required packages for shared mode if( MODE_SHARED ) .packages <- c(.packages, 'bigmemory', 'synchronicity') res.runs <- foreach(n=1:nrun , RNGobj = .RNG.seed , .verbose = debug , .errorhandling = 'pass' , .packages = .packages # , .export = .export # , .options.RNG=.RNG.seed ) %dopar% { #START_FOREACH_LOOP # Pass options from master process nmf.options(nmf.opts) # in mode sequential or debug: show details for each run if( MODE_SEQ && verbose > 1 ) cat("\n## Run: ",n, "/", nrun, "\n", sep='') # set the RNG if necessary and restore after each run if( MODE_SEQ && verbose > 2 ) message("# Setting up loop RNG ... ", appendLF=FALSE) setRNG(RNGobj, verbose=verbose>3 && MODE_SEQ) if( MODE_SEQ && verbose > 2 ) message("OK") # limited verbosity in simple mode if( verbose && !(MODE_SEQ && verbose > 1)){ if( verbose >= 2 ) mutex_eval( cat('', n) ) else{ # update progress bar (in mutex) mutex_eval(setTxtProgressBar(pbar, n)) # } } # check RNG changes if( n == 1 && .checkRandomness ){ .RNGinit <- getRNG() } # fit a single NMF model res <- nmf(x, rank, method, nrun=1, seed=seed, model=model, .options=.options, ...) if( n==1 && .checkRandomness && rng.equal(.RNGinit) ){ warning("NMF::nmf - You are running multiple non-random NMF runs with a fixed seed") } # if only the best fit must be kept then update the shared objects if( !keep.all ){ # initialise result list resList <- list(filename=NA, residuals=NA, .callback=NULL) ##LOCK_MUTEX mutex_eval({ # check if the run found a better fit .STATIC.err <- vOBJECTIVE() # retrieve approximation error err <- deviance(res) if( is.na(.STATIC.err) || err < .STATIC.err ){ if( n>1 && verbose ){ if( MODE_SEQ && verbose > 1 ) cat("## Better fit found [err=", err, "]\n") else if( verbose >= 2 ) cat('*') } # update residuals vOBJECTIVE(err) # update best fit on disk: use pid if not using shared memory resfile <- hostfile("fit", tmpdir=NMF_TMPDIR, fileext='.rds', pid=!MODE_SHARED) if( MODE_SEQ && verbose > 2 ) message("# Serializing fit object in '", resfile, "' ... ", appendLF=FALSE) saveRDS(res, file=resfile, compress=FALSE) if( MODE_SEQ && verbose > 2 ){ message(if( file.exists(resfile) ) 'OK' else 'ERROR') } # store the filename and achieved objective value in the result list resList$filename <- resfile resList$residuals <- err } ## CONSENSUS # update the consensus matrix if( MODE_SHARED && SINGLE_HOST ){ # on single host: shared memory already contains consensus vCONSENSUS(vCONSENSUS() + connectivity(res, no.attrib=TRUE)) }else{ # on multiple hosts: must return connectivity and aggregate at the end resList$connectivity <- connectivity(res, no.attrib=TRUE) } ## CALLBACK # call the callback function if necessary (return error as well) if( is.function(CALLBACK) ){ resList$.callback <- tryCatch(CALLBACK(res, n), error=function(e) e) } }) ##END_LOCK_MUTEX # discard result object res <- NULL # return description list res <- resList } # garbage collection if requested if( opt.gc && n %% opt.gc == 0 ){ if( verbose > 2 ){ if( MODE_SEQ ) message("# Call garbage collector") else{ mutex_eval( cat('%') ) } } gc(verbose= MODE_SEQ && verbose > 3) } # return the result res } ## END_FOREACH_LOOP if( verbose && !debug ){ if( verbose >= 2 ) cat(" ... DONE\n") else{ setTxtProgressBar(pbar, nrun+1) pbar$kill(.CLEANUP) } } ## 3. CHECK FIT ERRORS errors <- checkErrors(res.runs) if( errors$n > 0 ){ fstop(errors$n,"/", nrun, " fit(s) threw an error.\n" ,"# Error(s) thrown:\n", errors$msg) } ## 4. WRAP UP if( keep.all ){ # result is a list of fits # directly return the list of fits res <- res.runs }else{ # result is a list of lists: filename, .callback # loop over the result files to find the best fit if( verbose > 2 ) message("# Processing partial results ... ", appendLF=FALSE) ffstop <- function(...){ message('ERROR'); fstop(...) } # get best fit index idx <- which.min(sapply(res.runs, '[[', 'residuals')) if( length(idx) == 0L ) ffstop("Unexpected error: no partial result seem to have been saved.") resfile <- res.runs[[idx]]$filename # check existence of the result file if( !file_test('-f', resfile) ) ffstop("could not find temporary result file '", resfile, "'") # update res with a better fit res <- readRDS(resfile) if( !isNMFfit(res) ) ffstop("invalid object found in result file '", resfile, "'") if( verbose > 2 ) message('OK') # wrap the result in a list: fit + consensus res <- list(fit=res, consensus=NA) # CONSENSUS MATRIX if( !is.null(res.runs[[1]]$connectivity) ){ # not MODE_SHARED # aggregate connectivity matrices con <- matrix(0, ncol(x), ncol(x)) sapply(res.runs, function(x){ con <<- con + x$connectivity }) res$consensus <- con }else{ # in MODE_SHARED: get consensus from global shared variable res$consensus <- vCONSENSUS() cn <- colnames(x) if( is.null(cn) ) dimnames(res$consensus) <- NULL else dimnames(res$consensus) <- list(cn, cn) } # CALLBACKS if( !is.null(.callback) ){ res$.callback <- processCallback(res.runs) } } ## if( MODE_SEQ && verbose>1 ) cat("## DONE\n") # return result res } }####END_FOREACH_NMF else{####SAPPLY_NMF run.all <- function(x, rank, method, seed, model, .options, ...){ # by default force no verbosity from the runs .options$verbose <- FALSE if( verbose ){ message("Mode: sequential [sapply]") if( verbose > 1 ){ # pass verbosity options in this case .options$verbose <- verbose } } ## 1. SETUP # define static variables for the case one only wants the best result if( !keep.all ){ # statis list with best result: fit, residual, consensus best.static <- list(fit=NULL, residuals=NA, consensus=matrix(0, ncol(x), ncol(x))) } ## 2. RUN: # perform a single run `nrun` times if( verbose == 2 ){ showRNG() } if( verbose && !debug ) cat('Runs:') res.runs <- mapply(1:nrun, .RNG.seed, FUN=function(n, RNGobj){ #start_verbose if( verbose ){ # in mode verbose > 1: show details for each run if( verbose > 1 ){ cat("\n## Run: ",n, "/", nrun, "\n", sep='') }else{ # otherwise only some details for the first run cat('', n) } }#end_verbose # set the RNG for each run if( verbose > 2 ) message("# Setting up loop RNG ... ", appendLF=FALSE) setRNG(RNGobj, verbose=verbose>3) if( verbose > 2 ) message("OK") # check RNG changes if( n == 1 && .checkRandomness ){ .RNGinit <- getRNG() } # fit a single NMF model res <- nmf(x, rank, method, nrun=1, seed=seed, model=model, .options=.options, ...) if( n==1 && .checkRandomness && rng.equal(.RNGinit) ){ warning("NMF::nmf - You are running multiple non-random NMF runs with a fixed seed" , immediate.=TRUE) } if( !keep.all ){ # initialise result list resList <- list(residuals=NA, .callback=NULL) # check if the run found a better fit err <- residuals(res) best <- best.static$residuals if( is.na(best) || err < best ){ if( verbose ){ if( verbose > 1L ) cat("## Updating best fit [deviance =", err, "]\n", sep='') else cat('*') } # update best fit (only if necessary) best.static$fit <<- res best.static$residuals <<- err resList$residuals <- err } # update the static consensus matrix (only if necessary) best.static$consensus <<- best.static$consensus + connectivity(res, no.attrib=TRUE) # call the callback function if necessary if( !is.null(.callback) ){ resList$.callback <- tryCatch(.callback(res, n), error=function(e) e) } # reset the result to NULL res <- resList } # garbage collection if requested if( opt.gc && n %% opt.gc == 0 ){ if( verbose > 1 ) message("# Call garbage collection NOW") else if( verbose ) cat('%') gc(verbose = verbose > 3) } if( verbose > 1 ) cat("## DONE\n") # return the result res }, SIMPLIFY=FALSE) ## if( verbose && !debug ) cat(" ... DONE\n") ## 3. ERROR CHECK / WRAP UP if( keep.all ){ res <- res.runs }else{ res <- list(fit=best.static$fit, consensus=best.static$consensus) # CALLBACKS if( !is.null(.callback) ){ res$.callback <- processCallback(res.runs) } } res } }####END_SAPPLY_NMF ####END_DEFINE_RUN # perform all the NMF runs t <- system.time({res <- run.all(x=x, rank=rank, method=method, seed=seed, model=model, .options, ...)}) if( verbose && !debug ){ cat("System time:\n") print(t) } if( keep.all ){ # when keeping all the fits: join the results into an NMFfitXn object # TODO: improve memory management here res <- NMFfitX(res, runtime.all=t) return( exitSuccess(res) ) }else{# if one just want the best result only return the best # ASSERT the presence of the result stopifnot( !is.null(res$fit) ) # ASSERT the presence of the consensus matrix stopifnot( !is.null(res$consensus) ) res.final <- NMFfitX(res$fit, consensus=res$consensus/nrun , runtime.all=t, nrun=as.integer(nrun) , rng1=.RNG.seed[[1]]) # ASSERT and add callback if necessary if( !is.null(.callback) ){ stopifnot( !is.null(res$.callback) ) res.final$.callback <- res$.callback } return( exitSuccess(res.final) ) } }##END_MULTI_RUN # start_RNG # show original RNG settings in verbose > 2 if( verbose > 3 ){ message("# ** Current RNG settings:") showRNG() } # do something if the RNG was actually changed newRNG <- getRNG() .RNG.seed <- setupRNG(rng, 1, verbose=verbose-1) # setup restoration if( isRNGseed(rng) ){ if( verbose > 3 ) showRNG() # restore RNG settings on.exit({ if( verbose > 2 ) message("# Restoring RNG settings ... ", appendLF=FALSE) setRNG(newRNG) if( verbose > 2 ) message("OK") if( verbose > 3 ) showRNG() }, add=TRUE) } #end_RNG # CHECK PARAMETERS: # test for negative values in x only if the method is not mixed if( !is.mixed(method) && min(x, na.rm = TRUE) < 0 ) fstop('Input matrix ', substitute(x),' contains some negative entries.'); # test if one row contains only zero entries if( min(rowSums(x, na.rm = TRUE), na.rm = TRUE) == 0 ) fstop('Input matrix ', substitute(x),' contains at least one null or NA-filled row.'); # a priori the parameters for the run are all the one in '...' # => expand with the strategy's defaults (e.g., maxIter) parameters.method <- expand_list(list(...), .method_defaults) # if( is.nmf(seed) ){ if( !is.null(model) ) fwarning("Discarding argument `model`: directly using NMF model supplied in argument `seed`") # if the seed is a NMFfit object then only use the fit (i.e. the NMF model) # => we want a fresh and clean NMFfit object if( isNMFfit(seed) ) seed <- fit(seed) # Wrap up the seed into a NMFfit object seed <- NMFfit(fit=seed, seed='NMF') } else if( !inherits(seed, 'NMFfit') ){ ## MODEL INSTANTIATION : # default NMF model is retrieved from the NMF strategy .modelClass <- modelname(method) # if a character string then use this type of NMF model, but still look # for slots in `...` if( is.character(model) ){ .modelClass <- model model <- NULL } # some of the instantiation parameters are set internally # TODO: change target into x (=> impact on nmfModel ? parameters.model.internal <- list(rank=rank, target=0) parameters.model <- list() init <- if( is.nmf(model) ){ model }else{ # if 'model' is NULL: initialization parameters are searched in '...' if( is.null(model) ){ # extract the parameters from '...' that correspond to slots in the given class stopifnot( isNMFclass(.modelClass) ) parameters <- .extract.slots.parameters(.modelClass, parameters.method) # restrict parameters.method to the ones that won't be used to instantiate the model overriden <- is.element(names(parameters$slots), names(parameters.model.internal)) parameters.method <- c(parameters$extra, parameters$slots[overriden]) #- the model parameters come from the remaining elements parameters.model <- c(model=.modelClass, parameters$slots) } else if( is.list(model) ){ # otherwise argument 'model' must be a list # if the list is not empty then check all elements are named and # not conflicting with the internally set values if( length(model) > 0 ){ # all the elements must be named if( !hasNames(model, all=TRUE) ) fstop("Invalid argument `model` [elements must all be named]. See ?nmf.") # warn the user if some elements are conflicting and won't be used overriden <- is.element(names(model), names(parameters.model.internal)) if( any(overriden) ) warning("NMF::nmf - Model parameter(s) [" , str_out(model[overriden], use.names=TRUE, max=Inf) , "] discarded. Used internally set value(s) [" , str_out(parameters.model.internal[names(model[overriden])], use.names=TRUE, max=Inf) , "]" , call.=FALSE) } # add default model class if necessary if( is.null(model$model) ) model$model <- .modelClass # all the instantiation parameters come from argument 'model' parameters.model <- model }else{ fstop("Invalid argument 'model' [expected NULL, a character string, or a list to set slots in the NMF model class '",.modelClass,"']. See ?nmf.") } #- force the value of the internally set arguments for the instantiation of the model parameters.model <- .merge.override(parameters.model, parameters.model.internal) # at this point 'init' should be the list of the initialization parameters if( !is.list(parameters.model) ){ fstop("Unexpected error: object 'parameters.model' must be a list") } if( !is.element('model', names(parameters.model)) ){ fstop("Unexpected error: object 'parameters.model' must contain an element named 'model'") } parameters.model } ## SEEDING: # the seed must either be an instance of class 'NMF', the name of a seeding method as a character string # or a list of parameters to pass to the 'seed' function. parameters.seed <- list() seed.method <- NULL if( (is.character(seed) && length(seed) == 1) || is.numeric(seed) || is.null(seed) # || is(seed, 'rstream') ) seed.method <- seed else if( is.function(seed) ) seed.method <- seed else if( is.list(seed) ){ # seed is a list... if( !is.null(seed$method) ){ # 'seed' must contain an element giving the method... seed.method <- seed$method parameters.seed <- seed[-which(names(seed)=='method')] } else if ( is.null(names(seed)) || names(seed)[1] == '' ){ # ... or the first element must be a method seed.method <- seed[[1]] if( length(seed) > 1 ) parameters.seed <- seed[2:length(seed)] } else fstop("Invalid parameter: list 'seed' must contain the seeding method through its first element or through an element named 'method' [", str_desc(seed, 2L), "]") # check validity of the method provided via the list if( !is.function(seed.method) && !(is.character(seed.method) && length(seed.method)==1) ) fstop("The seeding method provided by parameter 'seed' [", str_desc(seed.method), "] is invalid: a valid function or a character string is expected") } else fstop("Invalid parameter 'seed'. Acceptable values are:\n\t- ", paste("an object that inherits from class 'NMF'" , "the name of a seeding method (see ?nmfSeed)" , "a valid seed method definition" , "a list containing the seeding method (i.e. a function or a character string) as its first element\n\tor as an element named 'method' [and optionnally extra arguments it will be called with]" , "a numerical value used to set the seed of the random generator" , "NULL to directly pass the model instanciated from arguments 'model' or '...'." , sep="\n\t- ")) # call the 'seed' function passing the necessary parameters if( verbose ) message("NMF seeding method: ", if( is.character(seed.method) || is.numeric(seed.method) ) seed.method else if( is.null(seed.method) ) 'NULL' else if( !is.null(attr(seed.method, 'name')) ) attr(seed.method, 'name') else if( is.function(seed.method) ) '' else NA) #seed <- do.call(getGeneric('seed', package='NMF') seed <- do.call(getGeneric('seed') , c(list(x=x, model=init, method=seed.method), parameters.seed)) # check the validity of the seed if( !inherits(seed, 'NMFfit') ) fstop("The seeding method function should return class 'NMF' [" , if( is.character(seed.method) ) paste('method "', seed.method, "' ", sep='') else NULL , "returned class: '", class(seed), "']") } # -> at this point the 'seed' object is an instance of class 'NMFfit' nmf.debug('nmf', "Seed is of class: '", class(seed), "'") # ASSERT just to be sure if( !inherits(seed, 'NMFfit') ) fstop("Invalid class '", class(seed), "' for the computed seed: object that inherits from class 'NMFfit' expected.") # check the consistency of the NMF model expected by the algorithm and # the one defined by the seed #if( none( sapply(model(method), function(c) extends(model(seed), c)) ) ) if( all( !inherits(fit(seed), modelname(method)) ) ) fstop("Invalid NMF model '", modelname(seed),"': algorithm '", name(method), "' expects model(s) " , paste(paste("'", modelname(method),"'", sep=''), collapse=', ') , " or extension.") # get the complete seeding method's name seed.method <- seeding(seed) ## FINISH SETUP OF THE SEED OBJECT: store some data within the seed so # that strategy methods can access them directly algorithm(seed) <- name(method) # algorithm name seed@distance <- objective(method) # distance name seed@parameters <- parameters.method # extra parameters run.options(seed) <- nmf.options() # set default run options run.options(seed, 'error.track') <- .OPTIONS$track if( is.numeric(.OPTIONS$track) ) run.options(seed, 'track.interval') <- .OPTIONS$track run.options(seed, 'verbose') <- verbose # store ultimate nmf() call seed@call <- match.call() ## ## print options if in verbose > 3 if( verbose > 3 ){ cat("## OPTIONS:\n") sapply(seq_along(.options) , function(i){ r <- i %% 4 cat(if(r!=1) '\t| ' else "# ", names(.options)[i],': ', .options[[i]], sep='') if(r==0) cat("\n") }) if( length(.options) %% 4 != 0 )cat("\n") } ## run parameters: parameters.run <- c(list(object=method, y=x, x=seed), parameters.method) ## Compute the initial residuals if tracking is enabled init.resid <- if( .OPTIONS$track && !is.partial.nmf(seed) ){ do.call('deviance', parameters.run) } ## RUN NMF METHOD: # call the strategy's run method [and time it] t <- system.time({ res <- if( !dry.run ){ do.call('run', parameters.run) }else{ seed } }) ## WRAP/CHECK RESULT res <- .wrapResult(x, res, seed, method=method, seed.method=seed.method, t) if( !isNMFfit(res) ){ # stop if error fstop(res) } ## ## CLEAN-UP + EXTRAS: # add extra information to the object # slot 'parameters' if( length(res@parameters) == 0L && length(parameters.method)>0L ) res@parameters <- parameters.method # last residuals if( length(residuals(res)) == 0 && !is.partial.nmf(seed) ){ parameters.run$x <- res residuals(res, niter=niter(res)) <- do.call('deviance', parameters.run) } # first residual if tracking is enabled if( .OPTIONS$track && !is.null(init.resid) ){ if( !hasTrack(res, niter=0) ) residuals(res, track=TRUE) <- c('0'=init.resid, residuals(res, track=TRUE)) } if( length(residuals(res)) && is.na(residuals(res)) ) warning("NMF residuals: final objective value is NA") res@runtime <- t # return the result exitSuccess(res) }) # wrap result .wrapResult <- function(x, res, seed, method, seed.method, t){ ## wrap into an NMFfit object (update seed) if( !isNMFfit(res) ){ # extract expression data if necessary if( is(res, 'ExpressionSet') ) res <- exprs(res) if( is(x, 'ExpressionSet') ) x <- exprs(x) # wrap if( is.matrix(res) ){ if( ncol(res) == ncol(x) ){# partial fit: coef # force dimnames colnames(res) <- colnames(x) res <- nmfModel(H=res) }else if( nrow(res) == nrow(x) ){# partial fit: basis # force dimnames rownames(res) <- rownames(x) res <- nmfModel(W=res) } }else if( is.list(res) ){ # build NMF model from result list res <- do.call('nmfModel', res) } # substitute model in fit object if( is.nmf(res) ){ tmp <- seed fit(tmp) <- res tmp@runtime <- t res <- tmp } } ## check result if( !isTRUE(err <- .checkResult(res, seed)) ) return(err) ## Enforce some slot values # slot 'method' algorithm(res) <- name(method) # slot 'distance' res@distance <- objective(method) # slot 'seed' if( seed.method != '' ) seeding(res) <- seed.method # set dimnames of the result only if necessary if( is.null(dimnames(res)) ) dimnames(res) <- dimnames(seed) res } # check result .checkResult <- function(fit, seed){ # check the result is of the right type if( !inherits(fit, 'NMFfit') ){ return(str_c("NMF algorithms should return an instance of class 'NMFfit' [returned class:", class(fit), "]")) } # check that the model has been fully estimated if( is.partial.nmf(fit) ){ warning("nmf - The NMF model was only partially estimated [dim = (", str_out(dim(fit), Inf),")].") } # check that the fit conserved all fixed terms (only warning) if( nterms(seed) ){ if( length(i <- icterms(seed)) && !identical(coef(fit)[i,], coef(seed)[i,]) ){ warning("nmf - Fixed coefficient terms were not all conserved in the fit: the method might not support them.") } if( length(i <- ibterms(seed)) && !identical(basis(fit)[,i], basis(seed)[,i]) ){ warning("nmf - Fixed basis terms were not all conserved in the fit: the method might not support them.") } } TRUE } #' Interface for NMF Seeding Methods #' #' @description #' The function \code{seed} provides a single interface for calling all seeding #' methods used to initialise NMF computations. #' These methods at least set the basis and coefficient matrices of the initial #' \code{object} to valid nonnegative matrices. #' They will be used as a starting point by any NMF algorithm that accept #' initialisation. #' #' IMPORTANT: this interface is still considered experimental and is subject #' to changes in future release. #' #' @param x target matrix one wants to approximate with NMF #' @param model specification of the NMF model, e.g., the factorization rank. #' @param method specification of a seeding method. #' See each method for details on the supported formats. #' @param ... extra to allow extensions and passed down to the actual seeding method. #' #' @return an \code{\linkS4class{NMFfit}} object. #' #' @inline #' @export setGeneric('seed', function(x, model, method, ...) standardGeneric('seed') ) #' This is the workhorse method that seeds an NMF model object using a given #' seeding strategy defined by an \code{NMFSeed} object, to fit a given #' target matrix. #' #' @param rng rng setting to use. #' If not missing the RNG settings are set and restored on exit using #' \code{\link{setRNG}}. #' #' All arguments in \code{...} are passed to teh seeding strategy. #' setMethod('seed', signature(x='matrix', model='NMF', method='NMFSeed'), function(x, model, method, rng, ...){ # debug message nmf.debug('seed', "use seeding method: '", name(method), "'") # temporarly set the RNG if provided if( !missing(rng) ){ orng <- setRNG(rng) on.exit(setRNG(orng)) } # save the current RNG numerical seed rng.s <- getRNG() # create the result NMFfit object, storing the RNG numerical seed res <- NMFfit() # ASSERT: check that the RNG seed is correctly set stopifnot( rng.equal(res,rng.s) ) # call the seeding function passing the extra parameters f <- do.call(algorithm(method), c(list(model, x), ...)) # set the dimnames from the target matrix dimnames(f) <- dimnames(x) # set the basis names from the model if any if( !is.null(basisnames(model)) ) basisnames(f) <- basisnames(model) # store the result into the NMFfit object fit(res) <- f # if not already set: store the seeding method's name in the resulting object if( seeding(res) == '' ) seeding(res) <- name(method) # return the seeded object res } ) #' Seeds an NMF model using a custom seeding strategy, defined by a function. #' #' \code{method} must have signature \code{(x='NMFfit', y='matrix', ...)}, where #' \code{x} is the unseeded NMF model and \code{y} is the target matrix to fit. #' It must return an \code{\linkS4class{NMF}} object, that contains the seeded #' NMF model. #' #' @param name optional name of the seeding method for custom seeding strategies. #' setMethod('seed', signature(x='ANY', model='ANY', method='function'), function(x, model, method, name, ...){ # generate runtime name if necessary if( missing(name) ) name <- basename(tempfile("NMF.seed.")) # check that the name is not a registered name if( existsNMFSeed(name) ) stop("Invalid name for custom seeding method: '",name,"' is already a registered seeding method") # wrap function method into a new NMFSeed object seedObj <- new('NMFSeed', name=name, method=method) # call version with NMFSeed seed(x, model, seedObj, ...) } ) #' Seeds the model with the default seeding method given by #' \code{nmf.getOption('default.seed')} setMethod('seed', signature(x='ANY', model='ANY', method='missing'), function(x, model, method, ...){ seed(x, model, nmf.getOption('default.seed'), ...) } ) #' Use NMF method \code{'none'}. setMethod('seed', signature(x='ANY', model='ANY', method='NULL'), function(x, model, method, ...){ seed(x, model, 'none', ...) } ) #' Use \code{method} to set the RNG with \code{\link{setRNG}} and use method #' \dQuote{random} to seed the NMF model. #' #' Note that in this case the RNG settings are not restored. #' This is due to some internal technical reasons, and might change in future #' releases. setMethod('seed', signature(x='ANY', model='ANY', method='numeric'), function(x, model, method, ...){ # set the seed using the numerical value by argument 'method' orng <- setRNG(method) #TODO: restore the RNG state? # call seeding method 'random' res <- seed(x, model, 'random', ...) # return result return(res) } ) #setMethod('seed', signature(x='ANY', model='ANY', method='rstream'), # function(x, model, method, ...){ # # # set the seed using the numerical value by argument 'method' # orng <- setRNG(method) # #TODO: restore the RNG state? # # # call seeding method 'random' # res <- seed(x, model, 'random', ...) # # # return result # return(res) # } #) #' Use the registered seeding method whose access key is \code{method}. setMethod('seed', signature(x='ANY', model='ANY', method='character'), function(x, model, method, ...){ # get the seeding method from the registry seeding.fun <- nmfSeed(method) #Vc#Use seeding method: '${method}' # call 'seed' with the seeding.function seed(x, model, method=seeding.fun, ...) } ) #' Seed a model using the elements in \code{model} to instantiate it with #' \code{\link{nmfModel}}. setMethod('seed', signature(x='ANY', model='list', method='NMFSeed'), function(x, model, method, ...){ ## check validity of the list: there should be at least the NMF (sub)class name and the rank if( length(model) < 2 ) stop("Invalid parameter: list 'model' must contain at least two elements giving the model's class name and the factorization rank") # 'model' must contain an element giving the class to instanciate if( is.null(model$model) ){ err.msg <- "Invalid parameter: list 'model' must contain a valid NMF model classname in an element named 'model' or in its first un-named element" unamed <- if( !is.null(names(model)) ) which(names(model) %in% c('', NA)) else 1 if ( length(unamed) > 0 ){ # if not the first unamed element is taken as the class name idx <- unamed[1] val <- unlist(model[idx], recursive=FALSE) if( is.character(val) && length(val)==1 && extends(val, 'NMF') ) names(model)[idx] <- 'model' else stop(err.msg) }else stop(err.msg) } # 'model' must contain an element giving the factorization rank if( is.null(model$rank) ){ err.msg <- "Invalid parameter: list 'model' must contain the factorization rank in an element named 'rank' or in its second un-named element" unamed <- if( !is.null(names(model)) ) which(names(model) %in% c('', NA)) else 1 if ( length(unamed) > 0 ){ # if not the second element is taken as the factorization rank idx <- unamed[1] val <- unlist(model[idx], recursive=FALSE) if( is.numeric(val) && length(val)==1 ) names(model)[idx] <- 'rank' else stop(err.msg) } else stop(err.msg) } nmf.debug('seed', "using model parameters:\n", capture.output(print(model)) ) # instantiate the object using the factory method model <- do.call('nmfModel', model) nmf.debug('seed', "using NMF model '", class(model), "'") # check that model is from the right type, i.e. inherits from class NMF if( !inherits(model, 'NMF') ) stop("Invalid object returned by model: object must inherit from class 'NMF'") seed(x, model, method, ...) } ) #' Seeds a standard NMF model (i.e. of class \code{\linkS4class{NMFstd}}) of rank #' \code{model}. setMethod('seed', signature(x='ANY', model='numeric', method='NMFSeed'), function(x, model, method, ...){ seed(x, nmfModel(model), method, ...) } ) ###% Extract from a list the elements that can be used to initialize the slot of a class. ###% ###% This function only extract named elements. ###% ###% @param class.name Name of the class from whose slots will be search into '...' ###% @param ... The parameters in which the slot names will be search for ###% ###% @return a list with two elements: ###% - \code{slots}: is a list that contains the named parameters that can be used to instantiate an object of class \code{class.name} ###% - \code{extra}: is a list of the remaining parameters from \code{parameters} (i.e. the ones that do not correspond to a slot). ###% .extract.slots.parameters <- function(class.name, ...){ # check validity of class.name if( !isClass(class.name) ) stop("Invalid class name: class '", class.name, "' dose not exist") # transform '...' into a list parameters <- list(...) if( length(parameters) == 1L && is.null(names(parameters)) ){ parameters <- parameters[[1L]] } # get the slots from the class name slots <- slotNames(class.name) # get the named parameters that correspond to a slot in.slots <- is.element(names(parameters), slots) # return the two lists list( slots=parameters[in.slots], extra=parameters[!in.slots]) } ###% Merges two lists, but overriding with the values of the second list in the case ###% of duplicates. .merge.override <- function(l1, l2, warning=FALSE){ sapply(names(l2), function(name){ if( warning && !is.null(l1[[name]]) ) warning("overriding element '", name, "'") l1[[name]] <<- l2[[name]] }) # return updated list return(l1) } #' Estimate Rank for NMF Models #' #' A critical parameter in NMF algorithms is the factorization rank \eqn{r}. #' It defines the number of basis effects used to approximate the target #' matrix. #' Function \code{nmfEstimateRank} helps in choosing an optimal rank by #' implementing simple approaches proposed in the literature. #' #' Note that from version \emph{0.7}, one can equivalently call the #' function \code{\link{nmf}} with a range of ranks. #' #' @details #' Given a NMF algorithm and the target matrix, a common way of estimating #' \eqn{r} is to try different values, compute some quality measures of the #' results, and choose the best value according to this quality criteria. See #' \cite{Brunet2004} and \cite{Hutchins2008}. #' #' The function \code{nmfEstimateRank} allows to perform this estimation #' procedure. #' It performs multiple NMF runs for a range of rank of #' factorization and, for each, returns a set of quality measures together with #' the associated consensus matrix. #' #' In order to avoid overfitting, it is recommended to run the same procedure on #' randomized data. #' The results on the original and the randomised data may be plotted on the #' same plots, using argument \code{y}. #' #' @param x For \code{nmfEstimateRank} a target object to be estimated, in one #' of the format accepted by interface \code{\link{nmf}}. #' #' For \code{plot.NMF.rank} an object of class \code{NMF.rank} as returned by #' function \code{nmfEstimateRank}. #' @param range a \code{numeric} vector containing the ranks of factorization #' to try. #' Note that duplicates are removed and values are sorted in increasing order. #' The results are notably returned in this order. #' #' @param method A single NMF algorithm, in one of the format accepted by #' the function \code{\link{nmf}}. #' #' @param nrun a \code{numeric} giving the number of run to perform for each #' value in \code{range}. #' #' @param model model specification passed to each \code{nmf} call. #' In particular, when \code{x} is a formula, it is passed to argument #' \code{data} of \code{\link{nmfModel}} to determine the target matrix -- and #' fixed terms. #' #' @param verbose toggle verbosity. This parameter only affects the verbosity #' of the outer loop over the values in \code{range}. #' To print verbose (resp. debug) messages from each NMF run, one can use #' \code{.options='v'} (resp. \code{.options='d'}) #' that will be passed to the function \code{\link{nmf}}. #' #' @param stop logical flag for running the estimation process with fault #' tolerance. When \code{TRUE}, the whole execution will stop if any error is #' raised. When \code{FALSE} (default), the runs that raise an error will be #' skipped, and the execution will carry on. The summary measures for the runs #' with errors are set to NA values, and a warning is thrown. #' #' @param ... For \code{nmfEstimateRank}, these are extra parameters passed #' to interface \code{nmf}. Note that the same parameters are used for each #' value of the rank. See \code{\link{nmf}}. #' #' For \code{plot.NMF.rank}, these are extra graphical parameter passed to the #' standard function \code{plot}. See \code{\link{plot}}. #' #' @return #' \code{nmfEstimateRank} returns a S3 object (i.e. a list) of class #' \code{NMF.rank} with the following elements: #' #' \item{measures }{a \code{data.frame} containing the quality #' measures for each rank of factorizations in \code{range}. Each row #' corresponds to a measure, each column to a rank. } #' \item{consensus }{ a #' \code{list} of consensus matrices, indexed by the rank of factorization (as #' a character string).} #' \item{fit }{ a \code{list} of the fits, indexed by the rank of factorization #' (as a character string).} #' #' @export #' @examples #' #' if( !isCHECK() ){ #' #' set.seed(123456) #' n <- 50; r <- 3; m <- 20 #' V <- syntheticNMF(n, r, m) #' #' # Use a seed that will be set before each first run #' res <- nmfEstimateRank(V, seq(2,5), method='brunet', nrun=10, seed=123456) #' # or equivalently #' res <- nmf(V, seq(2,5), method='brunet', nrun=10, seed=123456) #' #' # plot all the measures #' plot(res) #' # or only one: e.g. the cophenetic correlation coefficient #' plot(res, 'cophenetic') #' #' # run same estimation on randomized data #' rV <- randomize(V) #' rand <- nmfEstimateRank(rV, seq(2,5), method='brunet', nrun=10, seed=123456) #' plot(res, rand) #' } #' nmfEstimateRank <- function(x, range, method=nmf.getOption('default.algorithm') , nrun=30, model=NULL, ..., verbose=FALSE, stop=FALSE){ # fix method if passed NULL (e.g., from nmf('formula', 'numeric')) if( is.null(method) ) method <- nmf.getOption('default.algorithm') # special handling of formula: get target data from the formula if( is(x, 'formula') ){ # dummy model to resolve formula dummy <- nmfModel(x, 0L, data=model) # retrieve target data V <- attr(dummy, 'target') }else{ V <- x } # remove duplicates and sort range <- sort(unique(range)) # initiate the list of consensus matrices: start with single NA values c.matrices <- setNames(lapply(range, function(x) NA), as.character(range)) fit <- setNames(lapply(range, function(x) NA), as.character(range)) bootstrap.measures <- list() # combine function: take all the results at once and merge them into a big matrix comb <- function(...){ measures <- list(...) err <- which( sapply(measures, is.character) ) if( length(err) == length(measures) ){ # all runs produced an error # build an warning using the error messages msg <- paste(paste("#", seq_along(range),' ', measures, sep=''), collapse="\n\t-") stop("All the runs produced an error:\n\t-", msg) }else if( length(err) > 0 ){ # some of the runs returned an error # simplify the results with no errors into a matrix measures.ok <- sapply(measures[-err], function(x) x) # build a NA matrix for all the results n <- nrow(measures.ok) tmp.res <- matrix(as.numeric(NA), n, length(range)) rownames(tmp.res) <- rownames(measures.ok) # set the results that are ok tmp.res[,-err] <- measures.ok # set only the rank for the error results tmp.res['rank', err] <- range[err] # build an warning using the error messages msg <- paste(paste("#", err, measures[err], ' ', sep=''), collapse="\n\t-") warning("NAs were produced due to errors in some of the runs:\n\t-", msg) # return full matrix tmp.res } else # all the runs are ok sapply(measures, function(x) x) } # measures <- foreach(r = range, .combine=comb, .multicombine=TRUE, .errorhandling='stop') %do% { k.rank <- 0 measures <- sapply(range, function(r, ...){ k.rank <<- k.rank + 1L if( verbose ) cat("Compute NMF rank=", r, " ... ") # restore RNG on exit (except after last rank) # => this ensures the methods use the same stochastic environment orng <- RNGseed() if( k.rank < length(range) ) on.exit( RNGseed(orng), add = TRUE) res <- tryCatch({ #START_TRY res <- nmf(x, r, method, nrun=nrun, model=model, ...) # directly return the result if a valid NMF result if( !isNMFfit(res, recursive = FALSE) ) return(res) # store the consensus matrix c.matrices[[as.character(r)]] <<- consensus(res) # store the fit fit[[as.character(r)]] <<- res # if confidence intervals must be computed then do it # if( conf.interval ){ # # resample the tries # samp <- sapply(seq(5*nrun), function(i){ sample(nrun, nrun, replace=TRUE) }) # # bootstrap.measures[[as.character(r)]] <<- apply(samp, 2, function(s){ # res.sample <- join(res[s]) # summary(res.sample, target=x) # }) # } # compute quality measures if( verbose ) cat('+ measures ... ') measures <- summary(res, target=V) if( verbose ) cat("OK\n") # return the measures measures } #END_TRY , error = function(e) { mess <- if( is.null(e$call) ) e$message else paste(e$message, " [in call to '", e$call[1],"']", sep='') mess <- paste('[r=', r, '] -> ', mess, sep='') if( stop ){ # throw the error if( verbose ) cat("\n") stop(mess, call.=FALSE) } # pass the error message if( verbose ) message("ERROR") return(mess) } ) # return the result res } , ..., simplify=FALSE) measures <- do.call(comb, measures) # reformat the result into a data.frame measures <- as.data.frame(t(measures)) # wrap-up result into a 'NMF.rank' S3 object res <- list(measures=measures, consensus=c.matrices, fit=fit) #if( conf.interval ) res$bootstrap.measure <- bootstrap.measures class(res) <- 'NMF.rank' return(res) } #' @method summary NMF.rank #' @export summary.NMF.rank <- function(object, ...){ s <- summary(new('NMFList', object$fit), ...) # NB: sort measures in the same order as required in ... i <- which(!names(s) %in% names(object$measures)) cbind(s[, i], object$measures[match(object$measures$rank, s$rank), ]) } #' \code{plot.NMF.rank} plots the result of rank estimation survey. #' #' In the plot generated by \code{plot.NMF.rank}, each curve represents a #' summary measure over the range of ranks in the survey. #' The colours correspond to the type of data to which the measure is related: #' coefficient matrix, basis component matrix, best fit, or consensus matrix. #' #' @param y reference object of class \code{NMF.rank}, as returned by #' function \code{nmfEstimateRank}. #' The measures contained in \code{y} are used and plotted as a reference. #' It is typically used to plot results obtained from randomized data. #' The associated curves are drawn in \emph{red} (and \emph{pink}), #' while those from \code{x} are drawn in \emph{blue} (and \emph{green}). #' @param what a \code{character} vector whose elements partially match #' one of the following item, which correspond to the measures computed #' by \code{\link{summary}} on each -- multi-run -- NMF result: #' \sQuote{all}, \sQuote{cophenetic}, \sQuote{rss}, #' \sQuote{residuals}, \sQuote{dispersion}, \sQuote{evar}, #' \sQuote{silhouette} (and more specific *.coef, *.basis, *.consensus), #' \sQuote{sparseness} (and more specific *.coef, *.basis). #' It specifies which measure must be plotted (\code{what='all'} plots #' all the measures). #' @param na.rm single logical that specifies if the rank for which the #' measures are NA values should be removed from the graph or not (default to #' \code{FALSE}). This is useful when plotting results which include NAs due #' to error during the estimation process. See argument \code{stop} for #' \code{nmfEstimateRank}. #' @param xname,yname legend labels for the curves corresponding to measures from #' \code{x} and \code{y} respectively #' @param xlab x-axis label #' @param ylab y-axis label #' @param main main title #' #' @method plot NMF.rank #' @export #' @rdname nmfEstimateRank #' @import ggplot2 #' @import reshape2 plot.NMF.rank <- function(x, y=NULL, what=c('all', 'cophenetic', 'rss', 'residuals' , 'dispersion', 'evar', 'sparseness' , 'sparseness.basis', 'sparseness.coef' , 'silhouette' , 'silhouette.coef', 'silhouette.basis' , 'silhouette.consensus') , na.rm=FALSE , xname = 'x' , yname = 'y' , xlab = 'Factorization rank' , ylab = '' , main = 'NMF rank survey' , ... ){ # trick for convenience if( is.character(y) && missing(what) ){ what <- y y <- NULL } what <- match.arg(what, several.ok=TRUE) if( 'all' %in% what ){ what <- c('cophenetic', 'rss', 'residuals', 'dispersion', 'evar', 'sparseness', 'silhouette') } .getvals <- function(x, xname){ measures <- x$measures iwhat <- unlist(lapply(paste('^',what,sep=''), grep, colnames(measures))) # remove NA values if required if( na.rm ) measures <- measures[ apply(measures, 1, function(row) !any(is.na(row[iwhat]))), ] vals <- measures[,iwhat, drop=FALSE] x <- as.numeric(measures$rank) xlim <- range(x) # define measure type measure.type <- setNames(rep('Best fit', ncol(measures)), colnames(measures)) cons.measures <- c('silhouette.consensus', 'cophenetic', 'cpu.all') measure.type[match(cons.measures, names(measure.type))] <- 'Consensus' measure.type[grep("\\.coef$", names(measure.type))] <- 'Coefficients' measure.type[grep("\\.basis$", names(measure.type))] <- 'Basis' measure.type <- factor(measure.type) pdata <- melt(cbind(rank = x, vals), id.vars = 'rank') # set measure type pdata$Type <- measure.type[as.character(pdata$variable)] # define measure groups pdata$Measure <- gsub("^([^.]+).*", "\\1", pdata$variable) pdata$Data <- xname pdata } pdata <- .getvals(x, xname) # add reference data if( is(y, 'NMF.rank') ){ pdata.y <- .getvals(y, yname) pdata <- rbind(pdata, pdata.y) } p <- ggplot(pdata, aes_string(x = 'rank', y = 'value')) + geom_line( aes_string(linetype = 'Data', colour = 'Type') ) + geom_point(size = 2, aes_string(shape = 'Data', colour = 'Type') ) + theme_bw() + scale_x_continuous(xlab, breaks = unique(pdata$rank)) + scale_y_continuous(ylab) + ggtitle(main) # remove legend if not necessary if( !is(y, 'NMF.rank') ){ p <- p + scale_shape(guide = 'none') + scale_linetype(guide = 'none') } # use fix set of colors myColors <- brewer.pal(5,"Set1") names(myColors) <- levels(pdata$Type) p <- p + scale_colour_manual(name = "Measure type", values = myColors) # add facet p <- p + facet_wrap( ~ Measure, scales = 'free') # return plot p } NMF/R/options.R0000644000176200001440000001343613620502674012725 0ustar liggesusers###% Options management ###% ###% ###% @author Renaud Gaujoux \email{renaud@@cbio.uct.ac.za} ###% #.nmf.Options.Runtime <- character() # define functions nmf.options and nmf.getOptions #' NMF Package Specific Options #' #' @section Available options: #' \describe{ #' #' \item{cores}{Default number of cores to use to perform parallel NMF computations. #' Note that this option is effectively used only if the global option \code{'cores'} is #' not set. #' Moreover, the number of cores can also be set at runtime, in the call to \code{\link{nmf}}, #' via arguments \code{.pbackend} or \code{.options} (see \code{\link{nmf}} for more details).} #' #' \item{default.algorithm}{Default NMF algorithm used by the \code{nmf} function when argument #' \code{method} is missing. #' The value should the key of one of the registered NMF algorithms or a valid specification of an NMF algorithm. #' See \code{?nmfAlgorithm}.} #' #' \item{default.seed}{Default seeding method used by the \code{nmf} function when argument \code{seed} is missing. #' The value should the key of one of the registered seeding methods or a vallid specification of a seeding method. #' See \code{?nmfSeed}.} #' #' \item{track}{Toggle default residual tracking. #' When \code{TRUE}, the \code{nmf} function compute and store the residual track in the result -- if not otherwise specified in argument \code{.options}. #' Note that tracking may significantly slow down the computations.} #' #' \item{track.interval}{Number of iterations between two points in the residual track. #' This option is relevant only when residual tracking is enabled. #' See \code{?nmf}.} #' #' \item{error.track}{this is a symbolic link to option \code{track} for backward compatibility.} #' #' \item{pbackend}{Default loop/parallel foreach backend used by the \code{nmf} function when #' argument \code{.pbackend} is missing. #' Currently the following values are supported: \code{'par'} for multicore, #' \code{'seq'} for sequential, \code{NA} for standard \code{sapply} (i.e. do not use a foreach loop), #' \code{NULL} for using the currently registered foreach backend.} #' #' \item{parallel.backend}{this is a symbolic link to option \code{pbackend} for backward compatibility.} #' #' \item{gc}{Interval/frequency (in number of runs) at which garbage collection is performed.} #' #' \item{verbose}{Default level of verbosity.} #' #' \item{debug}{Toogles debug mode. #' In this mode the console output may be very -- very -- messy, and is aimed at debugging only.} #' #' \item{maxIter}{ Default maximum number of iteration to use (default NULL). #' This option is for internal/technical usage only, to globally speed up examples or tests #' of NMF algorithms. To be used with care at one's own risk... #' It is documented here so that advanced users are aware of its existence, and can avoid possible #' conflict with their own custom options. #' } #' } % end description #' #' #' @rdname options #' @name options-NMF NULL .OPTIONS <- setupPackageOptions( # default algorithm default.algorithm='brunet' # default seeding method , default.seed='random' # track error during NMF updates , error.track = option_symlink('track') # for backward compatibility , track=FALSE # define the tracking interval , track.interval=30 # define garbage collection interval , gc=50 # define default parallel backend , parallel.backend= option_symlink('pbackend') # for backward compatibility , pbackend= if( parallel::detectCores() > 1 ) 'par' else 'seq' # toogle verbosity , verbose=FALSE # toogle debug mode , debug=FALSE , RESET=TRUE) #' \code{nmf.options} sets/get single or multiple options, that are specific #' to the NMF package. #' It behaves in the same way as \code{\link[base]{options}}. #' #' @inheritParams base::options #' @param ... option specifications. For \code{nmf.options} this can be named arguments or #' a single unnamed argument that is a named list (see \code{\link{options}}. #' #' For \code{nmf.resetOptions}, this must be the names of the options to reset. #' Note that \pkg{pkgmaker} version >= 0.9.1 is required for this to work correctly, #' when options other than the default ones have been set after the package is loaded. #' #' @export #' @rdname options #' @examples #' #' # show all NMF specific options #' nmf.printOptions() #' #' # get some options #' nmf.getOption('verbose') #' nmf.getOption('pbackend') #' # set new values #' nmf.options(verbose=TRUE) #' nmf.options(pbackend='mc', default.algorithm='lee') #' nmf.printOptions() #' #' # reset to default #' nmf.resetOptions() #' nmf.printOptions() #' nmf.options <- .OPTIONS$options #' \code{nmf.getOption} returns the value of a single option, that is specific #' to the NMF package. #' It behaves in the same way as \code{\link[base]{getOption}}. #' #' @inheritParams base::getOption #' #' @export #' @rdname options nmf.getOption <- .OPTIONS$getOption #' \code{nmf.resetOptions} reset all NMF specific options to their default values. #' #' @param ALL logical that indicates if options that are not part of the default set #' of options should be removed. #' Note that in \pkg{pkgmaker <= 0.9} this argument is only taken into account when #' no other argument is present. This is fixed in version 0.9.1. #' #' @export #' @rdname options nmf.resetOptions <- .OPTIONS$resetOptions #' \code{nmf.printOptions} prints all NMF specific options along with their default values, #' in a relatively compact way. #' @export #' @rdname options nmf.printOptions <- .OPTIONS$printOptions #nmf.options.runtime <- function(){ # nmf.options(.nmf.Options.Runtime) #} # debugging utility nmf.debug <- function(fun, ...){ if( nmf.getOption('debug') ){ call.stack <- sys.calls() n <- length(call.stack) if( is.null(fun) ) fun <- as.character(call.stack[[n-1]]) message('DEBUG::', fun, ' -> ', ...) } return(invisible()) } NMF/R/NMFstd-class.R0000644000176200001440000001756513620502674013477 0ustar liggesusers# Class that implements the standard NMF model # # Author: Renaud Gaujoux \email{renaud@@cbio.uct.ac.za} ############################################################################### #' @include NMF-class.R NULL #' NMF Model - Standard model #' #' This class implements the standard model of Nonnegative Matrix #' Factorization. #' It provides a general structure and generic functions to manage #' factorizations that follow the standard NMF model, as defined by #' \cite{Lee2001}. #' #' Let \eqn{V} be a \eqn{n \times m} non-negative matrix and \eqn{r} a positive #' integer. In its standard form (see references below), a NMF of \eqn{V} is #' commonly defined as a pair of matrices \eqn{(W, H)} such that: #' #' \deqn{V \equiv W H,} #' #' where: #' \itemize{ #' \item \eqn{W} and \eqn{H} are \eqn{n \times r} and \eqn{r #' \times m} matrices respectively with non-negative entries; #' \item \eqn{\equiv} is to be understood with respect to some loss function. #' Common choices of loss functions are based on Frobenius norm or Kullback-Leibler #' divergence. #' } #' #' Integer \eqn{r} is called the \emph{factorization rank}. #' Depending on the context of application of NMF, the columns of \eqn{W} #' and \eqn{H} are given different names: #' \describe{ #' \item{columns of \code{W}}{basis vector, metagenes, factors, source, image basis} #' \item{columns of \code{H}}{mixture coefficients, metagene sample expression profiles, weights} #' \item{rows of \code{H}}{basis profiles, metagene expression profiles} #' } #' #' NMF approaches have been successfully applied to several fields. #' The package NMF was implemented trying to use names as generic as possible #' for objects and methods. #' #' The following terminology is used: #' \describe{ #' \item{samples}{the columns of the target matrix \eqn{V}} #' \item{features}{the rows of the target matrix \eqn{V}} #' \item{basis matrix}{the first matrix factor \eqn{W}} #' \item{basis vectors}{the columns of first matrix factor \eqn{W}} #' \item{mixture matrix}{the second matrix factor \eqn{H}} \item{mixtures #' coefficients}{the columns of second matrix factor \eqn{H}} #' } #' #' However, because the package NMF was primarily implemented to work with gene #' expression microarray data, it also provides a layer to easily and #' intuitively work with objects from the Bioconductor base framework. #' See \link{bioc-NMF} for more details. #' #' @slot W A \code{matrix} that contains the basis matrix, i.e. the \emph{first} #' matrix factor of the factorisation #' @slot H A \code{matrix} that contains the coefficient matrix, i.e. the #' \emph{second} matrix factor of the factorisation #' @slot bterms a \code{data.frame} that contains the primary data that #' define fixed basis terms. See \code{\link{bterms}}. #' @slot ibterms integer vector that contains the indexes of the basis components #' that are fixed, i.e. for which only the coefficient are estimated. #' #' IMPORTANT: This slot is set on construction of an NMF model via #' \code{\link[=nmfModel,formula,ANY-method]{nmfModel}} and is not recommended to #' not be subsequently changed by the end-user. #' @slot cterms a \code{data.frame} that contains the primary data that #' define fixed coefficient terms. See \code{\link{cterms}}. #' @slot icterms integer vector that contains the indexes of the basis components #' that have fixed coefficients, i.e. for which only the basis vectors are estimated. #' #' IMPORTANT: This slot is set on construction of an NMF model via #' \code{\link[=nmfModel,formula,ANY-method]{nmfModel}} and is not recommended to #' not be subsequently changed by the end-user. #' #' @export #' @family NMF-model #' @examples #' # create a completely empty NMFstd object #' new('NMFstd') #' #' # create a NMF object based on one random matrix: the missing matrix is deduced #' # Note this only works when using factory method NMF #' n <- 50; r <- 3; #' w <- rmatrix(n, r) #' nmfModel(W=w) #' #' # create a NMF object based on random (compatible) matrices #' p <- 20 #' h <- rmatrix(r, p) #' nmfModel(W=w, H=h) #' #' # create a NMF object based on incompatible matrices: generate an error #' h <- rmatrix(r+1, p) #' try( new('NMFstd', W=w, H=h) ) #' try( nmfModel(w, h) ) #' #' # Giving target dimensions to the factory method allow for coping with dimension #' # incompatibilty (a warning is thrown in such case) #' nmfModel(r, W=w, H=h) #' setClass('NMFstd' , representation( W = 'matrix' # basis matrix , H = 'matrix' # mixture coefficients matrix , bterms = 'data.frame' # fixed basis terms: nrow(bterms) = nrow(x) , ibterms = 'integer' # index of the fixed basis terms , cterms = 'data.frame' # fixed coef terms: ncol(cterms) = ncol(x) , icterms = 'integer' # index of the fixed coefficient terms ) , prototype = prototype( W = matrix(as.numeric(NA), 0, 0), H = matrix(as.numeric(NA), 0, 0) ) , validity = function(object){ # dimension compatibility: W and H must be compatible for matrix multiplication if( ncol(object@W) != nrow(object@H) ){ return(paste('Dimensions of W and H are not compatible [ncol(W)=', ncol(object@W) , '!= nrow(H)=', nrow(object@H), ']')) } # give a warning if the dimensions look strange: rank greater than the number of samples if( !is.empty.nmf(object) && ncol(object@H) && ncol(object@W) > ncol(object@H) ){ warning(paste('Dimensions of W and H look strange [ncol(W)=', ncol(object@W) , '> ncol(H)=', ncol(object@H), ']')) } # everything went fine: return TRUE return(TRUE) } , contains = 'NMF' ) #' Get the basis matrix in standard NMF models #' #' This function returns slot \code{W} of \code{object}. #' #' @examples #' # random standard NMF model #' x <- rnmf(3, 10, 5) #' basis(x) #' coef(x) #' #' # set matrix factors #' basis(x) <- matrix(1, nrow(x), nbasis(x)) #' coef(x) <- matrix(1, nbasis(x), ncol(x)) #' # set random factors #' basis(x) <- rmatrix(basis(x)) #' coef(x) <- rmatrix(coef(x)) #' #' # incompatible matrices generate an error: #' try( coef(x) <- matrix(1, nbasis(x)-1, nrow(x)) ) #' # but the low-level method allow it #' .coef(x) <- matrix(1, nbasis(x)-1, nrow(x)) #' try( validObject(x) ) #' setMethod('.basis', 'NMFstd', function(object){ object@W } ) #' Set the basis matrix in standard NMF models #' #' This function sets slot \code{W} of \code{object}. setReplaceMethod('.basis', signature(object='NMFstd', value='matrix'), function(object, value){ object@W <- value object } ) #' Get the mixture coefficient matrix in standard NMF models #' #' This function returns slot \code{H} of \code{object}. setMethod('.coef', 'NMFstd', function(object){ object@H } ) #' Set the mixture coefficient matrix in standard NMF models #' #' This function sets slot \code{H} of \code{object}. setReplaceMethod('.coef', signature(object='NMFstd', value='matrix'), function(object, value){ object@H <- value object } ) #' Compute the target matrix estimate in \emph{standard NMF models}. #' #' The estimate matrix is computed as the product of the two matrix slots #' \code{W} and \code{H}: #' \deqn{\hat{V} = W H}{V ~ W H} #' #' @param W a matrix to use in the computation as the basis matrix in place of #' \code{basis(object)}. #' It must be compatible with the coefficient matrix used #' in the computation (i.e. number of columns in \code{W} = number of rows in \code{H}). #' @param H a matrix to use in the computation as the coefficient matrix in place of #' \code{coef(object)}. #' It must be compatible with the basis matrix used #' in the computation (i.e. number of rows in \code{H} = number of columns in \code{W}). #' #' @export #' @inline #' #' @examples #' # random standard NMF model #' x <- rnmf(3, 10, 5) #' all.equal(fitted(x), basis(x) %*% coef(x)) #' #' setMethod('fitted', signature(object='NMFstd'), function(object, W, H, ...){ if( missing(W) ) W <- object@W if( missing(H) ) H <- object@H return(W %*% H) } ) NMF/R/algorithms-siNMF.R0000644000176200001440000000635713620502674014361 0ustar liggesusers# Implementation of siNMF from Badea (2008) # # Author: Renaud Gaujoux # Creation: 09 Jul 2012 ############################################################################### #' @include registry-algorithms.R NULL siNMF_R <- function(i, v, data, beta0=1, scale=TRUE, ...){ # retrieve each factor w <- basis(data); h <- coef(data); # fixed terms nb <- nbterms(data); nc <- ncterms(data) if( i == 1 ){ if( !nc ) stop("Method 'siNMF' requires a formula based model") if( !is.na(beta0) ){ # compute beta gr <- as.numeric(cterms(data)[[1L]]) beta <- beta0 * (norm(v[,gr==1], 'F') / norm(v[,gr==2], 'F'))^2 # make sweeping vector vbeta <- rep(1, ncol(v)) vbeta[gr==2] <- sqrt(beta) staticVar('vbeta', vbeta, init=TRUE) # sweep data staticVar('v', sweep(v, 2L, vbeta, '*', check.margin=FALSE), init=TRUE) } # store non-fixed coef indexes staticVar('icoef', icoef(data), init=TRUE) } #precision threshold for numerical stability eps <- 10^-9 sh <- h if( !is.na(beta0) ){ # retrieved swept matrix sv <- staticVar('v') vbeta <- staticVar('vbeta') # sweep h with beta sh <- sweep(h, 2L, vbeta, '*', check.margin=FALSE) } # compute standard euclidean updates w <- nmf_update.euclidean.w(sv, w, sh, eps=eps, nbterms=nb, ncterms=nc, copy=TRUE) h <- nmf_update.euclidean.h(v, w, h, eps=eps, nbterms=nb, ncterms=nc, copy=TRUE) # normalize columns of w if( scale ){ icoef <- staticVar('icoef') wb <- w[, icoef] d <- sqrt(colSums(wb^2)) w[, icoef] <- sweep(wb, 2L, d, '/') h[icoef, ] <- sweep(h[icoef, ], 1L, d, '*') } .coef(data) <- h .basis(data) <- w data } setNMFMethod('.siNMF', 'lee', Update=siNMF_R) siNMF <- function(i, v, data, beta0=1, scale=TRUE, eps=10^-9, ...){ # retrieve each factor w <- basis(data); h <- coef(data); # fixed terms nb <- nbterms(data); nc <- ncterms(data) if( i == 1 ){ if( !nc ) stop("Method 'siNMF' requires a formula based model") vbeta <- NULL if( !is.na(beta0) ){ # compute beta gr <- cterms(data)[[1L]] gr <- droplevels(gr) # make sweeping vector vbeta <- rep(1, ncol(v)) idx <- split(1:ncol(v), gr) # compute base value from first level beta <- beta0 * norm(v[,idx[[1]]], 'F')^2 vbeta <- lapply(idx[-1], function(j){ rep(beta / norm(v[,j], 'F')^2, length(j)) }) vbeta <- c(rep(1, length(idx[[1]])), unlist(vbeta, use.names=FALSE)) vbeta <- vbeta[order(unlist(idx))] } # store weights staticVar('beta', vbeta, init=TRUE) # store non-fixed coef indexes staticVar('icoef', icoef(data), init=TRUE) } # retrieve weights beta <- staticVar('beta') # compute standard euclidean updates w <- nmf_update.euclidean.w(v, w, h, eps=eps, weight=beta, nbterms=nb, ncterms=nc, copy=FALSE) h <- nmf_update.euclidean.h(v, w, h, eps=eps, nbterms=nb, ncterms=nc, copy=FALSE) # normalize columns of w if( scale ){ icoef <- staticVar('icoef') wb <- w[, icoef] d <- sqrt(colSums(wb^2)) w[, icoef] <- sweep(wb, 2L, d, '/', check.margin=FALSE) h[icoef, ] <- sweep(h[icoef, ], 1L, d, '*', check.margin=FALSE) } .coef(data) <- h .basis(data) <- w data } nmfAlgorithm.siNMF <- setNMFMethod('siNMF', 'lee', Update=siNMF) NMF/R/data.R0000644000176200001440000000407213620502674012137 0ustar liggesusers# Description and generation of data # # Author: Renaud Gaujoux ############################################################################### #' Golub ExpressionSet #' #' This data comes originally from the gene expression data from \cite{Golub1999}. #' The version included in the package is the one used and referenced in \cite{Brunet2004}. #' The samples are from 27 patients with acute lymphoblastic leukemia (ALL) and #' 11 patients with acute myeloid leukemia (AML). #' #' The samples were assayed using Affymetrix Hgu6800 chips and the original #' data on the expression of 7129 genes (Affymetrix probes) are available on #' the Broad Institute web site (see references below). #' #' The data in \code{esGolub} were obtained from the web page related to #' the paper from \cite{Brunet2004}, which describes an application of #' Nonnegative Matrix Factorization to gene expression clustering. #' (see link in section \emph{Source}). #' #' They contain the 5,000 most highly varying genes according to their #' coefficient of variation, and were installed in an object of class #' \emph{ExpressionSet}. #' #' @format There are 3 covariates listed. #' #' \itemize{ #' #' \item Samples: The original sample labels. \item ALL.AML: Whether the #' patient had AML or ALL. It is a \code{\link{factor}} with levels #' \code{c('ALL', 'AML')}. \item Cell: ALL arises from two different types of #' lymphocytes (T-cell and B-cell). This specifies which for the ALL patients; #' There is no such information for the AML samples. It is a #' \code{\link{factor}} with levels \code{c('T-cell', 'B-cell', NA)}. #' #' } #' #' @source #' Web page for \cite{Brunet2004}:\cr #' \url{http://www.broadinstitute.org/publications/broad872} #' #' Original data from Golub et al.:\cr #' \url{http://www-genome.wi.mit.edu/mpr/data_set_ALL_AML.html} #' #' @name esGolub #' @docType data #' @keywords datasets #' @examples #' #' # requires package Biobase to be installed #' if(requireNamespace("Biobase", quietly=TRUE)){ #' #' data(esGolub) #' esGolub #' \dontrun{pData(esGolub)} #' #' } #' NULL NMF/R/rnmf.R0000644000176200001440000003254513620502674012176 0ustar liggesusers# Generation of Random NMF Models # # Author: Renaud Gaujoux # Creation: 03 Jul 2012 ############################################################################### #' @include nmfModel.R NULL .rnmf_fixed <- oneoffVariable('none') #' Generates a random NMF model of the same class and rank as another NMF model. #' #' This is the workhorse method that is eventually called by all other methods. #' It generates an NMF model of the same class and rank as \code{x}, compatible with the #' dimensions specified in \code{target}, that can be a single or 2-length #' numeric vector, to specify a square or rectangular target matrix respectively. #' #' The second dimension can also be passed via argument \code{ncol}, so that #' calling \code{rnmf(x, 20, 10, ...)} is equivalent to \code{rnmf(x, c(20, 10), ...)}, #' but easier to write. #' #' The entries are uniformly drawn between \code{0} and \code{max} #' (optionally specified in \code{...}) that defaults to 1. #' #' By default the dimnames of \code{x} are set on the returned NMF model. #' This behaviour is disabled with argument \code{keep.names=FALSE}. #' See \code{\link{nmfModel}}. #' #' @param ncol single numeric value that specifies the number of columns of the #' coefficient matrix. Only used when \code{target} is a single numeric value. #' @param keep.names a logical that indicates if the dimension names of the #' original NMF object \code{x} should be conserved (\code{TRUE}) or discarded #' (\code{FALSE}). #' @param dist specification of the random distribution to use to draw the entries #' of the basis and coefficient matrices. #' It may be specified as: #' \itemize{ #' #' \item a \code{function} which must be a distribution function such as e.g. #' \code{\link{runif}} that is used to draw the entries of both the basis and #' coefficient matrices. It is passed in the \code{dist} argument of #' \code{\link{rmatrix}}. #' #' \item a \code{list} of arguments that are passed internally to \code{\link{rmatrix}}, #' via \code{do.call('rmatrix', dist)}. #' #' \item a \code{character} string that is partially matched to \sQuote{basis} or #' \sQuote{coef}, that specifies which matrix in should be drawn randomly, the #' other remaining as in \code{x} -- unchanged. #' #' \item a \code{list} with elements \sQuote{basis} and/or \sQuote{coef}, which #' specify the \code{dist} argument separately for the basis and coefficient #' matrix respectively. #' #' These elements may be either a distribution function, or a list of arguments that #' are passed internally to \code{\link{rmatrix}}, via #' \code{do.call('rmatrix', dist$basis)} #' or \code{do.call('rmatrix', dist$coef)}. #' } #' #' @inline #' @examples #' #' ## random NMF of same class and rank as another model #' #' x <- nmfModel(3, 10, 5) #' x #' rnmf(x, 20) # square #' rnmf(x, 20, 13) #' rnmf(x, c(20, 13)) #' #' # using another distribution #' rnmf(x, 20, dist=rnorm) #' #' # other than standard model #' y <- rnmf(3, 50, 10, model='NMFns') #' y #' \dontshow{ stopifnot( identical(dim(y), c(50L,10L,3L)) ) } #' \dontshow{ stopifnot( is(y, 'NMFns') ) } #' setMethod('rnmf', signature(x='NMF', target='numeric'), function(x, target, ncol=NULL, keep.names=TRUE, dist=runif){ # store original dimnames if( keep.names ) dn <- dimnames(x) # valid parameter 'target' if( length(target) != 1 && length(target) != 2 ) stop('NMF::rnmf - invalid target dimensions [length must be 1 or 2. Here length = ', length(target) ,']') if( any(is.na(target)) ) stop('NMF::rnmf - invalid target dimensions [NA values in element(s): ', paste(which(is.na(target)), collapse=' and '), ']') # shortcut for symetric case: provide only one dimension if( length(target) == 1L ){ ncol <- if( !is.null(ncol) ){ if( !is.numeric(ncol) || length(ncol) != 1 || is.na(ncol) ) stop("NMF::rnmf - invalid argument `ncol`: must be a single numeric value") ncol }else target target <- c(target, ncol) } # retrieve dimension of the target matrix n <- target[1]; m <- target[2]; # retrieve the factorization rank r <- nbasis(x) ## draw basis and coef matrices # interpret argument dist if( length(dist) == 0L ) dist <- runif if( is.character(dist) ){ dist <- match.arg(dist, c('basis', 'coef')) dist <- setNames(list(runif), dist) } if( is.function(dist) ){ dist <- list(basis = list(x=n, y=r, dist=dist) , coef = list(x=r, y=m, dist=dist)) }else if( is.list(dist) ){ if( !all(names(dist) %in% c('basis', 'coef')) ){ dist <- list(basis=c(list(x=n, y=r), dist) , coef=c(list(x=r, y=m), dist)) }else{ if( !is.null(dist$basis) ) dist$basis <- c(list(x=n, y=r), dist$basis) if( !is.null(dist$coef) ) dist$coef <- c(list(x=r, y=m), dist$coef) } } fixed <- .rnmf_fixed() #Vc# Initialize random matrix: W # NB: this will keep the values of fixed basis terms if( !is.null(dist$basis) && !('basis' %in% fixed) ){ basis(x) <- do.call('rmatrix', dist$basis); } #Vc# Initialize random matrix: H # NB: this will keep the values of fixed coef terms if( !is.null(dist$coef) && !('coef' %in% fixed) ){ coef(x) <- do.call('rmatrix', dist$coef); } # if one needs to keep the names (possibly or reducing/increasing) if( keep.names && !is.null(dn) ) dimnames(x) <- list(dn[[1]][1:n], dn[[2]][1:m], dn[[3]][1:r]) # return the modified object x } ) #' Generates a random NMF model compatible and consistent with a target matrix. #' #' The entries are uniformly drawn between \code{0} and \code{max(target)}. #' It is more or less a shortcut for: #' \samp{ rnmf(x, dim(target), max=max(target), ...)} #' #' It returns an NMF model of the same class as \code{x}. #' #' @param use.dimnames a logical that indicates whether the dimnames of the #' target matrix should be set on the returned NMF model. #' #' @inline #' #' @examples #' # random NMF compatible with a target matrix #' x <- nmfModel(3, 10, 5) #' y <- rmatrix(20, 13) #' rnmf(x, y) # rank of x #' rnmf(2, y) # rank 2 #' setMethod('rnmf', signature(x='ANY', target='matrix'), function(x, target, ..., dist=list(max=max(max(target, na.rm=TRUE), 1)), use.dimnames=TRUE){ # build a random NMF with the dimensions of the target matrix upper-bounded by the target's maximum entry. res <- rnmf(x, dim(target), ..., dist=dist) # compute the upper-bound of the random entries and enforce it if possible no.na <- abs(target[!is.na(target)]) if( length(no.na) > 0 ){ m <- max(no.na) basis(res) <- pmin(basis(res), m) coef(res) <- pmin(coef(res), m) } # set the dimnames from the target matrix if necessary if( use.dimnames ) dimnames(res) <- dimnames(target) # return result res } ) #' Shortcut for \code{rnmf(x, as.matrix(target))}. setMethod('rnmf', signature(x='ANY', target='data.frame'), function(x, target, ...){ rnmf(x, as.matrix(target), ...) } ) #' Generates a random NMF model of the same dimension as another NMF model. #' #' It is a shortcut for \code{rnmf(x, nrow(x), ncol(x), ...)}, which returns #' a random NMF model of the same class and dimensions as \code{x}. #' #' @examples #' ## random NMF from another model #' #' a <- nmfModel(3, 100, 20) #' b <- rnmf(a) #' \dontshow{ stopifnot( !nmf.equal(a,b) ) } #' setMethod('rnmf', signature(x='NMF', target='missing'), function(x, target, ...){ rnmf(x, c(nrow(x),ncol(x)), ...) } ) #' Generates a random NMF model of a given rank, with known basis and/or #' coefficient matrices. #' #' This methods allow to easily generate partially random NMF model, where one #' or both factors are known. #' Although the later case might seems strange, it makes sense for NMF models that #' have fit extra data, other than the basis and coefficient matrices, that #' are drawn by an \code{rnmf} method defined for their own class, which should #' internally call \code{rnmf,NMF,numeric} and let it draw the basis and #' coefficient matrices. #' (e.g. see \code{\linkS4class{NMFOffset}} and \code{\link{rnmf,NMFOffset,numeric-method}}). #' #' Depending on whether arguments \code{W} and/or \code{H} are missing, #' this method interprets \code{x} differently: #' \itemize{ #' #' \item \code{W} provided, \code{H} missing: \code{x} is taken as the number of #' columns that must be drawn to build a random coefficient matrix #' (i.e. the number of columns in the target matrix). #' #' \item \code{W} is missing, \code{H} is provided: \code{x} is taken as the number of #' rows that must be drawn to build a random basis matrix #' (i.e. the number of rows in the target matrix). #' #' \item both \code{W} and \code{H} are provided: \code{x} is taken as the target #' rank of the model to generate. #' \item Having both \code{W} and \code{H} missing produces an error, as the #' dimension of the model cannot be determined in this case. #' } #' #' The matrices \code{W} and \code{H} are reduced if necessary and possible #' to be consistent with this value of the rank, by the internal call to #' \code{\link{nmfModel}}. #' #' All arguments in \code{...} are passed to the function \code{\link{nmfModel}} #' which is used to build an initial NMF model, that is in turn passed to #' \code{rnmf,NMF,numeric} with \code{dist=list(coef=dist)} or #' \code{dist=list(basis=dist)} when suitable. #' The type of NMF model to generate can therefore be specified in argument #' \code{model} (see \code{\link{nmfModel}} for other possible arguments). #' #' The returned NMF model, has a basis matrix equal to \code{W} (if not missing) #' and a coefficient matrix equal to \code{H} (if not missing), or drawn #' according to the specification provided in argument \code{dist} #' (see method \code{rnmf,NMF,numeric} for details on the supported values for \code{dist}). #' #' @examples #' # random NMF model with known basis matrix #' x <- rnmf(5, W=matrix(1:18, 6)) # 6 x 5 model with rank=3 #' basis(x) # fixed #' coef(x) # random #' #' # random NMF model with known coefficient matrix #' x <- rnmf(5, H=matrix(1:18, 3)) # 5 x 6 model with rank=3 #' basis(x) # random #' coef(x) # fixed #' #' # random model other than standard NMF #' x <- rnmf(5, H=matrix(1:18, 3), model='NMFOffset') #' basis(x) # random #' coef(x) # fixed #' offset(x) # random #' setMethod('rnmf', signature(x='numeric', target='missing'), function(x, target, ..., W, H, dist=runif){ # get fixed matrices to restore on exit: # one must enforce honouring the fixed matrices to prevent the call to # rnmf from a sub-class method to change them. of <- .rnmf_fixed() on.exit( .rnmf_fixed(of) ) if( !missing(W) && missing(H) ){ # fixed basis matrix: x = n samples # one must not change the values H .rnmf_fixed('basis') x <- nmfModel(ncol(W), nrow(W), x, W=W, ...) dist <- list(coef=dist) }else if( missing(W) && !missing(H) ){ # fixed coef matrix: x = n features # one must not change the values H .rnmf_fixed('coef') x <- nmfModel(nrow(H), x, ncol(H), H=H, ...) dist <- list(basis=dist) }else if( !missing(W) && !missing(H) ){ # fixed basis and coef: x = rank # one must not change the values of W and H .rnmf_fixed(c('basis', 'coef')) x <- nmfModel(x, nrow(W), ncol(H), W=W, H=H, ...) }else stop("NMF::rnmf - Missing both arguments `W` and/or `H`: at least one of them must be specified.") rnmf(x, dist=dist) } ) #' Generates a random NMF model with known basis and coefficient matrices. #' #' This method is a shortcut for calling \code{rnmf,numeric,missing} with a #' suitable value for \code{x} (the rank), when both factors are known: #' code{rnmf(min(ncol(W), nrow(H)), ..., W=W, H=H)}. #' #' Arguments \code{W} and \code{H} are required. #' Note that calling this method only makes sense for NMF models that contains #' data to fit other than the basis and coefficient matrices, #' e.g. \code{\linkS4class{NMFOffset}}. #' #' @examples #' #' # random model other than standard NMF #' x <- rnmf(W=matrix(1:18, 6), H=matrix(21:38, 3), model='NMFOffset') #' basis(x) # fixed #' coef(x) # fixed #' offset(x) # random #' setMethod('rnmf', signature(x='missing', target='missing'), function(x, target, ..., W, H){ rnmf(min(ncol(W), nrow(H)), ..., W=W, H=H) } ) #' Generates a random standard NMF model of given dimensions. #' #' This is a shortcut for \code{rnmf(nmfModel(x, target, ncol, ...)), dist=dist)}. #' It generates a standard NMF model compatible with the dimensions passed in #' \code{target}, that can be a single or 2-length numeric vector, to specify #' a square or rectangular target matrix respectively. #' See \code{\link{nmfModel}}. #' #' @inheritParams nmfModel,numeric,numeric-method #' #' @examples #' #' ## random standard NMF of given dimensions #' #' # generate a random NMF model with rank 3 that fits a 100x20 matrix #' rnmf(3, 100, 20) #' \dontshow{ stopifnot( identical(dim(rnmf(3, 100, 20)), c(100L,20L,3L)) ) } #' # generate a random NMF model with rank 3 that fits a 100x100 matrix #' rnmf(3, 100) #' \dontshow{ stopifnot( identical(dim(rnmf(3, 100)), c(100L,100L,3L)) ) } #' setMethod('rnmf', signature(x='numeric', target='numeric'), function(x, target, ncol=NULL, ..., dist=runif){ rnmf(nmfModel(x, target, ncol, ...), dist=dist) } ) #' Generate a random formula-based NMF model, using the method #' \code{\link{nmfModel,formula,ANY-method}}. setMethod('rnmf', signature(x='formula', target='ANY'), function(x, target, ..., dist=runif){ # missing target is NULL if( missing(target) ) target <- NULL rnmf(nmfModel(x, target, ...), dist=dist) } ) NMF/R/registry.R0000644000176200001440000000760613620502674013104 0ustar liggesusers###% Define access/setup methods for NMF package registry. ###% ###% The registry is used to provide a common interface to NMF methods (algorithms, seeding methods, distance, ...). ###% It enables the user to add custom methods which will be accessible in the same way as the built-in ones. ###% ###% @author Renaud Gaujoux ###% @created 22 Jul 2009 ########################################################################### # COMMON REGISTRY ########################################################################### #' @import pkgmaker #' @import registry nmfRegistry <- function(...) pkgmaker::packageRegistry(...) # Returns the names of all the packages that contibute to all or a given # package's primary registry registryContributors <- function(package, regname = NULL){ regs <- packageRegistries(regname = regname, package = package, primary = TRUE) if( length(regs) ) unique(names(unlist(lapply(paste0(package, '::', regs), packageRegistries)))) } ###% Return a method stored in the NMF registry. ###% ###% @param name the key (a character string) of the method to be retrieved ###% @param regname the name of the sub-registry where to look for the \code{key} ###% @param exact a boolean. When set to \code{TRUE} the key is searched exactly, otherwise (default) the key ###% is matched partially with the keys registered in the registry. ###% @param error a boolean. When set to \code{TRUE} (default) the function will raise an error if the key is not found. ###% Otherwise it will not raise any error and return \code{NULL}. ###% nmfGet <- function(regname, name=NULL, ...){ # retrieve from the given package's sub-registry pkgmaker::pkgreg_fetch(regname, key=name, ...) } ###% Register a NMF method so that it is accessible via the common interface defined by the \code{nmf} function. ###% @param method an NMFStrategy object or a function that defines the method ###% @param key a non-empty character string that will be used as an identifier to access the method ###% @param overwrite a boolean that specify if an existing method (i.e. with exactly the same \code{key}) should be overwritten or not. ###% If \code{FALSE} and a method with the same key exists, an error will be thrown. ###% @param save [Not used] a boolean that if set to \code{TRUE} will save in database so that it is available in other R sessions. ###% @param ... [Not used] ###% ###% @return \code{TRUE} invisibly in case of success. ###% ###% @seealso nmf ###% setGeneric('nmfRegister', function(key, method, ...) standardGeneric('nmfRegister') ) setMethod('nmfRegister', signature(key='character'), function(key, method, regname, ...){ #TODO: add functionality to save the registered strategy into a file for use is other R sessions parent.method <- attr(method, 'parent') tmpl <- if( !is.null(parent.method) && parent.method != key ){ str_c(" based on template '", parent.method, "'") } setPackageRegistryEntry(regname, key, method, ..., where='NMF', msg=tmpl) } ) ####% Unregister a NMF method. ####% ####% @param name the key of the method to unregister [character string] ####% #nmfUnregister <- function(name, regname, quiet=FALSE){ # # return( pkgreg_remove(regname, key=name, quiet=quiet) ) # # add the strategy to the registry # obj <- nmfGet(name, exact=TRUE, error=FALSE, regname=regname) # regentry <- nmfRegistry(regname, entry=TRUE) # registry <- regentry$regobj # objtype <- regentry$entrydesc # # if( !is.null(obj) ){ # # get the method registry and the method's fullname # name <- attr(strategy, 'name') # # if( !quiet ){ # msg <- paste0("Removing ", objtype," '", name, "' from registry '", regname, "' [", class(obj), ']') # message(msg, ' ... ', appendLF=FALSE) # } # # delete from registry # registry$delete_entry(name) # if( !quiet ) message('OK') # TRUE # }else{ # if( !quiet ) # warning("Could not remove ", objtype, " '", name, "': no matching registry entry.", call.=FALSE) # FALSE # } #} NMF/R/seed-base.R0000644000176200001440000000060713620502674013056 0ustar liggesusers# Basic NMF seeding methods # # Author: Renaud Gaujoux ############################################################################### #' @include registry-seed.R NULL ## Register base seeding methods # None: do nothing and return object unchanged setNMFSeed('none', function(object, x, ...){object}, overwrite=TRUE) # Random: use function rnmf setNMFSeed('random', rnmf, overwrite=TRUE) NMF/R/NMFOffset-class.R0000644000176200001440000001145713620502674014125 0ustar liggesusers#' @include NMFstd-class.R NULL #' NMF Model - Nonnegative Matrix Factorization with Offset #' #' This class implements the \emph{Nonnegative Matrix Factorization with #' Offset} model, required by the NMF with Offset algorithm. #' #' The NMF with Offset algorithm is defined by \cite{Badea2008} as a modification #' of the euclidean based NMF algorithm from \code{Lee2001} (see section Details and #' references below). #' It aims at obtaining 'cleaner' factor matrices, by the introduction of an #' offset matrix, explicitly modelling a feature specific baseline #' -- constant across samples. #' #' @section Creating objects from the Class: #' #' Object of class \code{NMFOffset} can be created using the standard way with #' operator \code{\link{new}} #' #' However, as for all NMF model classes -- that extend class #' \code{\linkS4class{NMF}}, objects of class \code{NMFOffset} should be #' created using factory method \code{\link{nmfModel}} : #' #' \code{new('NMFOffset')} #' #' \code{nmfModel(model='NMFOffset')} #' #' \code{nmfModel(model='NMFOffset', W=w, offset=rep(1, nrow(w)))} #' #' See \code{\link{nmfModel}} for more details on how to use the factory #' method. #' #' @export #' @family NMF-model #' @examples #' #' # create a completely empty NMF object #' new('NMFOffset') #' #' # create a NMF object based on random (compatible) matrices #' n <- 50; r <- 3; p <- 20 #' w <- rmatrix(n, r) #' h <- rmatrix(r, p) #' nmfModel(model='NMFOffset', W=w, H=h, offset=rep(0.5, nrow(w))) #' #' # apply Nonsmooth NMF algorithm to a random target matrix #' V <- rmatrix(n, p) #' \dontrun{nmf(V, r, 'offset')} #' #' # random NMF model with offset #' rnmf(3, 10, 5, model='NMFOffset') #' setClass('NMFOffset' , representation( offset = 'numeric' # offset vector ) , contains = 'NMFstd' , prototype=prototype( offset = numeric() ) ) #' Show method for objects of class \code{NMFOffset} #' @export setMethod('show', 'NMFOffset', function(object) { callNextMethod() cat("offset: ") if( length(object@offset) > 0 ){ cat('[', head(object@offset, 5) , if( length(object@offset) > 5 ) "..." else NULL , ']') } else cat('none') cat("\n") } ) #' @section Initialize method: #' The initialize method for \code{NMFOffset} objects tries to correct the initial #' value passed for slot \code{offset}, so that it is consistent with the dimensions #' of the \code{NMF} model: #' it will pad the offset vector with NA values to get the length equal to the #' number of rows in the basis matrix. #' #' @param offset optional numeric vector used to initialise slot \sQuote{offset}. #' #' @rdname NMFOffset-class setMethod("initialize", 'NMFOffset', function(.Object, ..., offset){ .Object <- callNextMethod() # correct the offset slot if possible if( missing(offset) ) offset <- numeric() if( !is.numeric(offset) ) stop("Unvalid value for parameter 'offset': a numeric vector is expected") # force length to be consistent with the factorization's dimension n <- nrow(.Object) if( n > 0 ) .Object@offset <- c( offset, rep(NA, max(0, n - length(offset))) )[1:n] # return the initialized valid object .Object } ) #' @export setGeneric('offset', package='stats') #' Offsets in NMF Models with Offset #' #' The function \code{offset} returns the offset vector from an NMF model #' that has an offset, e.g. an \code{NMFOffset} model. #' @param object an instance of class \code{NMFOffset}. #' setMethod('offset', signature(object='NMFOffset'), function(object){ object@offset } ) #' Computes the target matrix estimate for an NMFOffset object. #' #' The estimate is computed as: #' \deqn{ W H + offset } #' #' @param offset offset vector #' @inline setMethod('fitted', signature(object='NMFOffset'), function(object, W, H, offset=object@offset){ if( missing(W) ) W <- object@W if( missing(H) ) H <- object@H object@W %*% object@H + offset } ) #' Generates a random NMF model with offset, from class \code{NMFOffset}. #' #' The offset values are drawn from a uniform distribution between 0 and #' the maximum entry of the basis and coefficient matrices, which are drawn #' by the next suitable \code{\link{rnmf}} method, which is the workhorse #' method \code{rnmf,NMF,numeric}. #' #' @examples #' #' # random NMF model with offset #' x <- rnmf(2, 3, model='NMFOffset') #' x #' offset(x) #' # from a matrix #' x <- rnmf(2, rmatrix(5,3, max=10), model='NMFOffset') #' offset(x) #' setMethod('rnmf', signature(x='NMFOffset', target='numeric'), function(x, target, ...){ # call the parent's 'rnmf' method to build a standard random NMF factorization res <- callNextMethod() #Vc# Initialize a random offset of length the number of genes res@offset <- runif(nrow(res), min=0, max=max(basis(res), coef(res))); # return the initialized NMFOffset object res }) NMF/R/fixed-terms.R0000644000176200001440000002620013620502674013452 0ustar liggesusers# Interface for NMF models that contain fixed terms # # Author: Renaud Gaujoux # Creation: 03 Jul 2012 ############################################################################### #' @include NMF-class.R #' @include NMFstd-class.R NULL #' Concatenating NMF Models #' #' Binds compatible matrices and NMF models together. #' #' @param x an NMF model #' @param ... other objects to concatenate. Currently only two objects at a time #' can be concatenated (i.e. \code{x} and \code{..1}). #' @param margin integer that indicates the margin along which to concatenate #' (only used when \code{..1} is a matrix): #' \describe{ #' \item{1L}{} #' \item{2L}{} #' \item{3L}{} #' \item{4L}{} #' } #' If missing the margin is heuristically determined by looking at common #' dimensions between the objects. #' #' @keywords internal setMethod('c', 'NMF', function(x, ..., margin=3L, recursive=FALSE){ y <- ..1 if( is.matrix(y) ){ if( missing(margin) ){ if( nrow(y) == nrow(x) ){ if( ncol(y) == ncol(x) ){ warning("NMF::`c` - Right argument match both target dimensions: concatenating basis columns." , " Use `margin=4L` to concatenate coefficient rows.") } margin <- 3L }else if( ncol(y) == ncol(x) ){ margin <- 4L }else{ stop("NMF::`c` - Incompatible argument dimensions: could not infer concatenation margin.") } } if( margin == 1L ){ # extend basis vectors if( nbterms(x) ){ # cannot extend models with fixed basis terms stop("NMF::`c` - Could not extend basis vectors:" , " NMF model has fixed basis terms [", nbterms(x), "]") } if( ncol(y) != nbasis(x) ){ stop("NMF::`c` - Could not extend basis vectors:" , " incompatible number of columns [", nbasis(x), '!=', ncol(y), "].") } # extend basis vectors basis(x) <- rbind(basis(x), y) } else if( margin == 2L ){ # extend basis profiles if( ncterms(x) ){ # cannot extend models with fixed coef terms stop("NMF::`c` - Could not extend basis profiles:" , " NMF model has fixed coefficient terms [", ncterms(x), "]") } if( nrow(y) != nbasis(x) ){ stop("NMF::`c` - Could not extend basis profiles:" , " incompatible number of rows [", nbasis(x), '!=', nrow(y), "].") } # extend basis profiles coef(x) <- cbind(coef(x), y) } else if( margin == 3L ){ # add basis vectors if( nrow(y) != nrow(x) ){ stop("NMF::`c` - Could not concatenate basis vectors:" , " incompatible number of rows [", nrow(x), '!=', nrow(y), "].") } # bind basis terms .basis(x) <- cbind(basis(x), y) dn <- colnames(.basis(x)) # bind dummy coef .coef(x) <- rbind(coef(x), matrix(NA, ncol(y), ncol(x))) basisnames(x) <- dn } else if( margin == 4L ){ # add basis profiles if( ncol(y) != ncol(x) ){ stop("NMF::`c` - Could not concatenate basis profiles:" , " incompatible number of columns [", ncol(x), '!=', ncol(y), "].") } # bind coef terms .coef(x) <- rbind(coef(x), y) dn <- rownames(.coef(x)) # bind dummy basis .basis(x) <- cbind(basis(x), matrix(NA, nrow(x), nrow(y))) basisnames(x) <- dn }else{ stop("NMF::`c` - Invalid concatenation margin: should be either" , " 1L (basis rows), 2L (coef columns), 3L (basis vectors/columns) or 4L (basis profiles/coef rows).") } }else if( is.nmf(y) ){ # check dimensions if( nrow(x) != nrow(y) ) stop("NMF::`c` - Could not concatenate NMF objects:" , " incompatible number of rows [", nrow(x), '!=', nrow(y), "]") if( ncol(x) != ncol(y) ) stop("NMF::`c` - Could not concatenate NMF objects:" , " incompatible number of columns [", ncol(x), '!=', ncol(y), "]") .basis(x) <- cbind(basis(x), basis(y)) .coef(x) <- rbind(coef(x), coef(y)) }else{ stop("NMF::`c` - Concatenation of an NMF object with objects of class '", class(y), "' is not supported.") } # return augmented object x } ) fterms <- function(value){ res <- list(n=0L, terms=NULL, df=NULL, i=integer()) if( !length(value) ) return(res) # convert into a data.frame if( is.factor(value) ) value <- data.frame(Group=value) else if( is.numeric(value) ) value <- data.frame(Var=value) else if( !is.data.frame(value) ) value <- as.data.frame(value) res$n <- length(value) res$df <- value # generate fixed term matrix terms <- model.matrix(~ -1 + ., data=value) res$terms <- terms # build indexes res$i <- 1:ncol(terms) res } ## #' Annotations in NMF Models ## #' ## #' NMF models may contain annotations for columns/rows and/or rows/features, in ## #' a similar way gene expression data are annotated ## #' \code{\linkS4class{ExpressionSet}} objects in Bioconductor. ## #' ## NULL #' Fixed Terms in NMF Models #' #' These functions are for internal use and should not be called by the end-user. #' #' They use \code{\link{model.matrix}(~ -1 + ., data=value)} to generate suitable #' term matrices. #' #' @param object NMF object to be updated. #' @param value specification of the replacement value for fixed-terms. #' #' @rdname terms-internal #' @keywords internal #' @inline setGeneric('bterms<-', function(object, value) standardGeneric('bterms<-')) #' Default method tries to coerce \code{value} into a \code{data.frame} with #' \code{\link{as.data.frame}}. setReplaceMethod('bterms', signature('NMFstd', 'ANY'), function(object, value){ if( nterms(object) ){ stop("Cannot set fixed basis terms on an object that already has fixed terms:", " these can be set only once and before setting any fixed coefficient term", " [coef=", ncterms(object), ", basis=", nbterms(object), "].") } # build terms t <- fterms(value) if( !t$n ) return(object) # check dimension if( nrow(t$terms) != nrow(object) ){ stop("Invalid fixed basis terms: all terms should have length the number of target rows" , "[terms=", nrow(t$terms), " != ", nrow(object), "=target]") } # set data object@bterms <- t$df # set indexes i <- t$i nv <- nbasis(object) object@ibterms <- nv + i # set terms object <- c(object, t$terms, margin=3L) object } ) #' \code{cterms<-} sets fixed coefficient terms or indexes and should only be #' called on a newly created NMF object, i.e. in the constructor/factory generic #' \code{\link{nmfModel}}. #' #' @rdname terms-internal #' @inline setGeneric('cterms<-', function(object, value) standardGeneric('cterms<-')) #' Default method tries to coerce \code{value} into a \code{data.frame} with #' \code{\link{as.data.frame}}. setReplaceMethod('cterms', signature('NMFstd', 'ANY'), function(object, value){ if( ncterms(object) ){ stop("Cannot set fixed coef terms on an object that already has fixed coef terms:", " these can be set only once", " [coef=", ncterms(object), ", basis=", nbterms(object), "].") } # build terms t <- fterms(value) if( !t$n ) return(object) # check dimension if( nrow(t$terms) != ncol(object) ){ stop("Invalid fixed coefficient terms: all terms should have length the number of target columns" , "[terms=", nrow(t$terms), " != ", ncol(object), "=target]") } # transpose term matrix t$terms <- t(t$terms) # set data object@cterms <- t$df # set indexes i <- t$i nv <- nbasis(object) object@icterms <- nv + i # set terms object <- c(object, t$terms, margin=4L) object } ) #' Fixed Terms in NMF Models #' #' @description #' Formula-based NMF models may contain fixed basis and/or coefficient terms. #' The functions documented here provide access to these data, which are #' read-only and defined when the model object is instantiated #' (e.g., see \code{\link[=nmfModel,formula,ANY-method]{nmfModel,formula-method}}). #' #' \code{ibterms}, \code{icterms} and \code{iterms} respectively return the #' indexes of the fixed basis terms, the fixed coefficient terms and all fixed #' terms, within the basis and/or coefficient matrix of an NMF model. #' #' @param object NMF object #' @param ... extra parameters to allow extension (currently not used) #' #' @export #' @rdname terms setGeneric('ibterms', function(object, ...) standardGeneric('ibterms') ) #' Default pure virtual method that ensure a method is defined for concrete #' NMF model classes. setMethod('ibterms', 'NMF', function(object, ...){ stop("NMF::ibterms is a pure virtual method of interface 'NMF'." ," It should be overloaded in class '", class(object),"'.") } ) #' Method for standard NMF models, which returns the integer vector that is #' stored in slot \code{ibterms} when a formula-based NMF model is instantiated. setMethod('ibterms', 'NMFstd', function(object){ object@ibterms } ) #' @export #' @rdname terms setGeneric('icterms', function(object, ...) standardGeneric('icterms') ) #' Default pure virtual method that ensure a method is defined for concrete #' NMF model classes. setMethod('icterms', 'NMF', function(object, ...){ stop("NMF::icterms is a pure virtual method of interface 'NMF'." ," It should be overloaded in class '", class(object),"'.") } ) #' Method for standard NMF models, which returns the integer vector that is #' stored in slot \code{icterms} when a formula-based NMF model is instantiated. setMethod('icterms', 'NMFstd', function(object){ object@icterms } ) #' @export #' @rdname terms iterms <- function(object, ...){ c(ibterms(object), icterms(object)) } #' \code{nterms}, \code{nbterms}, and \code{ncterms} return, respectively, #' the number of all fixed terms, fixed basis terms and fixed coefficient terms #' in an NMF model. #' In particular: i.e. \code{nterms(object) = nbterms(object) + ncterms(object)}. #' @export #' @rdname terms nterms <- function(object){ length(ibterms(object)) + length(icterms(object)) } #' @export #' @rdname terms nbterms <- function(object){ length(ibterms(object)) } #' @export #' @rdname terms ncterms <- function(object){ length(icterms(object)) } #' \code{bterms} and \code{cterms} return, respectively, the primary data for #' fixed basis and coefficient terms in an NMF model -- as stored in slots #' \code{bterms} and \code{cterms} . #' These are factors or numeric vectors which define fixed basis components, #' e.g., used for defining separate offsets for different \emph{a priori} groups #' of samples, or to incorporate/correct for some known covariate. #' #' @export #' @rdname terms bterms <- function(object){ object@bterms } #' @export #' @rdname terms cterms <- function(object){ object@cterms } #' \code{ibasis} and \code{icoef} return, respectively, the #' indexes of all latent basis vectors and estimated coefficients within the #' basis or coefficient matrix of an NMF model. #' @export #' @rdname terms ibasis <- function(object, ...){ i <- 1:nbasis(object) if( length(idx <- ibterms(object, ...)) ) i[-idx] else i } #' @export #' @rdname terms icoef <- function(object, ...){ i <- 1:nbasis(object) if( length(idx <- icterms(object, ...)) ) i[-idx] else i } #' @export t.NMFstd <- function(x){ # transpose and swap factors x <- t.NMF(x) # swap fixed terms bt <- bterms(x) ibt <- ibterms(x) x@bterms <- cterms(x) x@ibterms <- icterms(x) x@cterms <- bt x@icterms <- ibt # returns x } NMF/R/registry-seed.R0000644000176200001440000000744513620502674014023 0ustar liggesusers# Registry for NMF seeding method # # Author: Renaud Gaujoux ############################################################################### #' @include registry.R #' @include NMFSeed-class.R NULL # create sub-registry for seeding methods .registrySeed <- setPackageRegistry('seed', "NMFSeed" , description = "Initialization methods for NMF algorithms" , entrydesc = 'NMF seeding method') nmfSeedInfo <- function(show=TRUE){ obj <- .registrySeed if( show ) print(obj) invisible(obj) } #' Seeding Strategies for NMF Algorithms #' #' \code{nmfSeed} lists and retrieves NMF seeding methods. #' #' Currently the internal registry contains the following seeding methods, #' which may be specified to the function \code{\link{nmf}} via its argument #' \code{seed} using their access keys: #' #' \describe{ #' \item{random}{ The entries of each factors are drawn from a uniform #' distribution over \eqn{[0, max(x)]}, where $x$ is the target matrix.} #' \item{nndsvd}{ Nonnegative Double Singular Value Decomposition. #' #' The basic algorithm contains no randomization and is based on two SVD processes, #' one approximating the data matrix, the other approximating positive sections #' of the resulting partial SVD factors utilising an algebraic property of #' unit rank matrices. #' #' It is well suited to initialise NMF algorithms with sparse factors. #' Simple practical variants of the algorithm allows to generate dense factors. #' #' \strong{Reference:} \cite{Boutsidis2008}} #' \item{ica}{ Uses the result of an Independent Component Analysis (ICA) #' (from the \code{fastICA} package). #' Only the positive part of the result are used to initialise the factors.} #' \item{none}{ Fixed seed. #' #' This method allows the user to manually provide initial values for #' both matrix factors.} #' } #' #' @param name access key of a seeding method stored in registry. #' If missing, \code{nmfSeed} returns the list of all available seeding methods. #' @param ... extra arguments used for internal calls #' #' @export #' #' @examples #' #' # list all registered seeding methods #' nmfSeed() #' # retrieve one of the methods #' nmfSeed('ica') #' nmfSeed <- function(name=NULL, ...){ nmfGet('seed', name, ...) } #' \code{getNMFSeed} is an alias for \code{nmfSeed}. #' @rdname nmfSeed #' @export getNMFSeed <- nmfSeed #' \code{existsNMFSeed} tells if a given seeding method exists in the registry. #' #' @param exact a logical that indicates if the access key should be matched #' exactly or partially. #' #' @rdname nmfSeed #' @export existsNMFSeed <- function(name, exact=TRUE){ res <- !is.null( getNMFSeed(name, error=FALSE, exact=exact) ) return(res) } # specific register method for registering NMFSeed objects setMethod('nmfRegister', signature(key='NMFSeed', method='missing'), function(key, method, ...){ nmfRegister(name(key), key, ..., regname='seed') } ) #' Registering NMF Seeding Methods #' #' NMF seeding methods are registered via the function \code{setNMFSeed}, which #' stores them as \code{\linkS4class{NMFSeed}} objects in a dedicated registry. #' #' @param ... arguments passed to \code{NMFSeed} and used to initialise slots #' in the \code{\linkS4class{NMFSeed}} object, or to \code{\link[pkgmaker]{pkgreg_remove}}. #' @inheritParams setNMFMethod #' #' @export setNMFSeed <- function(..., overwrite=isLoadingNamespace(), verbose=TRUE){ # wrap function method into a new NMFSeed object method <- NMFSeed(...) # register the newly created object res <- nmfRegister(method, overwrite=overwrite, verbose=verbose) } nmfRegisterSeed <- setNMFSeed #' \code{removeNMFSeed} removes an NMF seeding method from the registry. #' #' @param name name of the seeding method. #' #' @export #' @rdname setNMFSeed removeNMFSeed <- function(name, ...){ pkgreg_remove('seed', key=name, ...) } NMF/R/run.R0000644000176200001440000000411013620502674012023 0ustar liggesusers# Functions produce reports # # Author: Renaud Gaujoux # Created: 23 Jul 2013 ############################################################################### #' Run NMF Methods and Generate a Report #' #' Generates an HTML report from running a set of method on a given #' target matrix, for a set of factorization ranks. #' #' The report is based on an .Rmd document \code{'report.Rmd'} stored in #' the package installation sub-directory \code{scripts/}, and is compiled #' using \pkg{knitr}. #' #' At the beginning of the document, a file named \code{'functions.R'} is #' looked for in the current directory, and sourced if present. #' This enables the definition of custom NMF methods (see \code{\link{setNMFMethod}}) #' or setting global options. #' #' @param x target matrix #' @param rank factorization rank #' @param method list of methods to apply #' @param colClass reference class to assess accuracy #' @param ... extra paramters passed to \code{\link{nmf}} #' @param output output HTML file #' @param template template Rmd file #' #' @return a list with the following elements: #' \item{fits}{the fit(s) for each method and each value of the rank.} #' \item{accuracy}{a data.frame that contains the summary assessment measures, #' for each fit.} #' #' @export #' @examples #' #' \dontrun{ #' #' x <- rmatrix(20, 10) #' gr <- gl(2, 5) #' nmfReport(x, 2:4, method = list('br', 'lee'), colClass = gr, nrun = 5) #' #' } nmfReport <- function(x, rank, method, colClass = NULL, ..., output = NULL, template = NULL){ requireNamespace('knitr') #library(knitr) if( is.null(template) ) template <- system.file('scripts/report.Rmd', package = 'NMF') x <- force(x) rank <- force(rank) method <- force(method) if( isString(method) ) method <- list(method) args <- list(...) nmfRun <- function(x, rank, method, ...){ args <- expand_dots(args) str(args) do.call(nmf, c(list(x, rank, method), args)) } accuracy <- NA res <- NA knitr::knit2html(template) res <- list(fits = res, accuracy = accuracy) saveRDS(res, file = 'report_results.rds') invisible(res) } NMF/R/algorithms-snmf.R0000644000176200001440000006056713620502674014353 0ustar liggesusers#' @include registry-algorithms.R NULL #' Fast Combinatorial Nonnegative Least-Square #' #' This function solves the following nonnegative least square linear problem #' using normal equations and the fast combinatorial strategy from \cite{VanBenthem2004}: #' #' \deqn{ #' \begin{array}{l} #' \min \|Y - X K\|_F\\ #' \mbox{s.t. } K>=0 #' \end{array} #' }{min ||Y - X K||_F, s.t. K>=0} #' #' where \eqn{Y} and \eqn{X} are two real matrices of dimension \eqn{n \times p}{n x p} #' and \eqn{n \times r}{n x r} respectively, #' and \eqn{\|.\|_F}{|.|_F} is the Frobenius norm. #' #' The algorithm is very fast compared to other approaches, as it is optimised #' for handling multiple right-hand sides. #' #' @details #' Within the \code{NMF} package, this algorithm is used internally by the #' SNMF/R(L) algorithm from \cite{KimH2007} to solve general Nonnegative #' Matrix Factorization (NMF) problems, using alternating nonnegative #' constrained least-squares. #' That is by iteratively and alternatively estimate each matrix factor. #' #' The algorithm is an active/passive set method, which rearrange the #' right-hand side to reduce the number of pseudo-inverse calculations. #' It uses the unconstrained solution \eqn{K_u} obtained from the #' unconstrained least squares problem, #' i.e. \eqn{\min \|Y - X K\|_F^2}{min ||Y - X K||_F^2} , so as to determine #' the initial passive sets. #' #' The function \code{fcnnls} is provided separately so that it can be #' used to solve other types of nonnegative least squares problem. #' For faster computation, when multiple nonnegative least square fits #' are needed, it is recommended to directly use the function \code{\link{.fcnnls}}. #' #' The code of this function is a port from the original MATLAB code #' provided by \cite{KimH2007}. #' #' @inheritParams .fcnnls #' @param ... extra arguments passed to the internal function \code{.fcnnls}. #' Currently not used. #' @return A list containing the following components: #' #' \item{x}{ the estimated optimal matrix \eqn{K}.} \item{fitted}{ the fitted #' matrix \eqn{X K}.} \item{residuals}{ the residual matrix \eqn{Y - X K}.} #' \item{deviance}{ the residual sum of squares between the fitted matrix #' \eqn{X K} and the target matrix \eqn{Y}. That is the sum of the square #' residuals.} \item{passive}{ a \eqn{r x p} logical matrix containing the #' passive set, that is the set of entries in \eqn{K} that are not null (i.e. #' strictly positive).} \item{pseudo}{ a logical that is \code{TRUE} if the #' computation was performed using the pseudoinverse. See argument #' \code{pseudo}.} #' #' @seealso \code{\link{nmf}} #' @references #' #' Original MATLAB code from Van Benthem and Keenan, slightly modified by H. #' Kim:\cr \url{http://www.cc.gatech.edu/~hpark/software/fcnnls.m} #' #' @author #' Original MATLAB code : Van Benthem and Keenan #' #' Adaption of MATLAB code for SNMF/R(L): H. Kim #' #' Adaptation to the NMF package framework: Renaud Gaujoux #' #' @keywords optimize multivariate regression #' @export #' @inline #' @examples #' #' ## Define a random nonnegative matrix matrix #' n <- 200; p <- 20; r <- 3 #' V <- rmatrix(n, p) #' #' ## Compute the optimal matrix K for a given X matrix #' X <- rmatrix(n, r) #' res <- fcnnls(X, V) #' #' ## Compute the same thing using the Moore-Penrose generalized pseudoinverse #' res <- fcnnls(X, V, pseudo=TRUE) #' #' ## It also works in the case of single vectors #' y <- runif(n) #' res <- fcnnls(X, y) #' # or #' res <- fcnnls(X[,1], y) #' #' setGeneric('fcnnls', function(x, y, ...) standardGeneric('fcnnls') ) #' This method wraps a call to the internal function \code{.fcnnls}, and #' formats the results in a similar way as other lest-squares methods such #' as \code{\link{lm}}. #' #' @param verbose toggle verbosity (default is \code{FALSE}). #' setMethod('fcnnls', signature(x='matrix', y='matrix'), function(x, y, verbose=FALSE, pseudo=TRUE, ...){ # load corpcor if necessary if( isTRUE(pseudo) ){ library(corpcor) } # call the internal function res <- .fcnnls(x, y, verbose=verbose, pseudo=pseudo, ...) # process the result f <- x %*% res$coef resid <- y - f # set dimnames if( is.null(rownames(res$coef)) ) rownames(res$coef) <- colnames(x) # wrap up the result out <- list(x=res$coef, fitted=f, residuals=resid, deviance=norm(resid, 'F')^2, passive=res$Pset, pseudo=pseudo) class(out) <- 'fcnnls' out } ) #' Shortcut for \code{fcnnls(as.matrix(x), y, ...)}. setMethod('fcnnls', signature(x='numeric', y='matrix'), function(x, y, ...){ fcnnls(as.matrix(x), y, ...) } ) #' Shortcut for \code{fcnnls(x, as.matrix(y), ...)}. setMethod('fcnnls', signature(x='ANY', y='numeric'), function(x, y, ...){ fcnnls(x, as.matrix(y), ...) } ) #' @export print.fcnnls <- function(x, ...){ cat("\n") cat("Dimensions:", nrow(x$x)," x ", ncol(x$x), "\n") cat("Residual sum of squares:", x$deviance,"\n") cat("Active constraints:", length(x$passive)-sum(x$passive),"/", length(x$passive), "\n") cat("Inverse method:", if( isTRUE(x$pseudo) ) 'pseudoinverse (corpcor)' else if( is.function(x$pseudo) ) str_fun(x$pseudo) else 'QR (solve)', "\n") invisible(x) } ###% M. H. Van Benthem and M. R. Keenan, J. Chemometrics 2004; 18: 441-450 ###% ###% Given A and C this algorithm solves for the optimal ###% K in a least squares sense, using that ###% A = C*K ###% in the problem ###% min ||A-C*K||, s.t. K>=0, for given A and C. ###% ###% ###% @param C the matrix of coefficients ###% @param A the target matrix of observations ###% ###% @return [K, Pset] ###% #' Internal Routine for Fast Combinatorial Nonnegative Least-Squares #' #' @description #' This is the workhorse function for the higher-level function #' \code{\link{fcnnls}}, which implements the fast nonnegative least-square #' algorithm for multiple right-hand-sides from \cite{VanBenthem2004} to solve #' the following problem: #' #' \deqn{ #' \begin{array}{l} #' \min \|Y - X K\|_F\\ #' \mbox{s.t. } K>=0 #' \end{array} #' }{min ||Y - X K||_F, s.t. K>=0} #' #' where \eqn{Y} and \eqn{X} are two real matrices of dimension \eqn{n \times p}{n x p} #' and \eqn{n \times r}{n x r} respectively, #' and \eqn{\|.\|_F}{|.|_F} is the Frobenius norm. #' #' The algorithm is very fast compared to other approaches, as it is optimised #' for handling multiple right-hand sides. #' #' @param x the coefficient matrix #' @param y the target matrix to be approximated by \eqn{X K}. #' @param verbose logical that indicates if log messages should be shown. #' @param pseudo By default (\code{pseudo=FALSE}) the algorithm uses Gaussian #' elimination to solve the successive internal linear problems, using the #' \code{\link{solve}} function. If \code{pseudo=TRUE} the algorithm uses #' Moore-Penrose generalized \code{\link[corpcor]{pseudoinverse}} from the #' \code{corpcor} package instead of \link{solve}. #' @param eps threshold for considering entries as nonnegative. #' This is an experimental parameter, and it is recommended to #' leave it at 0. #' #' @return A list with the following elements: #' #' \item{coef}{the fitted coefficient matrix.} #' \item{Pset}{the set of passive constraints, as a logical matrix of #' the same size as \code{K} that indicates which element is positive.} #' #' @export .fcnnls <- function(x, y, verbose=FALSE, pseudo=FALSE, eps=0){ # check arguments if( any(dim(y) == 0L) ){ stop("Empty target matrix 'y' [", paste(dim(y), collapse=' x '), "]") } if( any(dim(x) == 0L) ){ stop("Empty regression variable matrix 'x' [", paste(dim(x), collapse=' x '), "]") } # map arguments C <- x A <- y # NNLS using normal equations and the fast combinatorial strategy # # I/O: [K, Pset] = fcnnls(C, A); # K = fcnnls(C, A); # # C is the nObs x lVar coefficient matrix # A is the nObs x pRHS matrix of observations # K is the lVar x pRHS solution matrix # Pset is the lVar x pRHS passive set logical array # # M. H. Van Benthem and M. R. Keenan # Sandia National Laboratories # # Pset: set of passive sets, one for each column # Fset: set of column indices for solutions that have not yet converged # Hset: set of column indices for currently infeasible solutions # Jset: working set of column indices for currently optimal solutions # # Check the input arguments for consistency and initializeerror(nargchk(2,2,nargin)) nObs = nrow(C); lVar = ncol(C); if ( nrow(A)!= nObs ) stop('C and A have imcompatible sizes') pRHS = ncol(A); W = matrix(0, lVar, pRHS); iter=0; maxiter=3*lVar; # Precompute parts of pseudoinverse #CtC = t(C)%*%C; CtA = t(C)%*%A; CtC = crossprod(C); CtA = crossprod(C,A); # Obtain the initial feasible solution and corresponding passive set K = .cssls(CtC, CtA, pseudo=pseudo); Pset = K > 0; K[!Pset] = 0; D = K; # which columns of Pset do not have all entries TRUE? Fset = which( colSums(Pset) != lVar ); #V+# Active set algorithm for NNLS main loop oitr=0; # HKim while ( length(Fset)>0 ) { oitr=oitr+1; if ( verbose && oitr > 5 ) cat(sprintf("%d ",oitr));# HKim #Vc# Solve for the passive variables (uses subroutine below) K[,Fset] = .cssls(CtC, CtA[,Fset, drop=FALSE], Pset[,Fset, drop=FALSE], pseudo=pseudo); # Find any infeasible solutions # subset Fset on the columns that have at least one negative entry Hset = Fset[ colSums(K[,Fset, drop=FALSE] < eps) > 0 ]; #V+# Make infeasible solutions feasible (standard NNLS inner loop) if ( length(Hset)>0 ){ nHset = length(Hset); alpha = matrix(0, lVar, nHset); while ( nHset>0 && (iter < maxiter) ){ iter = iter + 1; alpha[,1:nHset] = Inf; #Vc# Find indices of negative variables in passive set ij = which( Pset[,Hset, drop=FALSE] & (K[,Hset, drop=FALSE] < eps) , arr.ind=TRUE); i = ij[,1]; j = ij[,2] if ( length(i)==0 ) break; hIdx = (j - 1) * lVar + i; # convert array indices to indexes relative to a lVar x nHset matrix negIdx = (Hset[j] - 1) * lVar + i; # convert array indices to index relative to the matrix K (i.e. same row index but col index is stored in Hset) alpha[hIdx] = D[negIdx] / (D[negIdx] - K[negIdx]); alpha.inf <- alpha[,1:nHset, drop=FALSE] minIdx = max.col(-t(alpha.inf)) # get the indce of the min of each row alphaMin = alpha.inf[minIdx + (0:(nHset-1) * lVar)] alpha[,1:nHset] = matrix(alphaMin, lVar, nHset, byrow=TRUE); D[,Hset] = D[,Hset, drop=FALSE] - alpha[,1:nHset, drop=FALSE] * (D[,Hset, drop=FALSE]-K[,Hset, drop=FALSE]); idx2zero = (Hset - 1) * lVar + minIdx; # convert array indices to index relative to the matrix D D[idx2zero] = 0; Pset[idx2zero] = FALSE; K[, Hset] = .cssls(CtC, CtA[,Hset, drop=FALSE], Pset[,Hset, drop=FALSE], pseudo=pseudo); # which column of K have at least one negative entry? Hset = which( colSums(K < eps) > 0 ); nHset = length(Hset); } } #V-# #Vc# Make sure the solution has converged #if iter == maxiter, error('Maximum number iterations exceeded'), end # Check solutions for optimality W[,Fset] = CtA[,Fset, drop=FALSE] - CtC %*% K[,Fset, drop=FALSE]; # which columns have all entries non-positive Jset = which( colSums( (ifelse(!(Pset[,Fset, drop=FALSE]),1,0) * W[,Fset, drop=FALSE]) > eps ) == 0 ); Fset = setdiff(Fset, Fset[Jset]); if ( length(Fset) > 0 ){ #Vc# For non-optimal solutions, add the appropriate variable to Pset # get indice of the maximum in each column mxidx = max.col( t(ifelse(!Pset[,Fset, drop=FALSE],1,0) * W[,Fset, drop=FALSE]) ) Pset[ (Fset - 1) * lVar + mxidx ] = TRUE; D[,Fset] = K[,Fset, drop=FALSE]; } } #V-# # return K and Pset list(coef=K, Pset=Pset) } # ****************************** Subroutine**************************** #library(corpcor) .cssls <- function(CtC, CtA, Pset=NULL, pseudo=FALSE){ # use provided function if( is.function(pseudo) ){ pseudoinverse <- pseudo pseudo <- TRUE } # Solve the set of equations CtA = CtC*K for the variables in set Pset # using the fast combinatorial approach K = matrix(0, nrow(CtA), ncol(CtA)); if ( is.null(Pset) || length(Pset)==0 || all(Pset) ){ K <- (if( !pseudo ) solve(CtC) else pseudoinverse(CtC)) %*% CtA; # K = pseudoinverse(CtC) %*% CtA; #K=pinv(CtC)*CtA; }else{ lVar = nrow(Pset); pRHS = ncol(Pset); codedPset = as.numeric(2.^(seq(lVar-1,0,-1)) %*% Pset); sortedPset = sort(codedPset) sortedEset = order(codedPset) breaks = diff(sortedPset); breakIdx = c(0, which(breaks > 0 ), pRHS); for( k in seq(1,length(breakIdx)-1) ){ cols2solve = sortedEset[ seq(breakIdx[k]+1, breakIdx[k+1])]; vars = Pset[,sortedEset[breakIdx[k]+1]]; K[vars,cols2solve] <- (if( !pseudo ) solve(CtC[vars,vars, drop=FALSE]) else pseudoinverse(CtC[vars,vars, drop=FALSE])) %*% CtA[vars,cols2solve, drop=FALSE]; #K[vars,cols2solve] <- pseudoinverse(CtC[vars,vars, drop=FALSE])) %*% CtA[vars,cols2solve, drop=FALSE]; #TODO: check if this is the right way or needs to be reversed #K(vars,cols2solve) = pinv(CtC(vars,vars))*CtA(vars,cols2solve); } } # return K K } ###% ###% SNMF/R ###% ###% Author: Hyunsoo Kim and Haesun Park, Georgia Insitute of Technology ###% ###% Reference: ###% ###% Sparse Non-negative Matrix Factorizations via Alternating ###% Non-negativity-constrained Least Squares for Microarray Data Analysis ###% Hyunsoo Kim and Haesun Park, Bioinformatics, 2007, to appear. ###% ###% This software requires fcnnls.m, which can be obtained from ###% M. H. Van Benthem and M. R. Keenan, J. Chemometrics 2004; 18: 441-450 ###% ###% NMF: min_{W,H} (1/2) || A - WH ||_F^2 s.t. W>=0, H>=0 ###% SNMF/R: NMF with additional sparsity constraints on H ###% ###% min_{W,H} (1/2) (|| A - WH ||_F^2 + eta ||W||_F^2 ###% + beta (sum_(j=1)^n ||H(:,j)||_1^2)) ###% s.t. W>=0, H>=0 ###% ###% A: m x n data matrix (m: features, n: data points) ###% W: m x k basis matrix ###% H: k x n coefficient matrix ###% ###% function [W,H,i]=nmfsh_comb(A,k,param,verbose,bi_conv,eps_conv) ###% ###% input parameters: ###% A: m x n data matrix (m: features, n: data points) ###% k: desired positive integer k ###% param=[eta beta]: ###% eta (for supressing ||W||_F) ###% if eta < 0, software uses maxmum value in A as eta. ###% beta (for sparsity control) ###% Larger beta generates higher sparseness on H. ###% Too large beta is not recommended. ###% verbos: verbose = 0 for silence mode, otherwise print output ###% eps_conv: KKT convergence test (default eps_conv = 1e-4) ###% bi_conv=[wminchange iconv] biclustering convergence test ###% wminchange: the minimal allowance of the change of ###% row-clusters (default wminchange=0) ###% iconv: decide convergence if row-clusters (within wminchange) ###% and column-clusters have not changed for iconv convergence ###% checks. (default iconv=10) ###% ###% output: ###% W: m x k basis matrix ###% H: k x n coefficient matrix ###% i: the number of iterations ###% ###% sample usage: ###% [W,H]=nmfsh_comb(amlall,3,[-1 0.01],1); ###% [W,H]=nmfsh_comb(amlall,3,[-1 0.01],1,[3 10]); ###% -- in the convergence check, the change of row-clusters to ###% at most three rows is allowed. ###% ###% #function [W,H,i] nmf_snmf <- function(A, x, maxIter= nmf.getOption('maxIter') %||% 20000L, eta=-1, beta=0.01, bi_conv=c(0, 10), eps_conv=1e-4, version=c('R', 'L'), verbose=FALSE){ #nmfsh_comb <- function(A, k, param, verbose=FALSE, bi_conv=c(0, 10), eps_conv=1e-4, version=c('R', 'L')){ # depending on the version: # in version L: A is transposed while W and H are swapped and transposed version <- match.arg(version) if( version == 'L' ) A <- t(A) #if( missing(param) ) param <- c(-1, 0.01) m = nrow(A); n = ncol(A); erravg1 = numeric(); #eta=param[1]; beta=param[2]; maxA=max(A); if ( eta<0 ) eta=maxA; eta2=eta^2; # bi_conv if( length(bi_conv) != 2 ) stop("SNMF/", version, "::Invalid argument 'bi_conv' - value should be a 2-length numeric vector") wminchange=bi_conv[1]; iconv=bi_conv[2]; ## VALIDITY of parameters # eps_conv if( eps_conv <= 0 ) stop("SNMF/", version, "::Invalid argument 'eps_conv' - value should be positive") # wminchange if( wminchange < 0 ) stop("SNMF/", version, "::Invalid argument 'bi_conv' - bi_conv[1] (i.e 'wminchange') should be non-negative") # iconv if( iconv < 0 ) stop("SNMF/", version, "::Invalid argument 'bi_conv' - bi_conv[2] (i.e 'iconv') should be non-negative") # beta if( beta <=0 ) stop("SNMF/", version, "::Invalid argument 'beta' - value should be positive") ## # initialize random W if no starting point is given if( isNumber(x) ){ # rank is given by x k <- x message('# NOTE: Initialise W internally (runif)') W <- matrix(runif(m*k), m,k); x <- NULL } else if( is.nmf(x) ){ # rank is the number of basis components in x k <- nbasis(x) # seed the method (depends on the version to run) start <- if( version == 'R' ) basis(x) else t(coef(x)) # check compatibility of the starting point with the target matrix if( any(dim(start) != c(m,k)) ) stop("SNMF/", version, " - Invalid initialization - incompatible dimensions [expected: ", paste(c(m,k), collapse=' x '),", got: ", paste(dim(start), collapse=' x '), " ]") # use the supplied starting point W <- start }else{ stop("SNMF/", version, ' - Invalid argument `x`: must be a single numeric or an NMF model [', class(x), ']') } if ( verbose ) cat(sprintf("--\nAlgorithm: SNMF/%s\nParameters: k=%d eta=%.4e beta (for sparse H)=%.4e wminchange=%d iconv=%d\n", version, k,eta,beta,wminchange,iconv)); idxWold=rep(0, m); idxHold=rep(0, n); inc=0; # check validity of seed if( any(NAs <- is.na(W)) ) stop("SNMF/", version, "::Invalid initialization - NAs found in the ", if(version=='R') 'basis (W)' else 'coefficient (H)' , " matrix [", sum(NAs), " NAs / ", length(NAs), " entries]") # normalize columns of W W= apply(W, 2, function(x) x / sqrt(sum(x^2)) ); I_k=diag(eta, k); betavec=rep(sqrt(beta), k); nrestart=0; i <- 0L while( i < maxIter){ i <- i + 1L # min_h ||[[W; 1 ... 1]*H - [A; 0 ... 0]||, s.t. H>=0, for given A and W. res = .fcnnls(rbind(W, betavec), rbind(A, rep(0, n))); H = res[[1]] if ( any(rowSums(H)==0) ){ if( verbose ) cat(sprintf("iter%d: 0 row in H eta=%.4e restart!\n",i,eta)); nrestart=nrestart+1; if ( nrestart >= 10 ){ warning("NMF::snmf - Too many restarts due to too big 'beta' value [Computation stopped after the 9th restart]"); break; } # re-initialize random W idxWold=rep(0, m); idxHold=rep(0, n); inc=0; erravg1 <- numeric();# re-initialize base average error W=matrix(runif(m*k), m,k); W= apply(W, 2, function(x) x / sqrt(sum(x^2)) ); # normalize columns of W next; } # min_w ||[H'; I_k]*W' - [A'; 0]||, s.t. W>=0, for given A and H. res = .fcnnls(rbind(t(H), I_k), rbind(t(A), matrix(0, k,m))); Wt = res[[1]] W= t(Wt); # track the error (not computed unless tracking option is enabled in x) if( !is.null(x) ) x <- trackError(x, .snmf.objective(A, W, H, eta, beta), niter=i) # test convergence every 5 iterations OR if the base average error has not been computed yet if ( (i %% 5==0) || (length(erravg1)==0) ){ # indice of maximum for each row of W idxW = max.col(W) # indice of maximum for each column of H idxH = max.col(t(H)) changedW=sum(idxW != idxWold); changedH=sum(idxH != idxHold); if ( (changedW<=wminchange) && (changedH==0) ) inc=inc+1 else inc=0 resmat=pmin(H, crossprod(W) %*% H - t(W) %*% A + matrix(beta, k , k) %*% H); resvec=as.numeric(resmat); resmat=pmin(W, W %*% tcrossprod(H) - A %*% t(H) + eta2 * W); resvec=c(resvec, as.numeric(resmat)); conv=sum(abs(resvec)); #L1-norm convnum=sum(abs(resvec)>0); erravg=conv/convnum; # compute base average error if necessary if ( length(erravg1)==0 ) erravg1=erravg; if ( verbose && (i %% 1000==0) ){ # prints number of changing elements if( i==1000 ) cat("Track:\tIter\tInc\tchW\tchH\t---\terravg1\terravg\terravg/erravg1\n") cat(sprintf("\t%d\t%d\t%d\t%d\t---\terravg1: %.4e\terravg: %.4e\terravg/erravg1: %.4e\n", i,inc,changedW,changedH,erravg1,erravg,erravg/erravg1)); } #print(list(inc=inc, iconv=iconv, erravg=erravg, eps_conv=eps_conv, erravg1=erravg1)) if ( (inc>=iconv) && (erravg<=eps_conv*erravg1) ) break; idxWold=idxW; idxHold=idxH; } } if( verbose ) cat("--\n") # force to compute last error if not already done if( !is.null(x) ) x <- trackError(x, .snmf.objective(A, W, H, eta, beta), niter=i, force=TRUE) # transpose and reswap the roles if( !is.null(x) ){ if( version == 'L' ){ .basis(x) <- t(H) .coef(x) <- t(W) } else{ .basis(x) <- W .coef(x) <- H } # set number of iterations performed niter(x) <- i return(x) }else{ res <- list(W=W, H=H) if( version == 'L' ){ res$W <- t(H) res$H <- t(W) } return(invisible(res)) } } ###% Computes the objective value for the SNMF algorithm .snmf.objective <- function(target, w, h, eta, beta){ 1/2 * ( sum( (target - (w %*% h))^2 ) + eta * sum(w^2) + beta * sum( colSums( h )^2 ) ) } snmf.objective <- function(x, y, eta=-1, beta=0.01){ .snmf.objective(y, .basis(x), .coef(x), eta, beta) } ###% Wrapper function to use the SNMF/R algorithm with the NMF package. ###% .snmf <- function(target, seed, maxIter=20000L, eta=-1, beta=0.01, bi_conv=c(0, 10), eps_conv=1e-4, ...){ # retrieve the version of SNMF algorithm from its name: # it is defined by the last letter in the method's name (in upper case) name <- algorithm(seed) version <- toupper(substr(name, nchar(name), nchar(name))) # perform factorization using Kim and Park's algorithm ca <- match.call() ca[[1L]] <- as.name('nmf_snmf') # target ca[['A']] <- ca[['target']] ca[['target']] <- NULL # seed ca[['x']] <- ca[['seed']] ca[['seed']] <- NULL # version ca[['version']] <- version # verbose ca[['verbose']] <- verbose(seed) e <- parent.frame() sol <- eval(ca, envir=e) # nmf_snmf(target, seed, ..., version = version, verbose = verbose(seed)) # return solution return(sol) } #' NMF Algorithm - Sparse NMF via Alternating NNLS #' #' NMF algorithms proposed by \cite{KimH2007} that enforces sparsity #' constraint on the basis matrix (algorithm \sQuote{SNMF/L}) or the #' mixture coefficient matrix (algorithm \sQuote{SNMF/R}). #' #' The algorithm \sQuote{SNMF/R} solves the following NMF optimization problem on #' a given target matrix \eqn{A} of dimension \eqn{n \times p}{n x p}: #' \deqn{ #' \begin{array}{ll} #' & \min_{W,H} \frac{1}{2} \left(|| A - WH ||_F^2 + \eta ||W||_F^2 #' + \beta (\sum_{j=1}^p ||H_{.j}||_1^2)\right)\\ #' s.t. & W\geq 0, H\geq 0 #' \end{array} #' }{ #' min_{W,H} 1/2 (|| A - WH ||_F^2 + eta ||W||_F^2 #' + beta (sum_j ||H[,j]||_1^2)) #' #' s.t. W>=0, H>=0 #' } #' #' The algorithm \sQuote{SNMF/L} solves a similar problem on the transposed target matrix \eqn{A}, #' where \eqn{H} and \eqn{W} swap roles, i.e. with sparsity constraints applied to \code{W}. #' #' @param maxIter maximum number of iterations. #' @param eta parameter to suppress/bound the L2-norm of \code{W} and in #' \code{H} in \sQuote{SNMF/R} and \sQuote{SNMF/L} respectively. #' #' If \code{eta < 0}, then it is set to the maximum value in the target matrix is used. #' @param beta regularisation parameter for sparsity control, which #' balances the trade-off between the accuracy of the approximation and the #' sparseness of \code{H} and \code{W} in \sQuote{SNMF/R} and \sQuote{SNMF/L} respectively. #' #' Larger beta generates higher sparseness on \code{H} (resp. \code{W}). #' Too large beta is not recommended. #' @param bi_conv parameter of the biclustering convergence test. #' It must be a size 2 numeric vector \code{bi_conv=c(wminchange, iconv)}, #' with: #' \describe{ #' \item{\code{wminchange}:}{the minimal allowance of change in row-clusters.} #' \item{\code{iconv}:}{ decide convergence if row-clusters #' (within the allowance of \code{wminchange}) #' and column-clusters have not changed for \code{iconv} convergence checks.} #' } #' #' Convergence checks are performed every 5 iterations. #' @param eps_conv threshold for the KKT convergence test. #' @param ... extra argument not used. #' #' @rdname SNMF-nmf #' @aliases SNMF/R-nmf nmfAlgorithm.SNMF_R <- setNMFMethod('snmf/r', .snmf, objective=snmf.objective) #' @aliases SNMF/L-nmf #' @rdname SNMF-nmf nmfAlgorithm.SNMF_L <- setNMFMethod('snmf/l', .snmf, objective=snmf.objective) NMF/R/versions.R0000644000176200001440000001330413620502674013074 0ustar liggesusers# Tracking/Updating S4 class versions # # Author: Renaud Gaujoux ############################################################################### #' @include utils.R NULL objectUpdater <- local({ .REGISTRY <- list() function(x, version=NULL, fun=NULL, vfun=NULL, verbose=FALSE){ if( missing(x) ) return( .REGISTRY ) if( is.null(version) ){ cl <- class(x) UPDATER <- .REGISTRY[[cl]] vmsg <- 'Class' if( is.character(verbose) ){ vmsg <- paste(verbose, ':', sep='') verbose <- TRUE } if( verbose ) message("# ", vmsg, " '", cl, "' ... ", appendLF=FALSE) if( !isS4(x) ){ if( verbose) message("NO") return(x) } # create new object from old slots newObject <- if( verbose ){ message() updateObjectFromSlots(x, verbose=verbose>1) }else suppressWarnings( updateObjectFromSlots(x, verbose=verbose>1) ) if( is.null(UPDATER) ){ if( verbose) message("AUTO") return(newObject) } # find object version v <- sapply(UPDATER, function(f) f$vfun(x)) v <- which(v) if( !length(v) ){ if( verbose) message("SKIP [version unknown]") return(newObject) } if( length(v) > 1L ){ warning("Object matched multiple version of class '", cl , "' [", paste(names(UPDATER)[v], collapse=", "), "]") if( verbose) message("SKIP [multiple versions]") return(newObject) }else if( verbose) message("UPDATING [", appendLF=FALSE) for(n in names(UPDATER[v[1L]])){ f <- UPDATER[[n]] if( verbose ) message(n, ' -> ', appendLF=FALSE) newObject <- f$fun(x, newObject) } if( verbose ) message("*]") # return updated object return(newObject) } stopifnot( is.character(x) ) if( is.null(version) ){ if( !is.null(fun) || !is.null(vfun) ) stop("Argument `version` is required for defining updater functions for class `", x, "`") return(.REGISTRY[[x]]) } if( is.null(.REGISTRY[[x]]) ) .REGISTRY[[x]] <<- list() # check result is a function stopifnot(is.function(fun)) stopifnot(is.function(vfun)) if( !is.null(.REGISTRY[[x]][[version]]) ) stop("An update for class '", x, "' version ", version, " is already defined") .REGISTRY[[x]][[version]] <<- list(vfun=vfun, fun=fun) # put updaters in order .REGISTRY[[x]] <<- .REGISTRY[[x]][orderVersion(names(.REGISTRY[[x]]))] invisible(.REGISTRY[[x]]) } }) # Taken from BiocGenerics 2.16 getObjectSlots <- function (object) { if (!is.object(object) || isVirtualClass(class(object))) return(NULL) value <- attributes(object) value$class <- NULL if (is(object, "vector")) { .Data <- as.vector(object) attr(.Data, "class") <- NULL attrNames <- c("comment", "dim", "dimnames", "names", "row.names", "tsp") for (nm in names(value)[names(value) %in% attrNames]) attr(.Data, nm) <- value[[nm]] value <- value[!names(value) %in% attrNames] value$.Data <- .Data } value } # Taken from BiocGenerics 2.16 updateObjectFromSlots <- function (object, objclass = class(object), ..., verbose = FALSE) { updateObject <- nmfObject if (is(object, "environment")) { if (verbose) message("returning original object of class 'environment'") return(object) } classSlots <- slotNames(objclass) if (is.null(classSlots)) { if (verbose) message("definition of '", objclass, "' has no slots; ", "returning original object") return(object) } errf <- function(...) { function(err) { if (verbose) message(..., ":\n ", conditionMessage(err), "\n trying next method...") NULL } } if (verbose) message("updateObjectFromSlots(object = '", class(object), "' class = '", objclass, "')") objectSlots <- getObjectSlots(object) nulls <- sapply(names(objectSlots), function(slt) is.null(slot(object, slt))) objectSlots[nulls] <- NULL joint <- intersect(names(objectSlots), classSlots) toUpdate <- joint[joint != ".Data"] objectSlots[toUpdate] <- lapply(objectSlots[toUpdate], updateObject, ..., verbose = verbose) toDrop <- which(!names(objectSlots) %in% classSlots) if (length(toDrop) > 0L) { warning("dropping slot(s) ", paste(names(objectSlots)[toDrop], collapse = ", "), " from object = '", class(object), "'") objectSlots <- objectSlots[-toDrop] } res <- NULL if (is.null(res)) { if (verbose) message("heuristic updateObjectFromSlots, method 1") res <- tryCatch({ do.call(new, c(objclass, objectSlots[joint])) }, error = errf("'new(\"", objclass, "\", ...)' from slots failed")) } if (is.null(res)) { if (verbose) message("heuristic updateObjectFromSlots, method 2") res <- tryCatch({ obj <- do.call(new, list(objclass)) for (slt in joint) slot(obj, slt) <- updateObject(objectSlots[[slt]], ..., verbose = verbose) obj }, error = errf("failed to add slots to 'new(\"", objclass, "\", ...)'")) } if (is.null(res)) stop("could not updateObject to class '", objclass, "'", "\nconsider defining an 'updateObject' method for class '", class(object), "'") res } #' Updating NMF Objects #' #' This function serves to update an objects created with previous versions of the #' NMF package, which would otherwise be incompatible with the current version, #' due to changes in their S4 class definition. #' #' This function makes use of heuristics to automatically update object slots, #' which have been borrowed from the BiocGenerics package, the function #' \code{updateObjectFromSlots} in particular. #' #' @param object an R object created by the NMF package, e.g., an object of class #' \code{\linkS4class{NMF}} or \code{\linkS4class{NMFfit}}. #' @param verbose logical to toggle verbose messages. #' #' @export #' nmfObject <- function(object, verbose=FALSE){ objectUpdater(object, verbose=verbose) } NMF/R/algorithms-base.R0000644000176200001440000004236313620502674014314 0ustar liggesusers# Standard NMF algorithms # # Author: Renaud Gaujoux # Creation: 30 Apr 2012 ############################################################################### #' @include NMFstd-class.R #' @include NMFOffset-class.R #' @include NMFns-class.R #' @include registry-algorithms.R NULL ################################################################################ # BRUNET (standard KL-based NMF) ################################################################################ #' NMF Algorithm/Updates for Kullback-Leibler Divergence #' #' The built-in NMF algorithms described here minimise #' the Kullback-Leibler divergence (KL) between an NMF model and a target matrix. #' They use the updates for the basis and coefficient matrices (\eqn{W} and \eqn{H}) #' defined by \cite{Brunet2004}, which are essentially those from \cite{Lee2001}, #' with an stabilisation step that shift up all entries from zero every 10 iterations, #' to a very small positive value. #' #' @param i current iteration number. #' @param v target matrix. #' @param x current NMF model, as an \code{\linkS4class{NMF}} object. #' @param eps small numeric value used to ensure numeric stability, by shifting up #' entries from zero to this fixed value. #' @param ... extra arguments. These are generally not used and present #' only to allow other arguments from the main call to be passed to the #' initialisation and stopping criterion functions (slots \code{onInit} and #' \code{Stop} respectively). #' @inheritParams nmf_update.KL.h #' #' @author #' Original implementation in MATLAB: Jean-Philippe Brunet \email{brunet@@broad.mit.edu} #' #' Port to R and optimisation in C++: Renaud Gaujoux #' #' @source #' #' Original MATLAB files and references can be found at: #' #' \url{http://www.broadinstitute.org/mpr/publications/projects/NMF/nmf.m} #' #' \url{http://www.broadinstitute.org/publications/broad872} #' #' Original license terms: #' #' This software and its documentation are copyright 2004 by the #' Broad Institute/Massachusetts Institute of Technology. All rights are reserved. #' This software is supplied without any warranty or guaranteed support whatsoever. #' Neither the Broad Institute nor MIT can not be responsible for its use, misuse, #' or functionality. #' #' @details #' \code{nmf_update.brunet_R} implements in pure R a single update step, i.e. it updates #' both matrices. #' #' @export #' @rdname KL-nmf #' @aliases KL-nmf nmf_update.brunet_R <- function(i, v, x, eps=.Machine$double.eps, ...) { # retrieve each factor w <- .basis(x); h <- .coef(x); # standard divergence-reducing NMF update for H h <- R_std.divergence.update.h(v, w, h) # standard divergence-reducing NMF update for W w <- R_std.divergence.update.w(v, w, h) #every 10 iterations: adjust small values to avoid underflow if( i %% 10 == 0 ){ #precision threshold for numerical stability #eps <- .Machine$double.eps h[h1L || obj=='') ) return(str_c("Slot 'name' must be a single non-empty character string [", obj, ']')) TRUE } ) #' Accessing Strategy Names #' #' \code{name} and \code{name<-} gets and sets the name associated with an object. #' In the case of \code{Strategy} objects it is the the name of the algorithm. #' #' @param object an R object with a defined \code{name} method #' @param ... extra arguments to allow extension #' @param value replacement value #' #' @export #' @inline #' @rdname Strategy-class setGeneric('name', function(object, ...) standardGeneric('name')) #' Returns the name of an algorithm #' @param all a logical that indicates if all the names associated with a strategy #' should be returned (\code{TRUE}), or only the first (primary) one (\code{FALSE}). setMethod('name', signature(object='Strategy'), function(object, all=FALSE){ n <- slot(object, 'name') if( length(n) && !all ) n[1L] else n } ) #' @export #' @inline #' @rdname Strategy-class setGeneric('name<-', function(object, ..., value) standardGeneric('name<-')) #' Sets the name(s) of an NMF algorithm setReplaceMethod('name', signature(object='Strategy', value='character'), function(object, value){ slot(object, 'name') <- value validObject(object) object } ) defaultArgument <- function(name, object, value, force=FALSE){ # taken from methods::hasArg aname <- as.character(substitute(name)) miss <- eval(substitute(missing(name)), sys.frame(sys.parent())) defaults <- attr(object, 'defaults') if( !miss && !force ) eval(substitute(name), sys.frame(sys.parent())) else if( aname %in% names(defaults) ) defaults[[aname]] else value } #' Virtual Interface for NMF Algorithms #' #' This class partially implements the generic interface defined for general #' algorithms defined in the \pkg{NMF} package (see \code{\link{algorithmic-NMF}}). #' #' @slot objective the objective function associated with the algorithm (Frobenius, Kullback-Leibler, etc...). #' It is either an access key of a registered objective function or a function definition. #' In the latter case, the given function must have the following signature \code{(x="NMF", y="matrix")} #' and return a nonnegative real value. #' #' @slot model a character string giving either the (sub)class name of the NMF-class instance used #' and returned by the strategy, or a function name. #' #' @slot mixed a logical that indicates if the algorithm works on mixed-sign data. #' #' @keywords internal setClass('NMFStrategy' , representation( objective = '.functionSlot' # the objective function used to compute the error (defined by name or function) , model = 'character' # NMF model to use , mixed = 'logical' # can the input data be negative? ) , prototype=prototype(objective='euclidean', model='NMFstd', mixed=FALSE) , validity=function(object){ # slot 'objective' must either be a non-empty character string or a function obj <- objective(object) if( is.character(obj) && obj == '' ) return("Slot 'objective' must either be a non-empty character string or a function definition.") # slot 'model' must be the name of a class that extends class 'NMF' obj <- modelname(object) if( !is.character(obj) ) return("Slot 'model' must be a character vector") if( any(inv <- !sapply(obj, isNMFclass)) ) return(paste("Slot 'model' must contain only names of a class that extends class 'NMF' [failure on class(es) " , paste( paste("'", obj[inv], "'", sep=''), collapse=', ') ,"]" , sep='')) # slot 'mixed' must be a single logical obj <- slot(object, 'mixed') if( length(obj) != 1 ) return( paste("Slot 'mixed' must be a single logical [length=", length(obj), "]", sep='') ) } , contains = c('VIRTUAL', 'Strategy') ) #' @export #' @rdname NMFStrategy-class setMethod('show', 'NMFStrategy', function(object){ cat('\n", sep='') cat(" name: ", name(object), " [", packageSlot(object), "]\n", sep='') svalue <- objective(object) svalue <- if( is.function(svalue) ) str_args(svalue, exdent=10) else paste("'", svalue,"'", sep='') cat(" objective:", svalue, "\n") cat(" model:", modelname(object), "\n") if( length(object@defaults) > 0L ){ cat(" defaults:", str_desc(object@defaults, exdent=10L), "\n") } return(invisible()) } ) # Coerce method for 'NMFStrategy' objects into 'character': give the main name setAs('NMFStrategy', 'character' , def = function(from) name(from) ) #' Factory Method for NMFStrategy Objects #' #' Creates NMFStrategy objects that wraps implementation of NMF algorithms into #' a unified interface. #' #' @param name name/key of an NMF algorithm. #' @param method definition of the algorithm #' @param ... extra arguments passed to \code{\link{new}}. #' #' @export #' @inline setGeneric('NMFStrategy', function(name, method, ...) standardGeneric('NMFStrategy') ) #' Creates an \code{NMFStrategyFunction} object that wraps the function \code{method} #' into a unified interface. #' #' \code{method} must be a function with signature \code{(y="matrix", x="NMFfit", ...)}, #' and return an object of class \code{\linkS4class{NMFfit}}. setMethod('NMFStrategy', signature(name='character', method='function'), function(name, method, ...){ # build a NMFStrategyFunction object on the fly to wrap function 'method' NMFStrategy(name=name, algorithm=method, ...) } ) #' Creates an \code{NMFStrategy} object based on a template object (Constructor-Copy). setMethod('NMFStrategy', signature(name='character', method='NMFStrategy'), function(name, method, ...){ package <- topns_name() # build an NMFStrategy object based on template object strategy <- new(class(method), method, name=name, ..., package=package) # valid the new strategy validObject(strategy) # add trace of inheritance from parent NMF algorithm attr(strategy, 'parent') <- name(method)[1] # return new object strategy } ) #' Creates an \code{NMFStrategy} based on a template object (Constructor-Copy), #' in particular it uses the \strong{same} name. setMethod('NMFStrategy', signature(name='NMFStrategy', method='missing'), function(name, method, ...){ # do not change the object if single argument if( nargs() == 1L ) return(name) # use the name as a key # NB: need special trick to avoid conflict between argument and function mname <- match.fun('name')(name) NMFStrategy(name=mname, method=name, ...) } ) #' Creates an \code{NMFStrategy} based on a registered NMF algorithm that is used #' as a template (Constructor-Copy), in particular it uses the \strong{same} name. #' #' It is a shortcut for \code{NMFStrategy(nmfAlgorithm(method, exact=TRUE), ...)}. setMethod('NMFStrategy', signature(name='missing', method='character'), function(name, method, ...){ NMFStrategy(nmfAlgorithm(method, exact=TRUE), ...) } ) #' Creates an \code{NMFStrategy} based on a template object (Constructor-Copy) #' but using a randomly generated name. setMethod('NMFStrategy', signature(name='NULL', method='NMFStrategy'), function(name, method, ...){ # use the name as a key # NB: need special trick to avoid conflict between argument and function mname <- match.fun('name')(method) mname <- basename(tempfile(str_c(mname, '_'))) NMFStrategy(name=mname, method=method, ...) } ) #' Creates an \code{NMFStrategy} based on a registered NMF algorithm that is used #' as a template. setMethod('NMFStrategy', signature(name='character', method='character'), function(name, method, ...){ NMFStrategy(name=name, method=nmfAlgorithm(method, exact=TRUE), ...) } ) #' Creates an \code{NMFStrategy} based on a registered NMF algorithm (Constructor-Copy) #' using a randomly generated name. #' #' It is a shortcut for \code{NMFStrategy(NULL, nmfAlgorithm(method), ...)}. setMethod('NMFStrategy', signature(name='NULL', method='character'), function(name, method, ...){ NMFStrategy(NULL, method=nmfAlgorithm(method, exact=TRUE), ...) } ) #' Creates an NMFStrategy, determining its type from the extra arguments passed #' in \code{...}: if there is an argument named \code{Update} then an #' \code{NMFStrategyIterative} is created, or if there is an argument #' named \code{algorithm} then an \code{NMFStrategyFunction} is created. #' Calls other than these generates an error. #' setMethod('NMFStrategy', signature(name='character', method='missing'), function(name, method, ...){ package <- topns_name() # check iterative strategy if( hasArg2('Update') ){ # create a new NMFStrategyIterative object new('NMFStrategyIterative', name=name, ..., package=package) }else if( hasArg2('algorithm') ){ new('NMFStrategyFunction', name=name, ..., package=package) }else{ stop('NMFStrategy - Could not infer the type of NMF strategy to instantiate.') } } ) #' Pure virtual method defined for all NMF algorithms to ensure #' that a method \code{run} is defined by sub-classes of \code{NMFStrategy}. #' #' It throws an error if called directly. #' @rdname NMFStrategy setMethod('run', signature(object='NMFStrategy', y='matrix', x='NMFfit'), function(object, y, x, ...){ stop("NMFStrategy::run is a pure virtual method that should be overloaded in class '", class(object),"'.") } ) #' Method to run an NMF algorithm directly starting from a given NMF model. #' @rdname NMFStrategy setMethod('run', signature(object='NMFStrategy', y='matrix', x='NMF'), function(object, y, x, ...){ run(object, y, NMFfit(fit=x, seed='none', method=name(object)), ...) } ) #' Computes the value of the objective function between the estimate \code{x} #' and the target \code{y}. #' #' @param x an NMF model that estimates \code{y}. #' #' @inline setMethod('deviance', 'NMFStrategy', function(object, x, y, ...){ obj.fun <- slot(object, 'objective') # return the distance computed using the strategy's objective function if( !is.function(obj.fun) ) deviance(x, y, method=obj.fun, ...) else # directly compute the objective function obj.fun(x, y, ...) } ) #' Gets the objective function associated with an NMF algorithm. #' #' It is used in \code{\link[=deviance,NMFStrategy-method]{deviance}} #' to compute the objective value for an NMF model with respect to #' a given target matrix. #' #' @export #' @rdname NMFStrategy-class setMethod('objective', 'NMFStrategy', function(object){ slot(object, 'objective') } ) #' Sets the objective function associated with an NMF algorithm, with a character string #' that must be a registered objective function. #' @export #' @rdname NMFStrategy-class setReplaceMethod('objective', signature(object='NMFStrategy', value='character'), function(object, value){ #TODO: test for the existence of objective method slot(object, 'objective') <- value validObject(object) object } ) #' Sets the objective function associated with an NMF algorithm, with a function #' that computes the approximation error between an NMF model and a target matrix. #' @export #' @rdname NMFStrategy-class setReplaceMethod('objective', signature(object='NMFStrategy', value='function'), function(object, value){ slot(object, 'objective') <- value validObject(object) object } ) #' Returns the model(s) that an NMF algorithm can fit. #' #' @examples #' # get the type of model(s) associated with an NMF algorithm #' modelname( nmfAlgorithm('brunet') ) #' modelname( nmfAlgorithm('nsNMF') ) #' modelname( nmfAlgorithm('offset') ) #' setMethod('modelname', signature(object='NMFStrategy'), function(object){ slot(object, 'model') } ) #' \code{is.mixed} tells if an NMF algorithm works on mixed-sign data. #' @export #' @rdname NMFStrategy-class is.mixed <- function(object){ return( slot(object, 'mixed') ) } #' Showing Arguments of NMF Algorithms #' #' This function returns the extra arguments that can be passed #' to a given NMF algorithm in call to \code{\link{nmf}}. #' #' @param x algorithm specification #' @param ... extra argument to allow extension #' #' @export nmfFormals <- function(x, ...){ UseMethod('nmfFormals') } #' @export nmfFormals.character <- function(x, ...){ s <- nmfAlgorithm(x) nmfFormals(s, ...) } #' @export nmfFormals.NMFStrategy <- function(x, ...){ m <- getMethod('run', signature(object='NMFStrategy', y='matrix', x='NMFfit')) args <- allFormals(m) # prepend registered default arguments expand_list(x@defaults, args) } #' \code{nmfArgs} is a shortcut for \code{args(nmfWrapper(x))}, to #' display the arguments of a given NMF algorithm. #' #' @rdname nmfFormals #' @export #' @examples #' #' # show arguments of an NMF algorithm #' nmfArgs('brunet') #' nmfArgs('snmf/r') nmfArgs <- function(x){ args(nmfWrapper(x)) } NMF/R/NMFplots.R0000644000176200001440000005107213710766114012733 0ustar liggesusers# Plotting functions for NMF objects # # Author: Renaud Gaujoux # Creation: 16 Aug 2011 ############################################################################### #' @include NMFSet-class.R NULL # Scales a matrix so that its columns sum up to one. sum2one <- function(x){ sweep(x, 2L, colSums(x), '/') } #' @import grDevices corplot <- function(x, y, legend=TRUE, confint=TRUE, scales = 'fixed', ..., add=FALSE){ cols <- rainbow(ncol(x)) # set default arguments gpar <- .set.list.defaults(list(...) , ylab=quote(substitute(y)) , xlab=quote(substitute(x)) , main="Correlation plot" , type='p' , pch=19 , cex=0.8 , col=alphacol(cols, alpha=90)) if( is.null(colnames(x)) ) colnames(x) <- paste("column", 1:ncol(x), sep='_') # draw plot using matplot pfun <- if( add ) matpoints else matplot #do.call(pfun, c(list(x, y), gpar)) # add perfect match line #abline(a=0, b=1) # initialise result res <- list(global=list()) gco <- lm(as.numeric(y) ~ as.numeric(x)) res$global$lm <- gco grsq <- CI.Rsqlm(gco) res$global$cortest <- cor.test( as.numeric(x), as.numeric(y) ) grsq$rho <- res$global$cortest$estimate grsq$alpha <- res$global$lm$coef[2L] # add legend if requested x <- provideDimnames(x, base = list(as.character(1:max(dim(x))))) y <- provideDimnames(y, base = list(as.character(1:max(dim(y))))) ct.labs <- colnames(x) if( legend ){ # separate correlations res$local <- list(lm=list(), cortest=list()) lco <- t(sapply(1:ncol(x), function(i){ co <- lm(y[,i] ~ x[,i]) res$local$lm[[i]] <<- co cotest <- cor.test( as.numeric(x[, i]), as.numeric(y[, i]) ) res$local$cortest[[i]] <<- cotest rsq <- CI.Rsqlm(co) return(round(c(Rsq=rsq$Rsq , confint=rsq$UCL - rsq$Rsq , rho=cotest$estimate , alpha=co$coef[2L]), 2)) # z <- as.numeric(cor.test(x[,i], y[,i])[c('estimate', 'p.value')]) # z[1] <- round.pretty(z[1], 2) # z[2] <- round.pretty(z[2], 3) # z } )) # ct.labs <- sapply(seq_along(ct.labs), function(i){ ci <- if( confint ) str_c(' +/- ', lco[i,2]) else '' bquote(.(sprintf('%s (', colnames(y)[i])) ~ alpha == .(sprintf(' %0.2f | ', lco[i,4])) ~ rho == .(sprintf(' %.02f | ', lco[i,3])) ~ R^2 == .(sprintf(' %0.2f %s)', lco[i,1], ci))) }) } df <- data.frame(x = melt(x), y = melt(y)) df[[5L]] <- factor(df[[5L]], levels = colnames(y)) ct <- colnames(df)[5L] ct.title <- gsub('y.', '', ct, fixed = TRUE) p <- ggplot(df, aes_string(x='x.value', y='y.value' , color = ct)) + geom_point() + xlab(gpar$xlab) + ylab(gpar$ylab) + scale_color_discrete(labels = ct.labs) + stat_smooth(method = lm) + geom_abline(slope = 1, linetype = 3) + facet_grid(paste0('~ ', ct), scales = scales) + labs(color = ct.title) if( legend ){ p <- p + theme(legend.position = 'bottom') + guides(color = guide_legend(ncol = 1)) }else{ p <- p + theme(legend.position = 'none') } p$correlations <- res p } #setMethod('corplot', signature(x='NMFfitXn', y='NMF') # , function(x, y, pch=19, ...){ # # i <- 1 # i0 <- which.best(x) # i2 <- which.best(x, maxAD, y) # .local <- function(f, skip, order, ...){ # # # reorder if necessary # if( !missing(order) && !is.null(order) ) # f <- match.nmf(f, order) # # # skip if needed # if( i == skip ) # return() # # # compute correlations between profiles # co <- diag(cor(t(scoef(f)), t(scoef(y)))) # if( i == 1 ){ # mp <- plot(co, ylim=c(-1,1), xaxt='n', ...) # mtext(side = 1, basisnames(y), at= 1:nbasis(y), line = 1) # } # else # lines(co, ...) # i <<- i+1 # # } # lapply(x, .local, skip=i0, col="#00000010", type='l', ...) # .local(x[[i0]], 0, col="red", type='o', pch=19, ...) # .local(x[[i2]], 0, col="red", type='o', pch=19, lty='dashed', ...) # invisible() # # } #) #' Plotting Expression Profiles #' #' @export profplot <- function(x, ...){ UseMethod('profplot') } #' #' The function \code{profplot} draws plots of the basis profiles, i.e. the rows #' of the coefficient matrix of NMF models. #' A given profile is composed of the contribution of the corresponding #' basis to each sample. #' #' When using NMF for clustering in particular, one looks for strong #' associations between the basis and a priori known groups of samples. #' Plotting the profiles may highlight such patterns. #' #' The function can also be used to compare the profiles from two NMF models or #' mixture coefficient matrices. In this case, it draws a scatter plot of the #' paired profiles. #' #' @param x a matrix or an NMF object from which is extracted the mixture #' coefficient matrix. It is extracted from the best fit if \code{x} is the #' results from multiple NMF runs. #' @param y a matrix or an NMF object from which is extracted the mixture #' coefficient matrix. #' It is extracted from the best fit if \code{y} is the results from multiple NMF runs. #' @param scale specifies how the data should be scaled before plotting. #' If \code{'none'} or \code{NA}, then no scaling is applied and the "raw" data is plotted. #' If \code{TRUE} or \code{'max'} then each row of both matrices #' are normalised with their respective maximum values. #' If \code{'c1'}, then each column of both matrix is scaled into proportions (i.e. to sum up to one). #' Default is \code{'none'}. #' @param match.names a logical that indicates if the profiles in \code{y} #' should be subset and/or re-ordered to match the profile names in \code{x} #' (i.e. the rownames). This is attempted only when both \code{x} and \code{y} #' have names. #' @param legend a logical that specifies whether drawing the legend or not, or #' coordinates specifications passed to argument \code{x} of #' \code{\link{legend}}, that specifies the position of the legend. #' @param confint logical that indicates if confidence intervals for the #' R-squared should be shown in legend. #' @param Colv specifies the way the columns of \code{x} are ordered before #' plotting. It is used only when \code{y} is missing. It can be: \itemize{ #' \item a single numeric value, specifying the index of a row of \code{x}, #' that is used to order the columns by \code{x[, order(x[abs(Colv),])]}. #' Decreasing order is specified with a negative index. \item an integer #' vector directly specifying the order itself, in which case the columns are #' ordered by \code{x[, Colv]} \item a factor used to order the columns by #' \code{x[, order(Colv)]} and as argument \code{annotation} if this latter is #' missing or not \code{NA}. \item any other object with a suitable #' \code{order} method. The columns are by \code{x[, order(Colv)]} } #' @param labels a character vector containing labels for each sample (i.e. #' each column of \code{x}). These are used for labelling the x-axis. #' @param annotation a factor annotating each sample (i.e. each column of #' \code{x}). If not missing, a coloured raw is plotted under the x-axis and #' annotates each sample accordingly. If argument \code{Colv} is a factor, then #' it is used to annotate the plot, unless \code{annotation=NA}. #' @param ... graphical parameters passed to \code{\link{matplot}} or \code{\link{matpoints}}. #' @param add logical that indicates if the plot should be added as points to a previous plot #' #' @seealso \code{\link{profcor}} #' @keywords aplot #' @rdname profplot #' @export #' @examples #' #' # create a random target matrix #' v <- rmatrix(40, 10) #' #' # fit a single NMF model #' res <- nmf(v, 3) #' profplot(res) #' #' # fit a multi-run NMF model #' res2 <- nmf(v, 3, nrun=2) #' # ordering according to first profile #' profplot(res2, Colv=1) # increasing #' #' # draw a profile correlation plot: this show how the basis components are #' # returned in an unpredictable order #' profplot(res, res2) #' #' # looking at all the correlations allow to order the components in a "common" order #' profcor(res, res2) #' profplot.default <- function(x, y, scale=c('none', 'max', 'c1'), match.names=TRUE , legend=TRUE, confint=TRUE , Colv, labels, annotation, ..., add = FALSE){ # initialise result list res <- list() # get extra graphical parameters gpar <- list(...) # plot a correlation plot of y is not missing if( !missing(y) ){ xvar <- deparse(substitute(x)) # extract mixture coefficient from x if( isNMFfit(x) ){ gpar <- .set.list.defaults(gpar , xlab=paste("NMF model", xvar, "- Method:", algorithm(x))) x <- fit(x) } if( is.nmf(x) ){ gpar <- .set.list.defaults(gpar , main="Mixture coefficient profile correlations" , xlab=paste("NMF model", xvar)) x <- coef(x) if( is.null(rownames(x)) ) rownames(x) <- paste("basis", 1:nrow(x), sep='_') }else if( is(x, 'ExpressionSet') ){ x <- Biobase::exprs(x) gpar <- .set.list.defaults(gpar , main="Expression profile correlations" , xlab=paste("ExpressionSet", xvar)) }else{ gpar <- .set.list.defaults(gpar , xlab=paste("Matrix ", xvar)) } # at this stage x must be a matrix if( !is.matrix(x) ) stop("NMF::profplot - Invalid argument `x`: could not extract mixture coefficient matrix") # extract mixture coefficient from y yvar <- deparse(substitute(y)) if( isNMFfit(y) ){ gpar <- .set.list.defaults(gpar , ylab=paste("NMF model", yvar, "- Method:", algorithm(y))) y <- fit(y) } if( is.nmf(y) ){ gpar <- .set.list.defaults(gpar , main="Mixture coefficient profile correlations" , ylab=paste("NMF model", yvar)) y <- coef(y) }else if( is(y, 'ExpressionSet') ){ y <- Biobase::exprs(y) gpar <- .set.list.defaults(gpar , main="Expression profile correlations" , ylab=paste("ExpressionSet", yvar)) }else{ gpar <- .set.list.defaults(gpar , ylab=paste("Matrix ", yvar)) } # at this stage y must be a matrix if( !is.matrix(y) ) stop("NMF::profplot - Invalid argument `y`: could not extract profile matrix") # match names if requested if( match.names && !is.null(rownames(x)) && !is.null(rownames(y)) ){ # match the row in x to the rows in y y.idx <- match(rownames(x), rownames(y), nomatch=0L) x.idx <- which(y.idx!=0L) # subset and reorder if possible if( length(x.idx) > 0L ){ res$y.idx <- y.idx[x.idx] y <- y[y.idx, , drop = FALSE] res$x.idx <- x.idx x <- x[x.idx, , drop = FALSE] } } # scale to proportions if requested if( missing(scale) ) scale <- NULL else if( isTRUE(scale) ) scale <- 'max' else if( isFALSE(scale) ) scale <- 'none' scale <- match.arg(scale) scales <- 'free' if( scale == 'max' ){ gpar <- .set.list.defaults(gpar , xlim=c(0,1), ylim=c(0,1)) # scale x iscale <- (xm <- apply(abs(x), 1L, max)) > 0 x[iscale, ] <- sweep(x[iscale, , drop = FALSE], 1L, xm[iscale], '/') # scale y iscale <- (ym <- apply(abs(y), 1L, max)) > 0 y[iscale, ] <- sweep(y[iscale, , drop = FALSE], 1L, ym[iscale], '/') scales <- 'fixed' } else if( scale == 'c1' ){ gpar <- .set.list.defaults(gpar , xlim=c(0,1), ylim=c(0,1)) x <- sum2one(x) y <- sum2one(y) }else{ Mx <- max(x, y); mx <- min(x, y) # extend default limits by a 0.25 factor Mx <- Mx * 1.25 mx <- mx * 0.75 gpar <- .set.list.defaults(gpar , xlim=c(mx,Mx), ylim=c(mx,Mx)) } gpar <- .set.list.defaults(gpar , main="Profile correlations") # plot the correlation plot p <- do.call(corplot, c(list(x=t(x), y=t(y), scales = scales, legend=legend, confint=confint, add=add), gpar)) p <- expand_list(p, list(idx.map = res)) # return result list return( p ) } # extract mixture coefficient xvar <- deparse(substitute(x)) if( isNMFfit(x) ){ gpar <- .set.list.defaults(gpar, main=paste("Mixture coefficient profiles\nNMF method:", algorithm(x), "- runs:", nrun(x))) x <- fit(x) } if( is.nmf(x) ){ gpar <- .set.list.defaults(gpar, main="Mixture coefficient profiles") x <- coef(x) }else if( is(x, 'ExpressionSet') ){ x <- Biobase::exprs(x) gpar <- .set.list.defaults(gpar, main="Expression profiles") } # at this stage x must be a matrix if( !is.matrix(x) ) stop("NMF::profplot - Invalid argument `x`: could not extract profile matrix") # scale to proportions if requested if( missing(scale) || !isTRUE(scale) ) scale <- FALSE if( scale ){ gpar <- .set.list.defaults(gpar, ylim=c(0,1)) x <- sum2one(x) } # reorder the samples if requested if( missing(labels) ){ labels <- if( !is.null(colnames(x)) ) colnames(x) else 1:ncol(x) } else if( length(labels) != ncol(x) ){ labels <- rep(labels, length.out=ncol(x)) # stop("NMF::profplot - Invalid argument `labels`: length should be equal to the number of columns in ", xvar, " [=", ncol(x),"]") } # check annotation if( !missing(annotation) && length(annotation) != ncol(x) ) stop("NMF::profplot - Invalid argument `annotation`:: length should be equal to the number of columns in ", xvar, " [=", ncol(x),"]") # reorder the columns if requested if( !missing(Colv) && !is_NA(Colv) ){ ord <- if( length(Colv) == 1 ){ if( !is.numeric(Colv) || abs(Colv) > nrow(x) ) stop("NMF::profplot - Invalid singel argument `Colv`: should be an integer between -nrow(x) and nrow(", xvar,") (i.e. [[-", nrow(x),",", nrow(x),"]])") order(x[abs(Colv),], decreasing=Colv<0) }else{ if( length(Colv) != ncol(x) ) stop("NMF::profplot - Invalid length for argument `Colv`: should be of length ncol(", xvar, ") [=", nrow(x),"]") if( is.integer(Colv) && length(setdiff(Colv, 1:ncol(x)))==0 ) Colv else order(Colv) } # use Colv as annotation if not requested otherwise if( missing(annotation) && is.factor(Colv) ) annotation <- Colv # reorder all relevant quantities x <- x[,ord] labels <- labels[ord] if( !missing(annotation) && !is_NA(annotation) ) annotation <- annotation[ord] } # set default arguments cols <- rainbow(nrow(x)) gpar <- .set.list.defaults(gpar , xlab="Samples" , ylab="Mixture coefficient value" , main="Profile plot" , type='o' , lty=1 , pch=19 , cex=0.8 , col=cols) # plot using matplot do.call(matplot, c(list(x=t(x)), gpar, xaxt='n')) # add legend if requested if( !isFALSE(legend) ){ if( isTRUE(legend) ) legend <- 'topleft' # use the rownames for the legend leg <- rownames(x) if( is.null(leg) ) leg <- paste('basis', 1:nrow(x), sep='_') legend(legend, legend=leg, col=gpar$col, lwd=1, pch=gpar$pch) } # axis ticks px <- 1:ncol(x) axis(1, at = px, labels = FALSE) # setup grid-base mixed graphic vps <- baseViewports() pushViewport(vps$inner, vps$figure, vps$plot) # clean up on exit on.exit(popViewport(3), add=TRUE) voffset <- 1 # add sample annotation if( !missing(annotation) && !is_NA(annotation) && is.factor(annotation) ){ grid.rect(x = unit(px, "native"), unit(-voffset, "lines") , width = unit(1, 'native'), height = unit(1, "lines") , gp = gpar(fill=alphacol(rainbow(nlevels(annotation))[annotation], 50), col = 'gray')) voffset <- voffset+1 } # add labels if( !is_NA(labels) ){ # setup grid-base mixed graphic #library(gridBase) #vps <- baseViewports() #pushViewport(vps$inner, vps$figure, vps$plot) # add axis adj <- if( is.character(labels) && max(nchar(labels)) >= 7 ) list(just='right', rot=45) else list(just='center', rot=0) grid.text(labels , x = unit(px, "native"), y = unit(-voffset,"lines") , just = adj$just, rot = adj$rot) voffset <- voffset+1 # clean up on exit #popViewport(3) } invisible(nrow(x)) # add xlab #if( nchar(xlab) > 0 ) # grid.text(xlab, x = unit(length(px)/2, "native"), y = unit(-voffset,"lines"), just = 'center') } #setGeneric('profplot', function(x, y, ...) standardGeneric('profplot')) #setMethod('profplot', signature(x='matrix', y='missing') # , function(x, y, ...){ # # gpar <- .set.list.defaults(list(...) # , xlim=c(0,1), ylim=c(0,1) # , main="Profile plot" # , type='b' # , pch=19) # # do.call(matplot, c(gpar, x=t(sum2one(x)), y=t(sum2one(y)))) # } #) #setMethod('profplot', signature(x='matrix', y='matrix') # , function(x, y, scale=FALSE, ...){ # # # x is the reference, y the estimation # if( scale ){ # gpar <- .set.list.defaults(list(...) # , xlim=c(0,1), ylim=c(0,1) # , main="Profile correlation plot") # do.call(corplot, c(gpar, x=t(sum2one(x)), y=t(sum2one(y)))) # }else # corplot(t(x), t(y), ...) # # } #) #setMethod('profplot', signature(x='matrix', y='NMF') # , function(x, y, ...){ # profplot(x, coef(y), ...) # } #) #setMethod('profplot', signature(x='NMF', y='ANY') # , function(x, y, ...){ # profplot(coef(x), y, ...) # } #) #setMethod('profplot', signature(x='matrix', y='NMFfit') # , function(x, y, ...){ # # if( !missing(y) ){ # x is the reference, y the estimation # # # map components to the references # title <- paste("Profile correlation plot - Method:", algorithm(y)) # gpar <- .set.list.defaults(list(...), # list(main=title)) # do.call(profplot, c(gpar, x=x, y=fit(y))) # } # } #) #setMethod('profplot', signature(x='matrix', y='NMFfitXn') # , function(x, y, ...){ # profplot(x, minfit(y), ...) # } #) #setMethod('profplot', signature(x='NMFfitXn', y='ANY') # , function(x, y, ...){ # profplot(minfit(x), y, ...) # } #) #' Silhouette of NMF Clustering #' #' @param x an NMF object, as returned by \code{\link{nmf}}. #' @param what defines the type of clustering the computed silhouettes are #' meant to assess: \code{'samples'} for the clustering of samples #' (i.e. the columns of the target matrix), #' \code{'features'} for the clustering of features (i.e. the rows of the #' target matrix), and \code{'chc'} for the consensus clustering of samples as #' defined by hierarchical clustering dendrogram, \code{'consensus'} for the #' consensus clustering of samples, with clustered ordered as in the #' \strong{default} hierarchical clustering used by #' \code{\link{consensusmap}} when plotting the heatmap of the consensus matrix #' (for multi-run NMF fits). #' That is \code{dist = 1 - consensus(x)}, average linkage and reordering based #' on row means. #' @param order integer indexing vector that can be used to force the silhouette #' order. #' @param ... extra arguments not used. #' #' @seealso \code{\link[NMF]{predict}} #' @export #' @import cluster #' @examples #' #' x <- rmatrix(75, 15, dimnames = list(paste0('a', 1:75), letters[1:15])) #' # NB: using low value for maxIter for the example purpose only #' res <- nmf(x, 4, nrun = 3, maxIter = 20) #' #' # sample clustering from best fit #' plot(silhouette(res)) #' #' # average silhouette are computed in summary measures #' summary(res) #' #' # consensus silhouettes are ordered as on default consensusmap heatmap #' \dontrun{ op <- par(mfrow = c(1,2)) } #' consensusmap(res) #' si <- silhouette(res, what = 'consensus') #' plot(si) #' \dontrun{ par(op) } #' #' # if the order is based on some custom numeric weights #' \dontrun{ op <- par(mfrow = c(1,2)) } #' cm <- consensusmap(res, Rowv = runif(ncol(res))) #' # NB: use reverse order because silhouettes are plotted top-down #' si <- silhouette(res, what = 'consensus', order = rev(cm$rowInd)) #' plot(si) #' \dontrun{ par(op) } #' #' # do the reverse: order the heatmap as a set of silhouettes #' si <- silhouette(res, what = 'features') #' \dontrun{ op <- par(mfrow = c(1,2)) } #' basismap(res, Rowv = si) #' plot(si) #' \dontrun{ par(op) } #' silhouette.NMF <- function(x, what = NULL, order = NULL, ...){ # compute prediction p <- predict(x, what = what, dmatrix = TRUE) # compute silhouette si <- silhouette(as.numeric(p), dmatrix = attr(p, 'dmatrix')) attr(si, 'call') <- match.call(call = sys.call(-1)) if( is_NA(si) ) return(NA) # fix rownames if necessary if( is.null(rownames(si)) ){ rownames(si) <- names(p) if( is.null(rownames(si)) ) rownames(si) <- 1:nrow(si) } if( is.null(order) && !is.null(attr(p, 'iOrd')) ){ # reorder as defined in prediction order <- attr(p, 'iOrd') } # order the silhouette if( !is.null(order) && !is_NA(order) ){ si[1:nrow(si), ] <- si[order, , drop = FALSE] rownames(si) <- rownames(si)[order] attr(si, 'iOrd') <- order attr(si, 'Ordered') <- TRUE } si } #' @export silhouette.NMFfitX <- function(x, ...){ si <- silhouette.NMF(x, ...) attr(si, 'call') <- match.call(call = sys.call(-1)) si } NMF/R/Bioc-layer.R0000644000176200001440000002266213620564313013217 0ustar liggesusers# Layer for Bioconductor # # - define methods with signature for use within Bioconductor # - define alias methods for use in the context of microarray analysis (metagenes, metaprofiles, ...) # # Author: Renaud Gaujoux \email{renaud@@cbio.uct.ac.za} ############################################################################### #' @include NMF-class.R #' @include transforms.R NULL #' Specific NMF Layer for Bioconductor #' #' The package NMF provides an optional layer for working with common objects #' and functions defined in the Bioconductor platform. #' #' It provides: #' \itemize{ #' \item computation functions that support \code{ExpressionSet} objects as #' inputs. #' \item aliases and methods for generic functions defined and widely used by #' Bioconductor base packages. #' \item specialised visualisation methods that adapt the titles and legend #' using bioinformatics terminology. #' \item functions to link the results with annotations, etc... #' } #' #' @rdname bioc #' @name bioc-NMF #' #' @aliases nmf,ExpressionSet,ANY,ANY-method #' @aliases nmf,matrix,ExpressionSet,ANY-method #' #' @aliases seed,ExpressionSet,ANY,ANY-method #' #' @aliases run,NMFStrategy,ExpressionSet,ANY-method #' #' @aliases nmfModel,ExpressionSet,ANY-method #' @aliases nmfModel,ANY,ExpressionSet-method #' #' @aliases rnmf,ANY,ExpressionSet-method #' #' @aliases nneg,ExpressionSet-method #' @aliases rposneg,ExpressionSet-method #' #' @aliases .atrack,ExpressionSet-method #' #' @aliases sampleNames,NMF-method #' @aliases sampleNames<-,NMF,ANY-method #' @aliases sampleNames,NMFfitX-method #' @aliases featureNames,NMF-method #' @aliases featureNames<-,NMF-method #' @aliases featureNames,NMFfitX-method #' #' @aliases nmeta #' @aliases metagenes metagenes<- #' @aliases metaprofiles metaprofiles<- #' #' @exportPattern ^featureNames #' @exportPattern ^sampleNames #' @exportPattern ^metagenes #' @exportPattern ^metaprofiles #' @exportPattern ^nmeta NULL # add extra package Biobase #setPackageExtra('install.packages', 'Biobase', pkgs='Biobase') if(!requireNamespace("Biobase")) BiocManager::install("Biobase") .onLoad.nmf.bioc <- function(){ if( pkgmaker::require.quiet('Biobase') ){ # load Biobase package requireNamespace('Biobase') #library(Biobase) #' Performs NMF on an ExpressionSet: the target matrix is the expression matrix \code{exprs(x)}. #' @rdname bioc setMethod('nmf', signature(x='ExpressionSet', rank='ANY', method='ANY'), function(x, rank, method, ...) { # replace missing values by NULL values for correct dispatch if( missing(method) ) method <- NULL if( missing(rank) ) rank <- NULL # apply NMF to the gene expression matrix nmf(Biobase::exprs(x), rank, method, ...) } ) #' Fits an NMF model partially seeding the computation with a given #' ExpressionSet object passed in \code{rank}. #' #' This method provides a shortcut for \code{nmf(x, exprs(rank), method, ...)}. #' #' @examples #' # partially seed with an ExpressionSet (requires package Biobase) #' \dontrun{ #' if(requireNamespace("Biobase")) BiocManager::install("Biobase"){ #' data(esGolub) #' nmf(esGolub, esGolub[,1:3]) #' } #' } #' setMethod('nmf', signature(x='matrix', rank='ExpressionSet', method='ANY'), function(x, rank, method, ...){ # replace missing values by NULL values for correct dispatch if( missing(method) ) method <- NULL nmf(x, Biobase::exprs(rank), method, ...) } ) #' Seeds an NMF model directly on an ExpressionSet object. #' This method provides a shortcut for \code{seed(exprs(x), model, method, ...)}. #' #' @examples #' # run on an ExpressionSet (requires package Biobase) #' \dontrun{ #' if(requireNamespace("Biobase")) BiocManager::install("Biobase"){ #' data(esGolub) #' nmf(esGolub, 3) #' } #' } #' setMethod('seed', signature(x='ExpressionSet', model='ANY', method='ANY'), function(x, model, method, ...) { # replace missing values by NULL values for correct dispatch if( missing(method) ) method <- NULL if( missing(model) ) model <- NULL # apply NMF to the gene expression matrix seed(Biobase::exprs(x), model, method, ...) } ) #' Runs an NMF algorithm on the expression matrix of an \code{ExpressionSet} object. setMethod('run', signature(object='NMFStrategy', y='ExpressionSet', x='ANY'), function(object, y, x, ...){ run(object, Biobase::exprs(y), x, ...) } ) ###% Method 'nmfModel' for 'ExpressionSet' target objects: ###% -> use the expression matrix of 'target' as the target matrix setMethod('nmfModel', signature(rank='ANY', target='ExpressionSet'), function(rank, target, ...){ if( missing(rank) ) rank <- NULL # call nmfModel on the expression matrix nmfModel(rank, Biobase::exprs(target), ...) } ) setMethod('nmfModel', signature(rank='ExpressionSet', target='ANY'), function(rank, target, ...){ if( missing(target) ) target <- NULL # call nmfModel on the expression matrix nmfModel(Biobase::exprs(rank), target, ...) } ) ###% Method 'rnmf' for 'ExpressionSet' target objects: ###% -> use the expression matrix of 'target' as the target matrix ###% setMethod('rnmf', signature(x='ANY', target='ExpressionSet'), function(x, target, ...){ rnmf(x, Biobase::exprs(target), ...) } ) ###% The method for an \code{ExpressionSet} object returns the data.frame that ###% contains the phenotypic data (i.e. \code{pData(object)}) setMethod('.atrack', 'ExpressionSet', function(object, data=NULL, ...){ if( is.null(data) ) data <- t(Biobase::exprs(object)) .atrack(Biobase::pData(object), data=data, ...) } ) #' Apply \code{nneg} to the expression matrix of an \code{\link{ExpressionSet}} #' object (i.e. \code{exprs(object)}). #' All extra arguments in \code{...} are passed to the method \code{nneg,matrix}. #' #' @examples #' #' E <- Biobase::ExpressionSet(x) #' nnE <- nneg(e) #' exprs(nnE) #' setMethod('nneg', 'ExpressionSet' , function(object, ...){ Biobase::exprs(object) <- nneg(Biobase::exprs(object), ...) object } ) #' Apply \code{rposneg} to the expression matrix of an \code{\link{ExpressionSet}} #' object (i.e. \code{exprs(object)}). #' #' @examples #' #' E <- Biobase::ExpressionSet(x) #' nnE <- posneg(E) #' E2 <- rposneg(nnE) #' all.equal(E, E2) #' setMethod('rposneg', 'ExpressionSet' , function(object, ...){ Biobase::exprs(object) <- rposneg(Biobase::exprs(object), ...) object } ) ###% Annotate the genes specific to each cluster. ###% ###% This function uses the \code{annaffy} package to generate an HTML table from the probe identifiers. # setGeneric('annotate', function(x, annotation, ...) standardGeneric('annotate') ) # setMethod('annotate', signature(x='factor', annotation='character'), # function(x, annotation, filename='NMF genes', outdir='.', name='Cluster specific genes', ...) # { # library(annaffy) # anncols<-aaf.handler()[c(1:3, 6:13)] # # # add html suffix to filename if necessary # if( length(grep("\\.html$", filename)) == 0 ) filename <- paste(filename, 'html', sep='.') # # # for each cluster annotate the genes set # print(head(x)) # by(names(x), x, function(g){ # print(head(g)) # if( length(g) == 0 ) return() # g <- as.character(g) # anntable <- aafTableAnn(g, annotation, anncols) # # generate HTML output # saveHTML(anntable, file.path(outdir,filename), title=paste(name, '[top', nrow(anntable),']')) # }, simplify=FALSE) # # # return nothing # invisible() # } # ) # # setMethod('annotate', signature(x='NMF', annotation='character'), # function(x, annotation, ...) # { # s <- extractFeatures(x) # class <- .predict.nmf(t(s)) # annotate(class, annotation=annotation, ...) # } # ) ## Assign BioConductor aliases ###% number of metagenes nmeta <- nbasis ###% get/set methods of basis matrix metagenes <- basis `metagenes<-` <- `basis<-` ###% get/set methods of mixture coefficients matrix metaprofiles <- coef `metaprofiles<-` <- `coef<-` ###% Get/Set methods for rows/columns names of the basis and mixture matrices # using the Biobase definition standard generics setGeneric('featureNames', package='Biobase') setGeneric('featureNames<-', package='Biobase') setMethod('featureNames', 'NMF', function(object){ rownames(object) } ) setReplaceMethod('featureNames', 'NMF', function(object, value){ rownames(object) <- value object } ) ###% For NMFfitX objects: returns the featureNames of the best fit ###% There is no replace method for NMFfitX objects setMethod('featureNames', 'NMFfitX', function(object){ rownames(fit(object)) } ) setGeneric('sampleNames', package='Biobase') setGeneric('sampleNames<-', package='Biobase') setMethod('sampleNames', 'NMF', function(object){ colnames(object) } ) setReplaceMethod('sampleNames', 'NMF', function(object, value){ colnames(object) <- value object } ) ###% For NMFfitX objects: returns the sampleNames of the best fit ###% There is no replace method for NMFfitX objects setMethod('sampleNames', 'NMFfitX', function(object){ colnames(fit(object)) } ) # # Export layer-specific methods [only if one is loading a namespace] # # NB: Only for R < 3.0.0 # if( pkgmaker::testRversion("2.15.3", -1L) ){ # ns <- pkgmaker::addNamespaceExport(c("nmeta" # ,"featureNames", "featureNames<-" # ,"sampleNames", "sampleNames<-" # ,"metagenes", "metagenes<-" # ,"metaprofiles", "metaprofiles<-")) # } # return TRUE TRUE } } NMF/R/seed-nndsvd.R0000644000176200001440000000764213710244544013445 0ustar liggesusers#' @include registry-seed.R NULL ###% Seeding method: Nonnegative Double Singular Value Decomposition ###% ###% @author Renaud Gaujoux ###% @creation 17 Jul 2009 ###% Auxliary functions .pos <- function(x){ as.numeric(x>=0) * x } .neg <- function(x){ - as.numeric(x<0) * x } .norm <- function(x){ sqrt(drop(crossprod(x))) } ###% This function implements the NNDSVD algorithm described in Boutsidis (2008) for ###% initializattion of Nonnegative Matrix Factorization Algorithms. ###% ###% @param A the input nonnegative m x n matrix A ###% @param k the rank of the computed factors W,H ###% @param flag indicates the variant of the NNDSVD Algorithm: ###% - flag=0 --> NNDSVD ###% - flag=1 --> NNDSVDa ###% - flag=2 --> NNDSVDar ###% ###% @note This code is a port from the MATLAB code from C. Boutsidis and E. Gallopoulos kindly provided by the authors for research purposes. ###% Original MATLAB code: http://www.cs.rpi.edu/~boutsc/papers/paper1/nndsvd.m ###% ###% @references C. Boutsidis and E. Gallopoulos, ###% SVD-based initialization: A head start for nonnegative matrix factorization, ###% Pattern Recognition, 2007 ###% doi:10.1016/j.patcog.2007.09.010 ###% .nndsvd.wrapper <- function(object, x, densify=c('none', 'average', 'random')){ # match parameter 'densify' densify <- match.arg(densify) flag <- which(densify == c('none', 'average', 'random')) - 1 res <- .nndsvd.internal(x, nbasis(object), flag) # update 'NMF' object .basis(object) <- res$W; .coef(object) <- res$H # return updated object object } ###% Port to R of the MATLAB code from Boutsidis .nndsvd.internal <- function(A, k, flag=0){ #check the input matrix if( any(A<0) ) stop('The input matrix contains negative elements !') #size of input matrix size = dim(A); m <- size[1]; n<- size[2] #the matrices of the factorization W = matrix(0, m, k); H = matrix(0, k, n); #1st SVD --> partial SVD rank-k to the input matrix A. s = svd(A, k, k); U <- s$u; S <- s$d; V <- s$v #------------------------------------------------------- # We also recommend the use of propack for the SVD # 1st SVD --> partial SVD rank-k ( propack ) # OPTIONS.tol = 0.00001; % remove comment to this line # [U,S,X] = LANSVD(A,k,'L',OPTIONS); % remove comment to this line #------------------------------------------------------- #choose the first singular triplet to be nonnegative W[,1] = sqrt(S[1]) * abs(U[,1]); H[1,] = sqrt(S[1]) * abs(t(V[,1])); # second SVD for the other factors (see table 1 in Boutsidis' paper) for( i in seq(2,k) ){ uu = U[,i]; vv = V[,i]; uup = .pos(uu); uun = .neg(uu) ; vvp = .pos(vv); vvn = .neg(vv); n_uup = .norm(uup); n_vvp = .norm(vvp) ; n_uun = .norm(uun) ; n_vvn = .norm(vvn) ; termp = n_uup %*% n_vvp; termn = n_uun %*% n_vvn; if (termp >= termn){ W[,i] = sqrt(S[i] * termp) * uup / n_uup; H[i,] = sqrt(S[i] * termp) * vvp / n_vvp; }else{ W[,i] = sqrt(S[i] * termn) * uun / n_uun; H[i,] = sqrt(S[i] * termn) * vvn / n_vvn; } } #------------------------------------------------------------ #actually these numbers are zeros W[W<0.0000000001] <- 0; H[H<0.0000000001] <- 0; if( flag==1 ){ #NNDSVDa: fill in the zero elements with the average ind1 <- W==0 ; ind2 <- H==0 ; average <- mean(A); W[ind1] <- average; H[ind2] <- average; }else if( flag==2 ){#NNDSVDar: fill in the zero elements with random values in the space :[0:average/100] ind1 <- W==0; ind2 <- H==0; n1 <- sum(ind1); n2 <- sum(ind2); average = mean(A); W[ind1] = (average * runif(n1, min=0, max=1) / 100); H[ind2] = (average * runif(n2, min=0, max=1) / 100); } # return matrices W and H list(W=W, H=H) } ########################################################################### # REGISTRATION ########################################################################### setNMFSeed('nndsvd', .nndsvd.wrapper, overwrite=TRUE) NMF/R/registry-algorithms.R0000644000176200001440000003543713620502674015256 0ustar liggesusers# NMF algorithm registry access methods # # Author: Renaud Gaujoux ############################################################################### #' @include registry.R #' @include NMFStrategy-class.R #' @include NMFStrategyFunction-class.R #' @include NMFStrategyIterative-class.R NULL # create sub-registry for NMF algorithm .registryAlgorithm <- setPackageRegistry('algorithm', "NMFStrategy" , description = "Algorithms to solve MF optimisation problems" , entrydesc = "NMF algorithm") nmfAlgorithmInfo <- function(show=TRUE){ obj <- .registryAlgorithm if( show ) print(obj) invisible(obj) } # specific register method for registering NMFStrategy objects setMethod('nmfRegister', signature(key='NMFStrategy', method='missing'), function(key, method, ...){ nmfRegister(name(key), key, ..., regname='algorithm') } ) #' Registering NMF Algorithms #' #' Adds a new algorithm to the registry of algorithms that perform #' Nonnegative Matrix Factorization. #' #' @inheritParams NMFStrategy #' @param ... arguments passed to the factory function \code{\link{NMFStrategy}}, #' which instantiate the \code{\linkS4class{NMFStrategy}} object that is stored #' in registry. #' @param overwrite logical that indicates if any existing NMF method with the #' same name should be overwritten (\code{TRUE}) or not (\code{FALSE}), #' in which case an error is thrown. #' @param verbose a logical that indicates if information about the registration #' should be printed (\code{TRUE}) or not (\code{FALSE}). #' #' @export #' @examples #' #' # define/regsiter a new -- dummy -- NMF algorithm with the minimum arguments #' # y: target matrix #' # x: initial NMF model (i.e. the seed) #' # NB: this algorithm simply return the seed unchanged #' setNMFMethod('mynmf', function(y, x, ...){ x }) #' #' # check algorithm on toy data #' res <- nmfCheck('mynmf') #' # the NMF seed is not changed #' stopifnot( nmf.equal(res, nmfCheck('mynmf', seed=res)) ) #' setNMFMethod <- function(name, method, ..., overwrite=isLoadingNamespace(), verbose=TRUE){ # build call to NMFStrategy constructor call_const <- match.call(NMFStrategy) call_const[[1]] <- as.name('NMFStrategy') call_const$verbose <- NULL call_const$overwrite <- NULL # swap name and method if method is missing and name is a registered method if( missing(method) && !missing(name) && is.character(name) && existsNMFMethod(name) ){ call_const$method <- name call_const$name <- NULL } # build the NMFStrategy object (in the parent frame to get the package slot right) e <- parent.frame() method <- eval(call_const, envir=e) # add to the algorithm registry res <- nmfRegister(method, overwrite=overwrite, verbose=verbose) # return wrapper function invisibly wrap <- nmfWrapper(method) } #' \code{nmfRegisterAlgorithm} is an alias to \code{setNMFMethod} for backward #' compatibility. #' #' @export #' @rdname setNMFMethod nmfRegisterAlgorithm <- setNMFMethod #' Registry for NMF Algorithms #' #' @name methods-NMF #' @rdname registry-algorithm #' @family regalgo Registry for NMF algorithms NULL #' Testing Compatibility of Algorithm and Models #' #' \code{canFit} is an S4 generic that tests if an algorithm can #' fit a particular model. #' #' @param x an object that describes an algorithm #' @param y an object that describes a model #' @param ... extra arguments to allow extension #' #' @export #' @inline #' @family regalgo setGeneric('canFit', function(x, y, ...) standardGeneric('canFit') ) #' Tells if an NMF algorithm can fit a given class of NMF models #' #' @param exact for logical that indicates if an algorithm is considered able to fit #' only the models that it explicitly declares (\code{TRUE}), or if it should be #' considered able to also fit models that extend models that it explicitly fits. #' setMethod('canFit', signature(x='NMFStrategy', y='character'), function(x, y, exact=FALSE){ if( !exact ){ # check for one model amongst all the models fittable by the strategy can <- if( length(mo <- modelname(x)) > 1 ) sapply(mo, function(m) extends(y, m)) else extends(y, mo) any(can) }else is.element(y, modelname(x)) } ) #' Tells if an NMF algorithm can fit the same class of models as \code{y} setMethod('canFit', signature(x='NMFStrategy', y='NMF'), function(x, y, ...){ canFit(x, modelname(y), ...) } ) #' Tells if a registered NMF algorithm can fit a given NMF model setMethod('canFit', signature(x='character', y='ANY'), function(x, y, ...){ canFit(nmfAlgorithm(x), y, ...) } ) #' \code{selectNMFMethod} tries to select an appropriate NMF algorithm that is #' able to fit a given the NMF model. #' #' @param name name of a registered NMF algorithm #' @param model class name of an NMF model, i.e. a class that inherits from class #' \code{\linkS4class{NMF}}. #' @param load a logical that indicates if the selected algorithms should be loaded #' into \code{NMFStrategy} objects #' @param all a logical that indicates if all algorithms that can fit \code{model} #' should be returned or only the default or first found. #' @param quiet a logical that indicates if the operation should be performed quietly, #' without throwing errors or warnings. #' #' @return \code{selectNMFMethod} returns a character vector or \code{NMFStrategy} objects, #' or NULL if no suitable algorithm was found. #' #' @rdname registry-algorithm #' selectNMFMethod <- function(name, model, load=FALSE, exact=FALSE, all=FALSE, quiet=FALSE){ # lookup for an algorithm suitable for the given NMF model if( !isNMFclass(model) ) stop("argument 'model' must be the name of a class that extends class 'NMF'") algo_list <- if( !missing(name) ){ algo <- nmfAlgorithm(name) name(algo) }else nmfAlgorithm() # lookup for all the algorithms that can fit the given model #NB: if only one model needs to be selected then first look for an exact fit as # this would need to be done with exact=FALSE and TRUE anyways w <- sapply(algo_list, canFit, model, exact= if(all) exact else TRUE) algo <- algo_list[w] # if no suitable algorithm was found, and an exact match is not required # then look for other potential non-exact algorithms if( !all && !exact && length(algo) == 0 ){ w <- sapply(algo_list, canFit, model, exact=FALSE) algo <- algo_list[w] } # return NULL if no algorithm was found if( length(algo) == 0L ){ if( !quiet ) stop("Could not find an NMF algorithm to fit model '", model, "'" , if( !missing(name) ) paste(" amongst ", str_out(algo_list, Inf))) return(NULL) } # if all=FALSE then try to choose the default algorithm if present in the list, or the first one res <- if( !all && length(algo) > 1L ){ idx <- which( algo == nmf.getOption('default.algorithm') ) if( !length(idx) ) idx <- 1L res <- algo[idx] if( !quiet ) warning("Selected NMF algorithm '", res, "' amongst other possible algorithm(s): " , paste(paste("'", algo[-idx], "'", sep=''), collapse=", ")) res }else # otherwise return all the algorithms algo # load the methods if required if( load ){ if( length(res) > 1 ) sapply(res, nmfAlgorithm) else nmfAlgorithm(res) } else res } #' \code{getNMFMethod} retrieves NMF algorithm objects from the registry. #' #' @param ... extra arguments passed to \code{\link[pkgmaker]{pkgreg_fetch}} #' or \code{\link[pkgmaker]{pkgreg_remove}}. #' #' @export #' @rdname registry-algorithm getNMFMethod <- function(...) nmfGet('algorithm', ...) #' Listing and Retrieving NMF Algorithms #' #' \code{nmfAlgorithm} lists access keys or retrieves NMF algorithms that are #' stored in registry. #' It allows to list #' #' @param name Access key. #' If not missing, it must be a single character string that is partially matched #' against the available algorithms in the registry. #' In this case, if \code{all=FALSE} (default), then the algorithm is returned #' as an \code{NMFStrategy} object that can be directly passed to \code{\link{nmf}}. #' An error is thrown if no matching algorithm is found. #' #' If missing or \code{NULL}, then access keys of algorithms -- that #' match the criteria \code{version}, are returned. #' This argument is assumed to be regular expression if \code{all=TRUE} or #' \code{version} is not \code{NULL}. #' @param version version of the algorithm(s) to retrieve. #' Currently only value \code{'R'} is supported, which searched for plain R #' implementations. #' @param all a logical that indicates if all algorithm keys should be returned, #' including the ones from alternative algorithm versions (e.g. plain R #' implementations of algorithms, for which a version based on optimised #' C updates is used by default). #' @param ... extra arguments passed to \code{\link{getNMFMethod}} when \code{name} #' is not \code{NULL} and \code{all=FALSE}. It is not used otherwise. #' #' @return an \code{\linkS4class{NMFStrategy}} object if \code{name} is not #' \code{NULL} and \code{all=FALSE}, or a named character vector that contains #' the access keys of the matching algorithms. #' The names correspond to the access key of the primary algorithm: e.g. #' algorithm \sQuote{lee} has two registered versions, one plain R (\sQuote{.R#lee}) #' and the other uses optimised C updates (\sQuote{lee}), which will all get #' named \sQuote{lee}. #' #' @export #' @family regalgo #' #' @examples #' #' # list all main algorithms #' nmfAlgorithm() #' # list all versions of algorithms #' nmfAlgorithm(all=TRUE) #' # list all plain R versions #' nmfAlgorithm(version='R') #' nmfAlgorithm <- function(name=NULL, version=NULL, all=FALSE, ...){ # if one passes an NMFStrategy just returns it if( is(name, 'NMFStrategy') ) return(name) # force all=TRUE if type is provided if( !is.null(version) ) all <- TRUE # directly return the algorithm object if a key is supplied and all=FALSE if( !is.null(name) && !all ) return( getNMFMethod(name, ...) ) # get all algorithms algo <- getNMFMethod(all=TRUE) # set names to match the primary key algo <- setNames(algo, sub("^\\.(.+#)?", '', algo)) # filter out hidden methods if( !all ) algo <- algo[!grepl("^\\.", algo)] # filter out methods not from the requested algorithm if( !is.null(name) ) algo <- algo[grepl(str_c("^", name), names(algo))] # filter out types if( !is.null(version) ){ type <- match.arg(version, c('R')) algo <- Filter( function(x) grepl(str_c("^\\.", version, '#'), x), algo) } # remove names if no arguments if( is.null(version) ) algo <- setNames(algo, NULL) # return the selected algorithm(s) algo } #' \code{existsNMFMethod} tells if an NMF algorithm is registered under the #' #' @param exact a logical that indicates if the access key should be matched #' exactly (\code{TRUE}) or partially (\code{FALSE}). #' #' @export #' @rdname registry-algorithm existsNMFMethod <- function(name, exact=TRUE){ !is.null( getNMFMethod(name, error=FALSE, exact=exact) ) } #' \code{removeNMFMethod} removes an NMF algorithm from the registry. #' #' @export #' @rdname registry-algorithm removeNMFMethod <- function(name, ...){ pkgreg_remove('algorithm', key=name, ...) } #' Wrapping NMF Algorithms #' #' This function creates a wrapper function for calling the function \code{\link{nmf}} #' with a given NMF algorithm. #' #' @param method Name of the NMF algorithm to be wrapped. #' It should be the name of a registered algorithm as returned by \code{\link{nmfAlgorithm}}, #' or an NMF algorithm object (i.e. an instance of \code{\linkS4class{NMFStrategy}}). #' @param ... extra named arguments that define default values for any arguments #' of \code{\link{nmf}} or the algorithm itself. #' @param .FIXED a logical that indicates if the default arguments defined in \code{...} #' must be considered as fixed, i.e. that they are forced to have the defined values and cannot #' be used in a call to the wrapper function, in which case, a warning about discarding them #' is thrown if they are used. #' Non fixed arguments may have their value changed at call time, in which case it is honoured and #' passed to the \code{nmf} call. #' #' \code{.FIXED} may also be a character vector that specifies which argument amongst \code{...} #' should be considered as fixed. #' @return a function with argument \code{...} and a set of default arguments defined #' in \code{...} in the call to \code{nmfWrapper}. #' #' @seealso \code{\link{nmfAlgorithm}}, \code{\link{nmf}} #' @keywords internal #' @export #' #' @examples #' #' # wrap Lee & Seung algorithm into a function #' lee <- nmfWrapper('lee', seed=12345) #' args(lee) #' #' # test on random data #' x <- rmatrix(100,20) #' res <- nmf(x, 3, 'lee', seed=12345) #' res2 <- lee(x, 3) #' nmf.equal(res, res2) #' res3 <- lee(x, 3, seed=123) #' nmf.equal(res, res3) #' #' \dontshow{ #' stopifnot(nmf.equal(res, res2)) #' stopifnot( !nmf.equal(res, res3)) #' } #' #' # argument 'method' has no effect #' res4 <- lee(x, 3, method='brunet') #' nmf.equal(res, res4) #' #' \dontshow{ #' stopifnot(nmf.equal(res, res4)) #' } #' #' nmfWrapper <- function(method, ..., .FIXED=FALSE){ # store original call .call <- match.call() # check that all arguments are named if( nargs() > 1L && any(names(.call)[-(1:2)]=='') ) stop("Invalid call: all arguments must be named.") # store fixed arguments from default arguments .fixedargs <- 'method' .defaults <- names(.call)[-1L] .defaults <- .defaults[!.defaults %in% 'method'] if( length(.defaults) ){ # e <- parent.frame() # for(n in .defaults){ # .call[[n]] <- eval(.call[[n]], envir=e) # } if( isTRUE(.FIXED) ) .fixedargs <- c(.fixedargs, .defaults) else if( is.character(.FIXED) ){ .FIXED <- .FIXED[.FIXED %in% .defaults] .fixedargs <- c(.fixedargs, .FIXED) } } # store in local environment .method <- method .checkArgs <- function(ca, args){ # check for fixed arguments passed in the call that need # to be discarded nm <- names(ca)[-1L] if( any(fnm <- !is.na(pmatch(nm, .fixedargs))) ){ warning("Discarding fixed arguments from wrapped call to ", .call[1L] , " [", str_out(nm[fnm], Inf), '].', immediate.=TRUE) ca <- ca[!c(FALSE, fnm)] } # # start with complete call .call <- ca # set values of wrapper default arguments if any if( length(.defaults) ){ defaults <- args[.defaults] .call <- expand_list(ca, defaults, .exact=FALSE) } # change into a call to nmf .call[[1L]] <- as.name('nmf') .call[['method']] <- force(.method) as.call(.call) } # define wrapper function fwrap <- function(...){ ca <- match.call() args <- formals() .call <- .checkArgs(ca, args) # eval in parent environment e <- parent.frame() eval(.call, envir=e) } # add default arguments to signature if( length(.defaults) ){ formals(fwrap) <- expand_list(formals(fwrap), as.list(.call[.defaults])) } # add arguments from the NMF algorithm if( length(meth <- nmfFormals(.method)) ){ formals(fwrap) <- expand_list(formals(fwrap), meth) } return( fwrap ) } NMF/R/atracks.R0000644000176200001440000003702613620502674012663 0ustar liggesusers# Generic framework for handling annotation tracks in plots, specifically in # in heatmaps generated by the function aheatmap. # # Author: Renaud Gaujoux # Creation: 24 Jan 2012 ############################################################################### setOldClass('annotationTrack') #' Annotation Tracks #' #' \code{.atrack} is an S4 generic method that converts an object into #' an annotation track object. #' It provides a general and flexible annotation framework that is used #' by \code{\link{aheatmap}} to annotates heatmap rows and columns. #' #' Methods for \code{.atrack} exist for common type of objects, which #' should provide enough options for new methods to define how annotation #' track are extracted from more complex objects, by coercing/filtering #' them into a supported type. #' #' @param object an object from which is extracted annotation tracks #' @param ... extra arguments to allow extensions and passed to the next method #' call. #' For \code{atrack}, arguments in \code{...} are concatenated into a single #' \code{annotationTrack} object. #' #' @rdname atrack #' @export #' @inline #' @keywords internal setGeneric('.atrack', function(object, ...) standardGeneric('.atrack')) #' \code{is.atrack} tests if an object is an \code{annotationTrack} object. #' #' @param x an R object #' #' @rdname atrack is.atrack <- function(x) is(x, 'annotationTrack') aname <- function(x, name){ return(x) if( missing(name) ){ cn <- colnames(x) an <- attr(x, 'aname') name <- if( !is.null(cn) ) cn else if( !is.null(an) ) an else class(x)[1] attr(x, 'aname') <- name }else{ attr(x, 'aname') <- name x } } #' \code{adata} get/sets the annotation parameters on an object #' #' @param value replacement value for the complete annotation data list #' #' @rdname atrack adata <- function(x, value, ...){ if( missing(value) ){ ad <- attr(x, 'annotationData') if( is.null(ad) ) ad <- list() # either return the annotationData itself or set values and return the object if( nargs() == 1L ) ad else{ ad <- c(list(...), ad) ad <- ad[!duplicated(names(ad))] adata(x, ad) } }else{ if( !is.list(value) ) stop("Annotation data must be a list.") attr(x, 'annotationData') <- value x } } #' \code{amargin} get/sets the annotation margin, i.e. along which dimension of #' the data the annotations are to be considered. #' #' @rdname atrack amargin <- function(x, value){ if( missing(value) ) adata(x)$margin else adata(x, margin=value) } #' \code{anames} returns the reference margin names for annotation tracks, #' from their embedded annotation data object. #' #' @rdname atrack anames <- function(x, default.margin){ if( is.numeric(x) && length(x) == 1L ) NULL else if( is.vector(x) ) names(x) else{ m <- amargin(x) if( is.null(m) && !missing(default.margin) ) m <- default.margin # special case for ExpressionSet objects whose dimnames method returns NULL if( is(x, 'ExpressionSet') ) x <- Biobase::exprs(x) if( !is.null(m) ) dimnames(x)[[m]] else NULL } } #' \code{alength} returns the reference length for annotation tracks, #' from their embedded annotation data object #' #' @param default.margin margin to use if no margin data is stored in the #' \code{x}. #' #' @rdname atrack alength <- function(x, default.margin){ if( is.numeric(x) && length(x) == 1L ) as.integer(x) else if( is.vector(x) ) length(x) else{ m <- amargin(x) if( is.null(m) && !missing(default.margin) ) m <- default.margin if( !is.null(m) ) dim(x)[m] else NULL } } test.match_atrack <- function(){ requireNamespace('RUnit') na <- paste("name_", 1:10, sep='') mat <- as.matrix(setNames(1:10, na)) checkEquals <- RUnit::checkEquals .check <- function(x){ cat(class(x), " [", str_out(x, Inf, use.names=TRUE), "] :\n") y <- match_atrack(x, mat) print(y) checkEquals( class(y), class(x), "Same class as input") checkEquals( length(y), nrow(mat), "Correct length") checkEquals( names(y), rownames(mat), "Correct names") } .test <- function(x){ .check(x) .check(sample(x)) .check(x[1:5]) .check(sample(x)[1:5]) .check(setNames(x, na)) .check(sample(setNames(x, na))) .check(setNames(x, rev(na))) .check(setNames(x, na)[1:5]) .check(setNames(x, na)[3:6]) .check(setNames(x, na)[c(3,2,6)]) x2 <- setNames(c(x[1:5], x[1:3]), c(na[1:5], paste("not_in_", 1:3, sep=''))) .check(x2) } .test(letters[1:10]) .test(1:10) .test(as.numeric(1:10) + 0.5) .test(c(rep(TRUE, 5), rep(FALSE, 5))) .test(factor(gl(2,5,labels=c("A", "B")))) } #' Extending Annotation Vectors #' #' Extends a vector used as an annotation track to match the number of rows #' and the row names of a given data. #' #' @param x annotation vector #' @param data reference data #' @return a vector of the same type as \code{x} #' @export #' match_atrack <- function(x, data=NULL){ if( is.null(data) || length(x) == 0L ) return(x) # reorder and extend if a reference data matrix is provided refnames <- anames(data, default.margin=1L) reflength <- alength(data, default.margin=1L) # if no ref length (=> no refnames either): do nothing if( is.null(reflength) ) return(x) # special handling of character vectors if( is.character(x) && is.null(names(x)) && !is.null(refnames) ){ # if( !any(names(x) %in% refnames) && any(x %in% refnames) ){ if( any(x %in% refnames) ){ vmessage("match_atrack - Annotation track [", str_out(x, 3, use.names=TRUE), "] has some values matching data names: converting into a logical using values as names.") x <- setNames(rep(TRUE, length(x)), x) } } # reorder based on names .hasNames <- FALSE if( !is.null(names(x)) && !is.null(refnames) ){ inref <- names(x) %in% refnames if( !all(inref) ){ vmessage("match_atrack - Annotation track [", str_out(x, 3, use.names=TRUE), "] has partially matching names: subsetting track to match data") x <- x[inref] if( length(x) == 0L ) vmessage("match_atrack - Subset annotation track is empty") }else vmessage("match_atrack - Annotation track [", str_out(x, 3, use.names=TRUE), "] using names as identifiers") .hasNames <- TRUE if( anyDuplicated(names(x)) ){ dups <- duplicated(names(x)) vmessage("match_atrack - Annotation track [", str_out(x, 3, use.names=TRUE), "]: removing duplicated names [", str_out(x[dups], 3, use.names=TRUE),"]") x <- x[!dups] } } lx <- length(x) if( lx > reflength ){ stop("match_atrack - Invalid annotation track [", str_out(x, 3, use.names=TRUE), "]: more elements [", lx, "] than rows in data [", reflength, "].") } if( lx == reflength ){ # reorder if necessary res <- if( !.hasNames ) x else x[match(refnames, names(x))] return(res) } # build similar vector of correct size res <- if( is.factor(x) ) setNames(factor(c(x, rep(NA, reflength-lx)), levels=c(levels(x), NA)), refnames) else setNames(c(x, rep(NA, reflength-lx)), refnames) res[1:lx] <- NA # if not using names if( !.hasNames ){ if( is.integer(x) ) res[x] <- x else res[1:lx] <- x }else{ # put the values of x at the write place res[match(names(x), refnames)] <- x } res } #' The default method converts character or integer vectors into factors. #' Numeric vectors, factors, a single NA or \code{annotationTrack} objects are #' returned unchanged (except from reordering by argument \code{order}). #' Data frames are not changed either, but class 'annotationTrack' is appended #' to their original class set. #' #' @param data object used to extend the annotation track within a given data #' context. #' It is typically a matrix-like object, against which annotation specifications #' are matched using \code{\link{match_atrack}}. #' #' setMethod('.atrack', signature(object='ANY'), function(object, data=NULL, ...){ # recursive on list if( is.list(object) ){ object <- object[!sapply(object, function(x) length(x) == 0 || is_NA(x) )] res <- if( length(object) == 0 ) NULL else{ # convert into a list of tracks sapply(object, .atrack, data=data, ..., simplify=FALSE) } return(res) }else if( is.null(object) || is_NA(object) || is.atrack(object) ) object else{ # extend to match the data object <- match_atrack(object, data) # apply convertion rules for standard classes if( is.logical(object) ) aname(as.factor(ifelse(object, 1, NA)), "Flag") else if( is.integer(object) ){ if( any(wna <- is.na(object)) ) aname(as.factor(ifelse(!wna, 1,NA)), "Flag") else aname(as.numeric(object), "Level") } else if( is.character(object) ) aname(as.factor(object), "Group") else if( is.factor(object) ) aname(object, "Factor") else if( is.numeric(object) ) aname(object, "Variable") else stop("atrack - Invalid annotation item `" , substitute(object) , "`: must be a factor, or a logical, character, numeric or integer vector") } } ) setMethod('.atrack', 'character', function(object, ...){ # check for special escaped track code if( length(i <- atrack_code(object)) ){ if( length(object) == 1L ) object else if( length(i) == length(object) ) as.list(object) else{ # spe <- object[i] # object <- sub("^\\\\:", ":", object[-i]) # t <- callNextMethod() # c(list(t), spe) callNextMethod() } }else{ # object <- sub("^\\\\:", ":", object) callNextMethod() } } ) setMethod('.atrack', 'matrix', function(object, ...) .atrack(as.data.frame(object), ...) ) setMethod('.atrack', 'data.frame', function(object, ...) .atrack(as.list(object), ...) ) # tells if an object is a special annotation track code is_track_code <- function(x) isString(x) && grepl("^[:$]", x) atrack_code <- function(x, value=FALSE){ # check each track item ac <- sapply(x, is_track_code) i <- which(ac) if( !value ) i # return indexes else if( length(i) ) unlist(x[i]) # return values } match_atrack_code <- function(x, table, ...){ # pre-pend ':' table.plain <- sub("^:", '', table) table <- str_c(':', table.plain) # convert into an annotation track if( !is.atrack(x) ) x <- atrack(x, ...) m <- sapply(x, function(x){ if( isString(x) ) charmatch(x, table, nomatch=0L) else 0L }) if( length(i <- which(m!=0L)) ){ if( is.null(names(m)) ) names(m) <- rep('', length(m)) names(m)[i] <- table.plain[m[i]] } m } #' \code{atrack} creates/concatenates \code{annotationTrack} objects #' #' @param order an integer vector that indicates the order of the annotation #' tracks in the result list #' @param enforceNames logical that indicates if missing track names should #' be generated as \code{X} #' @param .SPECIAL an optional list of functions (with no arguments) that are #' called to generate special annotation tracks defined by codes of the form #' \code{':NAME'}. #' e.g., the function \code{link{consensusmap}} defines special tracks #' \code{':basis'} and \code{':consensus'}. #' #' If \code{.SPECIAL=FALSE}, then any special tracks is discarded and a warning #' is thrown. #' #' @param .DATA data used to match and extend annotation specifications. #' It is passed to argument \code{data} of the \code{.atrack} methods, which #' in turn use pass it to \code{\link{match_atrack}}. #' #' @param .CACHE an \code{annotationTrack} object with which the generated #' annotation track should be consistent. #' This argument is more for internal/advanced usage and should not be used #' by the end-user. #' #' @return \code{atrack} returns a list, decorated with class #' \code{'annotationTrack'}, where each element contains the description #' of an annotation track. #' #' @rdname atrack #' @export atrack <- function(..., order = NULL, enforceNames=FALSE, .SPECIAL=NA, .DATA = NULL, .CACHE = NULL){ # cbind object with the other arguments l <- list(...) if( length(l) == 1L && is.atrack(l[[1]]) ) object <- l[[1L]] else if( length(l) > 0 ){ object <- list() #print(l) lapply(seq_along(l), function(i){ x <- l[[i]] if( is_NA(x) || is.null(x) ) return() xa <- .atrack(x, data=.DATA) if( is_NA(xa) || is.null(xa) ) return() n <- names(object) # convert into a list if( !is.list(xa) ) xa <- setNames(list(xa), names(l)[i]) # remove NA and NULL elements if( is.null(xa) || is_NA(xa) ) return() # cbind with previous tracks if( is.null(object) ) object <<- xa else object <<- c(object, xa) }) } # exit now if object is NULL if( is.null(object) ) return() if( !length(object) ) return( annotationTrack() ) # add class 'annotationTrack' if not already there # (needed before calling match_atrack_code) object <- annotationTrack(object) # substitute special tracks if( is.list(.SPECIAL) ){ # str(object) m <- match_atrack_code(object, names(.SPECIAL)) i_spe <- which(m!=0L) if( length(i_spe) ){ # add names where needed if( is.null(names(object)) ) names(object) <- rep('', length(object)) # remove duplicated special tracks if( anyDuplicated(m[i_spe]) ){ # enforce name consistency if necessary g <- split(i_spe, m[i_spe]) sapply(g, function(i){ n <- names(object)[i] if( length(n <- n[n!='']) ) names(object)[i] <<- n[1L] }) # idup <- which(duplicated(m) & m!=0L) object <- object[-idup] m <- m[-idup] i_spe <- which(m!=0L) } # # enforce names consistent with the CACHE if( anyValue(.CACHE) ){ if( !is.atrack(.CACHE) ) stop("Argument .CACHE should be an annotation track object. [", class(.CACHE), ']') i_spe_cache <- atrack_code(.CACHE) if( length(i_spe_cache) ){ .CACHE_SPE <- unlist(.CACHE[i_spe_cache]) if( !is.null(names(.CACHE_SPE)) ){ sapply(i_spe, function(i){ x <- object[[i]] if( names(object)[i] == '' && !is_NA(j <- match(x, .CACHE_SPE)) && names(.CACHE_SPE)[j] != ''){ names(object)[i] <<- names(.CACHE_SPE)[j] } }) } } } # compute value a <- sapply(m[i_spe], function(i) .SPECIAL[[i]](), simplify=FALSE) object[i_spe] <- a # NB: this does not change the names # reset names nm <- names(object)[i_spe] names(object)[i_spe] <- ifelse(nm!='', nm, names(a)) } # remove special tracks if necessary if( length(i <- atrack_code(object)) ){ warning("Discarding unresolved special annotation tracks: " , str_out(unlist(object[i]), use.names=TRUE)) object <- object[-i] } } # generate names if( enforceNames ){ n <- names(object) xnames <- paste('X', 1:length(object), sep='') if( is.null(n) ) names(object) <- xnames else names(object)[n==''] <- xnames[n==''] } # reorder if necessary if( !is.null(order) ){ object <- sapply(object, function(x) x[order], simplify=FALSE) #lapply(seq_along(object), function(i) object[[i]] <<- object[[i]][order]) } #print(object) # return object annotationTrack(object) } #' \code{annotationTrack} is constructor function for \code{annotationTrack} object #' #' @rdname atrack annotationTrack <- function(x = list()){ if( !is.atrack(x) ) class(x) <- c('annotationTrack', if( nargs() ) class(x)) x } #setGeneric('atrack<-', function(object, value) standardGeneric('atrack<-')) #setReplaceMethod('atrack', signature(object='ANY', value='ANY'), # function(object, value){ # # if the annotation track is not NA: convert it into a atrack # # and set the value # if( !is_NA(object) && length(value) > 0 ){ # object <- atrack(object, value) # } # object # } #) #setReplaceMethod('atrack', signature(object='annotationTrack'), # function(object, value, replace = FALSE){ # if( !replace && length(value) > 0 ) atrack(object, value) # else if( replace ) atrack(value) # else object # } #) NMF/R/NMF-class.R0000644000176200001440000026201513710555647012764 0ustar liggesusers#library(R.utils) #' @include utils.R #' @include versions.R #' @include algorithmic.R #' @include aheatmap.R NULL #' Advanced Usage of the Package NMF #' #' The functions documented here provide advanced functionalities useful when #' developing within the framework implemented in the NMF package. #' #' @rdname advanced #' @name advanced-NMF NULL # declare old S3 class 'proc_time' to use it as a slot for class NMF setOldClass('proc_time', prototype=numeric()) ################################ # Class: NMF ################################ #' Generic Interface for Nonnegative Matrix Factorisation Models #' #' The class \code{NMF} is a \emph{virtual class} that defines a common #' interface to handle Nonnegative Matrix Factorization models (NMF models) #' in a generic way. #' Provided a minimum set of generic methods is implemented by concrete #' model classes, these benefit from a whole set of functions and utilities #' to perform common computations and tasks in the context of Nonnegative Matrix #' Factorization. #' #' Class \code{NMF} makes it easy to develop new models that integrate well #' into the general framework implemented by the \emph{NMF} package. #' #' Following a few simple guidelines, new types of NMF models benefit from all the #' functionalities available for the built-in NMF models -- that derive themselves #' from class \code{NMF}. #' See section \emph{Implementing NMF models} below. #' #' See \code{\linkS4class{NMFstd}}, and references and links therein for #' details on the built-in implementations of the standard NMF model and its #' extensions. #' #' @slot misc A list that is used internally to temporarily store algorithm #' parameters during the computation. #' #' @export #' @family NMF-interface #' #' @section Implementing NMF models: #' #' The class \code{NMF} only defines a basic data/low-level interface for NMF models, as #' a collection of generic methods, responsible with data handling, upon which #' relies a comprehensive set of functions, composing a rich higher-level interface. #' #' Actual NMF models are defined as sub-classes that inherits from class #' \code{NMF}, and implement the management of data storage, providing #' definitions for the interface's pure virtual methods. #' #' The minimum requirement to define a new NMF model that integrates into #' the framework of the \emph{NMF} package are the followings: #' #' \itemize{ #' #' \item Define a class that inherits from class \code{NMF} and implements the #' new model, say class \code{myNMF}. #' #' \item Implement the following S4 methods for the new class \code{myNMF}: #' \describe{ #' \item{fitted}{\code{signature(object = "myNMF", value = "matrix")}: #' Must return the estimated target matrix as fitted by the NMF model #' \code{object}. #' } #' \item{basis}{\code{signature(object = "myNMF")}: #' Must return the basis matrix(e.g. the first matrix factor in #' the standard NMF model). #' } #' \item{basis<-}{\code{signature(object = "myNMF", value = "matrix")}: #' Must return \code{object} with the basis matrix set to #' \code{value}. #' } #' \item{coef}{\code{signature(object = "myNMF")}: #' Must return the matrix of mixture coefficients (e.g. the second matrix #' factor in the standard NMF model). #' } #' \item{coef<-}{\code{signature(object = "myNMF", value = "matrix")}: #' Must return \code{object} with the matrix of mixture coefficients set to #' \code{value}. #' } #' } #' #' The \emph{NMF} package provides "pure virtual" definitions of these #' methods for class \code{NMF} (i.e. with signatures \code{(object='NMF', ...)} #' and \code{(object='NMF', value='matrix')}) that throw an error if called, so #' as to force their definition for model classes. #' #' \item Optionally, implement method \code{rnmf}(signature(x="myNMF", target="ANY")). #' This method should call \code{callNextMethod(x=x, target=target, ...)} and #' fill the returned NMF model with its specific data suitable random values. #' } #' #' For concrete examples of NMF models implementations, see class #' \code{\linkS4class{NMFstd}} and its extensions (e.g. classes #' \code{\linkS4class{NMFOffset}} or \code{\linkS4class{NMFns}}). #' #' @section Creating NMF objects: #' Strictly speaking, because class \code{NMF} is virtual, no object of class #' \code{NMF} can be instantiated, only objects from its sub-classes. #' However, those objects are sometimes shortly referred in the documentation and #' vignettes as "\code{NMF} objects" instead of "objects that inherits from #' class \code{NMF}". #' #' For built-in models or for models that inherit from the standard model class #' \code{\linkS4class{NMFstd}}, the factory method \code{nmfModel} enables to easily create #' valid \code{NMF} objects in a variety of common situations. #' See documentation for the the factory method \code{\link{nmfModel}} for #' more details. #' #' @references #' Definition of Nonnegative Matrix Factorization in its modern formulation: \cite{Lee1999} #' #' Historical first definition and algorithms: \cite{Paatero1994} #' #' @family NMF-model Implementations of NMF models #' @seealso #' Main interface to perform NMF in \code{\link{nmf-methods}}. #' #' Built-in NMF models and factory method in \code{\link{nmfModel}}. #' #' Method \code{\link{seed}} to set NMF objects with values suitable to start #' algorithms with. #' #' @examples #' #' # show all the NMF models available (i.e. the classes that inherit from class NMF) #' nmfModels() #' # show all the built-in NMF models available #' nmfModels(builtin.only=TRUE) #' #' # class NMF is a virtual class so cannot be instantiated: #' try( new('NMF') ) #' #' # To instantiate an NMF model, use the factory method nmfModel. see ?nmfModel #' nmfModel() #' nmfModel(3) #' nmfModel(3, model='NMFns') #' setClass('NMF' , representation( misc = 'list' # misceleneaous data used during fitting ) , contains = 'VIRTUAL') #' Fitted Matrix in NMF Models #' #' Computes the estimated target matrix based on a given \emph{NMF} model. #' The estimation depends on the underlying NMF model. #' For example in the standard model \eqn{V \equiv W H}{V ~ W H}, the target matrix is #' estimated by the matrix product \eqn{W H}. #' In other models, the estimate may depend on extra parameters/matrix #' (cf. Non-smooth NMF in \code{\link{NMFns-class}}). #' #' This function is a S4 generic function imported from \link[stats]{fitted} in #' the package \emph{stats}. #' It is implemented as a pure virtual method for objects of class #' \code{NMF}, meaning that concrete NMF models must provide a #' definition for their corresponding class (i.e. sub-classes of #' class \code{NMF}). #' See \code{\linkS4class{NMF}} for more details. #' #' @param object an object that inherit from class \code{NMF} #' @param ... extra arguments to allow extension #' #' @return the target matrix estimate as fitted by the model \code{object} #' @export setGeneric('fitted', package='stats') setMethod('fitted', signature(object='NMF'), function(object, ...){ stop("NMF::fitted is a pure virtual method of interface 'NMF'. It should be overloaded in class '", class(object),"'.") } ) #' Accessing NMF Factors #' #' \code{basis} and \code{basis<-} are S4 generic functions which respectively #' extract and set the matrix of basis components of an NMF model #' (i.e. the first matrix factor). #' #' For example, in the case of the standard NMF model \eqn{V \equiv W H}{V ~ W H}, #' the method \code{basis} will return the matrix \eqn{W}. #' #' \code{basis} and \code{basis<-} are defined for the top #' virtual class \code{\linkS4class{NMF}} only, and rely internally on the low-level #' S4 generics \code{.basis} and \code{.basis<-} respectively that effectively #' extract/set the coefficient data. #' These data are post/pre-processed, e.g., to extract/set only their #' non-fixed terms or check dimension compatibility. #' #' @param object an object from which to extract the factor matrices, typically an #' object of class \code{\linkS4class{NMF}}. #' @param ... extra arguments to allow extension and passed to the low-level #' access functions \code{.coef} and \code{.basis}. #' #' Note that these throw an error if used in replacement functions \code{}. #' #' @rdname basis-coef-methods #' @family NMF-interface #' @export #' setGeneric('basis', function(object, ...) standardGeneric('basis') ) #' Default method returns the value of S3 slot or attribute \code{'basis'}. #' It returns \code{NULL} if none of these are set. #' #' Arguments \code{...} are not used by this method. setMethod('basis', signature(object='ANY'), function(object, ...){ if( is.list(object) && 'basis' %in% names(object) ) object[['basis']] else attr(object, 'basis') } ) #' @param all a logical that indicates whether the complete matrix factor #' should be returned (\code{TRUE}) or only the non-fixed part. #' This is relevant only for formula-based NMF models that include fixed basis or #' coefficient terms. #' setMethod('basis', signature(object='NMF'), function(object, all=TRUE, ...){ if( all || !length(i <- ibterms(object)) ){ # return all coefficients .basis(object, ...) } else { # remove fixed basis .basis(object, ...)[, -i] } } ) #' \code{.basis} and \code{.basis<-} are the low-level S4 generics that simply #' return/set basis component data in an object. #' They are defined so that some common processing may be implemented in #' \code{basis} and \code{basis<-}. #' #' The methods \code{.basis}, \code{.coef} and their replacement versions #' are implemented as pure virtual methods for the interface class #' \code{NMF}, meaning that concrete NMF models must provide a #' definition for their corresponding class (i.e. sub-classes of #' class \code{NMF}). #' See \code{\linkS4class{NMF}} for more details. #' #' @rdname basis-coef-methods #' @export setGeneric('.basis', function(object, ...) standardGeneric('.basis') ) setMethod('.basis', signature(object='NMF'), function(object, ...){ stop("NMF::.basis is a pure virtual method of interface 'NMF'. It should be overloaded in class '", class(object),"'.") } ) #' @export #' @rdname basis-coef-methods setGeneric('basis<-', function(object, ..., value) standardGeneric('basis<-') ) #' Default methods that calls \code{.basis<-} and check the validity of the #' updated object. #' @param use.dimnames logical that indicates if the object's dim names should be #' set using those from the new value, or left unchanged -- after truncating #' them to fit new dimensions if necessary. #' This is useful to only set the entries of a factor. #' setReplaceMethod('basis', signature(object='NMF', value='ANY'), function(object, use.dimnames = TRUE, ..., value){ # error if passed extra arguments if( length(xargs<- list(...)) ){ stop("basis<-,NMF - Unused arguments: ", str_out(xargs, Inf, use.names = TRUE)) } # backup old dimnames to reapply them on exit if( !use.dimnames ) odn <- dimnames(object) nb_old <- nbasis(object) # only set non-fixed terms if( !nbterms(object) ) .basis(object) <- value else{ i <- ibasis(object) .basis(object)[,i] <- value[, i] } # adapt coef if empty if( !hasCoef(object) ){ x <- basis(object) .coef(object) <- rbind(coef(object)[1:min(nb_old, ncol(x)), , drop = FALSE], matrix(NA, max(ncol(x)-nb_old, 0), 0)) # .coef(object) <- coef(object)[1:ncol(x), , drop = FALSE] } # check object validity validObject(object) # update other factor if necessary if( use.dimnames ) basisnames(object) <- colnames(basis(object)) else if( !length(odn) ) dimnames(object) <- NULL else dimnames(object) <- mapply(head, odn, dim(object), SIMPLIFY = FALSE) object } ) #' @param value replacement value #' @rdname basis-coef-methods #' @export setGeneric('.basis<-', function(object, value) standardGeneric('.basis<-') ) setReplaceMethod('.basis', signature(object='NMF', value='matrix'), function(object, value){ stop("NMF::.basis<- is a pure virtual method of interface 'NMF'. It should be overloaded in class '", class(object),"'.") } ) #' @export setGeneric('loadings', package='stats') #' Method loadings for NMF Models #' #' The method \code{loadings} is identical to \code{basis}, but do #' not accept any extra argument. #' #' The method \code{loadings} is provided to standardise the NMF interface #' against the one defined in the \code{\link{stats}} package, #' and emphasises the similarities between NMF and PCA or factorial analysis #' (see \code{\link{loadings}}). #' #' @rdname basis-coef-methods setMethod('loadings', 'NMF', function(x) basis(x) ) #' Get/Set the Coefficient Matrix in NMF Models #' #' \code{coef} and \code{coef<-} respectively extract and set the #' coefficient matrix of an NMF model (i.e. the second matrix factor). #' For example, in the case of the standard NMF model \eqn{V \equiv WH}{V ~ W H}, #' the method \code{coef} will return the matrix \eqn{H}. #' #' \code{coef} and \code{coef<-} are S4 methods defined for the corresponding #' generic functions from package \code{stats} (See \link[stats]{coef}). #' Similarly to \code{basis} and \code{basis<-}, they are defined for the top #' virtual class \code{\linkS4class{NMF}} only, and rely internally on the S4 #' generics \code{.coef} and \code{.coef<-} respectively that effectively #' extract/set the coefficient data. #' These data are post/pre-processed, e.g., to extract/set only their #' non-fixed terms or check dimension compatibility. #' #' @rdname basis-coef-methods #' @export setGeneric('coef', package='stats') setMethod('coef', 'NMF', function(object, all=TRUE, ...){ if( all || !length(i <- icterms(object)) ){ # return all coefficients .coef(object, ...) } else { # remove fixed coefficients .coef(object, ...)[-i, ] } } ) #' \code{.coef} and \code{.coef<-} are low-level S4 generics that simply #' return/set coefficient data in an object, leaving some common processing #' to be performed in \code{coef} and \code{coef<-}. #' #' @rdname basis-coef-methods #' @export setGeneric('.coef', function(object, ...) standardGeneric('.coef')) setMethod('.coef', signature(object='NMF'), function(object, ...){ stop("NMF::.coef is a pure virtual method of interface 'NMF'. It should be overloaded in class '", class(object),"'.") } ) #' @export #' @rdname basis-coef-methods setGeneric('coef<-', function(object, ..., value) standardGeneric('coef<-') ) #' Default methods that calls \code{.coef<-} and check the validity of the #' updated object. setReplaceMethod('coef', signature(object='NMF', value='ANY'), function(object, use.dimnames = TRUE, ..., value){ # error if passed extra arguments if( length(xargs<- list(...)) ){ stop("coef<-,NMF - Unused arguments: ", str_out(xargs, Inf, use.names = TRUE)) } # backup old dimnames to reapply them on exit if( !use.dimnames ) odn <- dimnames(object) nb_old <- nbasis(object) # only set non-fixed terms if( !ncterms(object) ) .coef(object) <- value else{ i <- icoef(object) .coef(object)[i, ] <- value[i, ] } # adapt basis if empty before validation if( !hasBasis(object) ){ x <- coef(object) .basis(object) <- cbind(basis(object)[, 1:min(nb_old, nrow(x)), drop = FALSE], matrix(NA, 0, max(nrow(x)-nb_old, 0))) } # check object validity validObject(object) # update other factor if necessary if( use.dimnames ) basisnames(object) <- rownames(coef(object)) else if( !length(odn) ) dimnames(object) <- NULL else dimnames(object) <- mapply(head, odn, dim(object), SIMPLIFY = FALSE) object } ) #' @export #' @rdname basis-coef-methods setGeneric('.coef<-', function(object, value) standardGeneric('.coef<-') ) setReplaceMethod('.coef', signature(object='NMF', value='matrix'), function(object, value){ stop("NMF::.coef<- is a pure virtual method of interface 'NMF'. It should be overloaded in class '", class(object),"'.") } ) #' @description Methods \code{coefficients} and \code{coefficients<-} are #' simple aliases for methods \code{coef} and \code{coef<-} respectively. #' #' @export #' @rdname basis-coef-methods setGeneric('coefficients', package='stats') #' Alias to \code{coef,NMF}, therefore also pure virtual. setMethod('coefficients', signature(object='NMF'), selectMethod('coef', 'NMF')) #' @description \code{scoef} is similar to \code{coef}, but returns the mixture #' coefficient matrix of an NMF model, with the columns scaled so that they #' sum up to a given value (1 by default). #' #' @param scale scaling factor, which indicates to the value the columns of the #' coefficient matrix should sum up to. #' #' @rdname basis-coef-methods #' @export #' #' @examples #' #' # Scaled coefficient matrix #' x <- rnmf(3, 10, 5) #' scoef(x) #' scoef(x, 100) #' setGeneric('scoef', function(object, ...) standardGeneric('scoef') ) setMethod('scoef', 'NMF', function(object, scale=1){ sweep(coef(object), 2L, colSums(coef(object)) / scale, '/') } ) setMethod('scoef', 'matrix', function(object, scale=1){ sweep(object, 2L, colSums(object) / scale, '/') } ) unit.test(scoef, { x <- rnmf(3, 10, 5) checkIdentical(colSums(scoef(x)), rep(1, nbasis(x)) , "Default call: columns are scaled to sum-up to one") checkIdentical(colSums(scoef(x, 100)), rep(1, nbasis(x)) , "Scale=10: columns are scaled to sum-up to 10") }) #' Rescaling NMF Models #' #' Rescales an NMF model keeping the fitted target matrix identical. #' #' Standard NMF models are identifiable modulo a scaling factor, meaning that the #' basis components and basis profiles can be rescaled without changing the fitted #' values: #' #' \deqn{X = W_1 H_1 = (W_1 D) (D^{-1} H_1) = W_2 H_2}{X = W H = (W D) (D^-1 H)} #' with \eqn{D= \alpha diag(1/\delta_1, \ldots, 1\delta_r)}{D= alpha * diag(1/delta_1, ..., 1/delta_r)} #' #' The default call \code{scale(object)} rescales the basis NMF object so that each #' column of the basis matrix sums up to one. #' #' @param x an NMF object #' @param center either a numeric normalising vector \eqn{\delta}{delta}, or either #' \code{'basis'} or \code{'coef'}, which respectively correspond to using the #' column sums of the basis matrix or the inverse of the row sums of the #' coefficient matrix as a normalising vector. #' If numeric, \code{center} should be a single value or a vector of length the #' rank of the NMF model, i.e. the number of columns in the basis matrix. #' @param scale scaling coefficient applied to \eqn{D}, i.e. the value of \eqn{\alpha}{alpha}, #' or, if \code{center='coef'}, the value of \eqn{1/\alpha}{1/alpha} (see section \emph{Details}). #' #' @return an NMF object #' #' @export #' @examples #' #' # random 3-rank 10x5 NMF model #' x <- rnmf(3, 10, 5) #' #' # rescale based on basis #' colSums(basis(x)) #' colSums(basis(scale(x))) #' #' rx <- scale(x, 'basis', 10) #' colSums(basis(rx)) #' rowSums(coef(rx)) #' #' # rescale based on coef #' rowSums(coef(x)) #' rowSums(coef(scale(x, 'coef'))) #' rx <- scale(x, 'coef', 10) #' rowSums(coef(rx)) #' colSums(basis(rx)) #' #' # fitted target matrix is identical but the factors have been rescaled #' rx <- scale(x, 'basis') #' all.equal(fitted(x), fitted(rx)) #' all.equal(basis(x), basis(rx)) #' scale.NMF <- function(x, center=c('basis', 'coef'), scale=1){ # determine base value if( missing(center) ) center <- match.arg(center) base <- center delta <- if( is.character(base) ){ base <- match.arg(center) if( base == 'basis' ) colSums(basis(x)) else{ scale <- 1/scale 1 / rowSums(coef(x)) } }else if( is.numeric(base) ) base else stop("Invalid base value: should be a numeric or one of " , str_out(c('none', 'basis', 'coef'))) # scale D <- scale/delta # W <- W * D basis(x) <- sweep(basis(x), 2L, D, '*') # H <- D^-1 * H coef(x) <- sweep(coef(x), 1L, D, '/') x } unit.test("scale", { r <- 3 x <- rnmf(r, 10, 5) .lcheck <- function(msg, rx, ref, target){ .msg <- function(...) paste(msg, ':', ...) checkTrue(!identical(basis(x), basis(rx)), .msg("changes basis matrix")) checkTrue(!identical(coef(x), coef(rx)), .msg("changes coef matrix")) checkEqualsNumeric(fitted(x), fitted(rx), .msg("fitted target is identical")) brx <- colSums(basis(rx)) crx <- rowSums(coef(rx)) if( target == 1 ){ checkEquals(brx, ref, .msg("correctly scales basis components")) checkTrue(!all(crx==ref), .msg("does not force scale on coefficient matrix")) }else{ checkTrue(!all(brx==ref), .msg("does not force scale on basis matrix")) checkEquals(crx, ref , .msg("correctly scales rows of coef matrix")) } } .check <- function(msg, ref, ...){ .lcheck(str_c(msg, " + argument center='basis'") , scale(x, center='basis', ...), ref, 1) .lcheck(str_c(msg, " + argument center='coef'") , scale(x, center='coef', ...), ref, 2) } .lcheck("Default call", scale(x), rep(1, r), 1) .check("Missing argument scale", rep(1, r)) .check("Argument scale=10", rep(10, r), scale=10) s <- runif(r) .check("Argument scale=numeric", s, scale=s) }) #' Generating Random NMF Models #' #' Generates NMF models with random values drawn from a uniform distribution. #' It returns an NMF model with basis and mixture coefficient matrices filled #' with random values. #' The main purpose of the function \code{rnmf} is to provide a common #' interface to generate random seeds used by the \code{\link{nmf}} function. #' #' If necessary, extensions of the standard NMF model or custom models must #' define a method "rnmf,,numeric" for initialising their #' specific slots other than the basis and mixture coefficient matrices. #' In order to benefit from the complete built-in interface, the overloading #' methods should call the generic version using function #' \code{\link{callNextMethod}}, prior to set the values of the specific slots. #' See for example the method \code{\link[=rnmf,NMFOffset,numeric-method]{rnmf}} #' defined for \code{\linkS4class{NMFOffset}} models: #' \code{showMethods(rnmf, class='NMFOffset', include=TRUE))}. #' #' For convenience, shortcut methods for working on \code{data.frame} objects #' directly are implemented. #' However, note that conversion of a \code{data.frame} into a \code{matrix} #' object may take some non-negligible time, for large datasets. #' If using this method or other NMF-related methods several times, consider #' converting your data \code{data.frame} object into a matrix once for good, #' when first loaded. #' #' @param x an object that determines the rank, dimension and/or class of the #' generated NMF model, e.g. a numeric value or an object that inherits from class #' \code{\linkS4class{NMF}}. #' See the description of the specific methods for more details on the supported #' types. #' @param target optional specification of target dimensions. #' See section \emph{Methods} for how this parameter is used by the different #' methods. #' @param ... extra arguments to allow extensions and passed to the next method #' eventually down to \code{\link{nmfModel}}, where they are used to initialise #' slots that are specific to the instantiating NMF model. #' #' @return An NMF model, i.e. an object that inherits from class #' \code{\linkS4class{NMF}}. #' #' @export #' @seealso \code{\link{rmatrix}} #' @family NMF-interface setGeneric('rnmf', function(x, target, ...) standardGeneric('rnmf') ) # Define the loading namespace .PKG.NAMESPACE <- packageEnv() #' Testing NMF Objects #' #' @description #' The functions documented here tests different characteristics of NMF objects. #' #' \code{is.nmf} tests if an object is an NMF model or a class that extends #' the class NMF. #' #' @details #' #' \code{is.nmf} tests if \code{object} is the name of a class (if a \code{character} #' string), or inherits from a class, that extends \code{\linkS4class{NMF}}. #' #' @note The function \code{is.nmf} does some extra work with the namespace as #' this function needs to return correct results even when called in \code{.onLoad}. #' See discussion on r-devel: \url{https://stat.ethz.ch/pipermail/r-devel/2011-June/061357.html} #' #' @param x an R object. See section \emph{Details}, for how each function #' uses this argument. #' #' @rdname types #' @export #' #' @examples #' #' # test if an object is an NMF model, i.e. that it implements the NMF interface #' is.nmf(1:4) #' is.nmf( nmfModel(3) ) #' is.nmf( nmf(rmatrix(10, 5), 2) ) #' is.nmf <- function(x){ # load definition for base class NMF clref <- getClass('NMF', .Force=TRUE, where=.PKG.NAMESPACE) is(x, clref) } unit.test(is.nmf,{ checkTrue(!is.nmf(1:4), "on vector: FALSE") checkTrue(!is.nmf(list(1:4)), "on list: FALSE") checkTrue(is.nmf('NMF'), "on 'NMF': TRUE") checkTrue(is.nmf('NMFstd'), "on 'NMFstd': TRUE") checkTrue( is.nmf( nmfModel(3) ), "on empty model: TRUE") checkTrue( is.nmf( rnmf(3, 20, 10) ), "on random model: TRUE") checkTrue( is.nmf( nmf(rmatrix(20,10), 3) ), "on NMFfit object: TRUE") }) isNMFclass <- function(x){ if( is.character(x) ){ # test object is a class that extends NMF # load definition for base class NMF clref <- getClass('NMF', .Force=TRUE, where=.PKG.NAMESPACE) cl <- getClass(x, .Force=TRUE, where=.PKG.NAMESPACE) if( is.null(cl) ) cl <- getClass(x, .Force=TRUE) extends(cl, clref) }else FALSE } ################################ # Taken from Biobase selectSome <- function (obj, maxToShow = 5) { len <- length(obj) if (maxToShow < 3) maxToShow <- 3 if (len > maxToShow) { maxToShow <- maxToShow - 1 bot <- ceiling(maxToShow/2) top <- len - (maxToShow - bot - 1) nms <- obj[c(1:bot, top:len)] c(as.character(nms[1:bot]), "...", as.character(nms[-c(1:bot)])) } else if (is.factor(obj)) as.character(obj) else obj } .showFixedTerms <- function(x, ...){ s <- sapply(x, function(t){ s <- if( is.factor(t) ) selectSome(levels(t), ...) else selectSome(t, ...) s <- str_out(s, Inf, quote=FALSE) if( is.factor(t) ) s <- str_c('<', s, ">") s }) paste(names(s), '=', s) } #' Show method for objects of class \code{NMF} #' @export setMethod('show', 'NMF', function(object) { cat("\n", sep='') cat("features:", nrow(object), "\n") cat("basis/rank:", nbasis(object), "\n") cat("samples:", ncol(object), "\n") # show fixed terms if( (n <- ncterms(object)) ){ cat("fixed coef [", n, "]:\n" , str_c(' ', .showFixedTerms(cterms(object), 4), collapse="\n") , "\n", sep='') } if( (n <- nbterms(object)) ){ cat("fixed basis [", n, "]:\n" , str_c(' ', .showFixedTerms(bterms(object), 4), collapse="\n") , "\n", sep='') } # show the miscellaneous model parameters if( length(object@misc) > 0L ){ cat("miscellaneous:", str_desc(object@misc, exdent=12L), ". (use 'misc(object)')\n") } } ) #' Dimension of NMF Objects #' #' @description #' The methods \code{dim}, \code{nrow}, \code{ncol} and \code{nbasis} return #' the different dimensions associated with an NMF model. #' #' \code{dim} returns all dimensions in a length-3 integer vector: #' the number of row and columns of the estimated target matrix, #' as well as the factorization rank (i.e. the number of basis components). #' #' \code{nrow}, \code{ncol} and \code{nbasis} provide separate access to each #' of these dimensions respectively. #' #' @details #' The NMF package does not implement specific functions \code{nrow} and \code{ncol}, #' but rather the S4 method \code{dim} for objects of class \code{\linkS4class{NMF}}. #' This allows the base methods \code{\link{nrow}} and \code{\link{ncol}} to #' directly work with such objects, to get the number of rows and columns of #' the target matrix estimated by an NMF model. #' #' The function \code{nbasis} is a new S4 generic defined in the package NMF, that #' returns the number of basis components of an object. #' Its default method should work for any object, that has a suitable #' \code{basis} method defined for its class. #' #' @param x an object with suitable \code{basis} and \code{coef} methods, such #' as an object that inherit from \code{\linkS4class{NMF}}. #' @param ... extra arguments to allow extension. #' #' @return a single integer value or, for \code{dim}, a length-3 integer vector, #' e.g. \code{c(2000, 30, 3)} for an \code{NMF} model that fits a 2000 x 30 #' matrix using 3 basis components. #' #' @export #' @rdname dims #' @aliases dim-NMF setGeneric('nbasis', function(x, ...) standardGeneric('nbasis') ) #' Default method which returns the number of columns of the basis matrix extracted #' from \code{x} using a suitable method \code{basis}, or, if the latter is \code{NULL}, #' the value of attributes \code{'nbasis'}. #' #' For NMF models, this also corresponds to the number of rows in the coefficient #' matrix. #' setMethod('nbasis', signature(x='ANY'), function(x, ...) { if( !is.null(n <- ncol(basis(x, ...))) ) n else if( is.list(x) && 'nbasis' %in% names(x) ) x[['nbasis']] else attr(x, 'nbasis') } ) #' method for NMF objects for the base generic \code{\link{dim}}. #' It returns all dimensions in a length-3 integer vector: #' the number of row and columns of the estimated target matrix, #' as well as the factorization rank (i.e. the number of basis components). #' #' @rdname dims #' @export setMethod('dim', signature(x='NMF'), function(x){ c(nrow(basis(x)), ncol(coef(x)), nbasis(x)) } ) #' Dimension names for NMF objects #' #' @description #' The methods \code{dimnames}, \code{rownames}, \code{colnames} and #' \code{basisnames} and their respective replacement form allow to get and set #' the dimension names of the matrix factors in a NMF model. #' #' \code{dimnames} returns all the dimension names in a single list. #' Its replacement form \code{dimnames<-} allows to set all dimension names at once. #' #' \code{rownames}, \code{colnames} and \code{basisnames} provide separate access #' to each of these dimension names respectively. #' Their respective replacement form allow to set each dimension names separately. #' #' @details #' #' The function \code{basisnames} is a new S4 generic defined in the package NMF, #' that returns the names of the basis components of an object. #' Its default method should work for any object, that has a suitable \code{basis} #' method defined for its class. #' #' The method \code{dimnames} is implemented for the base generic \code{\link{dimnames}}, #' which make the base function \code{\link{rownames}} and \code{\link{colnames}} #' work directly. #' #' Overall, these methods behave as their equivalent on \code{matrix} objects. #' The function \code{basisnames<-} ensures that the dimension names are handled #' in a consistent way on both factors, enforcing the names on both matrix factors #' simultaneously. #' #' @param x an object with suitable \code{basis} and \code{coef} methods, such #' as an object that inherit from \code{\linkS4class{NMF}}. #' @param ... extra argument to allow extension. #' #' @export #' @rdname dimnames #' @aliases dimnames-NMF #' #' @examples #' # create a random NMF object #' a <- rnmf(2, 5, 3) #' #' # set dimensions #' dims <- list( features=paste('f', 1:nrow(a), sep='') #' , samples=paste('s', 1:ncol(a), sep='') #' , basis=paste('b', 1:nbasis(a), sep='') ) #' dimnames(a) <- dims #' dimnames(a) #' basis(a) #' coef(a) #' #' # access the dimensions separately #' rownames(a) #' colnames(a) #' basisnames(a) #' #' # set only the first dimension (rows of basis): the other two dimnames are set to NULL #' dimnames(a) <- dims[1] #' dimnames(a) #' basis(a) #' coef(a) #' #' # set only the two first dimensions (rows and columns of basis and coef respectively): #' # the basisnames are set to NULL #' dimnames(a) <- dims[1:2] #' dimnames(a) #' basis(a) #' #' # reset the dimensions #' dimnames(a) <- NULL #' dimnames(a) #' basis(a) #' coef(a) #' #' # set each dimensions separately #' rownames(a) <- paste('X', 1:nrow(a), sep='') # only affect rows of basis #' basis(a) #' #' colnames(a) <- paste('Y', 1:ncol(a), sep='') # only affect columns of coef #' coef(a) #' #' basisnames(a) <- paste('Z', 1:nbasis(a), sep='') # affect both basis and coef matrices #' basis(a) #' coef(a) #' setGeneric('basisnames', function(x, ...) standardGeneric('basisnames') ) #' Default method which returns the column names of the basis matrix extracted from #' \code{x}, using the \code{basis} method. #' #' For NMF objects these also correspond to the row names of the coefficient matrix. #' @rdname dimnames setMethod('basisnames', signature(x='ANY'), function(x) { colnames(basis(x)) } ) #' The generic \code{basisnames<-} simultaneously sets the names of the basis #' components and coefficients of an object, for which suitable \code{basis} #' and \code{coef} methods are defined. #' #' @details #' The function \code{basisnames<-} is a new S4 generic defined in the package NMF, #' that sets the names of the basis components of an object. #' Its default method should work for any object, that has suitable \code{basis<-} #' and \code{coef<-} methods method defined for its class. #' #' @param x an object with suitable \code{basis} and \code{coef} methods, such #' as an object that inherit from \code{\linkS4class{NMF}}. #' @param ... extra argument to allow extension. #' @param value a character vector with the names of the basis components to be set #' @export #' @rdname dimnames setGeneric('basisnames<-', function(x, ..., value) standardGeneric('basisnames<-') ) #' Default method which sets, respectively, the row and the column names of the basis #' matrix and coefficient matrix of \code{x} to \code{value}. #' @export #' @rdname dimnames setReplaceMethod('basisnames', 'ANY', function(x, ..., value) { rownames(.coef(x)) <- value colnames(.basis(x)) <- value x } ) #' Returns the dimension names of the NMF model \code{x}. #' #' It returns either NULL if no dimnames are set on the object, #' or a 3-length list containing the row names of the basis matrix, #' the column names of the mixture coefficient matrix, and the column names of #' the basis matrix (i.e. the names of the basis components). #' #' @rdname dimnames #' @export setMethod('dimnames', 'NMF', function(x){ b <- dimnames(basis(x)) if( is.null(b) ) b <- list(NULL, NULL) c <- dimnames(coef(x)) if( is.null(c) ) c <- list(NULL, NULL) l <- c(b[1],c[2],b[2]) if( all(sapply(l, is.null)) ) NULL else l } ) #' Sets the dimension names of the NMF model \code{x}. #' #' \code{value} can be \code{NULL} which resets all dimension names, or a #' 1, 2 or 3-length list providing names at least for the rows of the basis #' matrix. #' #' The optional second element of \code{value} (NULL if absent) is used to set #' the column names of the coefficient matrix. #' The optional third element of \code{value} (NULL if absent) is used to set #' both the column names of the basis matrix and the row names of the #' coefficient matrix. #' #' @rdname dimnames #' @export setReplaceMethod('dimnames', 'NMF', function(x, value){ if( !is.list(value) && !is.null(value) ) stop("NMF::dimnames - Invalid value: must be a list or NULL.") if( length(value) == 0 ) value <- NULL else if( length(value) == 1 ) value <- c(value, list(NULL, NULL)) else if( length(value) == 2 ) # if only the two first dimensions reset the third one value <- c(value, list(NULL)) else if( length(value)!=3 ) # check length of value stop("NMF::dimnames - invalid argument 'value' [a 2 or 3-length list is expected]") # only set relevant dimensions if( length(w <- which(dim(x) == 0)) ){ value[w] <- sapply(value[w], function(x) NULL, simplify=FALSE) } # set dimnames dimnames(.basis(x)) <- value[c(1,3)] dimnames(.coef(x)) <- value[c(3,2)] # return updated model x } ) #' Sub-setting NMF Objects #' #' This method provides a convenient way of sub-setting objects of class \code{NMF}, #' using a matrix-like syntax. #' #' It allows to consistently subset one or both matrix factors in the NMF model, as well #' as retrieving part of the basis components or part of the mixture coefficients with #' a reduced amount of code. #' #' @details #' The returned value depends on the number of subset index passed and the #' value of argument \code{drop}: #' #' \itemize{ \item No index as in \code{x[]} or \code{x[,]}: the value is the #' object \code{x} unchanged. #' #' \item One single index as in \code{x[i]}: the value is the complete NMF #' model composed of the selected basis components, subset by \code{i}, #' except if argument \code{drop=TRUE}, or if it is missing and \code{i} is of length 1. #' Then only the basis matrix is returned with dropped dimensions: #' \code{x[i, drop=TRUE]} <=> \code{drop(basis(x)[, i])}. #' #' This means for example that \code{x[1L]} is the first basis vector, #' and \code{x[1:3, drop = TRUE]} is the matrix composed of the 3 first basis vectors -- in columns. #' #' Note that in version <= 0.18.3, the call \code{x[i, drop = TRUE.or.FALSE]} was equivalent to #' \code{basis(x)[, i, drop=TRUE.or.FALSE]}. #' #' \item More than one index with \code{drop=FALSE} (default) as in #' \code{x[i,j]}, \code{x[i,]}, \code{x[,j]}, \code{x[i,j,k]}, \code{x[i,,k]}, #' etc...: the value is a \code{NMF} object whose basis and/or mixture #' coefficient matrices have been subset accordingly. The third index \code{k} #' affects simultaneously the columns of the basis matrix AND the rows of the #' mixture coefficient matrix. In this case argument \code{drop} is not used. #' #' \item More than one index with \code{drop=TRUE} and \code{i} xor \code{j} #' missing: the value returned is the matrix that is the more affected by the #' subset index. That is that \code{x[i, , drop=TRUE]} and \code{x[i, , k, #' drop=TRUE]} return the basis matrix subset by \code{[i,]} and \code{[i,k]} #' respectively, while \code{x[, j, drop=TRUE]} and \code{x[, j, k, drop=TRUE]} #' return the mixture coefficient matrix subset by \code{[,j]} and \code{[k,j]} #' respectively. #' #' } #' #' @param i index used to subset on the \strong{rows} of the basis matrix (i.e. #' the features). #' It can be a \code{numeric}, \code{logical}, or \code{character} vector #' (whose elements must match the row names of \code{x}). #' In the case of a \code{logical} vector the entries are recycled if necessary. #' @param j index used to subset on the \strong{columns} of the mixture #' coefficient matrix (i.e. the samples). #' It can be a \code{numeric}, \code{logical}, or \code{character} vector #' (whose elements must match the column names of \code{x}). #' In the case of a \code{logical} vector the entries are recycled if necessary. #' @param ... used to specify a third index to subset on the basis components, #' i.e. on both the columns and rows of the basis matrix and mixture #' coefficient respectively. #' It can be a \code{numeric}, \code{logical}, or \code{character} vector #' (whose elements must match the basis names of \code{x}). #' In the case of a \code{logical} vector the entries are recycled if necessary. #' #' Note that only the first extra subset index is used. #' A warning is thrown if more than one extra argument is passed in \code{...}. #' @param drop single \code{logical} value used to drop the \code{NMF-class} #' wrapping and only return subsets of one of the factor matrices (see \emph{Details}) #' #' @rdname subset-NMF #' @export #' @examples #' # create a dummy NMF object that highlight the different way of subsetting #' a <- nmfModel(W=outer(seq(1,5),10^(0:2)), H=outer(10^(0:2),seq(-1,-10))) #' basisnames(a) <- paste('b', 1:nbasis(a), sep='') #' rownames(a) <- paste('f', 1:nrow(a), sep='') #' colnames(a) <- paste('s', 1:ncol(a), sep='') #' #' # or alternatively: #' # dimnames(a) <- list( features=paste('f', 1:nrow(a), sep='') #' # , samples=paste('s', 1:ncol(a), sep='') #' # , basis=paste('b', 1:nbasis(a)) ) #' #' # look at the resulting NMF object #' a #' basis(a) #' coef(a) #' #' # extract basis components #' a[1] #' a[1, drop=FALSE] # not dropping matrix dimension #' a[2:3] #' #' # subset on the features #' a[1,] #' a[2:4,] #' # dropping the NMF-class wrapping => return subset basis matrix #' a[2:4,, drop=TRUE] #' #' # subset on the samples #' a[,1] #' a[,2:4] #' # dropping the NMF-class wrapping => return subset coef matrix #' a[,2:4, drop=TRUE] #' #' # subset on the basis => subsets simultaneously basis and coef matrix #' a[,,1] #' a[,,2:3] #' a[4:5,,2:3] #' a[4:5,,2:3, drop=TRUE] # return subset basis matrix #' a[,4:5,2:3, drop=TRUE] # return subset coef matrix #' #' # 'drop' has no effect here #' a[,,2:3, drop=TRUE] #' setMethod('[', 'NMF', function (x, i, j, ..., drop = FALSE) { k <- NULL mdrop <- missing(drop) # compute number of arguments: x and drop are always passed Nargs <- nargs() - !mdrop single.arg <- FALSE k.notmissing <- FALSE if( !missing(i) && Nargs < 3L ){ k <- i single.arg <- TRUE } else if( Nargs > 3L ){ dots <- list(...) if( length(dots) != 1 ) warning("NMF::[ - using only the first extra subset index, the remaining ", length(dots)-1," are discarded.") k <- dots[[1]] k.notmissing <- TRUE } # no indice was provided => return the object unchanged if ( missing(i) && missing(j) && !k.notmissing ) { # check if there is other arguments if (length(list(...)) != 0) stop("NMF::[] method - please specify which features, samples or basis to subset. See class?NMF.") # otherwise return the untouched object return(x) } # subset the rows of the basis matrix if ( !missing(i) && !single.arg ) .basis(x) <- basis(x)[i, , drop = FALSE] # subset the columns of mixture coefficient matrix if (!missing(j)) .coef(x) <- coef(x)[, j, drop = FALSE] # subset the basis: columns of basis matrix and row of mixture coefficient matrix if( single.arg || k.notmissing ){ .basis(x) <- basis(x)[, k, drop = FALSE] # return basis only single arg and drop=TRUE if( single.arg && ((mdrop && length(k) == 1L) || drop) ) return( drop(basis(x)) ) .coef(x) <- coef(x)[k, , drop = FALSE] } # if drop is TRUE and only one dimension is missing then return affected matrix if( !single.arg && drop ){ if( missing(i) && !missing(j) ) return( drop(coef(x)) ) else if( missing(j) && !missing(i) ) return( drop(basis(x)) ) } # return subset object return(x) } ) #' The function \code{misc} provides access to miscellaneous data members stored #' in slot \code{misc} (as a \code{list}), which allow extensions of NMF models #' to be implemented, without defining a new S4 class. #' #' @param object an object that inherit from class \code{NMF} #' @param ... extra arguments (not used) #' #' @rdname NMF-class #' @export misc <- function(object, ...){ if( !isS4(object) && is.list(object) ) object[['misc']] else attr(object, 'misc') } #' shortcut for \code{x@@misc[[name, exact=TRUE]]} respectively. #' @rdname NMF-class #' @export setMethod('$', 'NMF', function(x, name){ x@misc[[name, exact=TRUE]]; } ) #' shortcut for \code{x@@misc[[name]] <- value} #' @rdname NMF-class #' @export setReplaceMethod('$', 'NMF', function(x, name, value) { x@misc[[name]] <- value x } ) #' @importFrom utils .DollarNames setGeneric('.DollarNames', package='utils') #' @method .DollarNames NMF #' @export .DollarNames.NMF <- function(x, pattern = "") grep(pattern, names(misc(x)), value=TRUE) #' Auto-completion for \code{\linkS4class{NMF}} objects #' @rdname NMF-class #' @export setMethod('.DollarNames', 'NMF', .DollarNames.NMF) #' \code{is.empty.nmf} tests whether an \code{NMF} object describes an empty NMF model, #' i.e. it contains no data. #' #' @details #' \code{is.empty.nmf} returns \code{TRUE} if the basis and coefficient matrices of #' \code{x} have respectively zero rows and zero columns. #' It returns \code{FALSE} otherwise. #' #' In particular, this means that an empty model can still have a non-zero number #' of basis components, i.e. a factorization rank that is not null. #' This happens, for example, in the case of NMF models created calling the factory method #' \code{\link{nmfModel}} with a value only for the factorization rank. #' #' @param ... extra parameters to allow extension or passed to subsequent calls #' #' @rdname types #' @export #' #' @examples #' #' # empty model #' is.empty.nmf( nmfModel(3) ) #' # non empty models #' is.empty.nmf( nmfModel(3, 10, 0) ) #' is.empty.nmf( rnmf(3, 10, 5) ) #' is.empty.nmf <- function(x, ...){ nrow(x) == 0 && ncol(x) == 0 } #' \code{hasBasis} tests whether an objects contains a basis matrix -- returned by #' a suitable method \code{basis} -- with at least one row. #' #' @rdname types #' @export hasBasis <- function(x) nbasis(x) && nrow(basis(x)) != 0L #' \code{hasBasis} tests whether an objects contains a coefficient matrix #' -- returned by a suitable method \code{coef} -- with at least one column. #' #' @rdname types #' @export hasCoef <- function(x) nbasis(x) && ncol(coef(x)) != 0L #' \code{is.partial.nmf} tests whether an NMF model object contains either an empty #' basis or coefficient matrix. #' It is a shorcut for \code{!hasCoef(x) || !hasBasis(x)}. #' #' @rdname types #' @export is.partial.nmf <- function(x) !hasCoef(x) || !hasBasis(x) #' Returns the target matrix estimate of the NMF model \code{x}, perturbated by #' adding a random matrix generated using the default method of \code{rmatrix}: #' it is a equivalent to \code{fitted(x) + rmatrix(fitted(x), ...)}. #' #' This method can be used to generate random target matrices that depart from #' a known NMF model to a controlled extend. #' This is useful to test the robustness of NMF algorithms to the presence of #' certain types of noise in the data. #' #' @examples #' # generate noisy fitted target from an NMF model (the true model) #' gr <- as.numeric(mapply(rep, 1:3, 3)) #' h <- outer(1:3, gr, '==') + 0 #' x <- rnmf(10, H=h) #' y <- rmatrix(x) #' \dontrun{ #' # show heatmap of the noisy target matrix: block patterns should be clear #' aheatmap(y) #' } #' \dontshow{ stopifnot( identical(dim(y), dim(x)[1:2]) ) } #' #' # test NMF algorithm on noisy data #' # add some noise to the true model (drawn from uniform [0,1]) #' res <- nmf(rmatrix(x), 3) #' summary(res) #' #' # add more noise to the true model (drawn from uniform [0,10]) #' res <- nmf(rmatrix(x, max=10), 3) #' summary(res) #' setMethod('rmatrix', 'NMF', function(x, ...){ a <- fitted(x) a + rmatrix(a, ...) } ) unit.test('rmatrix,NMF',{ x <- nmfModel(3, 20, 5) checTrue(is.matrix(y <- rmatrix(x)), "default call: no error") checkIdentical(dim(y), dim(x)[1:2], "default call: correct dimension") checkTrue( !any(is.na(basis(y))), 'default call: no NAs in basis anymore') checkTrue( !any(is.na(coef(y))), 'default call: no NAs in coef anymore') checkTrue( max( max(abs(basis(y)-basis(x))), max(abs(coef(y)-coef(x))) ) <= 1 , "default call: max difference is <= 1") set.seed(123) y <- rmatrix(x) set.seed(123) ref <- matrix(runif(nrow(x)*ncol(x)), nrow(x)) checkIdentical(ref, y - fitted(x), "default call: add uniform random noise to fitted matrix") set.seed(123) ref <- matrix(rnorm(nrow(x)*ncol(x)), nrow(x)) set.seed(123) y <- rmatrix(x, rnorm) checkIdentical(ref, y - fitted(x), "dist is taken into account: add normal random noise to fitted matrix") set.seed(123) y <- rmatrix(x, dist=rnorm) checkIdentical(ref, y - fitted(x), "dist is taken into account: add normal random noise to fitted matrix") set.seed(123) checTrue(is.matrix(y <- rmatrix(x, max=10)), "call with arg max=10: no error") checkTrue( max( max(abs(basis(y)-basis(x))), max(abs(coef(y)-coef(x))) ) <= 10 , "call with arg max=10: max difference is 10") checkTrue( max( max(abs(basis(y)-basis(x))), max(abs(coef(y)-coef(x))) ) >= 5 , "call with arg max=10: max difference is >= 5") }) ###% Produces different kind of plots. #setGeneric('plot', package='graphics') #setMethod('plot', signature( x='NMF', y='missing'), # function(x, y, type=c('hist', 'heatmap'), ...) # { # # retrieve what to plot # type = match.arg(type) # # # save graphical parameters # oldpar = par(no.readonly=TRUE) # on.exit( {par(oldpar)} ) # reset the graphical parameters on exit # # if( what == 'heatmap' ){ # #basicHM(metaprofiles(x), ...) # heatmap.2(metaprofiles(x), trace='none', ...) # } # else if( what == 'hist' ) hist(x, ...) # # } #) #setGeneric('hist', package='graphics') #setMethod('hist', signature(x='NMF'), # function(x, ref=1, alpha=20, ...) # { # stopifnot( ref >= 1 && ref <= ncol(metagenes(x)) ) # alpha = sprintf("%02d", alpha) #add leading zero to alpha if nessecary # # # save graphical parameters # oldpar = par(no.readonly=TRUE) # on.exit( {par(oldpar)} ) # reset the graphical parameters on exit # # # order genes by decreasing contribution to the reference factor # M = metagenes(x)[order(metagenes(x)[,ref], decreasing=T), ] # # #plot the contributions to the reference factor # par(lwd = 0.5) # x = seq(nrow(M)) # html.colors = apply( col2rgb( seq(ncol(M))+1 ), 2, function(x) paste("#", paste(intToHex(x), collapse=''), alpha, sep='') ) # plot(x=x, y=M[,ref], type='h' # , col=html.colors[ref], ylim=c(min(M), max(M)) # , main='Contribution to metagenes', xlab=paste('Genes ordered based on factor', ref), ylab='Contribution') # # # plot the remaining metagenes # remaining.factor = seq(ncol(M))[seq(ncol(M)) != ref] # sapply(remaining.factor, # function(f){ # lines(x=x, M[,f], type='h', col=html.colors[f]) # } # ) # # #put the legend # legend('top', legend=paste('Factor', seq(ncol(M))), fill=sub("^(#[a-f0-9]{6}).*", "\\1", html.colors, ignore.case=TRUE) ) # # invisible() # } #) ###% Utility function used to sets default elements in a list if they are ###% not already set ###% The default values are given in argument ... .set.list.defaults <- function(input.list, ...){ expand_list(input.list, ..., .exact=FALSE) } ###% Partially match arguments for a given function .match.call.args <- function(x, fun, in.fun=NULL, call=NULL){ stopifnot( is.character(fun) && length(fun) == 1 ) if( length(x) == 0 ) return(x) x.ind <- charmatch(x, args <- formalArgs(getFunction(fun))) sapply(seq(length(x)), function(i){ ind <- x.ind[i] # the argument is not part of the call: keep it unchanged if( is.na(ind) ) return(x[i]) # multiple matches: error if( ind == 0 ){ alt <- paste(grep(paste('^', x[i], sep=''), args, value=TRUE), collapse=', ') stop(if( !is.null(call) ) c(call, ' - '), "Multiple match for argument '", x[i], "' of function '" , if( is.null(in.fun) ) fun else in.fun, "' [use one of: ", alt, "]" , call.=FALSE) } # return the matched full names args[ind] }) } ###% Computes a set of measures usefull to assess the factorization's quality. ###% ###% ###% @param object a \code{NMF} object ###% @return a numeric vector of the measures. ###% #' Assessing and Comparing NMF Models #' #' @description #' The NMF package defines \code{summary} methods for different classes of objects, #' which helps assessing and comparing the quality of NMF models by computing a set #' of quantitative measures, e.g. with respect to their ability to recover known #' classes and/or the original target matrix. #' #' The most useful methods are for classes \code{\linkS4class{NMF}}, \code{\linkS4class{NMFfit}}, #' \code{\linkS4class{NMFfitX}} and \code{\linkS4class{NMFList}}, which compute summary measures #' for, respectively, a single NMF model, a single fit, a multiple-run fit and a list of heterogenous #' fits performed with the function \code{\link{nmf}}. #' #' @details #' Due to the somehow hierarchical structure of the classes mentionned in \emph{Description}, #' their respective \code{summary} methods call each other in chain, each super-class adding some #' extra measures, only relevant for objects of a specific class. #' #' @param object an NMF object. See available methods in section \emph{Methods}. #' @param ... extra arguments passed to the next \code{summary} method. #' #' @export #' @rdname assess #' @aliases summary-NMF #' #' @family assess Assessment measures for NMF models #' setGeneric('summary', package='base') #' Computes summary measures for a single NMF model. #' #' The following measures are computed: #' #' \describe{ #' \item{sparseness}{Sparseness of the factorization computed by the #' function \code{\link{sparseness}}.} #' \item{entropy}{Purity of the clustering, with respect to known classes, #' computed by the function \code{\link{purity}}.} #' \item{entropy}{Entropy of the clustering, with respect to known classes, #' computed by the function \code{\link{entropy}}.} #' \item{RSS}{Residual Sum of Squares computed by the function \code{\link{rss}}.} #' \item{evar}{Explained variance computed by the function \code{\link{evar}}.} #' } #' #' @param class known classes/cluster of samples specified in one of the formats #' that is supported by the functions \code{\link{entropy}} and \code{\link{purity}}. #' @param target target matrix specified in one of the formats supported by the #' functions \code{\link{rss}} and \code{\link{evar}} #' #' @rdname assess #' #' @examples #' #' # random NMF model #' x <- rnmf(3, 20, 12) #' summary(x) #' summary(x, gl(3, 4)) #' summary(x, target=rmatrix(x)) #' summary(x, gl(3,4), target=rmatrix(x)) #' setMethod('summary', signature(object='NMF'), function(object, class, target){ res <- numeric() ## IMPORTANT: if adding a summary measure also add it in the sorting ## schema of method NMFList::summary to allow ordering on it # rank res <- c(res, rank=nbasis(object)) # compute sparseness res <- c(res, sparseness=sparseness(object)) # if class is provided: also computes entropy and purity if( !missing(class) ){ # compute purity res <- c(res, purity=purity(object, class)) # compute entropy res <- c(res, entropy=entropy(object, class)) } # if the target is provided compute the RSS if( !missing(target) ){ RSS <- rss(object, target) res <- c(res, rss=RSS) # explained variance res <- c(res, evar=evar(object, target)) } # compute mean silhouette width siS <- silhouette(object, what = 'samples') siF <- silhouette(object, what = 'features') res <- c(res, silhouette.coef = if( !is_NA(siS) ) summary(siS)$avg.width else NA , silhouette.basis = if( !is_NA(siF) ) summary(siF)$avg.width else NA) # return result return(res) } ) #' Sparseness #' #' Generic function that computes the \emph{sparseness} of an object, as defined #' by \cite{Hoyer2004}. #' The sparseness quantifies how much energy of a vector is packed into only few components. #' #' In \cite{Hoyer2004}, the sparseness is defined for a real vector \eqn{x} as: #' \deqn{Sparseness(x) = \frac{\sqrt{n} - \frac{\sum |x_i|}{\sqrt{\sum x_i^2}}}{\sqrt{n}-1}}{ #' (srqt(n) - ||x||_1 / ||x||_2) / (sqrt(n) - 1)} #' #' , where \eqn{n} is the length of \eqn{x}. #' #' The sparseness is a real number in \eqn{[0,1]}. #' It is equal to 1 if and only if \code{x} contains a single nonzero component, #' and is equal to 0 if and only if all components of \code{x} are equal. #' It interpolates smoothly between these two extreme values. #' The closer to 1 is the sparseness the sparser is the vector. #' #' The basic definition is for a \code{numeric} vector, and is extended for matrices as the #' mean sparseness of its column vectors. #' #' @param x an object whose sparseness is computed. #' @param ... extra arguments to allow extension #' #' @return usually a single numeric value -- in [0,1], or a numeric vector. #' See each method for more details. #' #' @export #' @family assess setGeneric('sparseness', function(x, ...) standardGeneric('sparseness') ) #' Base method that computes the sparseness of a numeric vector. #' #' It returns a single numeric value, computed following the definition #' given in section \emph{Description}. setMethod('sparseness', signature(x='numeric'), function(x){ # get length of x n <- length(x) # compute and return the sparseness ( sqrt(n) - sum(abs(x)) / sqrt(sum(x^2)) ) / (sqrt(n)-1) } ) #' Computes the sparseness of a matrix as the mean sparseness of its column vectors. #' It returns a single numeric value. setMethod('sparseness', signature(x='matrix'), function(x){ # compute the sparseness of each column s <- apply(x, 2, sparseness) # return the mean sparseness mean(s) } ) #' Compute the sparseness of an object of class \code{NMF}, as the sparseness of #' the basis and coefficient matrices computed separately. #' #' It returns the two values in a numeric vector with names \sQuote{basis} and \sQuote{coef}. setMethod('sparseness', signature(x='NMF'), function(x){ # return the sparseness of the basis and coef matrix c(basis=sparseness(basis(x)), coef=sparseness(coef(x))) } ) #' Purity and Entropy of a Clustering #' #' The functions \code{purity} and \code{entropy} respectively compute the purity and the entropy #' of a clustering given \emph{a priori} known classes. #' #' The purity and entropy measure the ability of a clustering method, to recover #' known classes (e.g. one knows the true class labels of each sample), that are #' applicable even when the number of cluster is different from the number of known classes. #' \cite{KimH2007} used these measures to evaluate the performance of their alternate least-squares #' NMF algorithm. #' #' @details #' Suppose we are given \eqn{l} categories, while the clustering method generates #' \eqn{k} clusters. #' #' The purity of the clustering with respect to the known categories is given by: #' \deqn{Purity = \frac{1}{n} \sum_{q=1}^k \max_{1 \leq j \leq l} n_q^j} , #' #' where: #' \itemize{ #' \item \eqn{n} is the total number of samples; #' \item \eqn{n_q^j} is the number of samples in cluster \eqn{q} that belongs to #' original class \eqn{j} (\eqn{1 \leq j \leq l}). #' } #' #' The purity is therefore a real number in \eqn{[0,1]}. #' The larger the purity, the better the clustering performance. #' #' @param x an object that can be interpreted as a factor or can generate such an object, e.g. via #' a suitable method \code{\link{predict}}, which gives the cluster membership for each sample. #' @param y a factor or an object coerced into a factor that gives the true class labels for each sample. #' It may be missing if \code{x} is a contingency table. #' @param ... extra arguments to allow extension, and usually passed to the next method. #' #' @return a single numeric value #' @family assess #' @export #' #' @examples #' # generate a synthetic dataset with known classes: 50 features, 18 samples (5+5+8) #' n <- 50; counts <- c(5, 5, 8); #' V <- syntheticNMF(n, counts) #' cl <- unlist(mapply(rep, 1:3, counts)) #' #' # perform default NMF with rank=2 #' x2 <- nmf(V, 2) #' purity(x2, cl) #' entropy(x2, cl) #' # perform default NMF with rank=2 #' x3 <- nmf(V, 3) #' purity(x3, cl) #' entropy(x3, cl) #' setGeneric('purity', function(x, y, ...) standardGeneric('purity') ) #' Computes the purity directly from the contingency table \code{x} setMethod('purity', signature(x='table', y='missing'), function(x, y){ #for each cluster: compute maximum number of samples common to a class t <- apply(x, 1, max) # average and return the result sum(t) / sum(x) } ) #' Computes the purity on the contingency table of \code{x} and \code{y}, that is #' coerced into a factor if necessary. setMethod('purity', 'factor', function(x, y, ...){ # coerce `y` into a factor if necessary if( !is.factor(y) ) y <- as.factor(y) #compute the purity on the contingency table between clusters and true classes (clusters are in rows) purity(table(x, y), ...) } ) #' Default method that should work for results of clustering algorithms, that have a #' suitable \code{predict} method that returns the cluster membership vector: #' the purity is computed between \code{x} and \code{predict{y}} setMethod('purity', 'ANY', function(x, y, ...){ # compute the purity for the samples clusters defined by the profiles purity(predict(x), y, ...) } ) #' Entropy of a Clustering #' #' @details #' The entropy of the clustering with respect to the known categories is given by: #' \deqn{Entropy = - \frac{1}{n \log_2 l} \sum_{q=1}^k \sum_{j=1}^l n_q^j #' \log_2 \frac{n_q^j}{n_q}}{ #' - 1/(n log2(l) ) sum_q sum_j n(q,j) log2( n(q,j) / n_q )}, #' #' where: #' \itemize{ #' \item \eqn{n} is the total number of samples; #' \item \eqn{n}{n_q} is the total number of samples in cluster \eqn{q} (\eqn{1 \leq q \leq k}); #' \item \eqn{n_q^j}{n(q,j)} is the number of samples in cluster \eqn{q} that belongs to #' original class \eqn{j} (\eqn{1 \leq j \leq l}). #' } #' #' The smaller the entropy, the better the clustering performance. #' @inheritParams purity #' #' @return the entropy (i.e. a single numeric value) #' @family assess #' @rdname purity #' @export #' setGeneric('entropy', function(x, y, ...) standardGeneric('entropy') ) #' Computes the purity directly from the contingency table \code{x}. #' #' This is the workhorse method that is eventually called by all other methods. setMethod('entropy', signature(x='table', y='missing'), function(x, y, ...){ #for each cluster: compute the inner sum t <- apply(x, 1, function(n){ c.size <- sum(n); n %*% ifelse( n!=0, log2(n/c.size), 0)} ) # weight and return the result - sum(t) / ( sum(x) * log2(ncol(x)) ) } ) #' Computes the purity on the contingency table of \code{x} and \code{y}, that is #' coerced into a factor if necessary. setMethod('entropy', 'factor', function(x, y, ...){ # coerce `y` into a factor if necessary if( !is.factor(y) ) y <- as.factor(y) #copmute entropy on contingency table between clusters and true classes (clusters are in rows) entropy(table(x, y)) } ) #' Default method that should work for results of clustering algorithms, that have a #' suitable \code{predict} method that returns the cluster membership vector: #' the purity is computed between \code{x} and \code{predict{y}} setMethod('entropy', 'ANY', function(x, y, ...){ # compute the entropy for the samples clusters defined by the metagenes expression matrix entropy(predict(x), y) } ) ###% Extract the genes that characterize each factor. ###% ###% For each factor the genes are first sorted by decreasing contribution. The first successive ones whose contribution to the factor ###% is greater than their contribution to all other metagenes are selected. ###% ###% @param x the matrix of metagenes. That is a matrix with metagenes in column, genes in row, contain the genes' contribution to each factor ###% @return a list with number of metagenes elements, each being a vector containing the indexes of the characterizing genes #setGeneric('computeContrib', function(x, ...) standardGeneric('computeContrib') ) #setMethod('computeContrib', signature(x='matrix'), # function(x, ...){ # # determine the specific genes for each factor # lapply(1:ncol(x), # function(i){ # g <- x[,i] # #order by decreasing contribution to factor i # index.sort <- order(g, decreasing=TRUE) # # for( k in seq_along(index.sort) ) # { # index <- index.sort[k] # #if the gene contributes more to any other factor then return the genes above it # if( any(x[index,-i] >= g[index]) ) # { # if( k == 1 ) return(NULL) # else return(rownames(x)[index.sort[1:(k-1)]]) # } # } # # # all genes are meeting the criteria # rownames(x) # }) # } #) # #' Apply Function for NMF Objects #' #' The function \code{nmfApply} provides exteneded \code{apply}-like #' functionality for objects of class \code{NMF}. #' It enables to easily apply a function over different margins of #' NMF models. #' #' The function \code{FUN} is applied via a call to \code{\link{apply}} #' or \code{\link{sapply}} according to the value of argument \code{MARGIN} #' as follows: #' #' \describe{ #' \item{MARGIN=1}{ apply \code{FUN} to each \emph{row} of the basis matrix: #' \code{apply(basis(X), 1L, FUN, ...)}.} #' #' \item{MARGIN=2}{ apply \code{FUN} to each \emph{column} of the coefficient matrix: #' \code{apply(coef(X), 2L, FUN, ...)}.} #' #' \item{MARGIN=3}{ apply \code{FUN} to each \emph{pair} of associated basis component #' and basis profile: #' more or less \code{sapply(seq(nbasis(X)), function(i, ...) FUN(basis(X)[,i], coef(X)[i, ], ...), ...)}. #' #' In this case \code{FUN} must be have at least two arguments, to which are passed #' each basis components and basis profiles respectively -- as numeric vectors.} #' #' \item{MARGIN=4}{ apply \code{FUN} to each \emph{column} of the basis matrix, i.e. to each #' basis component: #' \code{apply(basis(X), 2L, FUN, ...)}.} #' #' \item{MARGIN=5}{ apply \code{FUN} to each \emph{row} of the coefficient matrix: #' \code{apply(coef(X), 1L, FUN, ...)}.} #' #' } #' #' #' @param X an object that has suitable \code{\link{basis}} and \code{coef} methods, #' e.g. an NMF model. #' @param MARGIN a single numeric (integer) value that specifies over which margin(s) #' the function \code{FUN} is applied. #' See section \emph{Details} for a list of possible values. #' @param FUN a function to apply over the specified margins. #' @param ... extra arguments passed to \code{FUN} #' @param simplify a logical only used when \code{MARGIN=3}, that indicates if \code{sapply} #' should try to simplify result if possible. #' Since this argument follows \sQuote{...} its name cannot be abbreviated. #' @param USE.NAMES a logical only used when \code{MARGIN=3}, that indicates if \code{sapply} #' should use the names of the basis components to name the results if present. #' Since this argument follows \sQuote{...} its name cannot be abbreviated. #' #' @return a vector or a list. #' See \code{\link[base]{apply}} and \code{\link[base]{sapply}} for more details on #' the output format. #' #' @export #setGeneric('nmfApply', function(object, ...) standardGeneric('nmfApply') ) nmfApply <- function(X, MARGIN, FUN, ..., simplify = TRUE, USE.NAMES = TRUE){ if( MARGIN == 1L ) apply(basis(X), 1L, FUN, ...) else if( MARGIN == 4L ) apply(basis(X), 2L, FUN, ...) else if( MARGIN == 2L ) apply(coef(X), 2L, FUN, ...) else if( MARGIN == 5L ) apply(coef(X), 1L, FUN, ...) else if( MARGIN == 3L ){ b <- basis(X) p <- coef(X) sapply(setNames(seq(nbasis(X), basisnames(X))) , function(i, ...) FUN(b[,i], p[i,], ...) , simplify = simplify, USE.NAMES = USE.NAMES) }else stop("invalid argument 'MARGIN' (expected values are: 1-basis rows, 2-coef columns, 3-(basis columns, coef rows), or 4-basis columns or 5-coef rows)") } ###% Utility function to compute the dominant column for each row for a matrix. .predict.nmf <- function(x, prob=FALSE){ if( !is.matrix(x) ) stop('NMF:::.predict.nmf : only works on matrices') if( !prob ){ #for each column return the (row) index of the maximum return( as.factor(apply(x, 1L, function(v) which.max(abs(v)))) ) } else{ #for each column return the (row) index of the maximum AND the associated probaility res <- apply(x, 1L, function(p){ p <- abs(p) i <- which.max(p) c(i, p[i]/sum(p)) } ) # return the result as a list of two elements return( list(predict=as.factor(res[1,]), prob=res[2,]) ) } } #' Clustering and Prediction #' #' The methods \code{predict} for NMF models return the cluster membership #' of each sample or each feature. #' Currently the classification/prediction of new data is not implemented. #' #' The cluster membership is computed as the index of the dominant basis #' component for each sample (\code{what='samples' or 'columns'}) or each feature #' (\code{what='features' or 'rows'}), based on their corresponding #' entries in the coefficient matrix or basis matrix respectively. #' #' For example, if \code{what='samples'}, then the dominant basis component #' is computed for each column of the coefficient matrix as the row index #' of the maximum within the column. #' #' If argument \code{prob=FALSE} (default), the result is a \code{factor}. #' Otherwise a list with two elements is returned: element \code{predict} #' contains the cluster membership index (as a \code{factor}) and element #' \code{prob} contains the relative contribution of #' the dominant component to each sample (resp. the relative contribution of #' each feature to the dominant basis component): #' #' \itemize{ #' \item Samples: \deqn{p_j = x_{k_0} / \sum_k x_k}{p(j) = x(k0) / sum_k x(k)}, #' for each sample \eqn{1\leq j \leq p}, where \eqn{x_k}{x(k)} is the contribution #' of the \eqn{k}-th basis component to \eqn{j}-th sample (i.e. \code{H[k ,j]}), and #' \eqn{x_{k_0}}{x(k0)} is the maximum of these contributions. #' #' \item Features: \deqn{p_i = y_{k_0} / \sum_k y_k}{p(i) = y(k0) / sum_k y(k)}, #' for each feature \eqn{1\leq i \leq p}, where \eqn{y_k}{y(k)} is the contribution #' of the \eqn{k}-th basis component to \eqn{i}-th feature (i.e. \code{W[i, k]}), and #' \eqn{y_{k_0}}{y(k0)} is the maximum of these contributions. #' #' } #' #' @param object an NMF model #' #' @family stats Methods for the Interface Defined in Package stats #' #' @cite Brunet2004,Pascual-Montano2006 #' @export setGeneric('predict', package='stats') #' Default method for NMF models #' #' @param what a character string that indicates the type of cluster membership should #' be returned: \sQuote{columns} or \sQuote{rows} for clustering the colmuns or the #' rows of the target matrix respectively. #' The values \sQuote{samples} and \sQuote{features} are aliases for \sQuote{colmuns} #' and \sQuote{rows} respectively. #' @param prob logical that indicates if the relative contributions of/to the dominant #' basis component should be computed and returned. See \emph{Details}. #' @param dmatrix logical that indicates if a dissimiliarity matrix should be #' attached to the result. #' This is notably used internally when computing NMF clustering silhouettes. #' #' @examples #' #' # random target matrix #' v <- rmatrix(20, 10) #' # fit an NMF model #' x <- nmf(v, 5) #' #' # predicted column and row clusters #' predict(x) #' predict(x, 'rows') #' #' # with relative contributions of each basis component #' predict(x, prob=TRUE) #' predict(x, 'rows', prob=TRUE) #' setMethod('predict', 'NMF', function(object, what=c('columns', 'rows', 'samples', 'features'), prob=FALSE, dmatrix = FALSE){ # determine which matrix to use for the prediction what <- match.arg(what) x <- if( what %in% c('features', 'rows') ) basis(object, all=FALSE) else t(coef(object, all=FALSE)) # compute the indice of the dominant row for each column res <- .predict.nmf(x, prob) # attach dissimilarity matrix if requested if( dmatrix ){ attr(res, 'dmatrix') <- 1 - cor(t(x)) } return( res ) } ) ####% Compute the dominant column for each row. ####% ####% @param x a matrix containing the mixture coefficients (basis vector in rows, samples in columns) ####% @return a factor of length the number of columns, giving the dominant column for each row ####% @note This function is now deprecated #setGeneric('clusters', function(object, newdata, ...) standardGeneric('clusters') ) ####% Compute the dominant metagene for each sample. ####% ####% @param x a NMF object ####% @return a factor of length the number of samples, giving the dominant metagene for each sample ####% @note This function is now deprecated #setMethod('clusters', signature(object='NMF', newdata='missing'), # function(object, newdata, ...){ # predict(object, ...) # } #) #' Correlations in NMF Models #' #' \code{basiscor} computes the correlation matrix between basis vectors, i.e. #' the \emph{columns} of its basis matrix -- which is the model's first matrix factor. #' #' @details #' Each generic has methods defined for computing correlations between NMF models #' and/or compatible matrices. #' The computation is performed by the base function \code{\link{cor}}. #' #' @param x a matrix or an object with suitable methods \code{\link{basis}} #' or \code{\link{coef}}. #' @param y a matrix or an object with suitable methods \code{\link{basis}} #' or \code{\link{coef}}, and dimensions compatible with \code{x}. #' If missing the correlations are computed between \code{x} and \code{y=x}. #' @param ... extra arguments passed to \code{\link{cor}}. #' #' @export #' @family NMFplots Plotting functions for NMF objects #' #' @examples #' #' # generate two random NMF models #' a <- rnmf(3, 100, 20) #' b <- rnmf(3, 100, 20) #' #' # Compute auto-correlations #' basiscor(a) #' profcor(a) #' # Compute correlations with b #' basiscor(a, b) #' profcor(a, b) #' #' # try to recover the underlying NMF model 'a' from noisy data #' res <- nmf(fitted(a) + rmatrix(a), 3) #' #' # Compute correlations with the true model #' basiscor(a, res) #' profcor(a, res) #' #' # Compute correlations with a random compatible matrix #' W <- rmatrix(basis(a)) #' basiscor(a, W) #' identical(basiscor(a, W), basiscor(W, a)) #' #' H <- rmatrix(coef(a)) #' profcor(a, H) #' identical(profcor(a, H), profcor(H, a)) #' setGeneric('basiscor', function(x, y, ...) standardGeneric('basiscor') ) #' Computes the correlations between the basis vectors of \code{x} and #' the columns of \code{y}. setMethod('basiscor', signature(x='NMF', y='matrix'), function(x, y, ...){ cor(basis(x), y, ...) } ) #' Computes the correlations between the columns of \code{x} #' and the the basis vectors of \code{y}. setMethod('basiscor', signature(x='matrix', y='NMF'), function(x, y, ...){ cor(x, basis(y), ...) } ) #' Computes the correlations between the basis vectors of \code{x} and \code{y}. setMethod('basiscor', signature(x='NMF', y='NMF'), function(x, y, ...){ basiscor(x, basis(y), ...) } ) #' Computes the correlations between the basis vectors of \code{x}. setMethod('basiscor', signature(x='NMF', y='missing'), function(x, y, ...){ basiscor(x, x, ...) } ) #' Correlations of Basis Profiles #' #' \code{profcor} computes the correlation matrix between basis profiles, #' i.e. the \emph{rows} of the coefficient matrix -- which is the model's second #' matrix factor. #' #' @rdname basiscor #' @export #' setGeneric('profcor', function(x, y, ...) standardGeneric('profcor') ) #' Computes the correlations between the basis profiles of \code{x} and #' the rows of \code{y}. setMethod('profcor', signature(x='NMF', y='matrix'), function(x, y, ...){ cor(t(coef(x)), t(y), ...) } ) #' Computes the correlations between the rows of \code{x} and the basis #' profiles of \code{y}. setMethod('profcor', signature(x='matrix', y='NMF'), function(x, y, ...){ cor(t(x), t(coef(y)), ...) } ) #' Computes the correlations between the basis profiles of \code{x} and \code{y}. setMethod('profcor', signature(x='NMF', y='NMF'), function(x, y, ...){ profcor(x, coef(y), ...) } ) #' Computes the correlations between the basis profiles of \code{x}. setMethod('profcor', signature(x='NMF', y='missing'), function(x, y, ...){ profcor(x, x, ...) } ) #' Clustering Connectivity and Consensus Matrices #' #' \code{connectivity} is an S4 generic that computes the connectivity matrix #' based on the clustering of samples obtained from a model's \code{\link{predict}} #' method. #' #' The connectivity matrix of a given partition of a set of samples (e.g. given #' as a cluster membership index) is the matrix \eqn{C} containing only 0 or 1 #' entries such that: #' \deqn{C_{ij} = \left\{\begin{array}{l} #' 1\mbox{ if sample }i\mbox{ belongs to the same cluster as sample }j\\ #' 0\mbox{ otherwise} #' \end{array}\right..}{ #' C_{ij} = 1 if sample i belongs to the same cluster as sample j, 0 otherwise} #' #' @param object an object with a suitable \code{\link{predict}} method. #' @param ... extra arguments to allow extension. #' They are passed to \code{\link{predict}}, except for the \code{vector} and #' \code{factor} methods. #' #' @return a square matrix of dimension the number of samples in the model, full #' of 0s or 1s. #' #' @seealso \code{\link{predict}} #' #' @export #' setGeneric('connectivity', function(object, ...) standardGeneric('connectivity') ) #' Default method which computes the connectivity matrix #' using the result of \code{predict(x, ...)} as cluster membership index. #' #' @examples #' #' # clustering of random data #' h <- hclust(dist(rmatrix(10,20))) #' connectivity(cutree(h, 2)) #' setMethod('connectivity', 'ANY', function(object, ...){ c <- predict(object, ...); outer(c, c, function(x,y) ifelse(x==y, 1,0)); } ) #' Computes the connectivity matrix using \code{x} as cluster membership index. #' #' @examples #' connectivity(gl(2, 4)) #' setMethod('connectivity', 'factor', function(object, ...){ outer(object, object, function(x,y) ifelse(x==y, 1,0)); } ) #' Equivalent to \code{connectivity(as.factor(x))}. setMethod('connectivity', 'numeric', function(object, ...){ connectivity(as.factor(object), ...) } ) #' Computes the connectivity matrix for an NMF model, for which cluster #' membership is given by the most contributing basis component in each sample. #' See \code{\link{predict}}. #' #' @param no.attrib a logical that indicates if attributes containing information #' about the NMF model should be attached to the result (\code{TRUE}) or not #' (\code{FALSE}). #' setMethod('connectivity', 'NMF', function(object, no.attrib=FALSE){ C <- callNextMethod(object=object, what='samples'); if( !no.attrib ){ class(C) <- c(class(C), 'NMF.consensus') attr(C, 'model') <- object attr(C, 'nrun') <- 1 attr(C, 'nbasis') <- nbasis(object) } C } ) # Unit test unit.test(connectivity,{ # build reference matrix n <- 10 ref <- matrix(0, 2*n, 2*n) ref[1:n,1:n] <- 1 ref[(n+1):(2*n),(n+1):(2*n)] <- 1 checkIdentical(connectivity(gl(2, n)), ref, 'Factor') checkIdentical(connectivity(as.numeric(gl(2, n))), ref, 'Vector') # test with NMF model i <- gl(2, n) x <- nmfModel(H=matrix(c(rev(i), i), 2, byrow=TRUE)) checkEquals(connectivity(x), ref, 'NMF model', check.attributes = FALSE) s <- sample.int(2*n) checkEquals(connectivity(x[,s]), ref[s,s], 'NMF model (shuffled)', check.attributes = FALSE) }) #' Residual Sum of Squares and Explained Variance #' #' \code{rss} and \code{evar} are S4 generic functions that respectively computes #' the Residual Sum of Squares (RSS) and explained variance achieved by a model. #' #' @param object an R object with a suitable \code{\link{fitted}}, \code{rss} or #' \code{evar} method. #' @param ... extra arguments to allow extension, e.g. passed to \code{rss} #' in \code{evar} calls. #' #' @return a single numeric value #' @export #' setGeneric('rss', function(object, ...) standardGeneric('rss')) #' Computes the RSS between a target matrix and its estimate \code{object}, #' which must be a matrix of the same dimensions as \code{target}. #' #' The RSS between a target matrix \eqn{V} and its estimate \eqn{v} is computed as: #' \deqn{RSS = \sum_{i,j} (v_{ij} - V_{ij})^2} #' #' Internally, the computation is performed using an optimised C++ implementation, #' that is light in memory usage. #' #' @param target target matrix #' #' @examples #' # RSS bewteeen random matrices #' x <- rmatrix(20,10, max=50) #' y <- rmatrix(20,10, max=50) #' rss(x, y) #' rss(x, x + rmatrix(x, max=0.1)) #' setMethod('rss', 'matrix', function(object, target){ # make sure the target is provided if( missing(target) ) stop("NMF::rss - Argument 'target' is missing and required to compute the residual sum of squares.") # use the expression matrix if necessary if( inherits(target, 'ExpressionSet') ){ # requires Biobase if( !require.quiet("Biobase") ) stop("NMF::rss - The 'Biobase' package is required to extract expression data from 'ExpressionSet' objects [see ?'nmf-bioc']") target <- Biobase::exprs(target) }else if( is.data.frame(target) ) target <- as.matrix(target) # return rss using the optimized C function .rss(object,target) } ) #' Residual sum of square between a given target matrix and a model that has a #' suitable \code{\link{fitted}} method. #' It is equivalent to \code{rss(fitted(object), ...)} #' #' In the context of NMF, \cite{Hutchins2008} used the variation of the RSS #' in combination with the algorithm from \cite{Lee1999} to estimate the #' correct number of basis vectors. #' The optimal rank is chosen where the graph of the RSS first shows an inflexion #' point, i.e. using a screeplot-type criterium. #' See section \emph{Rank estimation} in \code{\link{nmf}}. #' #' Note that this way of estimation may not be suitable for all models. #' Indeed, if the NMF optimisation problem is not based on the Frobenius norm, #' the RSS is not directly linked to the quality of approximation of the NMF model. #' However, it is often the case that it still decreases with the rank. #' #' @examples #' # RSS between an NMF model and a target matrix #' x <- rmatrix(20, 10) #' y <- rnmf(3, x) # random compatible model #' rss(y, x) #' #' # fit a model with nmf(): one should do better #' y2 <- nmf(x, 3) # default minimizes the KL-divergence #' rss(y2, x) #' y2 <- nmf(x, 3, 'lee') # 'lee' minimizes the RSS #' rss(y2, x) #' setMethod('rss', 'ANY', function(object, ...){ rss(fitted(object), ...) } ) unit.test(rss, { x <- rmatrix(20,10, max=50) y <- rmatrix(20,10, max=50) checkIdentical(rss(x, y), sum((x-y)^2), "Random matrices") y <- rnmf(3, x) # random compatible model r1 <- rss(y, x) checkIdentical(r, sum((x-fitted(y))^2), 'NMF model') checkIdentical(rss(y, Biobase::ExpressionSet(x)), sum((x-fitted(y))^2), 'NMF model (ExpressionSet)') y <- nmf(x, 3) r2 <- rss(y, x) checkIdentical(r2, sum((x-fitted(y))^2), 'Fitted NMF model') checkTrue(r2 < r1, 'Fitted NMF model has better RSS') y <- nmf(x, 3, 'lee') checkTrue(rss(y, x) < r2, "Fitted NMF model with 'lee' has better RSS than 'brunet'") }) #' Explained Variance #' #' The explained variance for a target \eqn{V} is computed as: #' \deqn{evar = 1 - \frac{RSS}{\sum_{i,j} v_{ij}^2} }{evar = 1 - RSS/sum v_{ij}^2}, #' #' where RSS is the residual sum of squares. #' #' The explained variance is usefull to compare the performance of different #' models and their ability to accurately reproduce the original target matrix. #' Note, however, that a possible caveat is that some models explicitly aim at #' minimizing the RSS (i.e. maximizing the explained variance), while others do not. #' #' @rdname rss #' @export #' setGeneric('evar', function(object, ...) standardGeneric('evar')) #' Default method for \code{evar}. #' #' It requires a suitable \code{rss} method to be defined #' for \code{object}, as it internally calls \code{rss(object, target, ...)}. setMethod('evar', 'ANY', function(object, target, ...){ # make sure the target is provided if( missing(target) ) stop("NMF::evar - Argument 'target' is missing and required to compute the explained variance.") # use the expression matrix if necessary if( inherits(target, 'ExpressionSet') ){ # requires Biobase if( !require.quiet("Biobase") ) stop("NMF::evar - The 'Biobase' package is required to extract expression data from 'ExpressionSet' objects [see ?'nmf-bioc']") target <- Biobase::exprs(target) } t <- as.numeric(target) 1 - rss(object, target, ...) / sum(t^2) } ) #' Distances and Objective Functions #' #' The NMF package defines methods for the generic \code{deviance} from the package \code{stats}, #' to compute approximation errors between NMF models and matrices, using a variety of #' objective functions. #' #' @return \code{deviance} returns a nonnegative numerical value #' @family stats #' #' @export setGeneric('deviance', package='stats') #' Computes the distance between a matrix and the estimate of an \code{NMF} model. #' #' @param y a matrix compatible with the NMF model \code{object}, i.e. \code{y} #' must have the same dimension as \code{fitted(object)}. #' @param method a character string or a function with signature #' \code{(x="NMF", y="matrix", ...)} that implements a distance measure between #' an NMF model \code{x} and a target matrix \code{y}, i.e. an objective function #' to use to compute the deviance. #' In \code{deviance}, it is passed to \code{nmfDistance} to get the function #' that effectively computes the deviance. #' @param ... extra parameters passed to the objective function. #' #' @family stats #' setMethod('deviance', 'NMF', function(object, y, method=c('', 'KL', 'euclidean'), ...){ fun <- nmfDistance(method) if( is.null(fun) ){ warning('Undefined distance method: distance cannot be computed [returned NA]') return(as.numeric(NA)) } # extract expression data from ExpressionSet objects if( is(y, 'ExpressionSet') ) y <- Biobase::exprs(y) # apply the function and return the result fun(object, y, ...) } ) #' \code{nmfDistance} returns a function that computes the distance between an NMF model and a #' compatible matrix. #' #' @return \code{nmfDistance} returns a function with least two arguments: #' an NMF model and a matrix. #' #' @export #' @rdname deviance nmfDistance <- function(method=c('', 'KL', 'euclidean')){ #message('compute distance') # determinate the distance measure to use if( is.null(method) ) return(NULL) if( is.character(method) ){ errMeth <- try(method <- match.arg(method), silent=TRUE) # if the method is not predefined, try to find a function with the given name if( inherits(errMeth, 'try-error') ){ #TODO: this is not working with local functions if( is.character(method) ){ errFun <- try(fun <- match.fun(method), silent=TRUE) if( inherits(errFun, 'try-error') ) stop("Could not find distance measure '", method, "':\n\t- not a predefined measures -> ", errMeth,"\t- not a function -> ", errFun) } else fun <- method if( !is.function(fun) ) stop('Invalid distance measure: should be a character string or a valid function definition') } else{ # compute and return the distance measure fun <- switch(method, euclidean = function(x, y, ...){ # call optimized C function .rss(y, fitted(x))/2 }, KL = function(x, y, ...){ # call optimized C function .KL(y, fitted(x)) } ) } } else if( is.function(method) ) fun <- method else stop('Invalid distance measure: should be a character string or a valid function definition') # return the distance function fun } #' Testing Equality of NMF Models #' #' The function \code{nmf.equal} tests if two NMF models are the same, i.e. they #' contain -- almost -- identical data: same basis and coefficient matrices, as #' well as same extra parameters. #' #' @details #' \code{nmf.equal} compares two NMF models, and return \code{TRUE} iff they are #' identical acording to the function \code{\link{identical}} when \code{identical=TRUE}, #' or equal up to some tolerance acording to the function \code{\link{all.equal}}. #' This means that all data contained in the objects are compared, which includes #' at least the basis and coefficient matrices, as well as the extra parameters #' stored in slot \sQuote{misc}. #' #' If extra arguments are specified in \code{...}, then the comparison is performed #' using \code{\link{all.equal}}, irrespective of the value of argument \code{identical}. #' #' @param x an NMF model or an object that is associated with an NMF model, e.g. #' the result from a fit with \code{\link{nmf}}. #' @param y an NMF model or an object that is associated with an NMF model, e.g. #' the result from a fit with \code{\link{nmf}}. #' @param identical a logical that indicates if the comparison should be made #' using the function \code{\link{identical}} (\code{TRUE}) or \code{\link{all.equal}} #' (\code{FALSE}). See description for method \code{nmf.equal,NMF,NMF}. #' @param ... extra arguments to allow extension, and passed to subsequent calls #' #' @export #' setGeneric('nmf.equal', function(x, y, ...) standardGeneric('nmf.equal') ) #' Compares two NMF models. #' #' Arguments in \code{...} are used only when \code{identical=FALSE} and are #' passed to \code{all.equal}. setMethod('nmf.equal', signature(x='NMF', y='NMF'), function(x, y, identical=TRUE, ...){ dots <- list(...) if( identical && length(dots) == 0 ) identical(x, y) else all.equal(x, y, ...) } ) # Match and Order Basis Components # # match.basis <- function(object, return.table=FALSE){ # compute the contingency table #pcmap <- predict(object, 'cmap') # build the tree from consensus matrix h <- hclust(as.dist(1-consensus(object)), method='average') # extract membership from the tree cl <- cutree(h, k=nbasis(object)) # change the class indexed to match the order of the consensus clusters cl <- match(cl, unique(cl[h$order])) pcmap <- as.factor(cl) occ <- table(consensus=pcmap, fit=predict(object)) # add names if present # if( !is.null(basisnames(object)) ){ # rownames(occ) <- colnames(occ) <- basisnames(object) # } # for each estimated component look for the maximum agreement T.tmp <- occ res <- rep(0, ncol(T.tmp)) for( i in 1:ncol(T.tmp) ){ # get the row and column index of the maximum over the remaining entries xm <- which.max(T.tmp)-1 jm <- xm %/% nrow(T.tmp) + 1 im <- xm - (jm-1) * nrow(T.tmp) + 1 # assign the estimate row to the inferred reference column stopifnot( res[im]==0 ) res[im] <- jm # erase the assigned estimate row T.tmp[im,] <- NA # erase the assigned reference column T.tmp[,jm] <- NA } # return the mapping as an integer vector res <- as.integer(res) if( return.table ) res <- list(match=res, table=occ) # return result res } NMF/R/algorithms-pe-nmf.R0000644000176200001440000000367713620502674014571 0ustar liggesusers#' @include registry-algorithms.R NULL ###% NMF Algorithm: Pattern Expression NMF ###% ###% Implements the PE-NMF algorithm from Zhang et al (2008). ###% ###% It is implemented using the iterative schema defined by the ###% NMFStrategyIterative class. ###% The algorithm minimizes the Frobenius norm, with two regularization terms ###% (one for each matrix factor) parametrized by two parameters: ###% ###% min_{W,H} 1/2 ||V - WH||^2 ###% + alpha \sum_{i<>j} W_i^T W_j ###% + beta \sum_{i,j} H_{ij} ###% ###% So there is two parameters: alpha and beta. ###% The updates for the matrix factors are (in R notations): ###% ###% H_{i+1} = H_i ( W_i^T %*% V ) / ( W_i^T %*% W_i %*% H_i + beta) ###% W_{i+1} = W_i ( V %*% H_i^T ) / ( W_i %*% H_i %*% H_i^T + alpha W_i %*% M ) ###% ###% with matrix M is full of one with diagonal zero. ###% ###% @author Renaud Gaujoux ###% @creation 17 Jan 2010 ###% penmf.objective <- function(fit, x, alpha, beta, ...) { w <- .basis(fit) 1/2 * sum( (x - fitted(fit))^2 ) + alpha * ( crossprod(w) - sum(w^2) ) + beta * sum(.coef(fit)) } nmf_update.penmf <- function(i, x, data, alpha, beta, ...){ # retrieve each factor w <- .basis(data); h <- .coef(data); # At the first iteration initialise matrix M if( TRUE || i == 1 ){ r <- ncol(w) M <- matrix(1, nrow=r, ncol=r) - diag(1, r) #staticVar('M', M, init=TRUE) } #else M <- staticVar('M') #precision threshold for numerical stability eps <- 10^-9 # H_{i+1} = H_i ( W_i^T %*% V ) / ( W_i^T %*% W_i %*% H_i + beta) h <- h * crossprod(w, x) / ( crossprod(w) %*% h + beta) # W_{i+1} = W_i ( V %*% H_i^T ) / ( W_i %*% H_i %*% H_i^T + alpha W_i %*% M ) w <- w * tcrossprod(x, h) / ( w %*% tcrossprod(h) + alpha * w %*% M ) #return the modified data .basis(data) <- w; .coef(data) <- h; data } # register PE-NMF nmfAlgorithm.peNMF <- setNMFMethod('pe-nmf', objective = penmf.objective , model='NMFstd' , Update= nmf_update.penmf , Stop='stationary') NMF/R/extractFeatures.R0000644000176200001440000003156713620502674014410 0ustar liggesusers# Feature selection functions # # Author: Renaud Gaujoux # Created: Mar 18, 2013 ############################################################################### #' @include NMF-class.R NULL #' Feature Selection in NMF Models #' #' The function \code{featureScore} implements different methods to computes #' basis-specificity scores for each feature in the data. #' #' One of the properties of Nonnegative Matrix Factorization is that is tend to #' produce sparse representation of the observed data, leading to a natural #' application to bi-clustering, that characterises groups of samples by #' a small number of features. #' #' In NMF models, samples are grouped according to the basis #' components that contributes the most to each sample, i.e. the basis #' components that have the greatest coefficient in each column of the coefficient #' matrix (see \code{\link{predict,NMF-method}}). #' Each group of samples is then characterised by a set of features selected #' based on basis-specifity scores that are computed on the basis matrix. #' #' @section Feature scores: #' The function \code{featureScore} can compute basis-specificity scores using #' the following methods: #' #' \describe{ #' #' \item{\sQuote{kim}}{ Method defined by \cite{KimH2007}. #' #' The score for feature \eqn{i} is defined as: #' \deqn{S_i = 1 + \frac{1}{\log_2 k} \sum_{q=1}^k p(i,q) \log_2 p(i,q)}{ #' S_i = 1 + 1/log2(k) sum_q [ p(i,q) log2( p(i,q) ) ] }, #' #' where \eqn{p(i,q)} is the probability that the \eqn{i}-th feature contributes #' to basis \eqn{q}: \deqn{p(i,q) = \frac{W(i,q)}{\sum_{r=1}^k W(i,r)} }{ #' p(i,q) = W(i,q) / (sum_r W(i,r)) } #' #' The feature scores are real values within the range [0,1]. #' The higher the feature score the more basis-specific the corresponding feature. #' } #' #' \item{\sQuote{max}}{Method defined by \cite{Carmona-Saez2006}. #' #' The feature scores are defined as the row maximums. #' } #' #' } #' #' @param object an object from which scores/features are computed/extracted #' @param ... extra arguments to allow extension #' #' @return \code{featureScore} returns a numeric vector of the length the number #' of rows in \code{object} (i.e. one score per feature). #' #' @export #' @rdname scores #' @inline #' setGeneric('featureScore', function(object, ...) standardGeneric('featureScore') ) #' Computes feature scores on a given matrix, that contains the basis component in columns. setMethod('featureScore', 'matrix', function(object, method=c('kim', 'max')){ method <- match.arg(method) score <- switch(method, kim = { #for each row compute the score s <- apply(object, 1, function(g){ g <- abs(g) p_i <- g/sum(g) crossprod(p_i, log2(p_i)) }) # scale, translate and return the result 1 + s / log2(ncol(object)) } , max = { apply(object, 1L, function(x) max(abs(x))) } ) # return the computed score return(score) } ) #' Computes feature scores on the basis matrix of an NMF model. setMethod('featureScore', 'NMF', function(object, ...){ featureScore(basis(object), ...) } ) #' The function \code{extractFeatures} implements different methods to select the #' most basis-specific features of each basis component. #' #' @section Feature selection: #' The function \code{extractFeatures} can select features using the following #' methods: #' \describe{ #' \item{\sQuote{kim}}{ uses \cite{KimH2007} scoring schema and #' feature selection method. #' #' The features are first scored using the function #' \code{featureScore} with method \sQuote{kim}. #' Then only the features that fulfil both following criteria are retained: #' #' \itemize{ #' \item score greater than \eqn{\hat{\mu} + 3 \hat{\sigma}}, where \eqn{\hat{\mu}} #' and \eqn{\hat{\sigma}} are the median and the median absolute deviation #' (MAD) of the scores respectively; #' #' \item the maximum contribution to a basis component is greater than the median #' of all contributions (i.e. of all elements of W). #' } #' #' } #' #' \item{\sQuote{max}}{ uses the selection method used in the \code{bioNMF} #' software package and described in \cite{Carmona-Saez2006}. #' #' For each basis component, the features are first sorted by decreasing #' contribution. #' Then, one selects only the first consecutive features whose highest #' contribution in the basis matrix is effectively on the considered basis. #' } #' #' } #' #' @return \code{extractFeatures} returns the selected features as a list of indexes, #' a single integer vector or an object of the same class as \code{object} #' that only contains the selected features. #' #' @rdname scores #' @inline #' @export #' setGeneric('extractFeatures', function(object, ...) standardGeneric('extractFeatures') ) # internal functio to trick extractFeatures when format='subset' .extractFeaturesObject <- local({ .object <- NULL function(object){ # first call resets .object if( missing(object) ){ res <- .object .object <<- NULL res }else # set .object for next call .object <<- object } }) #' Select features on a given matrix, that contains the basis component in columns. #' #' @param method scoring or selection method. #' It specifies the name of one of the method described in sections \emph{Feature scores} #' and \emph{Feature selection}. #' #' Additionally for \code{extractFeatures}, it may be an integer vector that #' indicates the number of top most contributing features to #' extract from each column of \code{object}, when ordered in decreasing order, #' or a numeric value between 0 and 1 that indicates the minimum relative basis #' contribution above which a feature is selected (i.e. basis contribution threshold). #' In the case of a single numeric value (integer or percentage), it is used for all columns. #' #' Note that \code{extractFeatures(x, 1)} means relative contribution threshold of #' 100\%, to select the top contributing features one must explicitly specify #' an integer value as in \code{extractFeatures(x, 1L)}. #' However, if all elements in methods are > 1, they are automatically treated as #' if they were integers: \code{extractFeatures(x, 2)} means the top-2 most #' contributing features in each component. #' @param format output format. #' The following values are accepted: #' \describe{ #' \item{\sQuote{list}}{(default) returns a list with one element per column in #' \code{object}, each containing the indexes of the selected features, as an #' integer vector. #' If \code{object} has row names, these are used to name each index vector. #' Components for which no feature were selected are assigned a \code{NA} value.} #' #' \item{\sQuote{combine}}{ returns all indexes in a single vector. #' Duplicated indexes are made unique if \code{nodups=TRUE} (default).} #' #' \item{\sQuote{subset}}{ returns an object of the same class as \code{object}, #' but subset with the selected indexes, so that it contains data only from #' basis-specific features.} #' } #' #' @param nodups logical that indicates if duplicated indexes, #' i.e. features selected on multiple basis components (which should in #' theory not happen), should be only appear once in the result. #' Only used when \code{format='combine'}. #' #' @examples #' #' # random NMF model #' x <- rnmf(3, 50,20) #' #' # probably no feature is selected #' extractFeatures(x) #' # extract top 5 for each basis #' extractFeatures(x, 5L) #' # extract features that have a relative basis contribution above a threshold #' extractFeatures(x, 0.5) #' # ambiguity? #' extractFeatures(x, 1) # means relative contribution above 100% #' extractFeatures(x, 1L) # means top contributing feature in each component #' setMethod('extractFeatures', 'matrix', function(object, method=c('kim', 'max') , format=c('list', 'combine', 'subset'), nodups=TRUE){ res <- if( is.numeric(method) ){ # repeat single values if( length(method) == 1L ) method <- rep(method, ncol(object)) # float means percentage, integer means count # => convert into an integer if values > 1 if( all(method > 1L) ) method <- as.integer(method) if( is.integer(method) ){ # extract top features # only keep the specified number of feature for each column mapply(function(i, l) head(order(object[,i], decreasing=TRUE), l) , seq(ncol(object)), method, SIMPLIFY=FALSE) }else{ # extract features with contribution > threshold # compute relative contribution so <- sweep(object, 1L, rowSums(object), '/') # only keep features above threshold for each column mapply(function(i, l) which(so[,i] >= l) , seq(ncol(object)), method, SIMPLIFY=FALSE) } }else{ method <- match.arg(method) switch(method, kim = { # KIM & PARK method # first score the genes s <- featureScore(object, method='kim') # filter for the genes whose score is greater than \mu + 3 \sigma th <- median(s) + 3 * mad(s) sel <- s >= th #print( s[sel] ) #print(sum(sel)) # build a matrix with: #-> row#1=max column index, row#2=max value in row, row#3=row index temp <- 0; g.mx <- apply(object, 1L, function(x){ temp <<- temp +1 i <- which.max(abs(x)); #i <- sample(c(1,2), 1) c(i, x[i], temp) } ) # test the second criteria med <- median(abs(object)) sel2 <- g.mx[2,] >= med #print(sum(sel2)) # subset the indices g.mx <- g.mx[, sel & sel2, drop=FALSE] # order by decreasing score g.mx <- g.mx[,order(s[sel & sel2], decreasing=TRUE)] # return the indexes of the features that fullfil both criteria cl <- factor(g.mx[1,], levels=seq(ncol(object))) res <- split(g.mx[3,], cl) # add the threshold used attr(res, 'threshold') <- th # return result res }, max = { # MAX method from bioNMF # determine the specific genes for each basis vector res <- lapply(1:ncol(object), function(i){ mat <- object vect <- mat[,i] #order by decreasing contribution to factor i index.sort <- order(vect, decreasing=TRUE) for( k in seq_along(index.sort) ) { index <- index.sort[k] #if the feature contributes more to any other factor then return the features above it if( any(mat[index,-i] >= vect[index]) ) { if( k == 1 ) return(as.integer(NA)) else return( index.sort[1:(k-1)] ) } } # all features meet the criteria seq_along(vect) } ) # return res res } ) } #Note: make sure there is an element per basis (possibly NA) res <- lapply(res, function(ind){ if(length(ind)==0) ind<-NA; as.integer(ind)} ) # add names if possible if( !is.null(rownames(object)) ){ noNA <- sapply(res, is_NA) res[noNA] <- lapply(res[noNA], function(x){ setNames(x, rownames(object)[x]) }) } # apply the desired output format format <- match.arg(format) res <- switch(format #combine: return all the indices in a single vector , combine = { # ensure that there is no names: for unlist no to mess up feature names names(res) <- NULL ind <- na.omit(unlist(res)) if( nodups ) unique(ind) else ind } #subset: return the object subset with the selected indices , subset = { ind <- na.omit(unique(unlist(res))) sobject <- .extractFeaturesObject() {if( is.null(sobject) ) object else sobject}[ind, , drop=FALSE] } #else: leave as a list ,{ # add component names if any names(res) <- colnames(object) res } ) # add attribute method to track the method used attr(res, 'method') <- method # return result return( res ) } ) #' Select basis-specific features from an NMF model, by applying the method #' \code{extractFeatures,matrix} to its basis matrix. #' #' setMethod('extractFeatures', 'NMF', function(object, ...){ # extract features from the basis matrix, but subset the NMF model itself .extractFeaturesObject(object) extractFeatures(basis(object), ...) } ) unit.test(extractFeatures, { .check <- function(x){ msg <- function(...) paste(class(x), ':', ...) checkTrue( is.list(extractFeatures(x)), msg("default returns list")) checkTrue( is.list(extractFeatures(x, format='list')), msg("format='list' returns list")) checkTrue( is.integer(extractFeatures(x, format='combine')), msg("format='combine' returns an integer vector")) checkTrue( is(extractFeatures(x, format='subset'), class(x)), msg("format='subset' returns same class as object")) } .check(rmatrix(50, 5)) .check(rnmf(3, 50, 5)) }) NMF/R/transforms.R0000644000176200001440000001711113620502674013422 0ustar liggesusers# Transformation methods for matrix-like and NMF objects # # Author: Renaud Gaujoux # Creation: 19 Jan 2012 ############################################################################### #' @include NMF-class.R NULL #' Transforming from Mixed-sign to Nonnegative Data #' #' \code{nneg} is a generic function to transform a data objects that #' contains negative values into a similar object that only contains #' values that are nonnegative or greater than a given threshold. #' #' @param object The data object to transform #' @param ... extra arguments to allow extension or passed down to \code{nneg,matrix} #' or \code{rposneg,matrix} in subsequent calls. #' #' @return an object of the same class as argument \code{object}. #' @export #' @inline #' @family transforms #' setGeneric('nneg', function(object, ...) standardGeneric('nneg')) #' Transforms a mixed-sign matrix into a nonnegative matrix, optionally apply a #' lower threshold. #' This is the workhorse method, that is eventually called by all other #' methods defined in the \code{\link{NMF}} package. #' #' @param method Name of the transformation method to use, that is partially #' matched against the following possible methods: #' \describe{ #' \item{pmax}{Each entry is constrained to be above threshold \code{threshold}.} #' #' \item{posneg}{The matrix is split into its "positive" and "negative" parts, #' with the entries of each part constrained to be above threshold \code{threshold}. #' The result consists in these two parts stacked in rows (i.e. \code{\link{rbind}}-ed) #' into a single matrix, which has double the number of rows of the input #' matrix \code{object}.} #' #' \item{absolute}{The absolute value of each entry is constrained to be above #' threshold \code{threshold}.} #' #' \item{min}{Global shift by adding the minimum entry to each entry, only if #' it is negative, and then apply threshold. #' } #' #' } #' #' @param threshold Nonnegative lower threshold value (single numeric). #' See argument \code{shit} for details on how the threshold is used and affects #' the result. #' @param shift a logical indicating whether the entries below the threshold #' value \code{threshold} should be forced (shifted) to 0 (default) or to #' the threshold value itself. #' In other words, if \code{shift=TRUE} (default) all entries in #' the result matrix are either 0 or strictly greater than \code{threshold}. #' They are all greater or equal than \code{threshold} otherwise. #' #' @seealso \code{\link{pmax}} #' @examples #' #' # random mixed sign data (normal distribution) #' set.seed(1) #' x <- rmatrix(5,5, rnorm, mean=0, sd=5) #' x #' #' # pmax (default) #' nneg(x) #' # using a threshold #' nneg(x, threshold=2) #' # without shifting the entries lower than threshold #' nneg(x, threshold=2, shift=FALSE) #' #' # posneg: split positive and negative part #' nneg(x, method='posneg') #' nneg(x, method='pos', threshold=2) #' #' # absolute #' nneg(x, method='absolute') #' nneg(x, method='abs', threshold=2) #' #' # min #' nneg(x, method='min') #' nneg(x, method='min', threshold=2) #' setMethod('nneg', 'matrix' , function(object, method=c('pmax', 'posneg', 'absolute', 'min'), threshold=0, shift=TRUE){ # match argument method <- match.arg(method) if( !is.numeric(threshold) || length(threshold) != 1L ) stop("nneg - Invalid threshold value in argument `threshold` [",threshold,"]: must be a single numeric value.") if( threshold < 0 ) stop("nneg - Invalid threshold value in argument `threshold` [",threshold,"]: must be nonnegative.") # 1. Transform if there is any negative entry m <- min(object) if( m < 0 ){ object <- switch(method , pmax = pmax(object, 0) , posneg = rbind(pmax(object, 0), pmax(-object, 0)) , absolute = pmax(abs(object), 0) , min = object - m , stop("NMF::nneg - Unexpected error: unimplemented transformation method '", method, "'.") ) } if( threshold > 0 ){ # 2. Apply threshold if any object <- pmax(object, threshold) # 3. Shifting: entries under threshold if( shift ) object[object<=threshold] <- 0 } # return modified object object } ) #' Apply \code{nneg} to the basis matrix of an \code{\link{NMF}} #' object (i.e. \code{basis(object)}). #' All extra arguments in \code{...} are passed to the method \code{nneg,matrix}. #' #' @examples #' #' # random #' M <- nmfModel(x, rmatrix(ncol(x), 3)) #' nnM <- nneg(M) #' basis(nnM) #' # mixture coefficients are not affected #' identical( coef(M), coef(nnM) ) #' setMethod('nneg', 'NMF', function(object, ...){ basis(object) <- nneg(basis(object), ...) object } ) #' \code{posneg} is a shortcut for \code{nneg(..., method='posneg')}, to split #' mixed-sign data into its positive and negative part. #' See description for method \code{"posneg"}, in \code{\link{nneg}}. #' #' @export #' @rdname nneg #' @examples #' # shortcut for the "posneg" transformation #' posneg(x) #' posneg(x, 2) #' posneg <- function(...) nneg(..., method='posneg') #' Transforming from Nonnegative to Mixed Sign Data #' #' \code{rposneg} performs the "reverse" transformation of the \code{\link{posneg}} function. #' #' @return an object of the same type of \code{object} #' @rdname nneg #' @inline #' setGeneric('rposneg', function(object, ...) standardGeneric('rposneg')) #' @param unstack Logical indicating whether the positive and negative parts #' should be unstacked and combined into a matrix as \code{pos - neg}, which contains #' half the number of rows of \code{object} (default), or left #' stacked as \code{[pos; -neg]}. #' #' @export #' @examples #' #' # random mixed sign data (normal distribution) #' set.seed(1) #' x <- rmatrix(5,5, rnorm, mean=0, sd=5) #' x #' #' # posneg-transform: split positive and negative part #' y <- posneg(x) #' dim(y) #' # posneg-reverse #' z <- rposneg(y) #' identical(x, z) #' rposneg(y, unstack=FALSE) #' #' # But posneg-transformation with a non zero threshold is not reversible #' y1 <- posneg(x, 1) #' identical(rposneg(y1), x) #' setMethod('rposneg', 'matrix' , function(object, unstack=TRUE){ # check that the number of rows is pair if( nrow(object) %% 2 != 0 ) stop("rposneg - Invalid input matrix: must have a pair number of rows [",nrow(object),"].") n2 <- nrow(object) n <- n2/2 if( unstack ) object <- object[1:n,,drop=FALSE] - object[(n+1):n2,,drop=FALSE] else object[(n+1):n2,] <- - object[(n+1):n2,,drop=FALSE] # return modified object object } ) #' Apply \code{rposneg} to the basis matrix of an \code{\link{NMF}} object. #' #' @examples #' #' # random mixed signed NMF model #' M <- nmfModel(rmatrix(10, 3, rnorm), rmatrix(3, 4)) #' # split positive and negative part #' nnM <- posneg(M) #' M2 <- rposneg(nnM) #' identical(M, M2) setMethod('rposneg', 'NMF' , function(object, ...){ basis(object) <- rposneg(basis(object), ...) object } ) #' Transformation NMF Model Objects #' #' \code{t} transpose an NMF model, by transposing and swapping its basis and #' coefficient matrices: \eqn{t([W,H]) = [t(H), t(W)]}. #' #' The function \code{t} is a generic defined in the \pkg{base} package. #' The method \code{t.NMF} defines the trasnformation for the general NMF interface. #' This method may need to be overloaded for NMF models, whose structure requires #' specific handling. #' #' @param x NMF model object. #' #' @family transforms #' @export #' @examples #' #' x <- rnmf(3, 100, 20) #' x #' # transpose #' y <- t(x) #' y #' #' # factors are swapped-transposed #' stopifnot( identical(basis(y), t(coef(x))) ) #' stopifnot( identical(coef(y), t(basis(x))) ) #' t.NMF <- function(x){ # transpose and swap factors w <- t(basis(x)) .basis(x) <- t(coef(x)) .coef(x) <- w # return object x } NMF/R/NMFns-class.R0000644000176200001440000001364113620502674013314 0ustar liggesusers#' @include NMFstd-class.R NULL #' NMF Model - Nonsmooth Nonnegative Matrix Factorization #' #' This class implements the \emph{Nonsmooth Nonnegative Matrix Factorization} #' (nsNMF) model, required by the Nonsmooth NMF algorithm. #' #' The Nonsmooth NMF algorithm is defined by \cite{Pascual-Montano2006} as a #' modification of the standard divergence based NMF algorithm (see section #' Details and references below). It aims at obtaining sparser factor #' matrices, by the introduction of a smoothing matrix. #' #' @details #' The Nonsmooth NMF algorithm is a modification of the standard divergence #' based NMF algorithm (see \code{\linkS4class{NMF}}). #' Given a non-negative \eqn{n \times p}{n x p} matrix \eqn{V} and a #' factorization rank \eqn{r}, it fits the following model: #' #' \deqn{V \equiv W S(\theta) H,}{V ~ W S(theta) H,} #' where: #' \itemize{ #' #' \item \eqn{W} and \eqn{H} are such as in the standard model, i.e. #' non-negative matrices of dimension \eqn{n \times r}{n x r} #' and \eqn{r \times p}{r x p} respectively; #' #' \item \eqn{S} is a \eqn{r \times r} square matrix whose entries depends on #' an extra parameter \eqn{0\leq \theta \leq 1} in the following way: #' \deqn{S = (1-\theta)I + \frac{\theta}{r} 11^T ,} #' where \eqn{I} is the identity matrix and \eqn{1} #' is a vector of ones. #' #' } #' #' The interpretation of S as a smoothing matrix can be explained as follows: #' Let \eqn{X} be a positive, nonzero, vector. Consider the transformed vector #' \eqn{Y = S X}. If \eqn{\theta = 0}, then \eqn{Y = X} and no smoothing on #' \eqn{X} has occurred. However, as \eqn{\theta \to 1}{theta tends to 1}, the #' vector \eqn{Y} tends to the constant vector with all elements almost equal #' to the average of the elements of \eqn{X}. This is the smoothest possible #' vector in the sense of non-sparseness because all entries are equal to the #' same nonzero value, instead of having some values close to zero and others #' clearly nonzero. #' #' @section Creating objects from the Class: #' #' Object of class \code{NMFns} can be created using the standard way with #' operator \code{\link{new}} #' #' However, as for all NMF model classes -- that extend class #' \code{\linkS4class{NMF}}, objects of class \code{NMFns} should be #' created using factory method \code{\link{nmfModel}} : #' #' \code{new('NMFns')} #' #' \code{nmfModel(model='NMFns')} #' #' \code{nmfModel(model='NMFns', W=w, theta=0.3} #' #' See \code{\link{nmfModel}} for more details on how to use the factory #' method. #' #' @section Algorithm: #' #' The Nonsmooth NMF algorithm uses a modified version of the multiplicative #' update equations in Lee & Seung's method for Kullback-Leibler divergence #' minimization. #' The update equations are modified to take into account the -- #' constant -- smoothing matrix. #' The modification reduces to using matrix \eqn{W S} instead of matrix \eqn{W} #' in the update of matrix \eqn{H}, and similarly using matrix \eqn{S H} #' instead of matrix \eqn{H} in the update of matrix \eqn{W}. #' #' After the matrix \eqn{W} has been updated, each of its columns is scaled so #' that it sums up to 1. #' #' @export #' @family NMF-model #' @examples #' #' # create a completely empty NMFns object #' new('NMFns') #' #' # create a NMF object based on random (compatible) matrices #' n <- 50; r <- 3; p <- 20 #' w <- rmatrix(n, r) #' h <- rmatrix(r, p) #' nmfModel(model='NMFns', W=w, H=h) #' #' # apply Nonsmooth NMF algorithm to a random target matrix #' V <- rmatrix(n, p) #' \dontrun{nmf(V, r, 'ns')} #' #' # random nonsmooth NMF model #' rnmf(3, 10, 5, model='NMFns', theta=0.3) #' setClass('NMFns' , representation( theta = 'numeric' # smoothing matrix ) , contains = 'NMFstd' , prototype = prototype( theta = 0.5 ) , validity = function(object){ if( object@theta < 0 || object@theta > 1 ) return(paste("Invalid value for theta (",object@theta,"): must be between 0 and 1", sep='')) TRUE } ) #' Show method for objects of class \code{NMFns} #' @export setMethod('show', 'NMFns', function(object) { callNextMethod() cat("theta:", object@theta, "\n") } ) #' Compute estimate for an NMFns object, according to the Nonsmooth NMF model #' (cf. \code{\link{NMFns-class}}). #' #' Extra arguments in \code{...} are passed to method \code{smoothing}, and are #' typically used to pass a value for \code{theta}, which is used to compute #' the smoothing matrix instead of the one stored in \code{object}. #' #' @param S smoothing matrix to use instead of \code{smoothing(object)} #' It must be a square matrix compatible with the basis and coefficient matrices #' used in the computation. #' @inline #' setMethod('fitted', signature(object='NMFns'), function(object, W, H, S, ...){ if( missing(W) ) W <- object@W if( missing(H) ) H <- object@H if( missing(S) ) S <- smoothing(object, ...) W %*% (S %*% H) } ) #' Smoothing Matrix in Nonsmooth NMF Models #' #' The function \code{smoothing} builds a smoothing matrix for using in Nonsmooth #' NMF models. #' #' For a \eqn{r}-rank NMF, the smoothing matrix of parameter \eqn{\theta} is #' built as follows: #' \deqn{S = (1-\theta)I + \frac{\theta}{r} 11^T ,} #' where \eqn{I} is the identity matrix and \eqn{1} is a vector of ones #' (cf. \code{\link{NMFns-class}} for more details). #' #' @param x a object of class \code{NMFns}. #' @param theta the smoothing parameter (numeric) between 0 and 1. #' @param ... extra arguments to allow extension (not used) #' #' @return if \code{x} estimates a \eqn{r}-rank NMF, #' then the result is a \eqn{r \times r} square matrix. #' @export #' #' @examples #' x <- nmfModel(3, model='NMFns') #' smoothing(x) #' smoothing(x, 0.1) #' smoothing <- function(x, theta=x@theta, ...){ # check validity of theta if( theta < 0 || theta > 1 ) stop("Invalid smoothing parameter theta [",theta,"]: theta must be susch that 0 <= theta <=1") diag(1-theta, nbasis(x)) + theta / nbasis(x) } NMF/R/grid.R0000644000176200001440000000436113620502674012154 0ustar liggesusers# Grid related functions # # Mainly functions that duplicate grid functions but do not create a new plot # if none is present. # # Author: Renaud Gaujoux # Creation: 04 Jun 2012 ############################################################################### #' @include options.R #' @import grid NULL #' Internal Grid Extension #' #' These functions enable mixing base and grid graphics in \code{\link{aheatmap}}, #' by avoiding calls to the grid internal function \code{'L_gridDirty'}. #' They are not exported (i.e. not tampering core functions) and are only meant for internal #' use within the \pkg{NMF} package. #' #' \code{tryViewport} tries to go down to a viewport in the current tree, #' given its name. #' #' @details #' \code{tryViewport} uses \code{\link[grid]{grid.ls}} and not #' \code{\link{seekViewport}} as the latter would reset the graphic device #' and break the mix grid/base graphic capability. #' #' @param name viewport name #' @param verbose toggle verbosity #' #' @rdname grid #' @keywords internal tryViewport <- function(name, verbose=FALSE){ if( verbose ) message("vp - lookup for ", name) l <- grid.ls(viewports=TRUE, grobs=FALSE, print=FALSE) if( name %in% l$name ){ downViewport(name) } } #' \code{current.vpPath_patched} aims at substituting \code{\link[grid]{current.vpPath}}, #' so that the graphic engine is not reset. #' This is essentially to prevent outputting a blank page at the beginning of PDF #' graphic engines. #' #' @rdname grid current.vpPath_patched <- local({ .current.vpPath <- NULL function(){ f_current.vpPath <- .current.vpPath if( !.use.grid.patch() ) f_current.vpPath <- grid::current.vpPath else if( is.null(f_current.vpPath) ){ # load patch from installed file patch <- source(packagePath('scripts', 'grid.R', package = 'NMF'), local = TRUE) .current.vpPath <<- patch$value f_current.vpPath <- .current.vpPath } # call f_current.vpPath() } }) # Add new option to enable/disable grid patch .OPTIONS$newOptions(grid.patch = FALSE) #' \code{.use.grid.patch} tells if the user enabled patching grid. #' @rdname grid .use.grid.patch <- function(){ !isCHECK() && nmf.getOption('grid.patch') } NMF/R/NMFStrategyFunction-class.R0000644000176200001440000000465013620502674016204 0ustar liggesusers#' @include NMFStrategy-class.R NULL #' Interface for Single Function NMF Strategies #' #' This class implements the virtual interface \code{\link{NMFStrategy}} for #' NMF algorithms that are implemented by a single workhorse R function. #' #' @slot algorithm a function that implements an NMF algorithm. #' It must have signature \code{(y='matrix', x='NMFfit')}, where \code{y} is the #' target matrix to approximate and \code{x} is the NMF model assumed to be #' seeded with an appropriate initial value -- as it is done internally by #' function \code{\link{nmf}}. #' #' Note that argument names currently do not matter, but it is recommended to #' name them as specified above. #' setClass('NMFStrategyFunction' , representation( algorithm = 'function' # the function that implements the algorithm ) , contains = 'NMFStrategy' ) #' Runs the NMF algorithms implemented by the single R function -- and stored in slot \code{'algorithm'} #' of \code{object}, on the data object \code{y}, using \code{x} as starting point. #' It is equivalent to calling \code{object@@algorithm(y, x, ...)}. #' #' This method is usually not called directly, but only via the function \code{\link{nmf}}, which #' takes care of many other details such as seeding the computation, handling RNG settings, or setting up #' parallelisation. #' #' @rdname NMFStrategy setMethod('run', signature(object='NMFStrategyFunction', y='matrix', x='NMFfit'), function(object, y, x, ...){ if( !is.function(fun <- algorithm(object)) ) stop("NMFStrategyFunction '", name(object), "': algorithm is not defined.") # run the function that defines the algorithm and return the result fun(y, x, ...) } ) #' @export nmfFormals.NMFStrategyFunction <- function(x, ...){ args <- formals(x@algorithm) args[-(1:2)] } #' Returns the single R function that implements the NMF algorithm -- as stored in #' slot \code{algorithm}. setMethod('algorithm', signature(object='NMFStrategyFunction'), function(object){ slot(object, 'algorithm') } ) #setReplaceMethod('algorithm', signature(object='NMFStrategyFunction', value='character'), # function(object, value){ # slot(object, 'algorithm') <- value # object # } #) #' Sets the function that implements the NMF algorithm, stored in slot \code{algorithm}. setReplaceMethod('algorithm', signature(object='NMFStrategyFunction', value='function'), function(object, value){ slot(object, 'algorithm') <- value object } ) NMF/R/nmf-package.R0000644000176200001440000001100013620502674013364 0ustar liggesusers#' @import graphics #' @import rngtools #' @import digest #' @import stringr #' @import stats #' @import methods NULL #library(digest) #' Defunct Functions and Classes in the NMF Package #' #' @name NMF-defunct #' @rdname NMF-defunct NULL #' Deprecated Functions in the Package NMF #' #' @param object an R object #' @param ... extra arguments #' #' @name NMF-deprecated #' @rdname NMF-deprecated NULL #' Algorithms and framework for Nonnegative Matrix Factorization (NMF). #' #' This package provides a framework to perform Non-negative Matrix Factorization (NMF). #' It implements a set of already published algorithms and seeding methods, and provides a framework #' to test, develop and plug new/custom algorithms. #' Most of the built-in algorithms have been optimized in C++, and the main interface function provides #' an easy way of performing parallel computations on multicore machines. #' #' \code{\link{nmf}} Run a given NMF algorithm #' #' @author Renaud Gaujoux \email{renaud@@cbio.uct.ac.za} #' @name NMF-package #' @aliases NMF #' @docType package #' @useDynLib NMF, .registration = TRUE #' #' @bibliography ~/Documents/articles/library.bib #' @references #' \url{http://cran.r-project.org/} #' #' \url{http://nmf.r-forge.project.org} #' @keywords package #' @seealso \code{\link{nmf}} #' @examples #' # generate a synthetic dataset with known classes #' n <- 50; counts <- c(5, 5, 8); #' V <- syntheticNMF(n, counts) #' #' # perform a 3-rank NMF using the default algorithm #' res <- nmf(V, 3) #' #' basismap(res) #' coefmap(res) #' NA devnmf <- function(){ .LOCAL_PKG_NAME <- 'NMF' requireNamespace('devtools') devtools::load_all(.LOCAL_PKG_NAME) compile_src(.LOCAL_PKG_NAME) } # local config info nmfConfig <- mkoptions() .onLoad <- function(libname, pkgname) { # set default number of cores if( pkgmaker::isCHECK() ){ options(cores=2) }else{ if( nchar(nc <- Sys.getenv('R_PACKAGE_NMF_CORES')) > 0 ){ try({ nmf.options(cores=as.numeric(nc)) }) } } # use grid patch? nmf.options(grid.patch = !isFALSE(Sys.getenv_value('R_PACKAGE_NMF_GRID_PATCH'))) pkgEnv <- pkgmaker::packageEnv() .init.sequence <- function(){ ## 0. INITIALIZE PACKAGE SPECFIC OPTIONS #.init.nmf.options() ## 1. INITIALIZE THE NMF MODELS .init.nmf.models() ## 2. INITIALIZE BIOC LAYER b <- body(.onLoad.nmf.bioc) bioc.loaded <- eval(b, envir=pkgEnv) nmfConfig(bioc=bioc.loaded) # 3. SHARED MEMORY if( .Platform$OS.type != 'windows' ){ msg <- if( !require.quiet('bigmemory', character.only=TRUE) ) 'bigmemory' else if( !require.quiet('synchronicity', character.only=TRUE) ) 'synchronicity' else TRUE nmfConfig(shared.memory=msg) } # } # run intialization sequence suppressing messages or not depending on verbosity options .init.sequence() if( getOption('verbose') ) .init.sequence() else suppressMessages(.init.sequence()) return(invisible()) } .onUnload <- function(libpath) { # unload compiled library dlls <- names(base::getLoadedDLLs()) if ( 'NMF' %in% dlls ) library.dynam.unload("NMF", libpath); } .onAttach <- function(libname, pkgname){ # build startup message msg <- NULL details <- NULL ## 1. CHECK BIOC LAYER bioc.loaded <- nmfConfig('bioc')[[1L]] msg <- paste0(msg, 'BioConductor layer') if( is(bioc.loaded, 'try-error') ) msg <- paste0(msg, ' [ERROR]') else if ( isTRUE(bioc.loaded) ) msg <- paste0(msg, ' [OK]') else{ msg <- paste0(msg, ' [NO: missing Biobase]') details <- c(details, " To enable the Bioconductor layer, try: install.extras('", pkgname, "') [with Bioconductor repository enabled]") } # 2. SHARED MEMORY msg <- paste0(msg, ' | Shared memory capabilities') if( .Platform$OS.type != 'windows' ){ conf <- nmfConfig('shared.memory')[[1L]] if( isTRUE(conf) ) msg <- paste0(msg, ' [OK]') else{ msg <- paste0(msg, ' [NO: ', conf, ']') details <- c(details, " To enable shared memory capabilities, try: install.extras('", pkgname, "')") } }else msg <- paste0(msg, ' [NO: windows]') # # 3. NUMBER OF CORES msg <- paste0(msg, ' | Cores ', getMaxCores(), '/', getMaxCores(limit=FALSE)) # # FINAL. CRAN FLAG if( pkgmaker::isCHECK() ){ msg <- paste0(msg, ' | CRAN check') } # # print startup message ver <- if( isDevNamespace() ){ paste0(' [', utils::packageVersion(pkgname), '-devel', ']') }#else{ # utils::packageVersion(pkgname, lib.loc = libname) # } packageStartupMessage(pkgname, ver, ' - ', msg) if( !is.null(details) ){ packageStartupMessage(paste(details, collapse="\n")) } } NMF/MD50000644000176200001440000002527413711174456011225 0ustar liggesusersa35566cf920ec62205b8796d013db823 *DESCRIPTION 4d9f82c23535500d92f82d1c3d108beb *NAMESPACE 192be80e51d0299e5f0e915bb8f1a19e *NEWS 431f640c6b15943a341a1f348be116b7 *R/Bioc-layer.R faa4fa1fa2ebdd0a0f0926657f4b669a *R/NMF-class.R cb92fbe2ad8ed07d3b6988790361cf89 *R/NMFOffset-class.R 56c06d6a59b72229d6fed8139b5917e1 *R/NMFSeed-class.R b290befb22d2dbb584a9cddd2746a3d9 *R/NMFSet-class.R 0b026fe9389466c7a387be01aac7372d *R/NMFStrategy-class.R b2710b639ac2f35ae198dbd8d355ad85 *R/NMFStrategyFunction-class.R b170ae1d07ff1d825f36ff42f537f33d *R/NMFStrategyIterative-class.R 9bfd9b9a7f095d2d23aad3192c7dad93 *R/NMFfit-class.R d4a9ccb2a64df0fcc003732b22087b29 *R/NMFns-class.R fec500637aa347bec6502173957d1cff *R/NMFplots.R 06ce7f9fb62284b85f8faea26cd9bc08 *R/NMFstd-class.R 614a1964d186c571fb4738d5a3209be1 *R/aheatmap.R 2215efb3fafbbae155830aed6b0b3ae1 *R/algorithmic.R 5c93753b2b401fd5b8c80b592f93193d *R/algorithms-base.R 015a5cef7738322d66ae1f280225f9e6 *R/algorithms-lnmf.R 6600c764faf7481013e0d6481fc25151 *R/algorithms-lsnmf.R ff9c9ac7006e8ef914ddb1d883316cbb *R/algorithms-pe-nmf.R c7345e2125689ff69487338672fead40 *R/algorithms-siNMF.R 65519453102daf686f7034a8b52cf316 *R/algorithms-snmf.R eca172be180d1e51dec38a7aaab54bee *R/atracks.R ac245d1088fea32f779b5c70000b012c *R/colorcode.R 481b4d73843f4cc38333f21979bd80e7 *R/data.R d5c1883b5350a8285e19c2772ceb50d8 *R/extractFeatures.R 8c3302dcde34a1119b0d895938be87dc *R/fixed-terms.R 1f90bca8a7b53737476d0956b55502ab *R/grid.R 5cf50ed45523796cab2a7a9533fd6981 *R/heatmaps.R 3f09bbebf53f4126bce5fb67619b7927 *R/nmf-package.R d560474d39fcc4c122e03c0b77e3d229 *R/nmf.R cf458865933fdc20ae922ec6a7951cf3 *R/nmfModel.R b775850b0cf2d25c9d4f674298030b3a *R/options.R 8ab03f9be773569a0f6e1b62474b4ded *R/parallel.R 9b7dc40acd74e5f2cdf32f5fb8a6e087 *R/registry-algorithms.R bd61f889b0bb039c703a7de9898e5fbc *R/registry-seed.R 66ca4afe8515dedd71d07dc05bbcc4d7 *R/registry.R c28c1144175993becd60f5a16156a3e2 *R/rmatrix.R b1d44e778d211021bc1754e90551c7c5 *R/rnmf.R 373909c677f0e1db2301f30638145a13 *R/run.R dd767a7d1fde7adfe30b3fee7cf86f5b *R/seed-base.R 1fcf1d8cb6162329731c7dd6d02c8a97 *R/seed-ica.R bcfb74089e84793cd6f41bd374bfd6fa *R/seed-nndsvd.R 71bf9f5ea7e161d5b3604040e37785ad *R/setNMFClass.R 5017945c10bf9b95560568b58c90559a *R/simulation.R 610b78f4f8f21a5a85d97967e4577bc8 *R/tests.R fce304c13565e5b26f0abf34bde1e761 *R/transforms.R ce8005200ba770a584eab5ae7f703bb9 *R/utils.R 77bc2b9255b6a8b6044e67e08ec91284 *R/versions.R 74a0940601ce18a8c64b4b2da1f32aab *README.md 551e2ca0a05eff9062c105483449fac4 *TODO 4d052be5fbd3cd7fc1f2b5c62441c1f5 *build/vignette.rds f8015e2bc710fb0bf869d2fdddf90e73 *data/esGolub.rda 7ee636d67aaf85d2b38c87f512559c1d *demo/00Index 5d230c7b9bd8450e8f3621f0fa2e8fe9 *demo/aheatmap.R 1772a84edd497138e1c2026608d99348 *demo/heatmaps.R 51502b8ceeab50a22383047790cc5ab6 *demo/nmf.R a481d25211ed91798b508d09b531253f *inst/CITATION c4d05e519580e330b6c7693b18650773 *inst/REFERENCES.bib 7b2c32a38b3f390936397beedd06e262 *inst/doc/NMF-vignette.R 6d5323e39e6b59d2c0e8ec68236f9e61 *inst/doc/NMF-vignette.Rnw 0c41d5aecdab6866ffc4095ef15fa2d8 *inst/doc/NMF-vignette.pdf ceeecbc8213e05277e9ec2b3de70de39 *inst/doc/consensus.pdf ebba5c890f3988b27a065e8c1f077bfe *inst/doc/heatmaps.R e9f3edd58047fbe5886c63c1fbb89ee9 *inst/doc/heatmaps.Rnw 8dbe7582d99717ac13087f6d85036a22 *inst/doc/heatmaps.pdf 21a62ee5e50af50c0b22f88d167d068c *inst/scripts/grid.R 2952972c46324b0beb29d6892b7e2e40 *inst/scripts/report.Rmd 767832f7e470c4704f4b7602d23a7798 *man/Frobenius-nmf.Rd a85b19bc2328054bc2f9b0f6254ee488 *man/KL-nmf.Rd e994bb5f22076c0034607b157e8d6b2e *man/NMF-class.Rd 3afb15f1474a46e4086d736a3c871caf *man/NMF-defunct.Rd dc30d7f32eb6954c2e9aa946f8b9df26 *man/NMF-deprecated.Rd c067d1fa1270349d839716420eedd3c5 *man/NMF-package.Rd 06d347a23a138dccfd978daa81ea0e1b *man/NMFList-class.Rd 201bfce1ddf5c18da7ac1bfe67ffb94f *man/NMFOffset-class.Rd 425940f01b128ed86c081405ae146ab0 *man/NMFSeed-class.Rd 895c97e9785c890a0181e953407ad117 *man/NMFStrategy-class.Rd 78a5599bceeaad4bfce2d04ce03c8a21 *man/NMFStrategy.Rd 5c94b9595c875433cb19171da1a62758 *man/NMFStrategyFunction-class.Rd 18cd07fb2cccaa6d2600fefe5c47b7a9 *man/NMFStrategyIterative-class.Rd 3092af863a3f6a9b76f3f2f6fe769335 *man/NMFfit-class.Rd b45d28a519f4470a137736e70c7afc53 *man/NMFfitX-class.Rd d9a31d45444d62f906d662fc8b265426 *man/NMFfitX.Rd f1b66c26b14dd584b069e513c8af2046 *man/NMFfitX1-class.Rd 7878abd1ed98e6144f030c857fc46273 *man/NMFfitXn-class.Rd f874ba9928c02022df7d9c8a2ac5bd4d *man/NMFns-class.Rd 0af573b61066884e29f8152d8295575b *man/NMFstd-class.Rd 78f09d6304dbbaaa447e57eee894daba *man/RNG.Rd 8b66c7ab49c34b0e5df0c652d45d437f *man/SNMF-nmf.Rd 42afa8e2c6f5d7ac5c045174a05bf8d5 *man/Strategy-class.Rd 28877eb83332222d0a67473a16148fbc *man/advanced.Rd 4fb111a0b55a5731ff84338e85c6cd33 *man/aheatmap.Rd 762c9a6fd51a723065df7becaa801083 *man/algorithm-commaNMFList-method.Rd 154a6705361e6ca6c244d4c921620a01 *man/algorithmic.Rd 9c3697730dd0a7aea87f13af6f726812 *man/assess.Rd 20413d9a4e878340f471a26415386685 *man/atrack.Rd a4855c2e0d6ece887e5e6b7fb27d640c *man/basis-coef-methods.Rd 435ac6f77719a0d9a5a18994c40989c1 *man/basiscor.Rd 1810d6750bc88981b602808d9f673c4a *man/bioc.Rd 182672f1ed5b39098c5127c0d1b96a8f *man/c-commaNMF-method.Rd 84c5ff2ee4546200a404c1968c7e4bd6 *man/canFit.Rd 4a680134ba34c9d60f7cf8448de0175c *man/ccBreaks.Rd 04aa500814501e01f971a9a9e649f7d6 *man/ccPalette.Rd 30b38d0eb549f60d498fea802752f9e8 *man/ccRamp.Rd ead0bcb1b2960042e6c225abaf312d3f *man/ccSpec.Rd 3c3630400842a8205c1698dd2ece18b9 *man/checkErrors.Rd 94b8ec67220a6c9cb58bf5a9d07c21ce *man/cluster_mat.Rd 491d24b685f65bb7aaafb80c3bcf908e *man/connectivity.Rd 079f0bf31cd00add0215f852c2671793 *man/consensus-commaNMFfitX1-method.Rd 4191760c707e50acc9085b946e6972e4 *man/consensus-commaNMFfitXn-method.Rd 1adf8241b895a1da992f6fd39b394d60 *man/consensushc.Rd 00357312e2fb0196a0c380f4134d2f32 *man/cophcor.Rd 656b6a8bfe95f66aa37d808b14427b72 *man/cutdendro.Rd 18de62f7c36df3b79d39e649b5f126c4 *man/deviance.Rd 4d758173653d9a9e4fc6826f10288896 *man/dimnames.Rd d17124d62761eea2293f997a41b3b978 *man/dims.Rd 0eda8c23e3906c11c2894eee60b12e26 *man/dispersion.Rd edcc1d52a2ad50aad945d5d74e65121a *man/dot-fcnnls.Rd 510ca3cc256b2f33fa18251cba9721f2 *man/esGolub.Rd 1913aebfe0827011d600e56f33587399 *man/fcnnls.Rd 26389df3557196423682198ca30e4490 *man/fit.Rd c1baa89aba303ba8bd4ef407e184e55c *man/fitted.Rd 06890b1489d4114507541c63b32ec5ed *man/foreach.Rd 4bdd231e0439efdac037a437ebd0fdd9 *man/gfile.Rd fedca5f307d98d0e1f276cf0595e6673 *man/grid.Rd 268ec51b8cf89183b78a2df2c0b6d767 *man/heatmaps.Rd c103f3feaa7e2c4ddb4b5cb2de75356d *man/inplace.Rd 42c4702adb4bd8432e51b7f0d2aab9be *man/lsNMF-nmf.Rd ad4bea9f6bf8da8c5b8af058d4e70581 *man/lverbose.Rd 83ccd902c78857036785b022791144a5 *man/match_atrack.Rd 17146cceec053543e3ad16d0570d092d *man/nmf-compare.Rd dddfd680a93830e364d2bb5af4e4aae7 *man/nmf.Rd b5a35acf90da3f5d014c3686d234368d *man/nmf.equal.Rd 625896d5746034a531a4d1da377fcb5f *man/nmfAlgorithm.Rd f9cb9d24d68ee319e7593c1d2fee3223 *man/nmfApply.Rd 0ad0ee7ca0345c8bb2ab226df30a1860 *man/nmfCheck.Rd 4952f5420fac8de51c8810e64b845974 *man/nmfEstimateRank.Rd d95047f4bf61f4c7ccaa1e921ba4b522 *man/nmfFormals.Rd 35544ed71b508eead30c64deb154c324 *man/nmfModel.Rd d4898d6e3b1cd1b6b178a60c2d576ee5 *man/nmfObject.Rd 49f4c64242cc5d0c47b94a9c42807ca2 *man/nmfReport.Rd fce06c76bfd398abc1a348d67e7e8c58 *man/nmfSeed.Rd 7780fe2dae233c6ba52e340d797e2a11 *man/nmfWrapper.Rd 48346311c56811ab4bfc502a46feac77 *man/nmf_update_KL.Rd 01282dba5a9467f5c34e9d7b2d405ded *man/nmf_update_euclidean.Rd c28f918be30a5da8db623facf38eea9f *man/nneg.Rd f481c5f6107309c849530b83206f18f2 *man/nsNMF-nmf.Rd ca58350f90998275f533e264d5ce5942 *man/objective-commaNMFfit-method.Rd 4082dd89c61fd2ae75e274171565642b *man/offset-commaNMFOffset-method.Rd c5c14f3e24e71b81dc726a027234fd77 *man/offset-commaNMFfit-method.Rd 00d3fe2de32a02827359c85f3b2838fe *man/offset-nmf.Rd b948e9807bd7e64baab6a364285014d4 *man/options.Rd 6ceade6a531a0e234b6871dd0effb817 *man/parallel.Rd 1b26866d20f593b9a4d791975d0768f2 *man/parse_formula.Rd 2959063b2e6978de03a0bf0d3b314046 *man/plot-commaNMFfit-commamissing-method.Rd 6e450dcc8156f699b0e5d63c598a89d8 *man/predict.Rd 98e318df3d7218b1a8e88108f57d5811 *man/profplot.Rd d8d2498c4b415b41cde4cf9e8049f8a5 *man/purity.Rd 35694f86b6139a3ee2cd3f82a41ad824 *man/randomize.Rd 3560f009b84aa4bf1d3dfcbc1ecb1e16 *man/registry-algorithm.Rd cb7ba12690237f166574749bb707d3f7 *man/residuals.Rd c8aa1d388b52049207fc058e157af5b6 *man/revPalette.Rd 622964f68f0911dff7cf9eede6fac98e *man/rmatrix.Rd 5200979607c31ee9e75d2e4f2e89a37c *man/rnmf.Rd 08b1b09c30f90240167a25a5dc1671ef *man/rss.Rd e589f075ac5a2404fe136375475ab67c *man/runtime-commaNMFList-method.Rd 67dd23b7d9dd5739289857be80513bbc *man/runtime.all-commaNMFfitXn-method.Rd b20c67b061270a8948c14f30c029d266 *man/scale.NMF.Rd 0e9aa7fe953cbc4d0b2cdfdd6a61fabb *man/scores.Rd b1deacdafd60ed14a2ee40ec5dbffba3 *man/seed.Rd 0a372ae7c4ebd520f9a087253bf5f5cc *man/setNMFMethod.Rd 90119b84cf9e8a6c4d6faeb60d0c6cd9 *man/setNMFSeed.Rd 571ad68968801bbafe7f2d2e25a22550 *man/setup.Rd 7bb22e6720734f511e803a871fb26e39 *man/show-commaNMF-method.Rd 2d06782ea4c4ad91ac5694f33575e923 *man/show-commaNMFList-method.Rd 13c4118576e5e6767234d6b61f4d9159 *man/show-commaNMFOffset-method.Rd bb3a39862b068259cab8ea954454bcc2 *man/show-commaNMFSeed-method.Rd 84b6af7f192d8e0919bf6a7795645353 *man/show-commaNMFStrategyIterative-method.Rd 515b08c8a685e823723afeb4a1a313c8 *man/show-commaNMFfit-method.Rd f36b652373f4f16f2bb6828c2b5d585f *man/show-commaNMFfitX-method.Rd c1c89600aba522e34a6ba28190f2bd7c *man/show-commaNMFfitX1-method.Rd f14bfc191bb4d6d39684d9ee54a39736 *man/show-commaNMFfitXn-method.Rd becd00d7a3051215763271cd1eb8325e *man/show-commaNMFns-method.Rd 398be317ddaf1e59d8d8bd2bb17ccabb *man/silhouette.NMF.Rd 9e8cf1622e25fed14b84bfcd0521ed35 *man/smoothing.Rd e6a7d35fb160e28931b03173e82cde35 *man/sparseness.Rd 127c40883b9775f0c9a8500378c0efed *man/staticVar.Rd b60bd8e815ca75ea07f7bcec5e2b4d3e *man/stop-NMF.Rd ae4a92d9c449e96cf2f7578d778b4beb *man/subset-NMF.Rd c8cf5183b055fd8a2b16c1d27f65f101 *man/syntheticNMF.Rd 4264ddc5ceb09fc3e2edf67a4ffea81e *man/t.NMF.Rd d0a67df32409d5fe6c871f24b2b71c7c *man/terms-internal.Rd b279c6ba4bf77ce4028e6e99ee3b6fb3 *man/terms.Rd 8af3bdb9baf9ace2bff7cf278528808e *man/txtProgressBar.Rd 608427df6c5d9db2129add0bef10d767 *man/types.Rd c1dd149f5e75b70b800565157584fe7a *man/utils.Rd 7cc4426f76a914383e70b17f2f35c312 *src/distance.cpp 74facf85e6495aea68dd04b725cc6787 *src/divergence.cpp e78941582fb2d47fd1f87f9a48a37729 *src/euclidean.cpp 7c22b1b500cab25872924e218f1645f5 *src/registerDynamicSymbol.c 0678d1eb5e2c82c47e9a296fee622380 *src/utils.cpp 6d5323e39e6b59d2c0e8ec68236f9e61 *vignettes/NMF-vignette.Rnw e80ff821d37969bdb7ff73081b8f7c53 *vignettes/consensus.pdf e9f3edd58047fbe5886c63c1fbb89ee9 *vignettes/heatmaps.Rnw d0b6a6b6a5d501ed47120a405f13c012 *vignettes/src/bmc.R NMF/inst/0000755000176200001440000000000013620502674011654 5ustar liggesusersNMF/inst/REFERENCES.bib0000644000176200001440000010516613620502674013764 0ustar liggesusers@Manual{R, address = {Vienna, Austria}, annote = {\{ISBN\} 3-900051-07-0}, author = {{R Development Core Team}}, organization = {R Foundation for Statistical Computing}, title = {{R: A Language and Environment for Statistical Computing}}, url = {http://www.r-project.org}, year = {2011}, } @Article{Gentleman2004, abstract = {The Bioconductor project is an initiative for the collaborative creation of extensible software for computational biology and bioinformatics. The goals of the project include: fostering collaborative development and widespread use of innovative software, reducing barriers to entry into interdisciplinary scientific research, and promoting the achievement of remote reproducibility of research results. We describe details of our aims and methods, identify current challenges, compare Bioconductor to other open bioinformatics projects, and provide working examples.}, author = {Robert C Gentleman and Vincent J Carey and Douglas M Bates and Ben Bolstad and Marcel Dettling and Sandrine Dudoit and Byron Ellis and Laurent Gautier and Yongchao Ge and Jeff Gentry and Kurt Hornik and Torsten Hothorn and Wolfgang Huber and Stefano Iacus and Rafael Irizarry and Friedrich Leisch and Cheng Li and Martin Maechler and Anthony J Rossini and Gunther Sawitzki and Colin Smith and Gordon Smyth and Luke Tierney and Jean Y H Yang and Jianhua Zhang}, doi = {10.1186/gb-2004-5-10-r80}, issn = {1465-6914}, journal = {Genome biology}, keywords = {Computational Biology,Computational Biology: instrumentation,Computational Biology: methods,Internet,Reproducibility of Results,Software}, number = {10}, pages = {R80}, pmid = {15461798}, shorttitle = {Genome Biol}, title = {{Bioconductor: open software development for computational biology and bioinformatics.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/15461798}, volume = {5}, year = {2004}, } @Article{Lee2001, author = {D D Lee and HS Seung}, file = {:home/renaud/Documents/articles/NMF/Algorithms for non-negative matrix factorization\_Lee2000.pdf:pdf}, journal = {Advances in neural information processing systems}, title = {{Algorithms for non-negative matrix factorization}}, url = {http://scholar.google.com/scholar?q=intitle:Algorithms+for+non-negative+matrix+factorization\#0}, year = {2001}, } @Article{Li2001, author = {Stan Z Li and Xinwen Hou}, file = {:home/renaud/Documents/articles/NMF/Learning Spatially Localized, Parts-Based Representation\_Li2001.pdf:pdf}, journal = {Convergence}, number = {C}, pages = {1--6}, title = {{Learning Spatially Localized, Parts-Based Representation}}, volume = {00}, year = {2001}, } @Article{Badea2008, abstract = {In this paper we introduce a clustering algorithm capable of simultaneously factorizing two distinct gene expression datasets with the aim of uncovering gene regulatory programs that are common to the two phenotypes. The siNMF algorithm simultaneously searches for two factorizations that share the same gene expression profiles. The two key ingredients of this algorithm are the nonnegativity constraint and the offset variables, which together ensure the sparseness of the factorizations. While cancer is a very heterogeneous disease, there is overwhelming recent evidence that the differences between cancer subtypes implicate entire pathways and biological processes involving large numbers of genes, rather than changes in single genes. We have applied our simultaneous factorization algorithm looking for gene expression profiles that are common between the more homogeneous pancreatic ductal adenocarcinoma (PDAC) and the more heterogeneous colon adenocarcinoma. The fact that the PDAC signature is active in a large fraction of colon adeocarcinoma suggests that the oncogenic mechanisms involved may be similar to those in PDAC, at least in this subset of colon samples. There are many approaches to uncovering common mechanisms involved in different phenotypes, but most are based on comparing gene lists. The approach presented in this paper additionally takes gene expression data into account and can thus be more sensitive.}, author = {Liviu Badea}, file = {:home/renaud/Documents/articles/NMF/Extracting Gene Expression Profiles Common to Colon and Pancreatic Adenocarcinoma Using Simultaneous Nonnegative Matrix Factorization\_Badea2008.pdf:pdf}, issn = {1793-5091}, journal = {Pacific Symposium on Biocomputing. Pacific Symposium on Biocomputing}, keywords = {Adenocarcinoma,Adenocarcinoma: genetics,Algorithms,Carcinoma,Colonic Neoplasms,Colonic Neoplasms: genetics,Computational Biology,Data Interpretation,Databases,Gene Expression Profiling,Gene Expression Profiling: statistics \& numerical,Genetic,Humans,Pancreatic Ductal,Pancreatic Ductal: genetics,Pancreatic Neoplasms,Pancreatic Neoplasms: genetics,Statistical}, month = {jan}, pages = {267--78}, pmid = {18229692}, title = {{Extracting gene expression profiles common to colon and pancreatic adenocarcinoma using simultaneous nonnegative matrix factorization.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/18229692}, volume = {290}, year = {2008}, } @Article{Zhang2008, abstract = {Independent component analysis (ICA) is a widely applicable and effective approach in blind source separation (BSS), with limitations that sources are statistically independent. However, more common situation is blind source separation for nonnegative linear model (NNLM) where the observations are nonnegative linear combinations of nonnegative sources, and the sources may be statistically dependent. We propose a pattern expression nonnegative matrix factorization (PE-NMF) approach from the view point of using basis vectors most effectively to express patterns. Two regularization or penalty terms are introduced to be added to the original loss function of a standard nonnegative matrix factorization (NMF) for effective expression of patterns with basis vectors in the PE-NMF. Learning algorithm is presented, and the convergence of the algorithm is proved theoretically. Three illustrative examples on blind source separation including heterogeneity correction for gene microarray data indicate that the sources can be successfully recovered with the proposed PE-NMF when the two parameters can be suitably chosen from prior knowledge of the problem.}, author = {Junying Zhang and Le Wei and Xuerong Feng and Zhen Ma and Yue Wang}, doi = {10.1155/2008/168769}, file = {:home/renaud/Documents/articles/NMF/Pattern Expression Nonnegative Matrix Factorization$\backslash$: Algorithm and Applications to Blind Source Separation\_Zhang2008.pdf:pdf}, issn = {1687-5265}, journal = {Computational intelligence and neuroscience}, pages = {168769}, pmid = {18566689}, shorttitle = {Comput Intell Neurosci}, title = {{Pattern expression nonnegative matrix factorization: algorithm and applications to blind source separation.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/18566689}, volume = {2008}, year = {2008}, } @Article{KimH2007, abstract = {MOTIVATION: Many practical pattern recognition problems require non-negativity constraints. For example, pixels in digital images and chemical concentrations in bioinformatics are non-negative. Sparse non-negative matrix factorizations (NMFs) are useful when the degree of sparseness in the non-negative basis matrix or the non-negative coefficient matrix in an NMF needs to be controlled in approximating high-dimensional data in a lower dimensional space. RESULTS: In this article, we introduce a novel formulation of sparse NMF and show how the new formulation leads to a convergent sparse NMF algorithm via alternating non-negativity-constrained least squares. We apply our sparse NMF algorithm to cancer-class discovery and gene expression data analysis and offer biological analysis of the results obtained. Our experimental results illustrate that the proposed sparse NMF algorithm often achieves better clustering performance with shorter computing time compared to other existing NMF algorithms. AVAILABILITY: The software is available as supplementary material.}, author = {Hyunsoo Kim and Haesun Park}, doi = {10.1093/bioinformatics/btm134}, file = {:home/renaud/Documents/articles/NMF/Sparse non-negative matrix factorizations via alternating non-negativity-constrained least squares for microarray data analysis Kim2007.pdf:pdf}, issn = {1460-2059}, journal = {Bioinformatics (Oxford, England)}, keywords = {Algorithms,Automated,Automated: methods,Cluster Analysis,Computational Biology,Computational Biology: methods,Data Interpretation,Databases,Entropy,Factor Analysis,Gene Expression,Genetic,Humans,Least-Squares Analysis,Microarray Analysis,Neoplasms,Neoplasms: classification,Neoplasms: genetics,Neoplasms: metabolism,Pattern Recognition,Statistical}, number = {12}, pages = {1495--502}, pmid = {17483501}, shorttitle = {Bioinformatics}, title = {{Sparse non-negative matrix factorizations via alternating non-negativity-constrained least squares for microarray data analysis.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/17483501}, volume = {23}, year = {2007}, } @TechReport{Albright2006, author = {Russell Albright and James Cox and David Duling and Amy N. Langville and C. Meyer}, booktitle = {Matrix}, file = {:home/renaud/Documents/articles/NMF/Algorithms, Initializations, and Convergence for the Nonnegative Matrix Factorization\_Langville2006.pdf:pdf}, institution = {NCSU Technical Report Math 81706. http://meyer. math. ncsu. edu/Meyer/Abstracts/Publications. html}, keywords = {60j22,65b99,65c40,65f10,65f15,65f50,alternating least squares,ams subject classi cations,clustering,convergence crite-,image processing,initializations,nonnegative matrix factorization,rion,text mining}, number = {919}, title = {{Algorithms, initializations, and convergence for the nonnegative matrix factorization}}, url = {http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.89.2161\&rep=rep1\&type=pdf http://meyer.math.ncsu.edu/Meyer/PS\_Files/NMFInitAlgConv.pdf}, year = {2006}, } @Article{Boutsidis2008, author = {C Boutsidis and E Gallopoulos}, doi = {10.1016/j.patcog.2007.09.010}, file = {:home/renaud/Documents/articles/NMF/SVD based initialization$\backslash$: A head start for nonnegative matrix factorization\_Boutsidis2008.pdf:pdf}, issn = {00313203}, journal = {Pattern Recognition}, month = {apr}, number = {4}, pages = {1350--1362}, title = {{SVD based initialization: A head start for nonnegative matrix factorization}}, url = {http://linkinghub.elsevier.com/retrieve/pii/S0031320307004359}, volume = {41}, year = {2008}, } @Article{Lecuyer2002, author = {Pierre L'Ecuyer and Richard Simard and E.J. Chen}, file = {:home/renaud/Documents/articles/stats/An Object-Oriented Random-Number Package with Many Long Streams and Substreams\_Lecuyer2002.pdf:pdf}, journal = {Operations Research}, number = {6}, pages = {1073--1075}, publisher = {JSTOR}, title = {{An object-oriented random-number package with many long streams and substreams}}, url = {http://www.jstor.org/stable/3088626}, volume = {50}, year = {2002}, } @Article{Hutchins2008, abstract = {MOTIVATION: Cis-acting regulatory elements are frequently constrained by both sequence content and positioning relative to a functional site, such as a splice or polyadenylation site. We describe an approach to regulatory motif analysis based on non-negative matrix factorization (NMF). Whereas existing pattern recognition algorithms commonly focus primarily on sequence content, our method simultaneously characterizes both positioning and sequence content of putative motifs. RESULTS: Tests on artificially generated sequences show that NMF can faithfully reproduce both positioning and content of test motifs. We show how the variation of the residual sum of squares can be used to give a robust estimate of the number of motifs or patterns in a sequence set. Our analysis distinguishes multiple motifs with significant overlap in sequence content and/or positioning. Finally, we demonstrate the use of the NMF approach through characterization of biologically interesting datasets. Specifically, an analysis of mRNA 3'-processing (cleavage and polyadenylation) sites from a broad range of higher eukaryotes reveals a conserved core pattern of three elements.}, author = {Lucie N Hutchins and Sean M Murphy and Priyam Singh and Joel H Graber}, doi = {10.1093/bioinformatics/btn526}, file = {:home/renaud/Documents/articles/NMF/Position-dependent motif characterization using non-negative matrix factorization\_Hutchins2008.pdf:pdf}, issn = {1367-4811}, journal = {Bioinformatics (Oxford, England)}, keywords = {Algorithms,Computational Biology,Computational Biology: methods,Messenger,Messenger: genetics,Messenger: metabolism,RNA,Regulatory Sequences,Ribonucleic Acid,Sequence Analysis}, month = {dec}, number = {23}, pages = {2684--90}, pmid = {18852176}, title = {{Position-dependent motif characterization using non-negative matrix factorization.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/18852176}, volume = {24}, year = {2008}, } @Article{Frigyesi2008, abstract = {Non-negative matrix factorization (NMF) is a relatively new approach to analyze gene expression data that models data by additive combinations of non-negative basis vectors (metagenes). The non-negativity constraint makes sense biologically as genes may either be expressed or not, but never show negative expression. We applied NMF to five different microarray data sets. We estimated the appropriate number metagens by comparing the residual error of NMF reconstruction of data to that of NMF reconstruction of permutated data, thus finding when a given solution contained more information than noise. This analysis also revealed that NMF could not factorize one of the data sets in a meaningful way. We used GO categories and pre defined gene sets to evaluate the biological significance of the obtained metagenes. By analyses of metagenes specific for the same GO-categories we could show that individual metagenes activated different aspects of the same biological processes. Several of the obtained metagenes correlated with tumor subtypes and tumors with characteristic chromosomal translocations, indicating that metagenes may correspond to specific disease entities. Hence, NMF extracts biological relevant structures of microarray expression data and may thus contribute to a deeper understanding of tumor behavior.}, author = {Attila Frigyesi and Mattias H\"{o}glund}, file = {:home/renaud/Documents/articles/NMF/Non-Negative Matrix Factorization for the Analysis of Complex Gene Expression Data$\backslash$: Identification of Clinically Relevant Tumor Subtypes\_Frigyesi2008.pdf:pdf}, issn = {1176-9351}, journal = {Cancer informatics}, keywords = {gene expression,metagenes,nmf,tumor classifi cation}, month = {jan}, number = {2003}, pages = {275--92}, pmid = {19259414}, title = {{Non-negative matrix factorization for the analysis of complex gene expression data: identification of clinically relevant tumor subtypes.}}, url = {http://www.ncbi.nlm.nih.gov/pmc/articles/PMC2623306/}, volume = {6}, year = {2008}, } @Article{Brunet2004, abstract = {We describe here the use of nonnegative matrix factorization (NMF), an algorithm based on decomposition by parts that can reduce the dimension of expression data from thousands of genes to a handful of metagenes. Coupled with a model selection mechanism, adapted to work for any stochastic clustering algorithm, NMF is an efficient method for identification of distinct molecular patterns and provides a powerful method for class discovery. We demonstrate the ability of NMF to recover meaningful biological information from cancer-related microarray data. NMF appears to have advantages over other methods such as hierarchical clustering or self-organizing maps. We found it less sensitive to a priori selection of genes or initial conditions and able to detect alternative or context-dependent patterns of gene expression in complex biological systems. This ability, similar to semantic polysemy in text, provides a general method for robust molecular pattern discovery.}, author = {Jean-Philippe Brunet and Pablo Tamayo and Todd R Golub and Jill P Mesirov}, doi = {10.1073/pnas.0308531101}, file = {:home/renaud/Documents/articles/NMF/Metagenes and Molecular pattern discovery using matrix factorization Brunet2004.pdf:pdf}, issn = {0027-8424}, journal = {Proceedings of the National Academy of Sciences of the United States of America}, keywords = {Algorithms,Central Nervous System Neoplasms,Central Nervous System Neoplasms: classification,Central Nervous System Neoplasms: genetics,Computational Biology,Data Interpretation,Genetic,Leukemia,Leukemia: classification,Leukemia: genetics,Medulloblastoma,Medulloblastoma: genetics,Models,Neoplasms,Neoplasms: classification,Neoplasms: genetics,Statistical}, number = {12}, pages = {4164--9}, pmid = {15016911}, title = {{Metagenes and molecular pattern discovery using matrix factorization.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/15016911}, volume = {101}, year = {2004}, } @Article{Pascual-Montano2006, author = {Alberto Pascual-Montano and Jose Maria Carazo and K Kochi and D Lehmann and R D Pascual-marqui}, file = {:home/renaud/Documents/articles/NMF/Nonsmooth nonnegative matrix factorization (nsNMF)\_Paascual-Montano2006.pdf:pdf}, journal = {IEEE Trans. Pattern Anal. Mach. Intell}, pages = {403--415}, title = {{Nonsmooth nonnegative matrix factorization (nsNMF)}}, volume = {28}, year = {2006}, } @Article{Lee1999, abstract = {Is perception of the whole based on perception of its parts? There is psychological and physiological evidence for parts-based representations in the brain, and certain computational theories of object recognition rely on such representations. But little is known about how brains or computers might learn the parts of objects. Here we demonstrate an algorithm for non-negative matrix factorization that is able to learn parts of faces and semantic features of text. This is in contrast to other methods, such as principal components analysis and vector quantization, that learn holistic, not parts-based, representations. Non-negative matrix factorization is distinguished from the other methods by its use of non-negativity constraints. These constraints lead to a parts-based representation because they allow only additive, not subtractive, combinations. When non-negative matrix factorization is implemented as a neural network, parts-based representations emerge by virtue of two properties: the firing rates of neurons are never negative and synaptic strengths do not change sign.}, author = {D D Lee and H S Seung}, doi = {10.1038/44565}, file = {:home/renaud/Documents/articles/NMF/Learning the parts of objects by non-negative matrix factorization\_Lee1999.pdf:pdf}, issn = {0028-0836}, journal = {Nature}, keywords = {Algorithms,Face,Humans,Learning,Models,Neurological,Perception,Perception: physiology,Semantics}, month = {oct}, number = {6755}, pages = {788--91}, pmid = {10548103}, title = {{Learning the parts of objects by non-negative matrix factorization.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/10548103}, volume = {401}, year = {1999}, } @Article{Paatero1994, abstract = {A new variant ?PMF? of factor analysis is described. It is assumed that X is a matrix of observed data and ? is the known matrix of standard deviations of elements of X. Both X and ? are of dimensions n × m. The method solves the bilinear matrix problem X = GF + E where G is the unknown left hand factor matrix (scores) of dimensions n × p, F is the unknown right hand factor matrix (loadings) of dimensions p × m, and E is the matrix of residuals. The problem is solved in the weighted least squares sense: G and F are determined so that the Frobenius norm of E divided (element-by-element) by ? is minimized. Furthermore, the solution is constrained so that all the elements of G and F are required to be non-negative. It is shown that the solutions by PMF are usually different from any solutions produced by the customary factor analysis (FA, i.e. principal component analysis (PCA) followed by rotations). Usually PMF produces a better fit to the data than FA. Also, the result of PF is guaranteed to be non-negative, while the result of FA often cannot be rotated so that all negative entries would be eliminated. Different possible application areas of the new method are briefly discussed. In environmental data, the error estimates of data can be widely varying and non-negativity is often an essential feature of the underlying models. Thus it is concluded that PMF is better suited than FA or PCA in many environmental applications. Examples of successful applications of PMF are shown in companion papers.}, author = {Pentti Paatero and Unto Tapper}, doi = {10.1002/env.3170050203}, journal = {Environmetrics}, keywords = {algorithm,nmf}, mendeley-tags = {algorithm,nmf}, number = {2}, pages = {111--126}, title = {{Positive matrix factorization: A non-negative factor model with optimal utilization of error estimates of data values}}, type = {Journal article}, url = {http://www3.interscience.wiley.com/cgi-bin/abstract/113468839/ABSTRACT}, volume = {5}, year = {1994}, } @Article{Hoyer2004, author = {PO Hoyer}, file = {:home/renaud/Documents/articles/NMF/Non-negative Matrix Factorization with Sparseness Constraints\_Hoyer2004.pdf:pdf}, journal = {The Journal of Machine Learning Research}, pages = {1457--1469}, title = {{Non-negative matrix factorization with sparseness constraints}}, url = {http://portal.acm.org/citation.cfm?id=1044709}, volume = {5}, year = {2004}, } @Article{Carmona-Saez2006, abstract = {BACKGROUND: The extended use of microarray technologies has enabled the generation and accumulation of gene expression datasets that contain expression levels of thousands of genes across tens or hundreds of different experimental conditions. One of the major challenges in the analysis of such datasets is to discover local structures composed by sets of genes that show coherent expression patterns across subsets of experimental conditions. These patterns may provide clues about the main biological processes associated to different physiological states. RESULTS: In this work we present a methodology able to cluster genes and conditions highly related in sub-portions of the data. Our approach is based on a new data mining technique, Non-smooth Non-Negative Matrix Factorization (nsNMF), able to identify localized patterns in large datasets. We assessed the potential of this methodology analyzing several synthetic datasets as well as two large and heterogeneous sets of gene expression profiles. In all cases the method was able to identify localized features related to sets of genes that show consistent expression patterns across subsets of experimental conditions. The uncovered structures showed a clear biological meaning in terms of relationships among functional annotations of genes and the phenotypes or physiological states of the associated conditions. CONCLUSION: The proposed approach can be a useful tool to analyze large and heterogeneous gene expression datasets. The method is able to identify complex relationships among genes and conditions that are difficult to identify by standard clustering algorithms.}, author = {Pedro Carmona-Saez and Roberto D Pascual-Marqui and Francisco Tirado and Jose Maria Carazo and Alberto Pascual-Montano}, doi = {10.1186/1471-2105-7-78}, file = {:home/renaud/Documents/articles/NMF/Biclustering of gene expression data by non-smooth non-negative matrix factorization\_Carmona-Saez2006.pdf:pdf}, issn = {1471-2105}, journal = {BMC bioinformatics}, keywords = {Algorithms,Artificial Intelligence,Automated,Automated: methods,Cluster Analysis,Factor Analysis,Gene Expression Profiling,Gene Expression Profiling: methods,Oligonucleotide Array Sequence Analysis,Oligonucleotide Array Sequence Analysis: methods,Pattern Recognition,Statistical}, pages = {78}, pmid = {16503973}, title = {{Biclustering of gene expression data by Non-smooth Non-negative Matrix Factorization.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/16503973}, volume = {7}, year = {2006}, } @Article{Wang2006, abstract = {BACKGROUND: Non-negative matrix factorisation (NMF), a machine learning algorithm, has been applied to the analysis of microarray data. A key feature of NMF is the ability to identify patterns that together explain the data as a linear combination of expression signatures. Microarray data generally includes individual estimates of uncertainty for each gene in each condition, however NMF does not exploit this information. Previous work has shown that such uncertainties can be extremely valuable for pattern recognition. RESULTS: We have created a new algorithm, least squares non-negative matrix factorization, LS-NMF, which integrates uncertainty measurements of gene expression data into NMF updating rules. While the LS-NMF algorithm maintains the advantages of original NMF algorithm, such as easy implementation and a guaranteed locally optimal solution, the performance in terms of linking functionally related genes has been improved. LS-NMF exceeds NMF significantly in terms of identifying functionally related genes as determined from annotations in the MIPS database. CONCLUSION: Uncertainty measurements on gene expression data provide valuable information for data analysis, and use of this information in the LS-NMF algorithm significantly improves the power of the NMF technique.}, author = {Guoli Wang and Andrew V Kossenkov and Michael F Ochs}, doi = {10.1186/1471-2105-7-175}, file = {:home/renaud/Documents/articles/NMF/LS-NMF A modified non-negative matrix factorization algorithm utilizing uncertainty estimates\_Wang2006.pdf:pdf}, issn = {1471-2105}, journal = {BMC bioinformatics}, keywords = {Algorithms,Automated,Automated: methods,Databases,Genetic,Messenger,Messenger: genetics,Oligonucleotide Array Sequence Analysis,Oligonucleotide Array Sequence Analysis: methods,Oligonucleotide Array Sequence Analysis: statistic,Pattern Recognition,RNA,Uncertainty}, month = {jan}, pages = {175}, pmid = {16569230}, title = {{LS-NMF: a modified non-negative matrix factorization algorithm utilizing uncertainty estimates.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/16569230}, volume = {7}, year = {2006}, } @Article{VanBenthem2004, author = {Mark H. {Van Benthem} and Michael R. Keenan}, doi = {10.1002/cem.889}, file = {:home/renaud/Documents/articles/NMF/Fast algorithm for the solution of large-scale non-negativity-constrained least squares problems\_Benthem2005.pdf:pdf}, issn = {0886-9383}, journal = {Journal of Chemometrics}, keywords = {als,mcr,nnls,non-negativity}, month = {oct}, number = {10}, pages = {441--450}, title = {{Fast algorithm for the solution of large-scale non-negativity-constrained least squares problems}}, url = {http://doi.wiley.com/10.1002/cem.889}, volume = {18}, year = {2004}, } @Article{Golub1999, abstract = {Although cancer classification has improved over the past 30 years, there has been no general approach for identifying new cancer classes (class discovery) or for assigning tumors to known classes (class prediction). Here, a generic approach to cancer classification based on gene expression monitoring by DNA microarrays is described and applied to human acute leukemias as a test case. A class discovery procedure automatically discovered the distinction between acute myeloid leukemia (AML) and acute lymphoblastic leukemia (ALL) without previous knowledge of these classes. An automatically derived class predictor was able to determine the class of new leukemia cases. The results demonstrate the feasibility of cancer classification based solely on gene expression monitoring and suggest a general strategy for discovering and predicting cancer classes for other types of cancer, independent of previous biological knowledge.}, author = {T R Golub and D K Slonim and P Tamayo and C Huard and M Gaasenbeek and J P Mesirov and H Coller and M L Loh and J R Downing and M a Caligiuri and C D Bloomfield and E S Lander}, file = {:home/renaud/Documents/articles/microarray/Molecular Classification of Cancer$\backslash$: Class Discovery and Class Prediction by Gene Expression\_Golub1999.pdf:pdf}, issn = {0036-8075}, journal = {Science (New York, N.Y.)}, keywords = {Acute Disease,Antineoplastic Combined Chemotherapy Protocols,Antineoplastic Combined Chemotherapy Protocols: th,Cell Adhesion,Cell Adhesion: genetics,Cell Cycle,Cell Cycle: genetics,Gene Expression Profiling,Homeodomain Proteins,Homeodomain Proteins: genetics,Humans,Leukemia, Myeloid,Leukemia, Myeloid: classification,Leukemia, Myeloid: drug therapy,Leukemia, Myeloid: genetics,Neoplasm Proteins,Neoplasm Proteins: genetics,Neoplasms,Neoplasms: classification,Neoplasms: genetics,Oligonucleotide Array Sequence Analysis,Oncogenes,Precursor Cell Lymphoblastic Leukemia-Lymphoma,Precursor Cell Lymphoblastic Leukemia-Lymphoma: cl,Precursor Cell Lymphoblastic Leukemia-Lymphoma: dr,Precursor Cell Lymphoblastic Leukemia-Lymphoma: ge,Predictive Value of Tests,Reproducibility of Results,Treatment Outcome}, month = {oct}, number = {5439}, pages = {531--7}, pmid = {10521349}, title = {{Molecular classification of cancer: class discovery and class prediction by gene expression monitoring.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/10521349}, volume = {286}, year = {1999}, } @Article{Cichocki2008, author = {Andrzej Cichocki and Rafal Zdunek and Shun-ichi Amari}, file = {:home/renaud/Documents/articles/NMF/Nonnegative Matrix and Tensor Factorization\_Cichocki2008.pdf:pdf}, journal = {IEEE Signal Processing Magazine}, pages = {142--145}, title = {{Nonnegative matrix and tensor factorization}}, volume = {25}, year = {2008}, } @article{Berry2007, author = {Berry, M.W. and Browne, M and Langville, Amy N. and Pauca, V.P. and Plemmons, R.J.}, file = {:home/renaud/Documents/articles/NMF/Algorithms and Applications for Approximate Nonnegative Matrix Factorization\_Berry2006.pdf:pdf}, journal = {Computational Statistics \& Data Analysis}, number = {1}, pages = {155--173}, publisher = {Elsevier}, title = {{Algorithms and applications for approximate nonnegative matrix factorization}}, url = {http://www.sciencedirect.com/science/article/pii/S0167947306004191}, volume = {52}, year = {2007} } @article{Chu2004, author = {Chu, M and Diele, F and Plemmons, R and Ragni, S}, file = {:home/renaud/Documents/articles/NMF/Optimality, computation, and interpretations of nonnegative matrix factorizations\_Chu2004.pdf:pdf}, journal = {SIAM Journal on Matrix Analysis}, keywords = {ellipsoid method,gradient method,kuhn-,least squares,linear model,mass balance,newton method,nonnegative matrix factorization,quadratic programming,reduced quadratic model,tucker condition}, pages = {4--8030}, publisher = {Citeseer}, title = {{Optimality, computation, and interpretation of nonnegative matrix factorizations}}, url = {http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.61.5758}, year = {2004} } @article{Gao2005, abstract = {MOTIVATION: Identifying different cancer classes or subclasses with similar morphological appearances presents a challenging problem and has important implication in cancer diagnosis and treatment. Clustering based on gene-expression data has been shown to be a powerful method in cancer class discovery. Non-negative matrix factorization is one such method and was shown to be advantageous over other clustering techniques, such as hierarchical clustering or self-organizing maps. In this paper, we investigate the benefit of explicitly enforcing sparseness in the factorization process. RESULTS: We report an improved unsupervised method for cancer classification by the use of gene-expression profile via sparse non-negative matrix factorization. We demonstrate the improvement by direct comparison with classic non-negative matrix factorization on the three well-studied datasets. In addition, we illustrate how to identify a small subset of co-expressed genes that may be directly involved in cancer.}, author = {Gao, Yuan and Church, George}, doi = {10.1093/bioinformatics/bti653}, file = {:home/renaud/Documents/articles/NMF/Improving molecular cancer class discovery through sparse non-negative matrix factorization\_Gao2005.pdf:pdf}, issn = {1367-4803}, journal = {Bioinformatics (Oxford, England)}, keywords = {Algorithms,Biological,Biological: classification,Biological: metabolism,Computer-Assisted,Computer-Assisted: methods,Diagnosis,Factor Analysis,Gene Expression Profiling,Gene Expression Profiling: methods,Humans,Neoplasm Proteins,Neoplasm Proteins: classification,Neoplasm Proteins: metabolism,Neoplasms,Neoplasms: classification,Neoplasms: diagnosis,Neoplasms: metabolism,Oligonucleotide Array Sequence Analysis,Oligonucleotide Array Sequence Analysis: methods,Reproducibility of Results,Sensitivity and Specificity,Statistical,Tumor Markers}, month = nov, number = {21}, pages = {3970--5}, pmid = {16244221}, title = {{Improving molecular cancer class discovery through sparse non-negative matrix factorization.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/16244221}, volume = {21}, year = {2005} } @article{Roux2008, author = {Roux, Jonathan Le and de Cheveign\'{e}, Alain}, file = {:home/renaud/Documents/articles/NMF/Adaptive Template Matching with Shift-Invariant Semi-NMF\_Le Roux2008.pdf:pdf}, journal = {Science And Technology}, title = {{Adaptive template matching with shift-invariant semi-NMF}}, url = {http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.143.6846&rep=rep1&type=pdf}, year = {2008} } @article{Ding2010, abstract = {We present several new variations on the theme of nonnegative matrix factorization (NMF). Considering factorizations of the form X=FG(T), we focus on algorithms in which G is restricted to containing nonnegative entries, but allowing the data matrix X to have mixed signs, thus extending the applicable range of NMF methods. We also consider algorithms in which the basis vectors of F are constrained to be convex combinations of the data points. This is used for a kernel extension of NMF. We provide algorithms for computing these new factorizations and we provide supporting theoretical analysis. We also analyze the relationships between our algorithms and clustering algorithms, and consider the implications for sparseness of solutions. Finally, we present experimental results that explore the properties of these new methods.}, author = {Ding, Chris and Li, Tao and Jordan, Michael I}, doi = {10.1109/TPAMI.2008.277}, file = {:home/renaud/Documents/articles/NMF/Convex and Semi-Nonnegative Matrix Factorization\_Ding2009.pdf:pdf}, issn = {1939-3539}, journal = {IEEE transactions on pattern analysis and machine intelligence}, month = jan, number = {1}, pages = {45--55}, pmid = {19926898}, title = {{Convex and semi-nonnegative matrix factorizations.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/19926898}, volume = {32}, year = {2010} }NMF/inst/doc/0000755000176200001440000000000013620502674012421 5ustar liggesusersNMF/inst/doc/NMF-vignette.pdf0000644000176200001440000165261613710766662015411 0ustar liggesusers%PDF-1.5 % 176 0 obj << /Length 202 /Filter /FlateDecode >> stream xڕP @ +Cl=GAqQCmҡUOON !{ /jUx%WXq,ČcVQl <28X K/lt54Zi{oУ `0D%PTMWӥqQW|,J;#Isgn/>pSd$A5ɘU?nMsq.X endstream endobj 194 0 obj << /Length 2860 /Filter /FlateDecode >> stream xZے}߯@)/`0લN;ɋ,1$aFڊlW%Ubs}gWW;_\]?#(tnLc!eĩA87ye bog1l]}{΍c!WHN~kN*86KixYJ\ێ @ ߎ)4ܓIxA*LP,U> X'R+ cff2OإR @Yx*iler/;!7}NCN~oNKHw^9c[?.%q&?Le aHD~<y$hrk!qb%* ΣD,P%a !#+ 0*&ḯ5mLXƞC-N KWd{/Ww,J"m󃾣}e L*%MB֦.74H3o_|B}7}ӖYۉLsHU\{iy!dBtIEdʋ# hܮ(v֛9J2Mza}S&r肅l5'iu 7X鱯j&z :ZROw%[bCV$0KEpځ?1N*K#!K)p-2l3>ܰF>8W}je p<#D\'y38ѳjN=?-W9mDLMvyae&\NCIᾴ0~~tp^oݜ/{t4EfH}3G3omFT-0v󧽿pڙyn{6[!?$&Y 7Tr~,ꝦOs)f#DTZ9Fw 9'FyԐ ;n Er(9~ v$P-G!e#%uCk[&;Ӓؾm;SPoNGh$N) q-&9x1} weeOܣ(ƾ1G 2w<l.7VŒm -1_Nf:¯.ȡkI:BU: Z;cv|$?J|D (PVXjO/-Gk=N"AQA _^lq?eTj1.@ D^@eZ'u VUF])d@ +4\peO*b֊9K#Cz20<"$F8;DU8-AMj5weRPH/mJsYm2Kd Al@]?sn)EI`$aHۈ5KR'ig |!?&ws̻$E<̾LD*58Xm&*HSYMN=k.yjzw׸SkH p24/#8Cz GZ}hC*&/~C6ojc"Hb!J2E,qo.Y?ջl&.<Q LOb`u: dTyZLѸdq(I͔ .Q6\b2mL/ k#]02sh]^d襢9ojXb>-Ibj^j=:/cvxV3!8&vL&;L)$[XXtӬu|bCB~%'2`<= !$;>,\@i6ߙNՊ2i> Tf3IkM&sf>z>2l]1vYs6=!tPݲ$[ûU Ф; <% 77?G>كGBS)0{G(OjPEy$ϕ`h5, endstream endobj 5 0 obj << /Type /ObjStm /N 100 /First 810 /Length 1745 /Filter /FlateDecode >> stream xڽXnH}Wc`ofY88 d0 Ezx8{qL%, ITթnH%'$FtKH pI4)_:S?UHCLy&!H& mE;2V J9Nb B!QdM+'đsx#+)Ն:3dSi=B"%tI ؄ňgcȥq^ OiuO Li KL\( 3&hPD$M*gR3M J*okso*c9p/r P9U@N5~cB&$Ȇ@1لEn])LT " q]W JdIi1f$ 98?*!JR #@T N107q(Q$QyHQ2%ByH-![沒XP"0kW(E"9ů5/I򮨫H>=|,ŸOv[7Y![ZNh֯uUUg]SiwuS7c A9)WSVFuUtEV6Q¢VuI6 gz~J١>ؑ,fU$Wn`^t@7#y~ʳ65FϲjQ>&~TҘVT&Ų+Rud>}ں7; Edkۮ^ZbjK=|ڼ>vyV(1aA鞰m[U>MVVlud,CIyI u|vǔ Y~MW Z12}W]Tl}$X]ӝ~F/RjT<%CG>O&g6ч{5ې$ql&붵W@dՇio݅pYt 6a| )n4+~0=2FϽ: eÿk7= Ģh!kџA)xE8OZ-Vհh잼ƯE('xoٓ=wm,>r ۩  8'AU__Î 9l<-fiHodREjjhf)#.ཷi|:z {+oʫ*>6c2v8830E0ؙ+ %xDo'栍n%L,ˣŞ. _k"'ur EÔpGx24ܫ endstream endobj 259 0 obj << /Length 1484 /Filter /FlateDecode >> stream xKsHဖy*'[Thy8N>03Q6*Kb{YWg;A Zw3+,ģĺZ,uK.g@|۳^L gLMm?K}l[ysm{p \ "T5Bv9ٟBp!dxXV9/63dO%mHc Pm|D 7aǏ, Wzq.A4W\*- ~~्ۤ7~I2 &8+^>s\v|Ņ?U^g2>|QȼJՋ7p$By>EF<4n?.d=Q\|X;ƥT+T+dVo=qYxt 0WVJx~($K5"j+=KzsA|@~}<ú'Fފ,UxZg۟r=jedd:26Rn7gkŵGjI)T$ȸ"o0 &ttpnjwm}(^J金 tPuoDG#:Ўppx%P;&<0Y^|lkuXQ Ξ*RJz$WܑfUu1eK Lfj7/ pJ w }nTiGgkv`5*}Jfe9[v&# ]:iuga09 ƥIWW֜ufX՞xӅPjes5 evO\`F2V϶CGBIg`d~6'Ֆ򡑻_;(tн endstream endobj 267 0 obj << /Length 3208 /Filter /FlateDecode >> stream xZYs~ׯ`M,`UR#d* ò俧{hM^$n ZWvuKFWKY*,X]VBE5#A~(o<Byj̴N e5A&;ZJTRi"IȒTuu(,m_4ql5M.73Pl;S?Fz}F% A?|THQ29ffK! M7oĴ v ^j+}WjUMGL6`L2RV5-`YQvN߽U)KnU6 y>o1'}&MVv Ϻwf¬kS'Arn'{,H,ȓN *7gj嫗6c7M\H2 P*(+M[􇪻<^ I7羽%|W{}_!t li {plki'! K9MǮfgt] !pH,GԶ[&ShA81$5Ώ/6Zx ǦjީCpD2˾,Ho5 mWTY=V7߀Mfd>*BhkTE=qe.u jA56hUBD{Bo'zV_ZIMm7蘒*kJL7\Nk[ZL34V yBɻ _|!gs> Aw0\i|{;4ۦzt3/Z2FZ˧j P٣:b>S=1 Ih`a"A) #j (qt*?iJlC nD&/%ufx@fjFJCP 3K۪xDtb1]>u{oX(#67 ;] 1J7X᥾Q*HhJ\n~hQMBunؚ'NE1O%]p81U^8x (Sݘs!%:*A ҧfÄ$sEcEϻ(w+ZkeDIlQ1c͢ V @T缅dF7,g48>s1J^̼"%ܾ>Pqk3O A&h %1=( Tl.uK1/DK}x֐m#c_4;{6}rbM/q[P@B EɩXiIV(1tتXõ@m endstream endobj 280 0 obj << /Length 2977 /Filter /FlateDecode >> stream x]۶~LM$H&igN.Nte D*$u˯.vA<ݍԉ~/[{YO_"ȓ$V^zi$P%{@BDdmv/2/r)|+" dKb6/絮tSlր΄=R_쾩[$lzڣo`.9v`h:ة36bV_̢g ]j2Y׃JK1nt=b A XAABOy*3+ sW1A9(s DR " (0ZY7# 9-wcF/q|p(Qzj;Q4% Qz1/D# pyu Di0uBk[fsKn͒qnDdc ܊94ՑR+(c/ؑ.ϡK`6yޥYŻ|H-,*@nhV\;3 @@2`ԭG-Zčߢ"0u5-nq+4 IDȲ>R=ޒ \%`ebA`n}m Aqb՚ B)BET7m]4 \*O@ JaR_-FtQhXFɱzN{2!N(;DYE֢X]#ONpCVx\s.ȑZq ִt>j 3  ԈK˰$IE D)v_74e.QciF -b^$iIh֘n#*1{& 4F f!Hڇp,Lƭ/|i]ߵy4:Zs`cHo4nM KIIBklyn՟Z)iAډe?=ēHY.Xʧ;H )cia )Np#&rS4E Ǥ$ Vo޷e{.E:4:ź0U/ul6L,Fr$z( 1YlilEqL , dx48G CZ{A~su+4!SE9$44sLjLُEq10ILt%5 m  H8W. 29Yo"Mq*S|L$Lt 's >HvjBwZ"ù@J&M=Z4Jw 0=8VkKte#~^Z|m@t6Z85 toף a[Xz<:`s|%zf,wuG9}skpsYrQ_31R /K/=DgS{U SqƋlb_׋_tAO_nesWo㲎}Nke9;3`zD xNaz{ݳXnd]}W}_tDxg,%95ow7h-(7YpY3d}u>~.z?rYWn7M5Lx\д.$cj| w ڪ3%w2Q#w3J O05i IT\10B`":f7ZSHBnu:/ [a2e_x-!T33!JV=Eq?9 _$]G%9Rޅu1(8fmX`O`+'x4U-InpŐ5I(jn.uztKݚF3$49VJjJW8\c`s-Ef6N1 1Ũ8ڴL79Y04rplќ6@#+<; crGە#i~K|*08o}"#M{ gdr6նz(D7l2L,&94l޵)Zl95۶xͨOM%u-a{{ʼ vwy#a"h#_jMܬ-Ho 9 n_"/bEp%țÙ[M[xĉL Oq/I (%kF yvddUKoOىA^͸-Lm?; endstream endobj 288 0 obj << /Length 2915 /Filter /FlateDecode >> stream xZs6~_==S 0355i'ڙK@K͘TJ 2)r䴙{1,vfW;?{mP<j_Ng&Բ`+b8(.H+ؖ EBwHv~IăԄ6^ƒp U9%9䧅S,LchԟW qW~^gLkhdح dl@0L(9K5-7 gt2,iG< {4Ĺw>b F鿢p=h„.5lUG4f֭־kx<7<(+#]To)XQt5sKC_ms8Lr/mhoW2*oh'߇ae{ׄ-+np02 `*H`!W#Q*W:.@#֬۔m頫uX# iyͷe= o`5@]'0-x'/:+ˋ)" `ECwƗ^b(FꜼOiO7}O὿.06zcLݦp{Z^\61 3(pRl +<7n֬%Zv71Z˧Tx)Պ[ -тS_)VUSG^6Ṿ<)w$FE6>@2#Cgj[-(0LﶘP͍̆hĈCtnYcXa[E=uNWR|H` bu߶{;>M$fj(#h5,OjHeׇ߶eBko @5l5Vo_c44KoB4yΧ=Sx[֞Wߩ@Ẻ!`?C)ekZx2I<ݏ' @ߍ,˥ P=Gi1uw"$ l W.VEZͫ{^wXC>@nSsMLU3ȵe]HE$%l[Cr%I6!mQD Ms@2PK <ʅR7P?J\_TahlkdO+•x>@cW18sU XExQ^CpG<ǂs lAXLYܳGrю֕Oe?w_Uw"ExY:riVd@ TMxG߫AlIj{D*Dl| f|ِSګ,4^z/oN;j.,Rux4.w;bMems bVu?"0f0EөC1r<dyA4!A"w֎x^@5DHrMmAi? F8{ >*T_owa.@t/B0ǔ}1e|Ge[΁6$.9)f$sLz ^}`.n*yTg`yn B=fS TRձN&&yBlxJ!`N9~r` (xP/_naSI&"Y?@kH8_ endstream endobj 307 0 obj << /Length 3041 /Filter /FlateDecode >> stream xZݓ۶_<?@7LlǩI:]Dݱ+I?bAx=X`[PayQ_O]%8v>A#Hb''=@J/ZĩGDib<)#]A[yN5"A8h>C-4ɑ]$۫ƯfnHyYϗ2H*ghϐ~jCVC?4[U]mvvzy]Ȭh d'*kZ0vDzv3F:[f"K^W5qæċcmxi8Ktd]#$]ЁDP"amL+l,icOOB&Ra!GX5E['-Y[gFvVۆМWYv /^ XSY?:y-ڪnĴC|Љ_q R:$y#~2 Cym;g2]nŸ́3Yoi* `Wndje bXM( _pUUsc 㜰l G3DiU*rtl# mM)!'inZi{l͢X) ~ @sz[i){6JLyL\ʡ+%T;Ÿ8oeU!lt2c~Cy=ID<:2Bf|7*#ˀso1ڬ[&aaKk>N4.fF ZFzB0)U a4l48l7d%m, .$}ύcU9NC؅wt=OúirQyIIkl ʩZRN=?]H/ʽ)nl=WE(Fm#r0TRŀ{\+R P`P J+E)$?W3-e4p[HKbֿ@ρ2ǥL,rCzL`B٠p8 څ&:zAPBZ8X26 eE.]aXs"JϪr)GdeX).?IUub5ԅ:-J{u!2W:AYDB~I>x{ Ta ob 'C aiCO`j>pM~&~aQYY/1`'$eplmDD{7.vj,Pr L.x]enҪ]JZY͓iZx`''=PD`T4 ?ks)F2*02Rd'!HHLV 2k|4#bxh?vP N9,,/Kg~$OnXg]b"*"Q BOLD/;$}pHs; =UcBzv+D@ߩwFл{9N+r=eCs"/:`Ӈ!T£I$?M@$i/>+H$SfXձd"7JD;:``Zlq<=VEI ,_eobށbXhK"Kˇns3|E(E3'>U0O, ¥ &V#` ۱7R;AFdP]}͖_Ňq{ۈ;9TN[wքBY'SG"qҺO|jW\'BLydlM1h~cScI;GDp2,% ݥn=dl]E7-.XuMɯsݳ4ǧ%:4^"67^"m(cyh +BzUoivKk9 Mm$c6Zhe__VїshNH$toIדJAn/۬X7ܻQ]kXXF{ONSg"cnE]CfCT~m`PG_~7-$9v;| J(=]%.RD54JǰN > stream xZ_6Oq"O׌(usyh2Igޝ{Jh&Hr@Pdk{J6o>7N:) ;BvԻWbM!igoK$2Ѩ5ĠZI[y\mI9iLk=yP-]sn8z#4ިRyNJ]UMz Xl `i, {7cY meȐ@MnKӬh \{WF{(ߋBgly }25%- dž6ls\È~<3S\kՌpƽ3)H3ݗ}B8,0../n8 //MC#tM ;Mũ]ǑIm<,v GG(KZ("Է=tztȡO[<m> "w1j&pu!IGrvc1#N #w/k/r ,}:g,p@qɻȆLts8-ߐa-kUm1.X "yObuV-2sˋం +YJ <T?ͧY9%w(B?xOKs  wpzb<.R?Ns 6{B$S"#oHړ7/2Ʒ yRG9!cg j_ff]c~'4=+Ս($hUdzvJhKz65\o3KMyC-S.3Z]/5p>>"Ks,LSrS0|Oz#7Nq;(< Ć6~Wdp7$}ky乘 Pb(sHjKu>e\Ȳ!ȪL)QB:YV$.R>KSI6jg^ \dK4Qz{ºnwk5D -B~Kjp2GJ#U lܴzBZC[Wd~If54áa(W ĩ48-.PKae~0>'vMaD ؑ!kSo DE 2hlk\_"81 88O=O e݀kheaWRڔa7U, 0@c]01=+` ,ؽTYڳ;" yO '#!ڴ{\Sx$V?YVotM@;7h8hiy~.("svY۝cyTkZU:82.@ 34JGOt*mmA2X$?,;ch4?`vA3GT #F8H$ljP#'ڝZ[@ m i?nw;.d OXs3U- n pT g`2Ͽ;`Ie葂v}1)1RCgB'o$Ԇ1ܮW}"z2){ޕb ߁ w]CYzd[Cڅe g(t?dotW>ԯxwqMgϲ&ljC'~QM~=_*}qRqDp2s/qd f>J|y'ǔ__b:P/j%X4ĥI //l8/0 e`~88iO&¢W 2?TqbfeNޭX-+w_k ݇Aj3}V{=uzwCsR@?}nc ./z"1ƨ ONTO;*iDO7TTpL:sR8S?ɻ$}?q9Nd-:,/2/VWj endstream endobj 324 0 obj << /Length 2262 /Filter /FlateDecode >> stream xZs6_io&4F~dⴝ6CJ$^(R%8nbAJ;G9N ,bB*`Q/}x$F'=!Kz77?4.-a}hɯ'O.N%^HDd>k77?<اÏJr$, bjZnJ5%Kɩ&稘ca)C_Xip`i5q pt[僤50"B7EB g(͋z\fNM~]XkxQSRbH?`q_>n׋^g̽4 8lܫ\kv+hVզoFUֺֽġMUIA3պi Mm -%XVuެֆYvL F4 WhJ }5ܿo*zF;56``|;@Q :T5a2݂}N ! x`<ͧ~OmPZl` #8,6y4 0LFp!n/Ash8Gf CȎݑ-\ʸbm^픨05ݗkȤ `3(hMSgq/ryak6Lt|̾|љ"Dg6;Xe퐐DL;ɖ'= Ks`McU[m. &S=Ly`?@v:f[c] ndio2_f(rY"N*xZX6&xq~gEJt ϳ ϣ$=WY<X3B]sT0 \V;jIwqEa| a F]2q!UVI_Ɓ\¸NU E*bJ>rZs/ߪjWPfȊ2#b=( QvkBL|[b@ZUWr MjӐ(.u z5׃40mUJ\:r˼- \:,b,RV Q֚PI㠑CMS2GZ3@:WQ0-,b9?s wD 4Ý4eg5n,mb҇!;$`~Fѡ!٫m^Z8}$Q yx"& F7p~y-> x1.Dpճ!tՠ2_]Nk]66<Zk~62j#6*;=QQرycl?5TاO_zG.\BTl)-uXڢ֣6+ y&QG}Ds3p]VdxOzu/elPeR]* T !ؖ1 U_ؽgے*7{A;އLR MnH:> stream xZKϯ>M$="[@ ZHaۊ73,|}4;N'=bՋXBrTѰֈZuQ-.V#FW\⤄E.E|:E pfT,bN։.Mya$/"%b:Q{R`$0Ԃ8H ?JހQ!G;AQN @T)SNu*=/s^.\+xav,T0NNS+$gs&N-WSrްS^@N -&OQ/dtL.C˩U-.3?}jP/&H bBMXrV$БK] 8ޖ)gWȱ% Dl+b/1&3~()`0qڊJu5ٵ_ *jM`b* mwU ¸9vȎ!V :!- c dFRpj=, J2 aEa 呲MGt"26j(v'';4Y_. +zuVFaiKeVj{ZtW ޟ?]rgưT#po@'Ϝxc,֖uZj-K^jR=jѣF=jѣF=jF=nF=nF=iѓFO=iѓg;yѝ^ym=]o-7N7Ylo/R}0 bVx'4aȓݧ~zO\땏,DÈg-'ٛ_-q^ h }fEYD(i*B/ Ef%[7; pX;آ$ҌT3),aA3xDP (@+4&tTÒiA*B@ANvQXMñf/&2f`#M3uP `/F<q!-OBڔ' g4kSX$uNAչZ-qmyrݢ -f4>bE -FPSw@H@h!Odh<3`Tp+NgDNm Rq &24CÈ#,0eJfqBüV1) B+3'!GfQ-ɎRr"m։ ͠ZPݲnfP-B T&230 kwA(OF  lNi F Td\t0dIvz2I4:G2N Qr[3OMD8 ™#!K^ѳx"5؉63Ȱ>k;O$}G,NBQ֓2ٱNj'pb9Df 3b>rDHͭ&Gefp }^e0ʦHVCFZJh8goǂxuFC!8U蠂 X-tDB(QIOM"cɎD^ dU?wgVqP0V9fdoM>weh~1ʈSlru0<6KuҞf{ @[ate,.0 :BW8VB4J{˒Vv򞲦}!jc@݊kr>&@vy" (4S{DER/}}~bELN)@JhE~o={< .*pXZe 9{z閻T%IJ!l#wwR(Úz%{^($|og E.HE.rLEAX20ޤu5< (GK_P}22 91%L|L>ԦnMt/V܇p+Hh.k

۝&T6CQ>@v<9R}AƢ()}='''ݓ>Nzio~n?\.6+Çכ_:7O?_m_>{ x+0J<>&|ou"Y15, ?ջzs_lWfyz`p,#ﻔtzH> stream xZKs6Wp 5xz2M҇N7J$T7eeGwq0 ps(Ti@p( $e <|`a&y]:ꍞ4YoGj8Qp\qH : bq ƱMeUhIq?^$kK__ T`: '6/ʚy1hm:nůxjl13C"3$G 2cRdӸM )G*"?29c-SQXz7NZHb*vGP*W.5kP{W/{1eH~x0" 18iQ8ɓvޱRGd&ZiȄL@e^橷dlY6slZ$5ԍ[9TYй,bϖYwCX&Mb({lIl\۪3,x`n8^-o8B%&$0"Iqd#P,U@B:"k}rALS2rX{}gյcQvNhu۽wIw!4MnazNY_%/>7u9f8};h7̄_;@;^TFg_~IP}t Fg?+"Q70 q{a|-W@FݷQ/wvֽ^ kr>|8ocLs<)籑AzY/K'rrU(S4Kfox]᝕U`[Iq3uMvWTvcǶAZ[^vC9#4jI(V> stream x[[o6~ϯU%azـXiNʒKJI_CJe'mZ7Ex9$bŇ:[ǻWM<IE-Z!N(hxKbPꉀ/: ƉGÈ(I(@X@xW$yD|>>x /AS/ 27xO#rM$>YY4WXLJjeO^m#'GX-l]{3ٸ.Fhb"'OO< U?$'X,$ l40ߞw8ȦD!iPFYt-z&lO2UY?vs64q H F*>$LiYIiԠ0Vy-afPޡL" p$ IH,/7IˋLu*yʦNEk5љ0 _5!4lIט$p 9cPߨ~zTE#sf>oζDt&Vn2'rث"it.햶g>y, FXS숌S7" heAg2WEĿ,̵̴֮{E5 6&YHr_=z1 )0.lgblQm9ǾT$e!EuX#en;jZYV78fyXuuD>T|B`F:Kc-EF 7.\We.zߗ<2ƌPˢ97%u(S1<2;(vx۪h@LX7d?S}1^MV+*MImrSAM5l括Z41 'C Dp.PS[(6IH#rDSXnK T颶D=/jH&mb#rF ` `=0wHDwȢ0$߮v!GJڔnWHr9v3y i{w{ysfWA0yܒn`ALuow6؃hJJA- !Z|݂[]:!췢kg@,tPzMvfF]Ùes*WkK*bh5'a`" ʯaJpmS& 9E]a}jP)Ǭ$䰛Ԏ9Z?kP[$ N/d=5^$”0XzZ( ytK]4V Xt @5 ru'28fyχahnM;xB# ,#pt2 endstream endobj 339 0 obj << /Length 2157 /Filter /FlateDecode >> stream xZ[sۺ~}:B\x<43i{3恢 Rt@2뻋i=Rn3XbŷT\Nۮdz.ʧ}(ƿwN|3{Q xҕHX'^X(ho{'iF +`a7J"sy)w96>YBMntKu^Qm3V8欺.ufOV=16CS}NN;x+Y;N ` =I?D=oh7T^W/=hz'UauOfq&A9JF-r 3\gѯMM*kwӠk,o^}/*EЦ*NL䵡*'.p +פg;K[`$Vi[Cpr/vTwn 0N975 žxyfӰ#ƒ 1x?]$T_< Ocrc3]A3P&,QSG} t?gg;'MH4mQZ<Ϩ,%Ǒ h0 Fͱñ_',Fly4X.c6O<bXk55ϙ d[?3?36n  /GO-oMWU9܁ख़{D"?!^߳i ]~ٯz_4G$Kyr-1ص=(&qLɨHCˢU?eGͥ:0+W&TD-_Cr0\i ppJp9P}n[w/6sQ-NGJڢ#c )> }BBJV^֦h;)#2 η]Q+D4ƸL[txb1S#"2yr$шhj%"7]/ u}r=~5NCJmqJHQhFЄ&RSQ4=n[qˑjM!HwrT>W8\@PBluqc+t;x[! 4m벬q g&ptL`Bnwh_%a F(ߢ*/ (OJxJⷩ$;\RYt$T8qWQZ_A1([) C=6 dQ}o!}Wt,bmn`xCU ZC]F>^]5 Y6Cc 5`QFY "c^{P40Lrr a F9X˱Vg`V^VbcfH-6Rwګ¸ YRٴYAl)5(=l8s4̵!/SXMN ZTlW+\Bku׎>!lqCTAIJ":뼦NhVk%)=q&_m!q^8/yt|z=[Cc'ƛf! )n~n}漅+{3O,Na/NfN3pg"} ) endstream endobj 344 0 obj << /Length 1761 /Filter /FlateDecode >> stream xZ_o6ϧ(m%ņ6RemNʒ'-wGRlniA$xݑ;gQ"dϖuzfHAiyW7g&4`Ae,P," !]* Am7fhoEA %IR6%L@m| > #gWxRdy a()$I ESn4HDX BpWhmAUL'a՞GNjz}Ex)#2@1+@P7\÷UV4vT)l ꇣIJXvɡ;%,fXU$J m[J+V s%V2PXƎwdy^c=5LgRUJxvDr?yWYħvNۥ]!Z6ژص-/n% Nb<N<_&[@vC  ,d;CHE>L:om‚Y6]*+,VL཭N'|%8ejnBtG+I7몘>۱'Z׋ւOEeA~!:J w jޝr7n Rܜn-{7 ,Ys?wZW&i`P6/ѪUS9t7f/q7z:+Cd o2H䀑Dy]W?z@.% /`Jhu3XdU'DhJiBDGpx]J tsD({S="x0>03r>[2/\/,~~FAj+o)6rF{pFӎ+ݯhX#F_!EAw?6lVƘq֙99~fTR]KCL%eGQi6#NZ#u*"X~ު8oZ]f '::ݟOeUrxq&]XiK^݃t7רao[mW| 'M "?o8kJsѴMgo"cEQ`QVxdpM;8? e޴Ki+fB{8=w΍<$]6 OfO/V b]!k> /Npqj:wvۖU%kkBs寲0t%YӁ]TJc^-ܱ ӎ{}>;{֣ L019"vw_y@0Ø,\PF/|_P!#FIz4q"n (@q s4vLs:@ɪl1: n>@^W&UpiID.]~v*?\_Pu}6/߁7cH.\^0 qKJXzq uiJ-{Ƙ #@3:VEòrutL&0.D/{`^𛺞 ;=Ӫ6dyr #v&.~3t8r endstream endobj 349 0 obj << /Length 1837 /Filter /FlateDecode >> stream xYK6ϯ`MR#x1*]qX)( ˇLLo79#)ot,(xqsbEEA8bm't2TFAWT7)OEhа6KVu Ufn'-Q0)iDֵ򮺝LdaZayl;_qj{P*R(K@ԋJyF(j2Sr'<Ŏwxv' j8%iNPK$#9 7 p_L-}*Mۨs@˛}.r2E֔<;Ky(+*H<(N˘Ma1%aaf倫пDTxnL nx+ m## ,Pr ,Çlx ȭc\(7׷z0;Нl!S,3vL%"B L>iLRz +TPc:*t8Coѯ صzQ\/tQ~hKU]kT|;~n,=W']GMopu>ו>(x OFcwgW^yHD_DUBMiHHʗ9U|\E,QrE\eV<}wTyM7(8w<~r<+6UCtHK^|݊ouRɱB4NQn&^\"s&y' 1IVૡVQyNL}״5Z =ו1 u;I-ьr]Y渠9.e8ߺb~2T*t38l mF=i*0?KՠW)!> stream xko6-`1CqspCqhArh/7áGqg%E8% k/OB}==y&4ӱbZ)/qMgjHnaysfu隚uEOa̰Ibb/h Zo^jj輦v+V7]Me3]a~Q**iK,3G5ؗP~UzIenKd Uo,> E?]uc\$ Thi:Wk *)(W ZSoVōՀ[_*RL6%u䛾m]% h86ZfI$A„ʚ,M ?UncPGu]DmqPC%ʷl[B䆇֭#AFD5z#Qx*AxhΛIYvثm0Z&7|؁i!6Z;V&-aZ Hk*YEYrΕ k`I뚖2x(#{0`#@ 3}2"4!%9-.Ngӓ_OP=X!_IzJL''$(!7?p'jCbc5ĢPw`n'`yn¸0;&9^s$2jH.p}4*5xֶ@4E}ۆg&(82A$"tc:˫{Mك-_@`zG,\TQ!#&a"n r\;@A?V`DX/y꣫mpaĹvKWځmL>Qir}u̲${p =Z.[ \ͽٻe3ͷr\u{G_}G_ {}6?S|I1b#1b=Wi w\c潣s}{?V .ʻ0> jrp4*Lo_xA 4ۛ Y9.K*P.ꋌ1WRJ*<ϩRF ]h)YB3ԗ&LPt0$OCM7p4qn"2VJEЍ_.C@.l:ʐ37`W=4n=6,f;K`V4; K LP ga"PR^hЃmha`4@y\AX(M} au$,Q)vVp`۶a-ag_ݿ3^RcW/M#L3`Iz$x>5ER=_d5& t0F0[cPFJAoe2pL 5+(a%LCv7O?8+Ln/:47$b}p}x-am514^C $'z]:B W%zȩ}PW7.>L&fzՖk!y1WlyFnnKjlFS(f'wl sU/m0rvleГ\$D0$;M>lM[J5L8@/T:o6pF>s޺4 vJA]C(MzqOa}$mf^7TXf]St[K& eL;qB7Sx|7KkY3D 1똁u:f`}u zofFYۮ.gTh6.{ 5<+j];>W|B죟#fX} =O߽]?>M'0)[!c0.Dy~R/\:wo\Phvcv1COڇ9xsY붫#W Sw9]unzqNpft8ݭ?Wx(&{`4~+4Qnm* uf>\U:n,In+Q&T[ؾ>>i> stream x[_s6/Ҍ?2dirso3Mh xH>bAJ4r],O)7H'Y:>h&-Gr0,&䱹>YUϵ?N/4LYlny^f.~MmfeMT 0y_XjA#T3M<5DOc8^~&2<K"1°c>CeK,ЕOg67]r +b`n:uZΤP&  po%#U)V GexDݫ|MNAIdଘ4gD/w96LwD fQtcy|+}n?&xz+K_<"9f+JwqGnc _%ܽ;|D-؍)^cpGQמW}7# JV8v/?R/9/}DW/-Aǰ/a :}"6x#Z> !,@s-Z2YIk;i*E没2uȀfvQI}/lk?=DdMC2ߴ~JfBX.-ww^D80@h fe@|3Q2T $76FX!8Ve `4^.P8Cq)ml w$ QFHBn* eFzjܛe>wp ;*ǢP8 t۾f>} E< ;s Z =v)1TǖbIÂ%ql;Q1 ӵiz%4$#6񌬈`}~D"]0uzCF_vȑ1}h'tD\U]\]Fva EDݪjA \(tM}]TS755#9/:H%͹Mt|z~i;iCK|pR)<"EлP&ޡco@j$ܞݡ#`j~ ;WRq1 9zPI> c'0,)Pz|#R"ClI *ePAW('$<*˲A@*"ҖMl ѯ}8tQ#|7A ZqW 25 zCUe%])îAcqU|0MZ9"BRc%xA: &Pz`vނb =cѶJY C0E1)df!LKy0OaZt% wD)q~S8=3ĐxNPOR{W1'Þp/T0rHÜ\~ʢ'ew( 7'/~U_)}Xq3$"h)Sv_-tT{Xmq{6Me2-g%:>SH:(IW]qOS ~vr L{@a 29nYHiL=@'`@(ܶS< zÅyZīOLWW*<\i@脢P5(yמ`xy~_" "ic ֋s҅&?;yO?h /+f :"%T+߄\Ci 5ѯrH a1#XS@v}F&\!|!< `ey#V%|>b:Un1ex~K>_o0iօ YWo#@h2g&nH\Bib!S.D>LD1+H2 Y ' n.y։pI dH HNIc.$FI>EXSL[wF-\enM1@Rz,]ѭ" J6AQ^0 Ɍ9/aZ' endstream endobj 389 0 obj << /Length 2987 /Filter /FlateDecode >> stream xZoH¸/z:H*eSز%yH?p$K# 8g8/Ꮴ' ;_g/߆EFrI FO?eQNgJ)^'wŦ!*j%_eRUiӤ,+}q>/ w#>s.iր%lST Re*/W2􊦦 p>)Wj\;â@ҟ(fS"Ґlɬ7JN`Lf2 ÐJ3!>N2l ho^dۦkb/>0 Xڋ'blD /}VD[n8q\xf|b{3 aB"_gRxԗ^J4n,qj,} A(iӞӊQ(Fh{k+H#dwټ(ˈu'H]4g'#@\ǓTĤ߉rYYrg~ᴴ)nt rwo؅FkJ$WΪ|F`aS-y01?-*oQ kDw7o9B֔@+h+[IiE*V BIbK5Flt7&B3Z@d۹_y7(/, [~kRЌ4-CVp8ΎƀEQLw" S5T|y{@o*qRGG/ՄV׃{m[+B=50u~9?,(dDWFbI:Ob۩Y&pKswN2:L8J=i# ETOh=ETI /5CW(:x^W-Jte%:II;< ZiM |Pu8Ljm ə G6Kq1-meƉPDxuBxb.=E,(YՇJ}TUSB,6lәFR"N0("i~k_ 툫yS">&.U \Zk}i,/oi(p;d+޲) prj﷠} )6ځ(c4۰|:wk[ǽ!N:x 8*+u &svšOBRߤ+g=LDB AwCrc0Lp 8.DRC(QΨbE>%r#3\y`/iC^M!@PB#4c CD7-,!Y rлir@l(@p"B͌=<[R4DmtspWw-c;[2.bS`NDd>x[0X2Ż` /nv+fB);%#.l@ ca<:,`#l BS/"WIMC FKM)|mIjVnf8DWajo?߅!!pvWS ?_Og7ͅR䴌zH+EZS Mg`v~r"v'n4*0%'X(1pFޡ$hV:h"qZsj`Zf&vdWD!n\=m op4GGަ EAӑ<;Zwy#L*ڏc@A%w5(4|4pIw[Ѿ>=cNgg' Cw8286b"x3??hxsYqЩAtIf1@pA_dw3 yjJ ~Kق p=Tsl|f- $P|nC/D`[u2Q(閔ԿIuk 4`HOJY]oWxݗpݗ2Y9tYGR̊rz=9BS[Q cz`XAՏq .Kܻ?f*J]LOa\>G~#g2Q& Ft` 8[ endstream endobj 405 0 obj << /Length 3055 /Filter /FlateDecode >> stream x[o6_!(R_r@iZ\q.׫VHZ;nJ+ie1\.5`(rD3R#9w<͑g/O'N"ӕxN+ᅁst~u}5Kxɮ)mUrdeQ~?qϙX8NNצGrS.h{?%W P1QA*@Oy$!00$ M4M~ȓ uK˩De>,;c@mN"-l6 lӺΈ2KWg@`R 8)J?w˜`WkĪ]ܸZX1Vʼe ^~ʮjD vf- KRheIa)2 C$Xj3#w(M |Ot§;?6!@lŵea+mM+Veng\fkE'Y>ljv˩V˘gOD#b[Si7k(5 |}CCX@ a+ / FQH|';Uiaw,flw]y}&`+o:L"z> CD!.H~ MW}t8dGcq\9X7TDp#DGсGq9UC{d]5uGй'"_'"U; |C7Ҽ7TվrџüTScML!W>Ʉh=qtZz@3 "G _ix ֏jHCD5PRNhG Z$R19&jiHyb= CG!b(TV%-JACGW*c!K[!OnkiZny_M#kF ;;7;?Jz|dL#\ <^avymnOFP0FЦlgua:@ h:PhAA*w+>ݼy&^ttWzS%MSXe>mR Pvjx\N;>Zg yfW١J{dYYc7H`F9&-+L%F^2?5?` n[oG#ƺy%TjZl}?:ztqCܻ~7rwnp˒hcCFmgx{R:@fq ΔvPS![A x) !)]-mM.Pmܳ )t}uwҬ] awDY]BWr|Xu;b[7ڢ8 {0!ݠufnMOagyDS&z=sF-6_;MΞ>X7;R&\;%\ۗYVD;c TMB7 iΠ [i[In,7鏐 [!4л<*-Gd~DGd~D"*=l,8.w`s`?F{W w0p?$_*!m?{17rg $Hw54?(mRz$hsY2).]ݘfe(]yB}n.mDjW2Hf|D _s!v&eaaaqfԶd-ՏǯlG=)|ghL5mtSo - 4J.pY-*}0Ȇx.ӾQ;JҺ[S/B}viJ/+N5,nb`>qBsKDt5`HT/jviʰ anw V Jk+0 o?e1:H$UbܲkR?Z% lJ,3uF +. #P3/_%DA^\Ƒaw6K:(Zy=B`jf9> stream x[Ys6~`fj* &xs*yH&df<>lRYH ߧ "Ml$ qmze٫I}-m' bq[mQ-8Pb[܊Bfsnyndad9g kvHyl#6#~(JhV L85YmPZc80r񞲋*chx jګv@BmŜf͌e_h 3jFsnE9qc-~%Q6iJRlyE/F1 To~I"śm3r.?* Om{je廍&k3G +i/;,8,\oe:l>z(628goL(PO)ejqFԦgL6>*.|"&uR3%mԻz fnn;2ƫYd #UZO[f |٘ DXխRIS榬ZRYqIf4mZ B$WPt9Mdcg-kb1ۡ)!?,Wjs ̽@v_VVܸ}V)žN#ka͛TnUd-⋇& v#D@'^zUxb6z{=Hj4/OH^90Pd8:@6 gw@B aTuQ. 4"#; '5ꅤiR!%&L U+Z$z؈< $yHL'CZu+txʒ3,GT\Yz`c9\Y[^؊ׁ(ChGDΗ No Gʹ=}GK`rY?m\c:K;Z$lR19bY*k uZw4/eCSչn82WAEJ/`)w|0vۃa+)Yt /3h$:we߃1:$,ԘX(ueB  iYHD Hp> , m/}RrWS(5ʹU"|3OtNCO XBIu ?],* %)#!䐺0TX=B vy`VJ\a.tc}'K$(RWLu׶U1iy',Uk|@7!$R4~ m5 5RY-f4!lg@{~0d(њB=D謘&:F> stream xZMo7ϯ19,ŪBq@ ,Olmc4WK3Ҵ>H䰫bL+DG Er1XktdbAR3n4UB(hAEYjE8bͨ`@hA(:KIi(U W2`ΨdTVUTet"b4eo89a0lF1t)˂2x"AERb@gcXKBhLXAhM-:Lɚ1gtW cVTUuŌ= k\(Svێ}107 X ]@5gzPNNLqT.@)T9$-E]RʥLP}RrH6Dv9bdB9ш1\/UtQZBDEYRŭ fTM**UM Qx%&66 dIhǥ2@+ թh쫍#bJU,`*%sʆ,Q ҌFO#^q!s L>_^6D3mf }r17k+'+HW.'p|Y5ϧɧ_?r2? x:#V4v? Fw?`venQnί@lz~2Shӱkތ|46U?Gd>̯̰Qz|5<;_mz9~q;5X Mhg3tşM&SxzPS;?>ͦC~:Ѐ9c:~ú%HnU0T, waFд"P:[:i0_1(j0`!Je>XB!aBislR]ʃ"_㿦׆@u}eN(F48*p#NqPாN?Φp!n^ng ]`'x|xs+е8)EsMAF7Y5CŢ#*1[< dحRvQEv_y.mY}!8N;"uEi͟N)C /_k2|7׈/K,svBx'4)?e r]3N#0S=M @r9=U؈56&x OCep"tNn'['+\RY0WOU]F~ޮӉͥңR;ߗ~˲DXʥ`4r L-w(5j?aɇSI><' ")MA k!pOaµh_qS7veNHt>,#SھwٞV3@)'A,[ 2|o9!Э氤~=v+~`k[G'c;ZuG"#/.b<+'=yj]إD^P`zIdk ^EhëPG-(.# 7e ҵ^+bv5dح_^:7 endstream endobj 419 0 obj << /Length 940 /Filter /FlateDecode >> stream xZO0_ai(5Ǵ>&PZ4$$$kISHP\|w>Q B"D$^E5> stream xێ6}BhQT.bF$%JJ v&)!Gm!2CYl0 t00Co&P/nx"`˃qOTמuN{I=,Lm.qɘ(^můbq?pSx~ŕR^q {lMzYEڼyuWw9Khu[l׆V@YOMO1;S^޻FoOa⌧Ȱ(ĮoI8?((?ys"R}Ӛ b;qX Խ~G6&?(b yp)(Gm= O;V/;Shυ/}(G_?,G5/<$!wU!/O GsH3IG$U|~#ڡ%gTɿ/.u_H8Is1e{ܼҥsH;>#V2e z5˘` xGl@e-l.AvehY ثJպ?J&I)a2I ?,W~e0jk1-j".Y@C#Qɔ:ЈTL`jׇ,Eu?K3%_Dw[940zivvEKľ IzBԟQ熀ziZv]|FnE.!S:,4Xٳ]Y~yz؟"{tYT'$Q{G;7YD҇I$L$aoPd$'@^5LKYL VmjgjcNt6F7]vEj@B8c%3D\58K91E$Z؍,YVN{2$ܮm8!eB3K, }׆qHr8%WWx"$DKs,Kxx XuNpA#=.:Pm18>zy[Y <|QW3$W%Yo|F~OE:͒x爯lW 0 r YHn5?80S 'n#ǧ yܳSf3)UQ6P"4P\M\BlY.~aZr8. O+SmW"c`Ʃ$pULtS@82+f5F. եCP=4 q"*" A` ]vNR@Qۤ%'=1$+>HI;mҊ ډ} `bE05,&jgjtU9 #P@&dѕyʬ)0mvclj.JCS;[)O6kCyDT<)TpBWٚ `ƶYk R2 4MFf@oRդ "O%(L u6ķ14dCaD[}io? r~L 2GL sZQDnt5W)8^+sOw:EXEu8pP6r/9jR] " D7ݤ0 &^8lKh2aheE X G?Djz*3 eoE]:( 0ž'8*C)YfX]M{>#`*5G3(*=>9 g)r%⬧⸌%F v=Bڦxuwwv׭C/hq82Utv $y7rige⩀eBg@WEu 7HTR0%`o6tfY^>J 1Cq?9=½[I5=M\B'%;~4.r!>̾bShvG5XSy}.ut' uN_K,AMݠ' eO#$$ endstream endobj 436 0 obj << /Length 2368 /Filter /FlateDecode >> stream xY[s6~ԌA݇tqI}j@S / I=P"%5[{יmg'<898t$tl >, `*V}<* d`HZ ~5 V0=g6#:U& ޟaXDZ}i(Б(㴏Ddx✜&Abd$ʊ,΄52\F2j5Yhki jdQLH\rG:N$Q2R~ YF>n׮=Y f$e{>h4+`̼D4ʄ7TO}>J@bx sW,Qdn<\ n&AX—RVaSH5e-.O&74ծeנQx?5<7Ըe:*b!NDK-M!!mXmAЬ2RͶs6i(s  1^aC6BFaƧ [DŽ.o><_,c÷ۼ*=.AVKlu<wۦsm ia0۵rNNr> r2X}d{*ISPצ]/'FQ$_V'nt$!8OXO 0?kzᱞ4?/J?}@NY3"W}$x'a3-{zW < '*{`V#FB8NG Er/Hlfٱ4w'lXwyřܦFλr]6F=i VsO ’PeVۺ@fTƘjO&V$Z}F2ΓhثkQ;oy~nGRSq5/ܕyЖ#*vn»gxSL{U3 ׅ1/ X Xx-x+rU!4hy>̨f06H96:܂uدN2Vfu{~*`=#{C}F]{5`mSXͥʬK܊;X$RQ׸.к|U<9`0)|c t]o9#df?G5=<eN =v@x)y/>ۢ+Bwm.9*_vOԷ۲x @C3wP|1WS./]- GXAP Xp%O³i2v%I;sicVc/`qJhfޏJsDOZA[%0qtЙ(6(21x M4 3fnKSN_v?wTcZ1o_z n-3 B|@CQyn5EE}$&uL y[LEoLf8& >eeBc2^//e^QFEǝcc©j1' ~i&^&Y ϟe` STG*@Pq6m`Fo#6‹R3;WcCY4V4CBaPZGm[$hk/dV̇n8ܕ3U/]Ok[{pIF(pI] +JWϛ_a9r훸Z\ XwPvTAWaNFo*>%jxDŽcZd~-nUEJ$\ebGR/o4 endstream endobj 431 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (/tmp/Rtmpei80N8/Rbuild1eea6baa0860/NMF/vignettes/figure/estimate_rank_plot-1.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 440 0 R /BBox [0 0 720 432] /Resources << /ProcSet [ /PDF /Text ] /Font << /F1 441 0 R/F2 442 0 R>> /ExtGState << >>/ColorSpace << /sRGB 443 0 R >>>> /Length 3741 /Filter /FlateDecode >> stream x\o8~_-˯-w p>n2#EҢ&Cf'KOD6|k}it7Ϳ_f{aM7ݧǟ:4[c/.lŁNb;tvֻ8,l_sL;:_i{;`v: o_8[D[}QHVa3d ];~msk]|B1(#Hֈbfl;+U|+A+d]]Cln2ţo{ 3bz Y!~k `ooc:nlZsۢ%ڈ2|;Z3^]M\}Qzlm=3hu<O; gocWp{ʯp6h AQ /ӭ& DXDX3f_˪W&S"YX;˙1]OϙTrx!#2bB_eE~il> jҲ$d~ѣOZ͞ZROΦ+*g~ 3%i8v[tjz/ ;QNHb49=&1?;#$mB3qQDŽ9#jޒBc q,0)\nPQs.qtEEnTd9󙴕-7c~z"1 w)D8sJH! Ob FQYI+.y3*"30NZSDf\Oo̍ #͞xaTU1.=X̔@TD9-y '!3Ņ'e\|Rc}Pu!5oX`C~i3\'lk/8s{Ra@QDEpIgAFt/BdBeSK"kThe8S ,v|nuv3U$k3dB1M u 7jceT$kxje jb`.BGѮŠf#m0cښ%S6q0;qfA۫au(8Wψ~ !F .)BbY9# #HV{YmYDzˊi0j&РBeӠBe-;rNQ˺gОdPWȲ"w&9nn&9 &9 *&`rBrvւvhzYXYI͢+R;woIvsI ZaR0 ;`QSOjg.gaڇcsܽz9r K'=M-5+cA2-Ի6S U8rjT! Yֈ1fOD-Ϲ,9)35++5Fv AFP >uѻUDzFwxd*pdhX(.0I[|)50I #+Oc{BclcnU؎aab*S?.^-Ox C)q+*dY#N倃i0LsąiP!2iP!2ip9=S$`Y#$$ɊrߙTrh!#"#2cBonvj/PZg-Q Ifʚ<7rPov_=pCF|bǙmTEP61zW ݛ=9+X`FV'SyUq`N*H!s\BM)#ʓd_Iq/⤿8gDyҿI0aƦS+**sw\WF5`' ɁXqP8I(NQ$'(OV4*q*誌]V0F%,ELJ"fL1YS(nb#ʛTSc\*=9EOrWPyE.#ʼ"OWEn+"oeDWcS<O|5F_QF5e`Y?EXSd)|QdY>(~֟j'≯ƨx1*j\-Ò=}Ìox.*LpјSݑk 4qڣ./42ebJ#, 5B˴B42P#$L+nRY$gHQa/ S CTii{)iLR꟒$*T2j5MAJ s8tqq7J‹}Rw/_R<,ҧ)}Gs8b }1',9>,0`ȓI}]:8ҊU􉛓sW@ΠٝL RnJ5B(4z>5_o^RRC,azkNn{n?Z319;+7P>_ <!*Qy0[;<\k endstream endobj 445 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 451 0 obj << /Length 2083 /Filter /FlateDecode >> stream x˒۸>_^* &IWru}8F4,!ק J4X%؞È@tٜ(XQE/8<f " Q/Ad7 X0.]sFEfR?D=QRq9"O@kLc:X!uT47~y|2fRcv7wyK5#9,caԝWv}Q,NkESgmcSWgQ4WP_ETkinY! ,p>SCpmzojc `đo%MaЀ)i>0^wfJļ]+]0[@aX՞m8ߋ4^䖶EO(|^]/`X'ps(qv+az9m>`̳0} Dp8vKTHx);zǥ"|эq̐xa\')vmiSř(jts,E{+ze*:֬g=Ls]/[1Ҏ;+_β`7wR-6qn-&Ӑ1F_~W;8>cn &4L|&8]ww;_\L)i,_x~֑{,c"l:lq +S> /ExtGState << >>/ColorSpace << /sRGB 456 0 R >>>> /Length 35099 /Filter /FlateDecode >> stream xϒ#U񹯢0,)I>@0'}]keWe=mzO*/UT^/߿g_x˯.ry]X_˯z{_-ivyME_߆u^9o˽HH룸e4]+鲽6N7~. ߽-nwt^-{i}p^p^^܍KݦםiXŝ<7pw:ދ%j=fڶjjKOOGOOڧVZe}F\Mulz:ÞΆ<_1"i2F$n(IQ↖&isF!(i$J↦*nJ↮}L,nHK↸.iċF0iDLȤ2jLB\\?N[%JҸ4n(KҨ4n(J3Z!JҸ4n(KҨ4n(K FqCiX7 QciP5 qCiX7fB7`i>/IZHFҸ4i$MIҤ4n(MIFҨ4i$JFҤ4n(MIҤ4n(MIҤ4n(MIFҸ4i$JFҤ4n(MIҤ}^O4i imqCiX7 QciP5 qCiF+qCiX7 QciP5 QcixQciP5 qCiX7FqCiP5 ƭ!&XK3MTHFҸ4i$MIҤ4n(MIFҨ4i$JFҤ4n(MIҤ4n(MIҤ4n(MIFҸ4i$JFҤ4n(MIҤ}^O4i i[HҨ4n(JҸ4j,JҌVHҨ4n(JҸ4j,JҸ5JҸ4j,JҨ4n(KҸ4j,J3Z![CM4nܫ_`4n(MIFҸ4i$JFҤ4j,MIҤ4i$JFҸ4i$Jyi2AҸ4i$JFҤ4n(MIҤ4i$JFҨ4i(MY.:U ϕQel7<]Fϗqf7 OQhD7r4j,JҨ4n(KҸ4Ҹ4j,JҨ4n(KҨ4n i<Ҩ4n(KҸ4j,JҸ4n(KҌVH֐F,祹ϯK4 I#iH7&qCiH4FI#iP4&qCiH7&qCi>/M&H7&qCiH4 I#iP4&qCiH5&xI4χ-wIҤ4i$JFҸ4i$MIҤ4n(MIFҸ4i$JFҸ4i&$JFҸ4i$MIҤ4n(MIFҸ4i$Kyi<Ҥ5Yݰ QciP7FqCiX7  QciP7FqCiX7FqkH FqCiX7 QciP5 qCiX7fB4`iXuߒj 5%j5|*5|:5|GR͈եjZ5jj5|z5|5|5^$#|5|5|GtbqGxbqG|#_F#_f6_J#El;mSF#GG#{Xx)9GшGb#{)9GGbc#GGb#{)9GG#{XxXyH#G #G(=JD٣Dȑ=JD#£Dȑ=JD#G(=rd#G(QF#G(=rdѣDȑ=JD٣D(=rd#£ĆGa;_MGd #GHȑ=R,kԑ?lTQGQGuOU,>rԑ?sԑ?tTQGQsGGOulxQGGuu U,>ԑ?T RGRGRcHsHG>ԱG"uxx{)9Gb#{Xx9G#V9GG#{Xx)9GG<)9GG#{Xx)9Gb#{4bc#96<ڞ#{%GQ"z%GbQ"z%G#{9G#{(##{9GQ"z%GQ"z9GbQb#G ԭ}"z%G#{9GQ"zXx9GQ"z%GQ"z%6<z%GQ"z9G#{%GQ"zXx#QbǣuaGb#{)9GG#{4b#{Xx9GGb#{Xx#b#{Xx9GGb#{)9G#V9vGD٣D(=rd#G(=JD #G(=JD٣Dȑ=JD٣ĆGA٣Dȑ=JD#G(=rdѣDȑ=JD y=J$/?e_6\Q_m1s·Fmlf`moݍׁ[<;|X0oo'X>e{nO4i\krM+^vցۼ\N4V~{}=X'+Dc|{5Y&:+Dgh|ߺrO4V=X':+5Y&:+Dgh ^hh{rO4VZ':sMth?xrMtV5Y&+χ5H2MFǟHeom|"Zg5ZFZHgȓ=YG:Hgi-_ZFZHki-_#{|t=YYG:Hg,#r^FZHki-_#ꘝ{|t=Z.ZFZHki-_#_{|t=Z.5ZFZHki-_#&{|t=Z.ZFZHki-_#}۝{|t=Y&YG:Hg,#]ik|5YYG:Hg,#5ZFZHki,?63X~FHk:|5ZF: L{|t=OǷc~s㿽\{vsy^7od]t_cܰ~,o훷1^/-_o۱qM㟺K7u 2Y}{Qdt}7?Qnʴ?}盢/ߔݔoʸݗyGCOw?o}VOޏ/Vwsg?/?oPA;,]7I:Dw#uǑi|kv`ǣv_JU#c;c zؠwY`l,|Y#w}ྼ[T e۾[<2v}mtYaь|avIûS<.xrY`l~]#w.׻w#Xe>̰]VQs?#lU{׼/yN#umߏLs Foø˪WGm=aƓm;ۃ#cw eCmG6.۲Gͧ˲q29jecp.m:ve ?o eY7#efp{7O9>̋}rvYƓ]e黌w"=>ܰ;m۸r߶#ȼ>.G6.)~jǑG8L]Gsd# lyCƺN;lyp?v:?^?ɱ۬cu0"tFny< w#ϟiȖq,vYtt=+#+lɏDZ|vH4m?=ȶGf.ϑ $.3tO22]e属G&'q1XYaLclw=:?6?vi\!>#lz6t_]_d9+#+lb`ӱ9r[,ǦY ތ xoo1 ˾~_LX^RtIDJGK*%%HTJK*.tD#_RtIDJGK*%%HTJK*.T:"]RtDxIx$vK*.T:"]R)/tDJxI#%JGK*%œscyƃǑQ,qdyYBGGǑqdyFqdy yYGGǑQ,qdy yYGGǑQlǑQ,qdy yYGGǑǑQ,qlS1GDyYD'qdyQG'IDy yQG'IDyYDǑIDyYD'qdyQǎ<AyQG'qdyQDǑIDy yQG'#}UǑQ,qdyYBGGǑqdyFqdy yYGGǑQ,qdy yYGGǑQlǑQ,qdy yYGGǑǑQ,qlȳN<(#˓$<,O"$<(b!O"$<(#˓8<(#˓$<,O"ؑ'#(O"$<,O"ʓ8<(b!O"$vy'U8<<,#ˣX(8<,ψ<,b!#(8<<,b!#(8<-y<8<<,b!#(8<#V8<< ye7 yQG'IDyYDǑIDyQBDǑIDyQG'qdyQG'IDyYDDZ#OFPDǑIDyYD'qdyQBDǑIlȳRV=qdyQDǑIDyYD'Q,IDyYD'qdyQG'qdyQDǑIDy;dIDyYDǑIDyQG'Q,IDyYĎߪqdyQDǑIDyYD'Q,IDyQ~1BRT"*J%RT"*J9Rc7R#+J9RT"*J%RR#+HJ׈_=rdѣDȑ=JD٣D(=R,S=o_;9cyfq~-oޖzmz;۱M㟺Ku?Y}{;mFu?a;/]_t?On=d2-h7w7B'I___QZF߯^ZE[rz+跨m? Vܾ B.>~o~+&c?_y}; Ώ'y)J/VL|+EmV>Vqem}G. #áFF K~yv/$Ⱥsqdc yd"Nw#Iᇑ푅#y;v_ܗydQܷ}yEG6z7qdތ\#3ۀB;4vC?o/T]x|瑉-=4vn\}H9aڇǣ~9zj;id_O[@; ꍸh{7rqDwlydOPZ*q^*.~_Ce|j[ ;*.Ɵ8l]-v:V lͻӱ92vSgѴϑve ?o2i\/F3{?N#3˼xG. 2$tt}z:V>G&.㿷qcvMq`ryvۺ?ӱ7_N#3ly"N#ly9+lyCƺN <\;ydcYOOGӱ_8rQ@Ff.ei9lҬGx:#c<2vH金ϑ;_*cm?EDZ? x::?V#:v!|>w+Wba</ru]O zNf3^bdn}P_z۟p;6:<?<iai__fz34_̅oo1B&&G$#G$#lD4ǧW&:-=&:vLHDl#8bDl#2#lba#X&:52#=&*&:l#X&X&*&:6LNsN#3&&6LHČlb"&*L&&hcČlb"&&h#0#301#h#&:hb"&&h#1z8"2#lcDLX&:#LX&:-=&*&:l#X1Q#-=1#lba#X&:l∕lbacۼ߾D4ѱcbFĆ阘4ёMLDDŖAD41Mt옘4ёMLDD41Mtd&zcbF&fMtdDG61MLDD4Q01Mtd&NS&:&U 41M|~MWdMtdD4Q2AD41MtD9~#뗈%~_"闈D;"v%]lW"ڕv9]hbaW"v%vzTlb.]4Ҳ#lba#vس#lba#v)v9]]lba#v)v9]]l#ۥXv)v9]lba#5be#ۥXذks[.ǎ]AvycWF.G+JD[verdѮDˑJDٮDˑJD.G+rdѮDˑJDٮD+rd.®DˑJص.{ݏv)رK#-MrdѮ#RB)#b#+JX)+X(J9RTfR,rd .GKˑR,rd..GKˑrd ٮ+..yGkyE"X{AeEK,ν x=RFO29h(Z"X{AgdEsdQDͱ#ZFP4G-EsdQD͑EKDYD-EsdQ4G-EKDYDM-Esd U?"-#(ZbC4tDȢ%h(bKh,Z"9vDȢ%h,Z"9h(#%h,Z"Ȣ%h(#)%h,Z",G~)#X6|zEs숦J4z-0#,b!#ȢȢR,rd.Ŗ]a ..GˑR,rd .GKˑR,rd..Gk.GK˱a>Y']2v%6HǮ]lW"ڕv)v%]lW"ڕv9vv%]lW"ڕv9]h#ەv%]lW"v%]h#ەv)v%]lWbǮmO*.Ŗ]a;vieG..GˑˑR,rd.Ŗ]a ..GˑR,rd .GKˑR,rd..Gk.GKˑկ?ӗ^"\g>;rcoYxDP~{72{dj{"oGjdiن~[Fn<e{|t=ZuښHki-_#kA=YG:Hkmn._#k|˓43X~F:>;zT[Hg,#{}Hki-_#ky#{Hg,#{wHki-_#kii-#{|t8k|5Ynu6]ߍv %oGpWoߎwۑ$Hg,#o4v|}xg{|j?I?о|pqg,#{|ODm-_#k|Q{|t=Z>í|5ZF:Cu=YG:Hgԙ΁#{|tqk|5X~\3R,{>~|Rv_nߥ7#"Ս5ZFZHgmg,#{|KO5ZFZHg\g,#{|Wf-PUXaX~Z.3Ro;T?-:\ֹi-_׺i-_#k|}=YG:Hgi-_o-_#k|=YG:Hgi-_k-_#=ZF(ھ,o,#{|˟0ގtwt=YG:Hk:)|5R,y~mz·}^cg,#{|SZHki-_#kÏoS_0\_~w/_?e?uo^_o~߾~/?ǯ']˶?7Ïm}/v{&vOݥz?:h2iZFu?a;/]_t?On=`2-h7w7B'I___Qeko뭠}+n~}6>TηB_tyz ܊o~+uob˗/)y)v.~9b?{?\E\PnIoi碃rn o_s]_Q??7tI\ncio7# #X㧑#tæF.Ǐ<2]>y<<`#}ac~#ߍ̯F%wq?̰]7I:w#Ir9cd4e<ɼ#l8$]qYiܲ}z zF;g1vu?htN#3l푅#yd/i-82^sut`/ܧydtF>ܰ;ۀB;dPxGf=̰]x|瑍-=x{Շe><5̰]Vݽz ˪j{]Gg@i]42vY`[瑕wlԍ(.9}Ri;+#3laE?Ft\MGƛ-;eY7# e?vyο̋}e}4eOdv:V>G.ME/#m۸ӱ;2y}\#lypC`=+#+lqyyie~ȁw#~z+\Hyϑ<!c]<\;˶/㈜z:V>G&.s<]OJǣ:G]ZXr:VnBOӏϑup L,cs:ߖi-~b%|:V>Gf@Ist|l]:sznyv2Om_5GjcsNEgX=5mq1vt|̰_/\i-7BG7#7/Qӱ9v8a\OGӱr{+W=RC`>(a翌Rӱ9B\]Ktt-|2ϧc\ѹmoF.z#W[Zw$-?5}etM h2/ܞOOeE'wǦY~! =;J=4];u3tDWT1o={hDWR./r;Ō2Tn'bQh\lq]wLxlxunof|ߌj<~}B,{8;;=w߾uu!WQZ_,/U~ cⅿ<]_wwpQ\He+\F* \Hqs*WR^<x]/.ߥkw||p5RYz]'/p.rw)=`\{`+,+\iӣz \ N~^]:/վ\ds¥+:4 W#uߌD)9ND8%Ji8'ND()N8+Jɒ=hI#[҈z d qCI҈4 1I#M #Nȓ4 EIcRHdOtLY%4E MMQBSFbS%4eU.WBSFbS%4e$6E MMQBSД%4e$6E M)nhZO)jl↦)nhdh↦)n S40࿬lD()NdD8)Jh8)NdD()Nd8)Jh=LI#S) SД42%LqCSȔ<Ȕ42%LqCS>o':l9{lĦ()#)Jhzt+)#)Jh2Ħ()JhHl2xBS74Ec'56 MqCS7424 MqCS)h D8)Jh8)NdD()Nd8)JhD()NdDd"SȔ42ŭaJ&74%LI#SД42%22%LI#SДϛ≎)eS%4e$6E MMQBSДMw2()#)JhHlĦ()#)Jh(4 MqCS:xMQcS74EMqCS CS74EMqk)󼟶8)NdD()Nd8)JhD()NdD8)Jh8)٣Ȕ42%Lqk 2 MI#S74%LɃLI#S74%xa2l'()NdD()NxZrgD̔%<7E ONNQS@xo` s 4 D*NdD9kJ9kD5%t͉\SBלȵZFXg\sk r ]K#57t-\\K#57t-\sCE*'Via>oVyr"*'J r"*%ʉRB>o*%ʉRBȪdUYְ*dZFVUnhUY:YFVUnhUYV-V)UJ @* 44V)U#UJhHlZVV)U#UJ 4VV)U#UJh_ʭa'*U@*7 Rc*?*7 Rc*5z[Piy<* 2RV\H4Zq) 5_ hj@j|9ъ}LuF+.W T_+{5_,#|!ņb_FH#_Hȑ$X\Jȑ%dbB|5!Gbq=!G-m?gMES6&Zi#ژh MEMEMEScF+DScF+DS눦 mB45mB45{5#,bK4hh,#XȢ͑Esd Y4E:Ƣhn 4 4(Ƣhj,憢hn((憢hn(Zj-!ZFP4ǎhAYD-EsdQ8%h(#9(DSc:h6&ZiEScF+DScF+DScX X :iEMEME^͢9vD#,b!#Ȣ)9hvEsdY4B4GMEohn(^V %ۅ& PTO]nd܉'X47M88 & Os#H<&yƖf)XJ7RtC)hA)ʻh#xDϑKDQ?G/_"ꗈ9~cq~>s D' )DL|D[OuD76aBXMR~wJR& )VO4JRj,JJR&,e"JR&(#KRF2LD)YDґ'NYjl>OTxo,O1Si# il4d6D6D??/φφOCgCau;2 DO%:SğN?*4 DO':cTi  )4Cll4d6D??? ٟ џgCgC!!p'[e?V3T`hJ$3`q)3xει6c'د5bMe9N,68QXlp;G ;ѐiML~iSp!Ӑ)8Ӑi? ٟO/ߑɪuF5 V%9XUi'9 ꃭgNU͂QJUЪblUv'еbZa'Pb=o[rV]kcךkB6φhUC>l4d6D6D??$φφOCgC<4LIQ\VV0#S6c'*1iE '%+81bsŒqFNN ћb|EmFO %hъ Fcy_g4;žL;g3d 3dl?C!Op!Op߬mvG_3>O$ub8O:\}wb8Osa'N)=l[xޟ ?=Dp rm3ZY ڌCd>o5BVmXuUUWHV]!Y!Zud:dUWHVmV]׉vu3kJ$Z4_;\w]+5kصłsN$U][lpXJDU]+Ʈ-6V][lpp(~b:®\Vpp!֐]+8֐]kk ٵZچU75aVu"UDbU'ЪfU`U:X [,U@UتfhU1Z!jA&VZՐjVmVmUUU ٪ Ѫh?ow^~ #~vy˫}Ztq궯˷/:OU8S`c[=pgXdqy|8rJo?jDRۉC:߼H_wy%J$͕H+4~J$͝;4w"i~z=效+4W"iD|.jD܉As'3j^Is%J K\As'N˓f啈>̕H>͕;4?nO5%J$͕H+4?nˏIs'N͝H뙨y%J$͕H+4?^ۨSsз6a{yv_ewz7-72iN紹ID͕H+4W"h~޿+jj{M"h|[{W64םJ$͝;4w"i^ŎNIs%J$}\w=w=Ÿ/ߑz3ाn :pR_=;/)|7~}4HRo3 w߷VI}E'xxnaCm?L/aiwzG/|s2ؑ~EKnۻHR7IIQPߑá]$;DTw+I+2ٟ$b]$$wRobw$'NxT_GyR_+>/\?{#%ξ.qֿڝɻ5c/M+2wً _RKj%{#OYKeVT_+DXR_ {ᛤH>A}G/J{݊'{#I({hr~Yq{,Cs ںĦ)ѻɻ`~t ;2?//> \y:HߓڒL|:^NdSy~H~:wCǓPʨ'{gP#&ﱟA}G="`OocY.o /Anv9\^/߿~o";?/ N6q[{ݼz[޷D۱;ը=hm뱼Ͻ2nn?ރ>}:q=V~U˖wNސ#!zeC>moF1.6c.۱y:6ߖq zeۀc m۠\aNujeu= oÝc/?x^rقCoW#kq`z/_-zv٭+=/ç!oAv}eSZrJeꃓ(ZMpC;SB"y7ޥ;@OY=ӧ>Non.؟OCOCOC!N;2Ӑig? ٟO,CG؟\E5i4C?ПbO3S ٌLiN4C'Љ~ffdf.]NRe}ej2dw5Dw5w+!!p:#vd8S^G"wk.K%*6KUlpKUlp.1v.1vW]b.1vW]J$.1vIv!ː%8ː%8ːeK]..Cv!K0r#Iu=NM]b.3t ecw]f.3t %2Cww9K vItWCtWCt!!ː0p#쮆>^] ] ]:ˑ]Z %cw%*6K%*6KUlpKUlp.1vX.%wUbp$ːeee2Lܥ.CvWv!ː%ˑ]$=]f.1vKe2Cw]b.3tKecw]bܥ $!!ːejjvWCtW讆讆.] H[F2Cw]f.1v %2Cw]f.1vKe2Cw%r"p.3tW讆讆.CvWCt!!a.G] ]} wu$p#K%*6KUlpKUlp.1v.`ĂN犡皑wO !M" s<0#ha ىD{hN4d' WHk]4r fFchfh434 &F3Chfє`Fs&F3ChOFH`4Gh 9#ha5Dh h hlh4hZ&Fc&F+6M&F+6MVl0MVl0XbJ7N^>p+18Q轚8LA̔ 4dSLruMiRlJMiȦ4dS 4dS )oZ/LٌLٌLilF4CS6#S6#S))ДȔȔfhfϛ҉ S6#S=o?'~~J[[#9RGs+dϑ7;[!m#hVVHF[!!md.<M&F+6MVl0MVl0`416`4hMVl0XbJ FcyOe&Fs$1" )2͐fMhl4{fF3d F3d Ѯ #=hbl434 fF fvg҅l?3Og)oN :7|@'؉D3tb|Kᦣ)t))W S:¦ll))) ٔ єl[Mi4CS)ДblJ34ROg3C~g%s?';O`'{f':NDvEؐ) ))$ĆĆDCvbCtc)(Nc'(N,68Q(N,68QXlp;QXlpXJDNT(~b|gƞcTb{{*{9:3H9E⹟ad4Ghl4hl4{fF3d F3d v fFchfh434 &F3ChfєH &F3  {j`FH`4Gh h:FkF3d5Dh h hlh4h#M&F+6MVl0MVl0`416`4hMVl0XbJ FcyOMHb4G)2͐&6!Mp0!` h hlӊF3Chfh416 &F3Cx_ Xo'_ _3upu'6|?scNЉ ~+$5d5D ] #lh!!͐(;_7mbc'+6O'+6OWlۏ%~Kƫ(9Nē'' ^%SwHLYblJ|Et*8XՐx}cGxh8֐]kk ٵZZ>Q$qOZеfZ1vV]k5#Ø{Yl^t,6x-DpMDz'hY'hr%/;xY z!xi|"9 weꃽ5#)Д})#骣#HDGت ѪHĎk#a7D :3^;G"/_߆U/ ^c/%^`3`_صlJ e;+:T"#>تbe-eC='ʑˆeA>m6L ``C66d!v9ҦEfZ3t ]+Ʈ5CךU"9v"0l`_v ,6}Kub8O%.ۥE%?xǒ`B7d?6pC4!!!~`Ȯmmkkk ݎB8֡0c c ,K \ >5K\[bbӷhn%V"9vbq@T;1|;8t+1X c lؑ 6d 6dm6!ؐ ,g ]Gמwil ]+Ʈ5C׊kеfk`ךU]k tXZ'*։NU"qk`ךkB6 \ۑkk ٵ ѵ ѵچڶ!!֐]0p#k8)C c׊k c\+Ʈc^ƒi00;S ƒ9.횑k`'pt f cyf&s$" ~ ~l?~l?{g3d 3đ.`ϙsb93{ =gxNsNS=gx { ='ƞ3 <zN=gcϙzF5 <בs s s s칆z!z!zΐ==g8yfXMyy =׌˿XrWw \0Rq.2}׋3NF"dIR__QDO}}灠#CmòۻHT_ kGmT~'િQ}uI^7HRLkɨ_:!.^]62?[ttZ*xA}JTI^3/HRz~/V%dRZHum֚kWI+2uڳm$*ޞ4ܖumyvcNۡx=Wts\F-_{W.-B{Nr]pze?ǻm/ !eA=!67A#_fgcڙ1xEvl޷K6ߖ ze@?z56cAl~Me6 _j zye [s6{7C菧׷KH.v_\OcϘ嗕o 9s C ;sҮʜ>^ɣv2~ߦBRC;3"T7ݧ \wڧ>)߳8}I&~㻛9Von%sW//ǯ[\Ӯ9/f7?7UI endstream endobj 458 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 462 0 obj << /Length 1011 /Filter /FlateDecode >> stream x]6=BhgU߶uw @t/]|%g9Qs@9[A,"ER")Js!"æ] BuEEŠrd7'րcIu: qIgŻC2ĄE? b#1MshucQk"L(ELcexbiS2hc?B,*jWƞtH `j4A璢} $e= O"$,(s@iH/2wmZIn]rc6QZߵEZ^'y2!gpJ!P2ޘ&ۖ(i\?Fz.OY]1]%&oWAAtVzsGHj ;셇b-Wo}n6@Y۴H2&uF*b]ӖJ3p>"99)ޚ$8S.SIM@nLMzͭ9%]l6k<+I-8tsO^Ǒzd\'>,p6lbSՎ|L=⢨⧯,(y{QH?^0RSUluEwr…UL4=IڳAձ?vOQ/vy7.WMԽ- #]7ԳtR.M~EzR0iEX~0G1#ؙxFOk=0}boR~O-D2R_И]"8ɉʍmc_Ze(8 ^.^8`PeM+Aevj]R34›`R`崁ћbuqޔ&iZ{H_;l ڞX{Զ2t'B"֬QuB9rrFڑⳳQv6 ro_z:~ja <=b> tQXR JS*r̄ԋ endstream endobj 448 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (/tmp/Rtmpei80N8/Rbuild1eea6baa0860/NMF/vignettes/figure/estimate_rank_random-1.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 464 0 R /BBox [0 0 720 432] /Resources << /ProcSet [ /PDF /Text ] /Font << /F1 465 0 R/F2 466 0 R>> /ExtGState << >>/ColorSpace << /sRGB 467 0 R >>>> /Length 5043 /Filter /FlateDecode >> stream x][\7~_Q6sFw:;Y @yᴑuwI*E$GRbw?Yww>Í4;^o k槿zck??M_Xv%fiĚROY0{@~u)F7o!إhŐbb 2 ?1]R[v5G8A怿€;њcXGKKjDYżĨV 뺤FY_ؿ'ReujJDkPъY9V%xɗ3<5/nc[Rp2- ~ k߹A$1z?@Vc c@lpyw-ww_wiY _@2. ׻wVΛ?;պ\m孭Ѡ`vy*KN9H.-(oA,uO$\Xwx#S$ иz cqOT$6hKGP3) U,Aѵ `QІ6 hl#bAeJ ɒQ* mqxp 1{5` }J C`TCPsz[̬3 >LvDa DMdr_T:ٿƢi醘h5@#b$U|/`800( h ~T"4 Yf!sZ_ٿ.n+!R iaUZ1@5QK8Чn͸$NB1# k%"jܭAi2LXhAEDWSc^5;ӊhBhaQbP,ܺpPGq"\`#X٬SO{ϰ/ Y4q}vf fw680l`mgb9ap ~޿]iڂyuh^y^CVm$݇ņ8e81-jlpbM8ͽRy7kgםa6B*"g=mUvl$fKq[cº.&6!Յ`!=GC#Ԃaax%h% 4+)uYXԅh%  W8SMv@5x?fpNQњTAIq]9VDw7]`Zs8`Fʂ2}09DQ=i=3П̈-a5GʴDžhh ]9(ˏh=wS܉Dkbbs8l>rLy w(%gr)gpkFX=9r˽'05g-=AWsғFC3:1bԥ\|a.D:vh&mޖ.Eڶ/vqń?nѣe uzD[]ѵ,u~OMoӏ{C$IQ>˾دUh͑ڤA(JOV4UvEW9bՇASz7Ը3\(DkhA8Ǣ8ɠU!Z;z[ *?6188FML hdkYn54C <;o~$:Fғ4ғ{ dkƥK<H4)͘dz3F &(-ɺq@'eg2rhqK7.c@t6m3u;kjl({8mX\gq H6 hlT&Q4Y*"_UR<\d(aZ@ieZs 1 )Ufe' ) s0(r玹=w@2LkO58A.j )jR9x4l et.Y6[-o J?(EQJLLn X"٪%mxNMڎjm[?++$:~#wy̟1d7y9v.|p<3OJ?g<1eZ'iѱ k*lY/[;ua,ƉL&?V:70W9e Wj1V]j2ٖ@'꺛@ԘDy y S"Pfk9ʚ>U%I3saŬeamrX1 YaEh ;V +=vX-J;V+z լ9&uk>E2lT8ɒ |"dlBKy%jH%MIIFh-`+?[$#4IfkdC^-Y2VLvgԓKJ2N%y]d$Xs>,Jg$QdL¥.FO9D%Wu2ƅ|.OqV1fj~;9\;w7 ;fmY;ޑq+]C[oa5 ~}|f-ijvS UEnGx@ kw[~{ۏT\)ϊ{:&P7ʵDpbNr=b}V5R?ǻA${>U0=!9!qQ_~?;s^\Yx᷻_>o'Kh#:'^\YBzěݫ^~}ݻ ˓%uх'Hyv[OYb}[E߬oe^=}=\Vnp2AлQ?O -d{HjRkmXTCO]}K.ta;pK9[W,8Z奺^\ڥ^ORVS'K9 5u1 5u1 5u1 5u1]QA]ݔNw(SJHR*C 4p^\ڹ,p9k p9k9sM9sM9T9kJ .N)R*J%oiW,)f-)-*-K |3:7T |3J7T |/T&TsjBJL:G C7Ԉ51Ԉ51ֈ^foFfoFfR B-gjճ\@:yiJiYҥsVji-3Z-X^sH-X9| R >ֆlo\뢸!.jͅn/+(,o}hŜcqZV9wjɔŝ ŝ`= ᅶ/Kaܥ@ 7*[pn5J\Ufm?ݫ뉕ݣkG?ޫr~W9xL ^KsRzKf@^XKwW"U!wUǻ/h 0d3t;_mQ۳ۮ븞cCTWGt~/o FvKSvMY㳍%Ɗ7_ulu+N{txK=F:MHI_`uX{K4&/OjUv)Y/^jOe|a[XhO$4!®%~+g~۞^!T˫#X?~^qvf2ӫ_` _ݾ6^t?'.%Q2Nw}pRh}M=ŪBuSލiz7Jp`dFu,ot`u>y;>\ky;v8n~NkrwTYmgDY>[W7N`e.mA3k63ez<8O>/Iyn=pbW'擷](h0:G[:ϓ'Nx Iyn]:13œ=iyn]\=OビMDօ|%f N4_B燻u0Wc3|PDW@ U h z'E endstream endobj 469 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 482 0 obj << /Length 3231 /Filter /FlateDecode >> stream xko~(7iN4ۻc#|XV;Q' 7-&ѮgI"x>lLs0ߝq$xZpi):*gW"eM#i&L.Z VESMjw̗g^se@HreIdbIE:fE][6- ukGM(\G/exʯ*wލ_Y}$Zp2i'E8 ?`_cۡKuY^<Ľ c=ѽzw/\uS WM?ZOO\U'Vi? m3-O+$1kι%r[a!eyFYju*Fp-Bep5WPH,m&G`F؆̄|! »6M}$+07Uty}Z=ThG41ͺa0~.rف#Ae2.`j.SR]'e b7@X^U#_(D`{em`v5-$e6-_(H#fvZIX;.c g<(S ܣN/]H@0blj\ `iUnэzȵ-T~6/mN1kbl~uSyLx>WDK1n vmQCIia~B5f#~@`H /q0)z]~1TUt4h"g'"lIT 58Ꚁ_L.@ߟkRѫ{7$_#)*&ZhA;Ƶnr ?w*_ o^{֮.n!VC;@k1,po_b_'`IxZ"9)i(^"THsK1¤0 % Ȥ?b0&Fք_55 т51t][WK+MӬ!s-*$UwRNxv<9 OǿobI%ej~$ #%NM I8IHFΔ/7'aEGMOQ'ޝx]9TEF4³F_ kA\=k7.'.oWRhfWF2kOaj  6q;AZHkEvF lAaS?x&UeODl w_K#͓BEo K4ڹ$o3h#.K=6"g]Hp%2:T@lCJG!({}{P(?Nfm?4/'vv? ŋ',%?y5xƒk@嵺UhX}w_i(XTSYe*I]2KbxRC1/TۓSR}!"tCZ"bFSC*]o]wg>^`"ōbb݉ k9 dNœj*/N-|raG}DÎˉCHYRun8͗LgVKEfBо͆pywC#j煊}CC$B~hOL9%x>q;'}!u"dem5%sX[JNu1V~[ϒց{5E|CʾT|',u)25$u@9-à;?2@|aWH:I7=;r E- AG #RXC'w>N4M'7dewث?uz6=3KV7S3f\ƿz]@?=T_m7Cht9-`N7ˀsNnU(&椃 g&x{3G+5+e"lSP6)ou X m^O0HX4 h]ES5C]P1&tּn8|n}"_PJae,z\xS"ÑT};.sb#k8a U瓪aܸjG(穐bS*3(͸Q vb櫻 Tc3-6m໬< \X&?śTp^g珱i| Y_F-Ww!n 8"IH㻹D +To} eLAI&)oLI"gߚf]6an싌h9% (!id I 2oVPCO"S F5& yּ] ؾyr(8UщCLdW $\\[AVV?԰} /7~ ;w!) ܄o>oqjÒ%&>1jf}u7AP2ҳ\C62,2 "ta"((aCjR;y7(ᖏ)deD>܁~!B3sd,$ɘ%QXW eYH.Q"޺bGv} ^')T3Sdx7ˆ)Sz<&nV \-~x\oaY8k~JR1P0Tiʼn_ OZ t}nl|0!P}L|Bͨp|y7"J'톫o ;\&ޡbܕ~dpGYePÂ(g?DK[it푶7hҵ~:n `䖌 V &IoDm}{),2}Xe93PgE׬]/Agšv[uHÛ?g'{>\ =F endstream endobj 496 0 obj << /Length 2233 /Filter /FlateDecode >> stream xYݓ۶>|$3d:f&e'A~$Hə˜A"X,~6ѷW?^ xHDaF(Ue2VϿh cE<gi4M]Ea*#2҉f*~Od1/#tEG. wnΉ<&e\Ȥ)T ]ˌ\"XMhNsr #*O2bр՗D堂4&y,YG?fwO{/xf!5k' *vXy=JٹV!TLM F*~ 3 *mhгdqo+:-I" 1zۢQPR&N =< {,=T>viV`C޴w_<6=T1)%g$FJw;PO+/ǎ$#"kΓx49pdmUWޒ=l9T\3J"eyz]X| w@۠9׷BPa#AVr9M ,wl PP9ڠƞupe>?nlGM,?JAAug=1abou`Nh΢:bPe_ukU#ʭX{߂~ٲ6 0G^xWQtCmh=+{#AERH.@Nc`qO6bƹD}GsڦM|0̱v'{1@JAd1t{u\2PfIt|s! ~GJ]:8jK%h0I! #'؉lf;mg@v[@ZCau^"ݕÎ@$8'܄| $;9ڶsY)<ҿm- rOJFBCd/ ڟt]lvQW_zPs(q=X&cNWVk{@l>AFb?[KfLȱ؇]u<͑/- X`%2*b%v!ѣ ={G  &@ T&B螌nZ.@I[X5S"5B#O 5(pgnY=V&kU4PVòl.A Ki#t$a$g>H.ݖ@&^TP%Kî4/jUTU%ښ:xh⏥.J]lq/(d߆<)>.^.N˅ޜcW@pGl!8jnXuZ 5d= Bc4<ט t.k#G|cG2Y_ž/VO}Q, Ki1z'(*0Y}PRA9!"/!< ~ 8RcOTH}g?YPH- s3>u'YYv +Z (2tK#](Wqь$Ϗ+WA(B˼xFp@Ha3}{85>!T:Ypl Tҿ+ףsϑPL* |P1.ɀXlP5C g(wwCHLl^BF`[yN?<#O ?oH P79 8%>ܳ<q~qF;~ikz'y gqŁEgȾ芚H1l.];y?v]c k)EgFfr]#^zO guKϾ"Uإw*700m Э@xUF]L + iXc endstream endobj 472 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (/tmp/Rtmpei80N8/Rbuild1eea6baa0860/NMF/vignettes/figure/errorplot-1.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 498 0 R /BBox [0 0 504 504] /Resources << /ProcSet [ /PDF /Text ] /Font << /F1 499 0 R/F2 500 0 R/F3 501 0 R>> /ExtGState << >>/ColorSpace << /sRGB 502 0 R >>>> /Length 920 /Filter /FlateDecode >> stream xWMsHW>0;==`k-Wq!&vؿGA*Qz޴zL'tC_wB1S=y&9&8ڮ=5~A/kD+k-|.͇Oda:Mi񖼰qnMdLIiˎ\.gr8(8Of`0Nq &FbE`DlaV,L9qr2QM`K+Tp4E>, b *%%K2)g7WixɆkNCVZsݕj[Ύ5vOo{A 7t`u0|cwpʼnnKwpm>- Ο.QJ|7f:+䓖%nW?Hn j^j?I80E}qP]e-M ulw?bo Ǘ.?vgdɳSMB/} wx{m8+C[Ei^ɳ"PE@tfZ7MhW6!n{ϴ9I뇁joO_zw}u=X@ |@?_zO$S hz!vBnV'?5=k>߬/ߖ8hbs?|\qWWƏ'm{ Ot>]m8KaѹE/n\DSO882C 6lgȋҌ䵟\KC{p7MWřcէ1fmC; un0̡gd.%Vz !VcGR/E𣠔jsxLWKmPt5Z ڱw*/ endstream endobj 504 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 473 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (/tmp/Rtmpei80N8/Rbuild1eea6baa0860/NMF/vignettes/figure/errorplot-2.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 505 0 R /BBox [0 0 504 504] /Resources << /ProcSet [ /PDF /Text ] /Font << /F1 506 0 R/F2 507 0 R/F3 508 0 R>> /ExtGState << >>/ColorSpace << /sRGB 509 0 R >>>> /Length 2108 /Filter /FlateDecode >> stream xZMo ϯ|"/A ] a$22֒>zmɎUUX#۴Xhyqyc)#ļr^2xIB_:|_/?\b1.?߶oK\hy!_X~:r+[Ro gÆj lj(4<6έZOn,wZ}~5#w,B0,u z\gßtSI![l%$ڱ)COoK(Ef0' jS'z n,F4>n6npO̡nOgnͷw;Sf .#c*rT&䎏|a>=4N>wyY>5DWs )\|)A"˖?l$M#d{ܗ?r}w2ϯLrݳߖO4|g3ueH.޽:]YͿ-2?:~zR"$tj%U5GgJgK]`M*]-.Qy֢C` */S^lNy֜r7e7_b?As*!UGcoyD~Dz{l/ Mkۉ~JGz~qzF_9${7&E٨}Q\6۬LYLߛ}yj}@ޤ> mJ*ߠ4&7M⟠) 2zܲ endstream endobj 511 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 514 0 obj << /Length 845 /Filter /FlateDecode >> stream xW_o ϧ@݋#0`jN.{j88ǭ(w;sj<%=sqp6>!f=(""%`Rg[f0vaŵBZ2Ci }tCQX 1ackQe̝$;фP8 La*;*a > hr=ȝLFgT !0&  )` MfKbv#mA,AВ"t}QUZoQf(6o6&\sh]b9G'i>Js@3嗲 ;6 &<S-A@aq.ZN@@˻FnWP 5S5< )_UOo!vHu.GvCn\_pŧyv/Ͳ* -ܪɢC0Ar5-Mu??/"KUaZd$IѸf84KMr^ASGN6e/L*8%s너Nj4/Dr(.LbDX+K[@GWѯ7 `-bԕەPfOҹKՑ mvynwss3<\A޴=Ag37J1 eQU)CM)En֥mѷ #qyZ>*2Kfa*v-ofh \MLuklا^7uϜ& xM9#$6wk;6Cs*J`4O; @ w endstream endobj 490 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (/tmp/Rtmpei80N8/Rbuild1eea6baa0860/NMF/vignettes/figure/heatmap_coef_basis_inc-1.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 516 0 R /BBox [0 0 1008 504] /Resources << /ProcSet [ /PDF /Text ] /Font << /F2 517 0 R/F3 518 0 R>> /ExtGState << >>/ColorSpace << /sRGB 519 0 R >>>> /Length 5498 /Filter /FlateDecode >> stream x]Y~>gC-affߔ%[La??v{`JHT_Ԧ=߁6N\Ulv%@0>Qה[HV#w0,ana.3Avn?*D !md|i~ p˱jכr] |Jhd6r9R~!48ӊbG/Gl372?q>UÈ;BnS~!tk a{IW3_G+P~!):G 'huGO+׏ [__)/̇®3_~Twxg3e\?1>U+YM'mdLx§uK'f3 6HoaB͞~%dbd|Jh|B(?G0Hjg|J|yBhިMy/?,qX Bǟ> JD3>%tw۞(z'>[h o7k)ߤWJ(՝~ NSʧ q~!?G`kτ'|jPOY {'h2g?!j_Q1~&ti ,  Z!v/~x>7q׏o^T^˨}Rɥ # BBt@ s&@D&mHD#M?~*|(pB0#LJC\0Xq38,ɵ&~O˱%Z!RZ501 fOE vY cGij[է')~u]#y]<+gi}ZtbňkmdY: k9l8/*,:00Qq<.C^,&q#ݗ= SQ\}܍e7c*S(-+f.+w+w_1/xi ù&~G A&fNp[?y^XA3'w뒖_M<_A9ȅ+QC6OnsF Swfﲗܟ 45/"fgήM*O+heA:LCFn+f3 ĩFAW̥_LfڻE{ 4w%̎1DmX%M]'z ٧a7:Lbt*Kd鵭]bZB ? m16輀" ;Tmyu&j2Veul^W^ V^ _] u}%2:Jf҂m$뫫mk"Y_]̶Ƒu-#3Efֵ̬kY")df];̺F M Ubj_/~i^K dw+l?Y>.VdK`|Sʖ+riV(JG qR~c+"7ƶb0*S {aK Vs ns ;#$p:;&b&Cg3 c:蘘wNmǀv0yqئ6r`cL1L1cYaBN8 8>j1☓/84ǜ|~1ܦ:6|6 ☓79;Fs}/:ׄvC91w11wn8s8G󺎳1'ulɘ_٘=JEwl p3sJƜp b>sy2hU٘tl|211'udIG| #sFLƜt11'vd̅\{.ml<kddy.tdIg 3|&ɘN:f2X3s5skLjc}Z\a캋g`s7 ܮe8:<]+.ckebp?e,rO_s8]ӕG"8x S ĠS kK)Zt(bSB$`qgDz.ĎQ3袩F9$З+&Q(hhI0 8uӜt0 b2&(Dc=.Y)B$c@"Lڃ吀NIp1oN9$\jJ09*Z >FrH6Sk6`"q1H\'P :x 61N9$kITS HRB)](Sit8nF9$ q~xtIKaCQTtP˰5RKHIQB$/e/4PW_} {zE[]J2|x[c`6^ɏՠwr=kRAG)Y<41UM0ēI~}Ty%F v$pԛiYS5Al/AS5WM {~3d-tUM=ٴikɢ&$l3n6wO8n9|`p և|-b/;Oſ~VkFT)GUċ@1fyv:={-eM~4IVeMxsIEjYOx)zt.kBz&SLx1yKj9)q~&Y Ry+Ǔc''`́jf 6nLJzMݔ+Vyݑ\ܘq˭\dpgI݂r?㖳iyRz=Գθu-G3n9oq[fr5㖓 ]_iz3H̬ש'z{­W'ܲ!>n q>RlqWjVN0up-t3n9^p̌[6Ffܲ+1Q,%Œ$wB$5E@oR+AZ9@.VV )}EJfEx˔Z͊ڔZ )ZItZN#[6GZRa>|,ꘅZRa}#ԎYH1 )fG8ꘅZRcRjU ;f!V,jizйcRjuBJs;f!V,ꘅZ 3Mc'iwBJYH04vBJYH1 ):f!VL ):f!V,ji;᰷cRjuBJRjuBJYH1 )f6IRcRj5lsvBJYH1 )*fNzg1oR+_gRj506\OU8;K)Z ny),+w! Yӏ_ͷfz endstream endobj 521 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 524 0 obj << /Length 1863 /Filter /FlateDecode >> stream x]o6=B  #%]+:}h)[-yݦ;~Heŵq IQ'xyxͦ>/'hw ,v=?'0D!0vcD$Bq9 N!䏝0ٍBky(|e3Aqxr3's)FO1;g \ްFbj!Ls)桔i61O<^4TUE=)@cG}Po Uyb -u- o{0$^$?A{WSQ z֟84Ml}\43i98\}!J*9EzeVfÈF5ԃF>aXqʱ[0H, <3m~ Jwb$̀rK3_*IMo4eE- Jϟ=Y' @p8M[e̟~l^P/h2vY*3܌Ly6 W^C&hE FM.t}"/ym&ba ^&"z}j\aƓe#y|W3&e_6 "JeƂ|<jlĘ5 7HYUr7GfPmS;Mr|`/PӑYl8mqL1WZҼBhV?̪܂=ջN?{l'0$\'{^T,Ŭu7$=IcF8Pah{՚ )Bjwu Ҽ_G81_H^k`cHuXg@[cר~kP3c: m 5&g L4 Q dg%|$Li#;(Jʘ Wj$`Dar^V .Gf&9D:f$x"WX6Q_u%vK)Ni6J|]L}]9{ѺmO%*BqhԸD\)wwխwC=>':WO]-O,Ym>~ϓh*{T*['"MB[LܤZzyҲyQ*4CJh:7hWA;bK^џ4oMUknuG(`=&}?3}[RӣLԺxr7O p.i)16 T72-^e!$C$I:Jšr@M'ufj( w*dKg3+l חc3թ@+u?R7B;8ϋ037:kCFk4> \dLٱTEK@<ݧTJ|}bPBݠf *=_Wٕ_~z8T:>Uң6]aLml}pWٷ`{1z - H"֊^ށ-?ed:o}Qkm{NjFk2cFm{LL`w!(.s[vAǜV }·?^Ac5nSr]m$|+۷0媎Tc127. rh΢Dkw =Wgfnu`ۆ䟅Ltc0z2< Ct2pJhy endstream endobj 529 0 obj << /Length 1674 /Filter /FlateDecode >> stream xr6L.Ԍ ii⤓%I; lpI*i%J5:M>bĸH}U~|~zn:>= 9" NH) I$cj#7i-4l2>zu'N/NΈH4L2''.M`Himmq m,pZy ]< -OZP1;?.tD֐͵L7 esBXK)Yp+,.ʺ1d3Te޲a,EW.t)\Le(:7S&ocN(g|rz #ŲUdT7vةmqT*#e$ڸMZ<9p((ke ^ad1?`;Qb!4d-\a60xL%B t0Z% I`+RɁЧ5|J%,n+-!At!_h Hd`.Hf2n qD"|?_N#r4u&F jbc9dS+{W k Z }`Oo!q|'yihi{Se}/%&SOhX[8,w vkpnrS)o=Fm\;͡8x~&=r0~3 IOM/_ɍE"NO#{)>M} < T#< ^tKΒ)]=ʁeDԢef#/T)_/ vR⌴2 + SP;hiV~f6WȄkL|2!+drN)V.nL:y!{H/ΐM5H!ߓun-怛c}Ho3o}Fcm@zu_D?=Q:7mE!2DH66,gbϞk*nVgP5!M[D%9<%?ڼgѴzCWaZ#,s}0lEVHj iүV{N7k]lrp-ɻ>߄F;z'?>K>zYBFDiѫpGI"V*\)ęN`-PCvom' 0qp e IrѸ{i4%qS[ծeZs^봩# ૿0`߇}N#ݶls/@B}3WW\+$&/*r6QWgke 0.8 84b YbdFbo#[jv} endstream endobj 536 0 obj << /Length 2874 /Filter /FlateDecode >> stream xZ[6~_! ))Ҵ[4]4>$#3ؒ+If﹐'YIɘ:<"yn9") rDEZ;%މ+ LH4EҠ\ =rQdy";.~{~J^|2T!y*әJ$|OgIJRtQspOLx%8+#c\<.2]2[4EoNߜϔE2{^uK˃lV%,FD쫦dv|c/4i fΉޕfi”}>Q֗ϫUU_><ģ,b֭ziҬ;;?jRj7娭ͣ9ϣb9P 9/T %p}(3Zuܹ:˦UǴErcU}ѡ]uY3qnzB=xNr|q+&Nj+[Fy,Dߢ)*+DT4/\FT)qIK"fHL=MmV/eu7F3#E2y:GԵ4}W`R¶:®u\`L]5Y0U9)I2l,2h}@jٮP6u׷b;tge敩X#H4IMؚ何Dގ;^C=G2ǃEyJSd\ (X~9xM|?DMVmKo9ٜ}\WL[)zI#U{ d*쪚4`V=;%"vAXGRɛ)A Rcxl>DR{*D6 Ӓe+f~yKNn ^ThނpxCgYcinz&, K3;<9fHI"=ק]lQ^B_W466`0|YBdǤu\ungt s4~5ڛZ dXlx5e&@P2"Tv`Mk"Źtܭ-mλ|ISkخHbkzS&pמ[w٬) Y#c A0c"ad1'ͧi//DA0%2VDV&DLX8^b2#eCy튟I@]:{P@0j]4 sklb/!19Ζ.pa6,7 8rKL@$l zAKQ(w -Of1 cMT)w1 ;ܘvNO/t<(K 9g L6@]̙i9M]Wy*pI$[ȫet&9sw>!Hl^-dB$j?_[6BsGm+j؛eͨ`,m*7e2xUo`^9J Y HsHq=Tc#VP(o8ſmqٯ\_7e0w^4s|x [~~~mЅAk귎_:xGs~H~,TxC趁E#culyA<~U~ҥ+kBԩJ%:0RRJf?.]*rYͭnwz!v eBG)z#9#*Pi!TGXד_x .RAx ..`,||2ȀQ*:!XE;siz:5fb)@V4> stream xZIs7WhaI)V$qIʢMd)L9~פ$.bR2,Ç7IB o0 o0 I(o#~HZ$~9S{ :ơáA{6L*1قpL9 =,HGa#.Ac)' k,7<L.kV 瀉F8bi'L0`pS-@ o;>x3ƚK1X&̈$H 8`1 Xr6Iɣ'L< I1rcq|mc676a+ -@@e G^G2cF#E#hxhD 㘱lD$>E D,FAqOB6HyO*ivq%L7vMv J|pdH ^Ҟ5 b fN< 8HK&I8As`99VE,`98訷^B).ˎ:M9lF W'GG<'%273&o_qQXq.Mw\5堼h`Up4˺TEwܔۯ㫋AI5ATWE}]b .@Xb)ڪ  eQŸQe5,?nFV R ?bU+V;Zs>vVH;m6p4xaagBV9Kk^xbmQtx+0qaDž FA }24jRgmONOb]]KBhd,l"X>u\Y._8ʭ!F`h0arF{sB+]n/< wןO=,y1wb}rk{*/P %͋jлsmo '58i ӯ3QnDDctn\'33ANv'%y]ic&ApHٓT|)U/4yv4(YDC꺬z\NL;^ZVy˟M6N8Ap|! C@c$u.1q m d/8j9ao-|"cD)1 7sVrq߲Ka=I<{IʶHLnCa݆:S>=߳Ms=Vn] eu]ɪT0L]{K 2.{WQ5Q4qrμhF#Q!7J2O [ഝNE(hq6cq1CuxBrBйm^G+Hl!5;3q#i%ō_el" F?ѝ1c:0^s1!s2mySw!I|&W;x mgՄv5P Y~83W<΋$a1"溒o:"]'ݙݴMkuX *> stream x[s_䁞9i:m2wM;sNryIf AłIɭ$;s/"Xe9K{37;s,X&WΉOJ[qέ0sJ3n̒(%Zig?=i%O۫7<x J+x wW Dd'[uyV(%E579, WEU7xy0uMFZz!1|Xum=nbn%6|oR5-%M<Khvtl31>NSh*hW،74>a <%"J #ȪuvmfDhjgJlLjihqXh% ڤ4c'k9O}D;w#[~¦lqU'$N C[7M[t[OcDPleĎ:mi lV)Lcz sRM8h`su<*]rtw`upۡ0{3V `sE-QUV`3+ѵŚݻNVFMF't =1C^bߟÄ#Tq9nE}#:C[~ӉFvDcCX.[;eϒpH7NX9D ;pv2,?tMKԈ-А35!N,ޤm,La4_˲Ьx>ک(u),r$k JVv,h7vpDƘf[&x,7m~.%`~ϟ67\AcfU }iǩm.s!@C8 y&!ΉASˎ} GOiG7MNWF.!J.CX s)U+{ k uъct#I<4 wcZݻCut3W=uQ"h'h L0ïTTdĥ 3#8q*(R W5;a Q:"h({ D؛@1"1%uF1X Bi%oCE&ׁZ%[ȿ1 I:YA*ôx .9 p5rt[mxl.ϕv$fnAZ;!m}+2);FaJ VJripmP5JÞ[&>j"ndM E jL:CϘ_ !2 ڷKKe^2`7o5oFtB%kρl/;[?6M>h1=kiEÌ6^sm1h9yYjmixu` hH*'|AFC ~1Tǜ7`Ї9qR91_$''s#hn@1KgM%[ 𖐣D eSAib)ͱ k .p]ZקZjKEKQnZ֝:LiTtxiB騕ŧ|}Lt4 %|[O&f)lP7rH.u!,`g(\nsI2q;qrȠD¸B9.:a lXv'&?4/[߂>A-(*T?hjF4 DZ-7lhf{I2>C`u cXׇ^ot:Y v: HOWO<a|4kؿ _jueW_#W'&{ .V>}9 &X- Eg</.w~bd? mfΟ}0^\́}Í9nNC?XEi@9j5:8'!jOe=/Is~Ĝh8T 1x`xzWg2 endstream endobj 549 0 obj << /Length 1470 /Filter /FlateDecode >> stream xZ[o6~ϯg$QAۇ]ҡ+!ERm4;)ٖV"i IQ<<~1n[^dכu[ʂ~_!ߪoq[.„X#HPb#Ω@+,\D)2iEG5X`d ptv#avtzN&c"ÖKTju1xjS{.e^ ]iLݹ.,Po(eq|9{7_.sUf3|'0e1)!ȳmSzQWߦ/`vҋR FVenTdCrbTy}#~18ҠWiǗB{jA sbÊPLUz.QEzv_HGқ0,RE" o5Xܟy~{;mhUcoSΞIY52lI4L=y36[{.M8¥^]s*%޼u0ٺ҅]d,xwK318ǿH0*ySa4h昽5&u 3%0sco,%g.}ɜ(;B"IȮNa~HdA=qȲ q(;G3Wb՟3tEWK?j;ZGϗwJy/N52(s y!MH&2]R1!eWe d QKύp D\3S(LT4'3bTbRzz.Zџ@Lxz'0eFfI' R(x"M0Y0upf$$(4/֢Bf^ng9y r$"@Ys$Nl|#GjOiWH㜷& nSeV>@A P~rV*}1;DU]V ߄m+ w)kO A`N)lE\s%܀EEb \.q`NBƮW\F@ Fٮ ,9؍=K9+V!2l{՟ϡZݖvPvfo ϸ;CkЋ1B!(] P+lh'/1pl;N"&ŜV={ 3\MFU):$F|w\6K;zLbQ61#X8|=+KͶdQfNT B;w;~ۦSN୅|=ZSCH‘9XGBrV'ǂM0rlOyu&)PxMF+UڛgT.h;ӫ8P?ri\ԓ2ֱ}~"|^vs^v53F^)vM> stream xY_۸O!8DR$hIms[.EBdkO7XwCɒ,{-b#jș?:b2VA f<ﮢ^X`N_+'$EZJ&RvWHERbF_uW߿*HsuF8Y~+#723֒I(_|+p9K|| yG4wCƘ*R@󟸎T.:ZZUbTj~u^4m_Ybm]:A~>WmQ?b1MfFh~q㿴ΐyEbW7έ6>4gh-ƞ5а&bpyҏyφ+Ec kXƒ?i pBmpC-xxJb{&{6_Hf'wܚ;TfhWWDhd΄$%["HRGHX*@KxҤma>fMHhnFF,Lс5oBv}>z𬶮`tؠ1dRo34/d؀7Y]>к!ܤ,;y27e҈/WG,QjYOK{owܞ)&' s HTf:ѴvJ3g@V)vʖ&YPrLE}V0`0US/4OO_;ϐ:b1%Kh[Gs r(J5k'<].Xz=Bŏ4x{۾8o8W݋_A_&aӭйk,/3 |Ty7 `|'_N uYiKOv. R?8xpXOCw1 0uC]=-u`]e> N/amŲ0O!aӟc`UUfZsW*2 vxZӓX0 ۊHl%ZyJVgeiKZwmlwwh2: ovp [9Ɛ2aBjӜ-"iA'|' Zf_6hƝ+ݩ컩V;mdʴxAc[3A/qAr<txMJ1D'k@K$w@ʦѱ ſzÃC[,G2,<w.C7)JY%TGJn(f>S⾝fwlvp8eIڃiՁLq |>&kQG.F-׸hID3)LhxD:yhggU HK ykhYS fL/ȱ24& :5tdMCӫ[ɹp޿%Br\^MѴϪ(+IL`,f̣S#W`3`tO\D6f^.A@aY>2N;,ra':a rH6c&0o<Ϛd3!9G5z`t92!nPqV1gI, x˝ nb ȇ5;Xz C<4?FXɼMgbyqǃ0kԆޕZҭ%֥E'&(UATDګq# XMʄ5M] Vן2@% )K"xvp |AzgRc5!ClV*4$~)G^O endstream endobj 557 0 obj << /Length 1852 /Filter /FlateDecode >> stream xYmo6_!*5#^a@5Cݺ: DBVQNG)RNn/y<9?xߟ$C|$ytsWu]ӱK݋ş?_œ/eЇ]-b)p)MucwX³)eQ__iQE<, l 9~‚h4gt$u#wpv9-dMզ}*ʢGp٭\x;K; Mjp> =zwh ^8k!YiyHo1!A˧h L/cWic˔`4Tm ٓ+X9rW0Q剛7nz@F P `@?Q.Bj 7/^p`Aם6 C\8fG|&y8ONm ݫNYY>"R({m3~[i0WͮyP(Qx$nFL#M$jtG.NTkݜV tC~k::{;Ml7Vh0}`={;?ywGp'ǹHH+'N1K@ ctYv/[/Rh6`<=?O2~yOJ}/Í( b)gUYƶgDЬ%'µA~a?<{gw}js iZӛx!7eQ5*v4xW>0 @|7Qn~ + !xvc@&QPxGgO J;v\Fkp cD=g>1A^0Pw[29J%\7\[3l›`|((g0a<;&n.ĿO@~O?+|H\o{_[z!pX`@$y endstream endobj 561 0 obj << /Length 1367 /Filter /FlateDecode >> stream xڵW]o8}_G" .kyHS:ݤ0Yp*c6m]S{qF}%F#H&śQ|㹣8mcBZ+ֶyUm֬T?{KHF!=I2 '{- ڥ8c~|5\1zr`h; ~肈{LC}.{{- !bd^mX -ؗ P@ӟM?oW6:xo6N^.luC[`X<[΢!|G#h@֫H(,N"h|VoGM|ٷ,NJL|=D'K! ,"Y| hw-v- )ECbOKb詔 ?M'JE#(>`]Yel+S֜Gf+y7]0hzǫW7M֍rKxelBmhW('fDWrֵY7 U5+mm𫄉@&On&c!SE] …WնpCo2p/,1sZՊr[5>Oz'<1;ƷPMZЯ-7KwHeuQqd4ΊnE.-2O F٦E y{*Ѓ]rŊ1T쓢IwTCswRlYQ .h M HRL;d}{]4C1Iݘ kVѢAfT)@]`z7m ?Pr9j5o~'y??Z`q%uPU88k›yEckJ8|de#@A$q8"Gӿ endstream endobj 565 0 obj << /Length 856 /Filter /FlateDecode >> stream xXYo@~ϯXT r=  rujaǩgvisXr*3f3YBnQ HK,)GRIkr;AKGsѵ,Q VJ@guhU HY`g ^??9i# ^vHdĀQ۫m^IiM{5 a)?9˫'S;xw_Js87S|D4ӗ#Du_2qtgĕP\q;/"HaG;Rl&<:绸aڢf&.;^"x]]wbɺJb[I+Ǡ4}jumdsRۢ(y/b8ˋIyËcԙiٴ)-p4J[Jm7 26Cv@+!ϢU|*Ûev#mk n5 {k w9)0^pL)[z'ɻRW~?X#3Gh;m䯯(b&1P_q̵rs;[(o3=`T8j[C+s6OCW{N%y~kuWr^enw}d I[m32С\{ td׎P6um.nm4/l> /ExtGState << >>/ColorSpace << /sRGB 570 0 R >>>> /Length 8084 /Filter /FlateDecode >> stream xMuZ& |YzĀa=@A` L`O㧊$V9`sO~}/Ϗ~2wǥǸo|/?᷿}黾?J[?__߿ _e;<~et4_ݗbóOlֳ xYrcz [Dzwz=Fm5_lx~o뇽߇n{{wEnzᮗrkzӫC=ҒPcn!!?F{[>#e(=[>c֡>:Q18{[>c8h[pzi喏HknC}~^llDRq{TkKrg{ ^>-GM[h$\<[^{5eF9X5eY/(|p>_(Qly}G٣^c;e.nl{Tk[oŖk.oGYn8n)obG{5>Q1Gu'{5ץ>ʺ~z&,|ǘZQ~j!]喏Q1N#Mܣ^c[G[l=ގvVqcu\n=5^u-G!}c޺pfu<Ǘ5-=.k[ ~[ 喏v[K-X5GC=Q=5J-לߎ2Q1}7WO8<ގr~t-w\e(*|GZs?X=-1,\~({uePnl-Nj}u[n=5/ϴB{z|Ona;|?}kίaοLOW}9K,bZ:_e>\꼶K}^Y &^Χ+ۥۆ >{zF\n^)6ĽZIk q_Op~u֟K q7ZںlCܫ|:C}~M<{!|>> 7OoMϥۆwtk_j~ɯ.6ĽO3MMM/?浿G>t.Z\ƽZ}Zw a/>ߣZ-Wxx=+^kۆ >:hC;t~n{2jCܫ|:{}~M֟+K q 7Z?y4m{5Oz~>.!Bj!C~ 7w"^H*<Ԇ -6ĽZ_6Ľӕy^?G~Ewq6>a]k?7b~_a,>?zq|%`x\m^?> W/|(g!L!C䵟nz9}z2>jCKoziCܫ|:C}~Mﻩ؆^ΧәW'm^ߞV_jt]޾!+~n{8 ?mC˟ a[k?7b~n,>?,{?|]uZҟ[?|%y[kz_윆nÖ/K6M6m>cXۺv{1lK8ට !-ҟKTz1KZ=1a͗9W )Z<ׇA;?n˖_rKr|m[2'ux82=ύ%e$| nm6گm8n1 ߉-}pߚ%_rۦ|=mۭK7|nwb1TI$Kl72I MRC$54I MF&Ijhx#}>j꣆>H5G QC}g t9cQCg5tF FΨ3j:8CkYpc x#g5tF QCgo:Ψ3j7r;Ψ3j:㍜QCg5tFM3k:Ψ3j7rF QCgg4̖`gΜ)pF Xcg3k:cXcg3jg3k5vF Xcg3Z vF 9[5v;cΨ3k5vZ36LVz:cFΨ3j:Ψ35tF QCgo5vF QCg5t9Ψ3jZ3@g35tF QCgo:Ψ3j h3;c x#g5tF QCgo:Ψ3j7r;Ψ3j:㍜QCg5tFM3k:Ψ3j7rF QCgg4Lb<5FyF#bxDo<"8HxDr<93;G#<#xD<6#(3AlGD#*=#A%yD<6`#Mmy@-H5hd1b@#F@#F,)Fg4hd1b@#Eb@#F4@#E茑F,Y 4hY 4hd1bF6ҢQkd-#j)F"kyDY#EH5Rd3k왵3"{Ȟ)g"{=Sd[ƞY <)g"{Ȟ)g3EL3x#zf1L=Sd3E#zȞ)g-i3g3g"{Ȟ)g3EL=Sd[mgg <;["{f1b3g3g<&l$L=;c3g,)g,YlF<3E쌑g,Y <xȞY <xf1bg6ٸ=ƞY <)g"{Ȟ)g3EL3x#zf1L=Sd3E#zȞ)g-i3g3g"{Ȟ)g3EL=Sd[L`1}>Q {7xߏjcOs}tsG˚Oz)m? SzYϟϏ:tv5o~|Xo 9Lrw9߬c_~7 ˯t_4Uw_{ͧ@D}ލro2!x+xFv<{}e<7O@O=wGz}$xҘ_̟>/-4J{]ܭ ]Nݭ4K9_»[hneȗ^߭GJ{7\[i[rKu˪CnJS?t~_޿]=k/VJr~И.+9͌zu,w+p߿sґf}-_~Juw /\}nBr^f8\=φίLsy/_I>>/"- o=O11r M4c=h9Ɩt h8F>M4#mh8FZa >t-}(6r!]4~,i98⦣h(%h(iƏE#-G|te`t{}i9J\h({76~,i9Jz?~wvQ4rMGHQ_vi8~]Q|(;Z>r-0MGHQvi9$h$8ʱid?K5b?9:xe=fi9Ҧh(l;~WQ4r;MGHQa4E#MG9zQl(/GsGZbʢ(i:mG-HQ<ʧ_z-~aw)n:czE׿-+pq7~Is~[@\^A?8C=[Tooq|t{_ ̴/T7|$m|3|姒^;Ï}*h>o#v-;(܏5|~;_\b{}(=~rh_#ѯ#9?L-?hyMC15(ס1&|zsyMS1=g=?OS^}}'6>}(o(j endstream endobj 572 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 493 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (/tmp/Rtmpei80N8/Rbuild1eea6baa0860/NMF/vignettes/figure/heatmap_consensus_inc-2.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 573 0 R /BBox [0 0 504 504] /Resources << /ProcSet [ /PDF /Text ] /Font << /F1 574 0 R/F2 575 0 R>> /ExtGState << >>/ColorSpace << /sRGB 576 0 R >>>> /Length 522 /Filter /FlateDecode >> stream xT=o0+n^xǣHvL5t2%`bѢLوU[,H;x " $\ gj5,Ek- ˛6xc“!Xoe(o5Wǵc%N- =6 65^Юl7K?1[@o0 r9 g r͙s.;ټ#I&ȃBͻd rwѮXZϋ(X6 rdz*XC> Sf+`h9Ŀ}ԙ^Bt:` ޗf\aVH9_t9OZXsAqlw> V{":|+.{ |1$B(,_68EtGS<]QӈebamW ]2=QZ>߼]EFXٷק98 hgKuɃ6\þ3P endstream endobj 578 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 604 0 obj << /Length1 2266 /Length2 18652 /Length3 0 /Length 19981 /Filter /FlateDecode >> stream xڌP["qwwww}Np'8]!Xp.Ow^սE|ckε QVc1s0J:ػ002DYl̬pV.(4 +{X9]e. YW[ Cg^@ `Q98z:[YX'ڔE/w`b {hjl Ps0xOj~KG^&&wwwFc; = ݀f(GPPs0wq7vV@{л&#Prm,=07ܿ de/gcSS;Gc{O+{ -$)B07ofleklnҍ"*w2urt1lW6K؛9]@p'n 4}'ӿ?/fLVN@ۼ,.fff.Nn 0d+#_J|4V?p c7 O"8 hae'h7~?g+.9z13IhoN+3q]?|e<ާ7]q)+ߊ$]mmGolgeouuy]ZWWd\wAmIZy͔\L--=Pd``af?2y>@3/}y7_[~ƞp8,h\]|p('I/߈$q n`/b0IA,&?}4 6]z.gW޳+Aٕ߳+AT|j;IzϮ_Ezng7޳qLl?v$vv:Y&DxP1x'aس/?M,+S;oKOGK?,eVm97|mxw:?ߛ~/8],?;pey/'_n{?~\,h;w8Sr|?;!ջ?|'{$/ߩgM]_ݼK-/8[w׊3ORk0x/;w}DLSyN$yemWVxcX[Jϳv)BXu'@V.Y\'Wn$|{~)ձЅ}N9h9 q8,1?'w:8*䡒6Vf|%TA1޺oyſ!|]6x='L/^-uj1n1P ;yw\X_~˰b"UdqMdǰh@hMIef(9xM-JG_Ydo |[ӌ~'eN0t0ֳ;p.\a/Ub5dDBx;>nPQK"l/zvvia ɷVCP#f~%F)Y\$.v9Mv磝D{d Q#"y(dm:Ň7Վo ը,#^3f KӨw,sd& ,Nj"L̉oF|Yэ3BbQlQ0#,*gylkTQ; vvQmriu3IS%FoR͕{>k&6ݒ%|fBdtqgj*{HOTUzF*QAH;뭽&sjHD D+)R#N3?on {QSN%s_AGvQ@Q'| 0|elC6gXbtgp$3_'w l4 ݔُdNm6~0彛V=HDUNTas*22]5Eg郞3GBI ig.ӕcdvksk/#߻FNrH={[AVl n.. |Xw 1x`^$H|ͯ)5 cdlO_,@\&$hm_#Cꉡ&sOّwk#xhѻ|Q}b+_:]@~ N~1EoG"E7-l&RfY'S!+eeXbMJ;wO4 o.7srۍc?vS7Ӡ:õ46H /@CeL`(%־͖H$y{s"k"SjcDZdeO$$g2׏{d72Q{tM@eE٪=K#C'2]ʳ>.\0l ;^B:߄U1EN.NW5%D,mҚP2?CC$PSq+aj`_m !:4Z1AmA]hĚReż ?zˀYQ{o(}e DAL%|fV~ j攰$N1|b<+ɉ%0ls=\rT$ Mt.}.E]U]'(\ŲғdFsTG5W c[uZf { tY_d>"UHy c7FT%h&Ų}ЋքKwhyRW\ Ԓ<6gpLNk#.+~oB[*AsJr(C[g ߻#?},gѾyrDeM*wW |8txs`ܐ8O ^kveT+*ֹA/O1gAq( %%wf^kwDkᲿFg@[^'<7|h#HtXC$ (*k_.4tҿ+N'L jakAh"=P%[ɠ+Ȑsł3;T4湻L#:y)bx=)+dprZN f7XAi&0~B-l.@!i44bp^]hwGܶA^ bD6Q)/i5A?df,*. U}=d-d. )D±[vH\DDJ oq==}1C5mƌ_~B!uV[ ;&%'S#T†s^Q 81)M S#G!3_¨X^M +RFx ?Xˢzk r61*#2A֣LiΑksIcB܏ݞ}ȱُigABpVᓎ`&;zΰ1$1DB30GBwxY>lZEWE^ 2Q<"0 mw}F`"WS&#ZR! $>6 NzJs, rft4qNݖާt܌a$%B_}Ɔo.N;k  tDp.fPN]尟_䖄}ŵңfo hn_BV)Xΐs\NaR/K[ӊyKv3Nz2[mk`]l4p\\ Yh%p6FqP(@DêBX؞=Z ˵beY]e?.OV18& _7r. OmIgR.T;)_P=GNVHjOtA:#9COO= f΃ҏ*Xsie4$@~LZijՔ߲ݓ)n9A?C>*;A dVs.M}'vd\0t{?p(ii#a5 L^>4 `#LHHZ0z:n-ǒIkt_Uy@еd&WcC[{ B!+ >n u~TtwN+zh\H2fuG?WQt~4Mubp5:;cϚ'\=f|4"\2.%b%B..Y+1ʇ͝Ae#:naOOԋImle6ś+9*kTYTò3XеmGP򘦿*,r| =Z[ں\^ M &TQc9^Drm6Wƻш:?PӆE Oqʚ9Io"= rk2HXQEl/NǩQhԋaLKTZ'FyOl 1`R|SVGSjQq7(o"QL0 :h5\V.- a rC"F|` %n;fZė7)¸Q׊35ެțTE-\,VJe i#ozV}(jT}=5'"kz_doF~A|H\V,ڵt,sKbqĈ`pktn\ P pBG&.LU7b`$ 3h;* ɠdᲶss,?_>ռP4Ez4פM.jĥj9];PjdM:dep2Qkڷ#*$ yBi;崊\$N-OCp+SJV2٪jXǻt1$7WQCqۨNľq//.ʢQ]CF\EwE _!"Ar$x\S#fwQ72R$'s1LߋC9c-?-elP3YG~f9</R__3>7c9eKnz73qjW4Ԉni,}t?0*Uk$d.1zn0WEO")58Sfa*ɚkHR6&q=C鑁%YTc.2= hvo"])蝰r$HMsqkxE \fnͥxXpQR <5;ʤQ0UW\c&^]<6t?-%O{,%8{ lIvxY+L؊g|YtwJ#7~hU&w"NOP>C\-W큐ŏB\(EnKwG8g~*YLῠ7'[-}զ'9/< CA}|xm[^x}>Ti[3`j&uIb}*\ U(ݦbv n L;Og^p0 ]zL(z)t ƚř<;YȩE' YIy@Q';C `$/Z2tW>6?`˛+2epOGUsl^T9|| Iͫwҋ$Hה )KG1(qm [\@ZF.!<$^|`F@b+aFٷH_dj34ɂH)Ufq=o d:콣`̋ Tn< !HUF?Ug4~ѩSڬ/i٘9xC֓'f&5mLT}U4I;0h[UQ[wRpA%ʑň o?s3 aS5wt7 A;36^i>,IWV_p69SȾ292Qnf7gwy%I!VLl\) K\%mm U%.6u"-~Z6>N,R̥۟ ia_.T*QA^V8WCExyT~|v1'1$4Y'$Pg-0ඬSܰi5TA"z EK~>Q Y -%w)aCH2RlF?BB>lKWklPU!Z.%lhVE<)/yvZ.j!Y1cvĹ^(EDBWP:ؽofy aʌFfF<пfS4JAsMH?m#)Fp4ms|}lJ|_b7"{ULؒ$ORcazNuFX|sNd[F}$`DbtċH$ǼYXaoa^b8Ŋi (D;r{'^;]T bK06#,-ܞN5vyXt>BJ<Zt̆Ngz@Jia>G7^x+ę_t&1cU`0PhCQl ECٳ Rh0.@ E*f](n^ ƕvȭCAyzx)x+>5DJgW;gFͻ9=ȹ<5¶M~AloWS3hSC(pfhĔw%vXdo(~0 ~:oblf Cެ˛(ĶnoTQ[5,4GMXϙdlRRTF ygѻeGcPت;7IBO4 6+$? 6.Qԥk+w#aݷ"^y#:[A`'ǧ]x[eMxMRqi\`C(#{$Cem$l>xDjCq U߃ 9,Փ1֕^%%g@6EVKڬQfٻx]!2;J~^deEo.CW/RrQсWUD( t]>JnJOO),q֝Oyi.u832#w;-IJ#7&܃{sl$ۜ3mo$=ʆANhl4 Lo8uMů ިS̋oU]dq@0߼k1֦>$keYtyoDXo3qGuDDsS5 ˇ&nP/XmEc 2UQd̯vvWH/eg㢟1U$d%DnLf$-h47 Xl)?6Pw2:qqqFƁ8֦_qe un JmNu!N uu5&M6Ի"aPxXjiaqv"V3w2',5az=H9+DV(w7- CteD6ƒ/a V@}DYqNmW ~W iwdTI-ZF􁿌QGq(>Om?I~*ӡ57͗Ԣl ZiÕjsQ]3h2 W3auzDdf'/l#ެ_tL#&pZ՟mO@|K2LL|JHx[u0 [쨽~ef=L}EG}.&Cٽ!1yX TA9ӹL8<ٞ rh)(̩]'YDc^=B#yֱŵ0Ӱ}\nժI(a_zЀ?޸$IV]/Fޚ00g[ͽ7ΰ+^\_?3sE$L\/au'$ C S;My\Nu0B8Ysl\nTE&qu~i9Vx/I@ àQXAxNxGX|"ZDKGS#׸;oy)܉_$(y+h+R3 B)tk[7Jݢ0G3#c6f,Cxҕ% cj;(cjg _cs$s!󈛒!zp]+cQ{lq .Yte)M [|ocX>!WS0o MmLOȉG1V'%Ht[J#\gmSswN!釡g{<] -85 fqJ{Π3?"[夹{ANnhTkSB^P?`Ar}g',IDx/^xs[5$WRcF ؍MR^zj\D\Pa2`҂X8p)tS́x0_~\s֖· A/tO - ?[n֌S֧BBdh"#'փ>SVΫ6֝%3Polv;LU45Bbg(V~eF:՝R-g&-]y -}UTQ[ 粶mP}z\*$|UReѢUv wGb'EA;w(FڵN0QQ9ZdF&Mu!eX2:j! ӞOļUD8v'C.G"5+n44lw048hk{9i(goL PvlTt|X(/{x(!ZkYGU >9T3ubIӐҧW+d3uzl]A2:}t38*ӷAHZ ]%r2Di~kqi8<8b*ٲpc K,9l1$z J9VwNiu"xؗn1_'ms.h0(b=n%ͥP}0Ú:%q֨o޶U:D_=DF^%4I&,ʧl n;U8QjA1Egk 9 g qLDW5Nh(Өy!;_Oik"c5x3Nma3Zsj~ç"*VШ"Y?\oH|]Af7eU\`IM6;U*4R^<fAo*-a*pu_'vǼ%yk#;9Ԣ_.Zt/^ s Q]l\$e-V$yd\*s4~=1ֽ336i=+ɔrlA>e\Nq KD А1P?4QVVϞcܡv1v呁JInF̈%ddXImMtp܃՝TU7<ӳN{l!oF_Eؕl NkkzG_ӻ][^%-(Z=dBTKyGPe zn<~lHMIHjvvCpUc-y:]ڧ5Wqh}l,|"&9SH]g Djhk*0B8㫮Znbҏh0bs䴽&/ēgq8Mv z0DJHY=BZE_1+ar8Oci!{N5;>_J W;*%Sˆ5Xr.H@!j'}oHve >ʓI/-ثoǯ/oS25EAo@h@)Väg ̒0H7dS4yHڕ=}7u= 9ы)w`2[O,B97$A&R~# FPm v 2ܗorMg_?a2RG(":~x=58]ɋM\4FM}7?Fj \Zi~~-H](9g"=;Hc笺8hkz`C+uVB,!0 ]MڧB=&jW;`S0@eV@զb>hMjj@~G- $UUZHU2E"|YtxngE:7jR'fܯBr>U]˃}Mp̈́pJ?& G'dAtmg,֯V%Կ Z"HB`?ŬU9w:/ƿ%u]8@v*Vts%ݨ*[(OZi!X,{%9坄ݸQƟmp3b%L^ E0}1|4\ e塕YUTS7җ!`!3zyWJ65@齭(.|4taçYˍ1E\oJ1_fR•܅]ZeOܲY~:ہA4rVdk#o,S;A*#;3TtkZ#G|-:ۗ/=Xt=Hn{ N.iűϬvͭ3^?)!G@r}>ASyn:%ke}}%7WE+L&lK];}4f4SNCkWi'=x=t2!H3Qq%'Tcj?ttع[xP7B<-q;dNX El]>=DŽ|JZұj@X5N bt&c 昇@3F[asˀa?fX`EH+}\mOD!du]ݕ:?+$t'i>73ٍG!Qm49+pA/rnHe7p_aӞȚэB-w՗'{C2hބE:\[2óhow^W\<ݏ_m62[q&; 1$ͧ2͇6Xٓx{G9m_{1<6\?aFbaK,{JHG8!-a&aڽ0!Dr)f"ͦbִGA[&C"d`nzO*n_}&۷x^oYB(ۭG%lEVN9 4s8:t1{SURUX8D0F/SF&Omݒi'I礜(Ƥ1- <0pB ֎AKt׌2uI׀} ]3tEMa*%N(fC{#x{<#5z>\mze5S\gN /.Xƫ_]/[X!w:_ X'q_֫)SE8*{Y$t Z ~J Tr8yWN/C%]b>kaݼ̃r$)}a (EDA- P؇0)z&!7{:dqL iR2Wg{ł13^=Z׌2T6 k:k$f:5 (fFTw>Ħb(IA2gn>'*\RU{8X'f%yf(x?tOoG{V5c@)P伛Y%ߤDG6+*v T,]pJv!u~u*N_pЩ7YUT&vo+C/c䔄KL+RJ0~sϖ %h8VWGT0W쁾ҲHAHm\d4D{aKLsp֓R{-L~f1:bgR͚ KN6&*%ˢ={tޚIUD-9cבgk%7m4D@8.`j~4!6&Xq*Ka){j 4)?iI<hi+Un`?i$F`YevdFJGP;'" |%v- DTw [pq=cz6gGK([XFFYWoN@{bn1~f e2jJ(M%8Gz] íȆ3k*/ 3`+rCWxz,&6<\X|ACOG2\J6& 'y.óY!8~("5QxEWed}>Me+jDE?=1Z7\@%S<I;Q*WhչusQ6dfH7SɂE?c@p<ŧ}TNۮ2OcoDm=K8 U#MZ<%'\R^xw>󽬖T , m}M.UTd믥g'ntOGqAVu|9xAsG)U6Z _<p&FOaTT V=@M^ۨٻF,0+9uQO%v64ݫkPWLgr.~{N5qHG-g RJ](*]3T.[ }J"HaY2oX2a"0]m[C (hc_`HX]#v*bRcvԔ Npͅf E.PG@1w3$y,϶,-xt@GL27^!@O)3$6f[JLeNI*%͘6E&ß{G:+BS.`r |*vZ[@z8JͳQ*Wniv2zcSrJwoRTI}OOa-Ġh[^CG^Vi hZ&T9-:fP60MX 0H0Q&mY#K^歫UIǕuYGk*wYȴb]h?B֧a9|d1lJ{+d*$!|RaQd.yB,_=SVg-Ϲ~}Fd-s0 PkxMdi2݅Fo[oB}aSSw >ˎMT#|5,;du;wïA'Kd l\(j<ذ:Z2{NWgQq޿Jp'a6I85Vffl:+!XA4hjp+Vs/w0FBvFpRa8SEH}# SVr!1){Du߹SN#i(ڿ)rP? JZXǾ`i02v)U%OHNV[]bRxv9dm2xA"J? Z\(8Jy)Cm !P i ąnY1}{XM *}wgD̼2eQnKdio0tEPWv/aK9:xhMoѾ~SUV@+}|zʌIً)q̿":{*s+_$eE_ H{&hsO,ZaX@[l=/@:aF9=7默s`Guf0i4ol"I't5Hӷ>+f~+,WúSC7WeUR8Pw GRM{RݰnEf~!K2 8 )Md[42 '"g; K\=>hZ%R*e:he)H՘yekdkr%"GE3 9PQJ0aGh5JQwƄ_̽c*[V1.m?Ao]q˪+y 1Hub֜"HOH+mؠSbJxir')Ro;g>GEF+؛D0?%CbBŅgeq5l%Oa`J7E@HS$l7Y(jk{oo7P2Gyq8ޯxB9ZJx9[Y-/QMio.oC~suTs$=_˕;p:|ZeoҕҤR.VA=fgKCA G9wVvv^ OQyۆ*%AIύ0vJ)U?MXkNBcOy䍏'Gz+I\(-_ڜ"-eV5 ;ۗZGCYԊfɼO>3}`_E\"0gP&T{UV,=?Dz^cbOS'ۜ@_v@ ۽)u(fCiCɈl=}ĂHHذ)]@ E]{2,f;\ dD EgT)v4>apkev;)+Y6omI~Z.nU iѬNtjtD^j0!CA15O'9oKVwwu f,5R:9uw)GÌ2/Z Gy/X҇]?ޞGՂ  endstream endobj 606 0 obj << /Length1 2096 /Length2 14738 /Length3 0 /Length 15998 /Filter /FlateDecode >> stream xڍpk 'Ml'm۶t0m۶mk=$:skZZӤ ʴ&F@1['ZF:.# T 9 ֆ@CӇ @ `dbdb`010pց bbaHaHm,̝>@aL `d h t06:?2Zm-NɎՕڑjdP:\&( FC P1pBXYm?\mLeI_229F:_,lv6463q1ZXb2tNnN4C m? ] - > . &0`o~vNtVq+GEmLm6N0'b4;?/&v6@I|`̀NVv6Nt36+o%_ޞvv@o S   ## `4C 48 761IcLlm} 2rJR_@`dd27ſ`+icj W}O./%c~L.@guX??.(+s[O/->&c dm?vBV&W'd 6fVmD_/_feaTuj220v[~\3 l?^DPTfGAcm ƏQ8Gi.? Y??.?14cGNp`?dL?y g[>g tì,s}|u9;OS=sš *&3`N0ii}OV`*bdz׋~' Cx*^^j`m=R n Ck!K?jؤa_hUtKH h?S\!.ͣ|'>f.fy\بTar&E%:JYZ+YlׅhhRGسS>}gX_qo6$XqThJ9 %dQQ#j!!L)4m K-?dQQe!N9Ƅw_e&<'>OC7O߿|־Qoƹ(?d KQ M9~x+,X>HEMjN['>gj)3GlKdl8> jQPUdMJpNdZ4 )LqY uIe7C|Z'7Lq*hZA[EnfKq9]xE{5(BR(8qJ1MXx75X!:XQ c/43 kEHTt ,/ja'ixYj]kócKjinYi}@0Ic,Ҡt{G!(NS_@q2yNīwA >ST\I5& @@BwY\ W@d5]s&k*i ov\>(&J<2[z4i#Yz')?Ͽixy(ѫ?#BwKB4}6V4"l;Զh)L$EVsFpڕdo0( MS4 \V+q Z$<49>2vM,n/8M\oEȜ:g6f$4 dUBZfO k5fڟR` 2קu82gsD4ݬvΘDT.2U.~v!ԔED@`ڕN}n!jCb]ƒ䡥'x K>p}YFswp$?e cI㙷A_y7{t<,Î wy@U\W~:<<漕$i$l#N o}z܆@f{~fI: @RwFdeN ,V$t`Ezx>6miU84d&$o)IzN({|{J>_W*J{RNtujeح$Xdލb* zd4&<sB(I1eM7wr q5.ߜ+g86(K+ vzyMDǞg:^gj4F}V2co*R\.8/QrTreLMKa @&m' sw~9ztaf@Tn&*ѧ9`>&c,ds=^6j*L}ءT!0h qZ9-wct{fq"PCռX^Q=OsK乴~~bݮ*q5"RO}P]*-7Ljr[s1WݤBQWk$x?hvrBqmSV$ , Z+JUɥ(;G2f$7vr@~)Uhaa4V])Km-6 wύ=ݐ:7lxv,i̹3(ۖxO:1@B^Lhgo΁r?3'UH {5].kP$ ӐKu >2794]ӹ!}^/f֮"0W`qz8Nj<q|'C 0Ks ʐu}38xRgz<ʧ!Gn9ӎ)μIIP1ouiO0 ŽIzht&?w@zIqi7۠$f#w>wty1PNݤ:#\5 9G#@Pi[18CSɜMSqJ Á)uiMo@e# cE~I{Gj|߈a&njaz̬#|CRw кI6+1f3H|YQQshgK{6A\0biƖr 6O.?)ғIg=?TV7눹ܪY!u6k\@: S" Ngi%\ؾNF@9CjӬ()O&^;pseuVJ`!OR#\~LyC` Ģk3_m$~U_m'[)')\mZ z:`PHϪa4,A{qrM6|}vpP!KlvձQ{ DŽ5`,9qQ -g~=z4=͗gM~=lP{; u XlޏY ]!i/4gNB!,%U/{0q$ 4n ʌό Dl.@ѦK߮;@nʮP C\^s0C92hF%Hu3C?vx·&׾,]h+F? ۢAĮܯ~^{^*jSkEH%۷PYl}5-UǯQjDO ,-xϒF,",”B8?$!Hwgɣ0ڟ(X?dǦ*p*= #`]Y_%W:c3o*`YcCY,,h 2EUE?[SGMpFb؇@O2-$6EH&9SSҢpv~[Fe\2A@x1EXg!)5V NܞvgI$lN^U f3)r5­[c+\N~yP JNhuWeo/Pk&Y {J^iu~^p[eujRܦVe5J18SV!^..\'AXڤ˺Ʉyktb"d"X[`p#"Sd}pR^cDӯV3,В'KDQ*-ms萨;qL^ _}*YYN֧C،nN9 0Ֆ@/Za^ F@jt$̆\F,,s/j#͡"6 kh!EBuH >JџÒ6zJ7 hqbwzMV)mrE*Ga|kQ0W˜>Xui{MW-hZ=odջOLӷuLtEv39dI:DWA܇p0GHE#;Lgy9RXi ]gwC9KEcLGdm7אpU>YóMp&W 2#v ۗ]=9gq[6z/қv4{6v}_d|EM٪*Р4X ŋ2/:` !ųO &K]B&sƊH/Nhp]w`[' 㦈nw2:c c0G.^P/7&lTܖQi- xbr5 UPCkTݵ~S~,f9sœcOA-[z$C9>95R?]VO o*!xp`Л`MjjH("Y jHOMк'inM-\(ys:ـL.keHѬ/vU,pPqGs_f_;dJ^D`}^%hAPZU$r%s"q'7+Zjt#H4rIe#MCtӱb|M1$#|t9:{Ef)m\r'+t0xp4vo-|/oBTRu(wZ*l:sa-8=[ǎ|J_jfN!VBµe3UڌXIz""Bg` zliԚO`wN骎4s{4s4 r&}H\Dh7y^(؞J䙵HZ8*feG(^{L~⮃@ tڭK g3yDX2HΜ|g9Dt`ܸBz^/\҈}T0zʻ*ZEfV}.)|DIsicHFIk"WM+<~VO6ظE[v, SC์;s\)ow۴`~e+Hvse赵-Ug{ZjsDW̋ǎM9l+OS<$l)Ni_ =y(?{*O1Nf6Tssx*'&kDB#@S/ =9f̮s"Vd\'^.] ,RKKQU};8t{atS~LX;HY C'"Y5 X 4 sWa3>kAzԞM&FԳ& g ϣ s\Six-\s S |uѷS+K^G[Nfߙ+Lx3-*czt.|ȚM /;\qKIinDP?8Uu1&bCqt n 2ۋ78)N_ Ʉtb0]rLBSHYx~yjQ~4By9Cm򒭎^ C*7~UI)AKP_[LJD vEm=~P/]>j2m(JͰ<͢$ mON')R`u9\*U7ijZ$t+iؠskwo|q9alԁi\…)vT|BSG4\m{_X4uNGq4@L?QȊ@Pmu؛/|@1KGng k(cf7dw6|cqT)M#/Aҵ&BK#ݺt઱%KzyIi`iwr _Z*vR"~mBU;-04 sk=|WJ^@^6DaY͉[x0Ku'YLl+Ԫv5|}sj3;298ј4>YtJBb,݀/@,+b¼ܩ=eD>!JWK0yQ'r7W^-,㽖Fښ''j?ԔA |Ԙ w>0L+ĮMAKzBtxW}2 ˮB@jAp oW $OE<6n*%xfkT kJz[D;B6AKm)$/`!b;0;!a%~A):/8՞`ajC;ID ؙNmvP{CD/ ?1jfF  Rodgn2j/2rtc+ƾ8T%rARF-pP'A'&TMݵ/(_43 g}Gɶ& b^N7HKU˶\vNR}6!n22S`f^tWHi//;Ӂ-=~&T-ey16&vd-B :%|qM?k|W]cOi/MUUtŖQ<]}:gÐHiihQ!r cg~z+kK@rև4uAH'=" ?'l\ʮ(pwsdgÈ:Q .uVK 2ý(vG) ';ȣeyix.QHUZg(XK|sYiq n,O!ֈ nUIQk{uNѸIzǟn<\n!dQ7p0}}+HTw`λXTJ!6^ \)S\Ǥr&mX%OSN~!593/v@qiR^*!j+6ݡ,sCJqνTnbcThd%1]'4_.Y*H1%~cy1)-Us<͠RzΚZʉInM>1$)~Y O}lU&}8n؆`ͩ_}VYg꭛Jr{`*و [XWuD 43]jRul)̃4;D6 >7۫C:HOi@*6 6},KʼnqP>H0ǞxmyiSp๎ ]Wl1o{=+R ,~@/L!_c(@G,7Q~ׁ1&5JaYin4y!v~PUo)|(.,bQ.r}I`%r+k]eOh&VJӠ2<(_S-ީe'Mh5OQK6QE(8 f0Cŏv{N$7qaWOjCSa7EDaFfϛVwbT5iH< ,\]NxIv3#wοq]aBV ãТE>I?10l1 :^.5Zj^nI:;4pTx"րOblhrm.0E¢(ƞz2-@d0JҞg#-K-bǍwv=x- LHPH#vTdD!Is#jy!rK_Z"9",)Z+;뺷x>&?\:JƚDh9͘[e:nhjܧ[K_"Ekt2٦x }d,pLp0֫jVhY8^9U~U^S]V!4& S-.|=h>d5k oQX;vFu4W& X ϝx^wDü~R#l쉍RbS̳B #AR`^^׾Š02 7pVy{(Q=qm'a6xȞz=ֻr}+jL9Q|J v <-1"X&~$wXX*sZoD`ɔ9MMd˕6^1fN M׭"zWB[ <عE "y r]}4[ *)jޓ4׌?^rh7vmp1 fgFnIp\!Kp d-)`l˹R d5|t1JN ֊?0DQXg*֠Βg ?g]'*4x =+5Nw*,FHrڨ Cp +!G~iVO4GIG2B7(7r[HӢ P 4#l6A-5zp -KX;9szZܦ \@ ~)\]` 9̣eF3oRbW{,%,T"^@o̺C$4xaңc7&C ɶT /<)qGI3?cbo,*4;n;rRMn ??NRg6 0MRL&GxJX- ]a_Dy. rȸ3ˆG g.&6u|y|D",?^{<AVzF-Zt{N5$pjNAZ9ŃI~ck Q# "]xPQ8M&')ߪU,(¤BIbW$#J^"Tr"N9/3S8g2Ey[J'r\(LU!{hGgqׁ[onL&z*Jlk$8ضɜP[34Hbh A|ʒc|i,ss- '6/z~Pگ4}m8.x\%gCw-!Sv5a1WKPksF.A`dcuhjJnFY.7cU] n#A!&¥O41<;[C(XC؋N~n8Hq`th8g"̾vxML0krQ(2'PT^DE=RFd e%H1߱WSdM^ʰX-g|P%z>:U;loɅ X6=*J!ᠩaiBJ:gItAN/a9ޘeMF3y}` g0SGj@ۂ(K(Hz$xB[2Wc={ 6ra(ëܾAnzKAV'KڎL!CIf3^dܵDaJޔ0W&#W&ѕ3hivh`ʅXV"SH{=6'dR A{?[|(:ZSSwTKF=vhl4[K [8L| D933+x "/,%k(,VME NiE ?<Aƕ Ǝ=K$NMTw t_2~NyT1&Yk| ?~fgq7;6 R撦Q0K?Qat-qU^J"',WZ3a޽W*3rbO>y) 'K2Oq6,quI>IgO%-=yEp_%loż-Ru0ouyAoDy׈Jb{~Az9wQݣ(OJYl҆%[7DS2f6NyxݕLqh^3BEmSը'`=M0 P^\ElmvU9ǭiXv 0~Qؗwծ=R'wlj Ծ7~sp pK9g&vK*Np@Wm{XZ>*)0[y&J%|/h# J!~p$Wj(ƇvH#7}鄅{n]h?? p% 4~1 ץRޙúx!48^$oe86ƈEw~i%,D^ɧ+&lu?n]2ȡ:8(ک'P5h RjnnN+[yݦD#pۺ`dťw'( :Y+#rL LhzkkvOju~ݛ4H-t"z+=EM!<7rqLn7Uf=$~X;1M6!`R]+(Ea-,ج޻jXI\D4'4a sO91G-+yDte{=GuڷBDrg̈́_&gB!]*3ɀoО3ړtN4ɰNeWkz?}_L 4(lQ~3܂@zg3&,?7 vS䥲Pon)g<%\jG]D(7|rGJq )˚ltISUMsrwzO5ˆ~F7' _<5=O¦ݵ# 3ɺq{KRďii=gX,wr7`RK|Tmd΅_ mXhoys0{i3g}iz^hbC~] ;v9{;=]вBҾh|둊hT~ "dǺČgzۑhcPr"2wG 7 5H*}ٟ# ۱sօ%NZ3,6-m7VxG ~JkoSuTXުK:L y2NΤ=TY~#b]ɒ[i[L_A4dP7^ڧƼ7jO  .ƸCAڎpi{\KA\* )g$o8,Yd?f-7s׫KEʧGG\4Tlo68LZ $ΛfUT01YG"Roy\րJ@X8aۨZ0VRi- cЏ-ŅvU'-o;;EM3 *Kf'kz3\r! *Ok uUr~6x켐uvnY<+.1VzLo+kۗ[k,WPNr̛ %?%,z;Y41PJꖖcabí_d5y}{2y-53j/!ĥ^~tS`=w+x_ kV{PX۹$%>Z&crDn(~aճ[Nk%5ٵFQ> siIj҆Gub{ 2JGƒHOgPgg$S}M^Nh"[FjDs1/xҁG}B۬3nX4z,tp%^3Y endstream endobj 608 0 obj << /Length1 1456 /Length2 7293 /Length3 0 /Length 8284 /Filter /FlateDecode >> stream xڍwT]6-C7 R! 0 Ctw+ -J+%9 -]"H >k}ߚf}k9>VF}^y; XE 4@!> Pp`S!x0(@ }( " /@=pX`np#n=8l9b<`8h`׻mA.}-G )G!?7ՃwxC=jr` 8p@lPO[vC5x @OtEJ\@P_`q5>jrŃ@w .t_y!n>˯e% ExOm/u¼&< wO_;ߘb;c+S|A `8by'8?pv[; c>0iON^v0ϗL_DIO=yE$"@@!it@;VjHnU_k88̥S-o[Ew_g)WM]o7oB\|"܉q717gh\ۧݍgH~2p'&^N-VztQPQҡ ?T/+yjraf? k~( <;`5im:C/sK`[ɧ!\ik0>7ۚ+}3nڼ&fYp`Vfĥm1X7D^Jқ Z{4a $oIn7s f~ީ-\XB 3 ZAʭ^WfA2[际7KCF@AH{s d3fyc:2 egzbBAc*X ^tf!MѷPkl=y,uNhҚ[I\L0U:w~fBhYba]{gtz| bb!⁥~(ͻ'˜˷G?GxAqq!OG=c\I3\1ۇɖDkS] N*Q6Tv[m4H%>Uw͌v#4$>{I5zΊ5T$\%ߏ2ĩWmY]XdJ'CԑfccBm oqjabRS25f7OZA0{Wh 5hy4[ߓz#6FܕaYx1pJoі0t1g|E-֠ ɭn^͜gԃI]M;鑓.#/5KFq'ddFQI]bdfXg>Iչj: )O*A)MڼQhV Zyx둻e H,|8j0*%du~8 LB 2>/5uwR{ tGaLXT`R#MLqVKי` h<*6\|\ h-ɏV&?(<:?_ 'T㲹z GEÐaJN1&qOkO"ɏGdV(,w4;m_:Hj89q;&#@8%H:y% IݝC kzD}%.(C<$<83w;0ל4+6´ Kh Qzק)h'1.Y*͞'22DӁ>7\C?+ JXDܾhbYbwPEISY0ih7zK' >"N5Җ#6[WU,sKM_;>x)bի9n/LyD(@N77,,[qr|%h8bށab a*zE2l ՜[5-%FHwY{NҰSv׽n -{=>(?k_qX|5ɶRyG`sA LVfãezU6](r3_i.7TlpӍ_CS`B&_1`Ns?%}#ʷ\W Pԙ12 M/zM$z9Kj{?%l&mꇉ4WUɻ@wM+X5fj' f8<=|yȗa K\ @zGA1~+.r-(D\;vzEGl_j[v׬%OD`k2ͅ} 0 9~/^zF)q i䓵)Yc](s33 s_'58\CuO=҃1 bpQz3D(yA>KRJ);P2`~:=S^[{%;} ْK{yeHn {6ސK? ^CP~,Z9;:y%A)2[O6 Dzc,> ׋z_oy\ThvVUea" Rk1@Lx7Ze'?>UY"#ly)0$a~b5AkY6>G{; >Ϻo-*)6R>Yq)|e2gpBN8||oUWAi>N]U](>m_U[)ċ|Ӓ@9^o c{>z\}"gT!p0lu,Z+2>|aОa GVP.a-YL M"v2CNCITVC`IRO<!{[[]3WPxCF3 ωE&l pW Ƃ,R<~m%A<\/|a$FJ6-m[ \-t bu Vp3T>~ UA0Q8D2lJx"mnbUfȰ54tWt%=0[O9;h/6xepFk ƃL ?5T zLr+e R|!ؕKa3_\imP>?LӈRFef.yJ%,u}uaffqǰؗ\,0/1(EP!VGurpJ(rؚɒ@u,sB_Paiwx_ y7~K-Y+9U(gS/)%?.t E>D"EzHr$$ ^gqx凕QLNЖ3;c+[ӛ&EAFM8jyy1S,ner4{Xr )6%f{P% 1ɝx[rg4 =jsWBA!s) DUyʢRH)\0L@PZywkJl..=@"̢tkitIzTOErxL<3E#(~reՅWk'2ͳZ5ASm1++oU;'zNR LR}#`6Ohr)ߗH61U?-l?oV;]Hmo'i'thiEX8;½Q߻|4{@~}a㣣$ur:J=fg:#sx0mº2qr)3AsL]Kް߬JKFaVDRRcn&= Dx4Nvlzwȥ}ZZ.yrca@#٢qV=QIR"DMALh?я27BˍzM<\q{(yŚdآh[& 33W{p]Y(ZYԻ>O҆9~/,jgH7<>x=9yIdp\UkByٖuvJz{8B0oQہo#&x@Obq#~ Et+E_<}&{[li0Iy(rXZ˃:svx}f4?_!ix'X$^6agVwrW֋?oh$%u06ʍ:IP }Yxs$_|(TI(mʐvN{u(Ź@]JݹhI T[K ӜMešl}05{= ]yP ?pB\4˱t=$.#VcOQKQ*}ɼl& *U6=p.}1'~xluQC~ \}һ)7@Euhq+S<*!^aMéj=W97x]d[gnOfLdPJq)>Y )Q/=T)'vs'<. Abí{THW7'4N7&e< v(4\[7?`եND=aFM(>yOl[1Y_Y3p.}E"ɂ |͝?pSF'Ut M t|D!cTя Nn鎄UnC:FCr26VAqFzP:o;ʏPεu Iu;/ xIpyN~בPas+9UOncYe>bdC3D,ntJƶ'V5]+1٬BqơZ,LKmDkuyF" 9˗KFM.4ǍakrtA#qL4I,Mj@|3VF3ax\™ ƭP2-^} )oѴn1rZɦfcx;Yuw=+bo[2bGcČ9iZ*#?1oONu+>V}jvo{SiAXlNkc5n}?Ho/}Pܒˤ%X0I KhB7:k&h2V )3O8 o;Q,>3z?s!t ic\\k&Ј1ڐ}X;I]0g3o5,6%sClncvStM Jƨϟy J)w>guO#ˠ`gPvLhuy=CQP5+}Wo,Y4kNypHRV[4 ko450;ޚ\)@%CZ/\@^,}3%#2~ZȾVaG޿+y:CǺ&㻓+פj~ EhbUWӅkCv/:`=VDkrWJ3~W|,VB4>1BZs;v*GhZ[i*X5@*2M,7&0-yދ[?谕ސ^H DKx%䣬"~]:ρr/g cP~J鞗sKD4[C>ofc?R@K>9d!\:="n{H Eʽ:Sϴ$x}7"`Z>Jy[{b_S806?(UeL>f;n qeyfY?Fօˁ=ÔE2!-GHhÈAOg* %h&Nu-݇TcZ`%eaT `uq^~Kgeg`I+}#u/-8d: 5e74dhw™[k5h({l\ c8,/"qS#j[sZƔ/&WF ey$=aUM Wf3˟$`EwN >WZTD8O Џ=jgPd kzJK]qBB;uOMK  &-ZkKZ`E他_ 'a|gOG)ibtpܫ< +]?ʲ&ls=OE}\߳@JTE^ޅaOYj&$(s8:Y}-(vcr?W5BfVudb$^͜ ~% ۡxa`s7{  Mqk f9h|o, XΆNQIAIYT9Y8n;I]4knaB؜՟:F9ߟ5ծAt F8]_&e`tHz$8QLfuۙ5?)#<q]%Ѽz^7YķQ]{7BfJ/7t/0O>p|mzӅ\~iTm%`LdlG 6&Ϟq||Ỏ9YDicx t*CM}(ΰ?|GBg-CzO/;vs876^i)dtb g݃ ,^ Jr¼7s F$bĎn2S1W{Z l~reL<nk,CB%˙Y4rQ>8e $J= }vν\2@ xF`bPyzY85P穀V DPmqz@&Z{xQh\Z/ϐ+8ncb/c40*$ِc _%DUqE掺@ghj{@"}ܸδ Fqń3䐠G)՛@ n.u98hv" } Z/N.*]ּTh$i endstream endobj 610 0 obj << /Length1 1586 /Length2 6857 /Length3 0 /Length 7885 /Filter /FlateDecode >> stream xڍT6Nҍt0ƀ1SZ$T)iPJ@J@RNAAxo|9۞|aa瑳@0$_d @ΐ,F;C!ol Q{8PT /(PP[&/@(]P{M(/w `Mr rP_!8 || w^8^ E:  b 2@ / Ї!=A C`7.0[pv~5~@P/g wq0{VEz! O"~B ¯Ae9]?PW;;g|?܌Y fwq?S" {\' ن+! QSù1m ""v 4 wݴAn~|A(BH  #;b|sF~@Y(s L4?P^BBQQ造 ]͠G6XZB+݂_|/?U+Rpv&p Fț5Є,TcՄB=\!A7 w P/ vvß AtП fN7/,AwJ%nsE Mps7'a/f#m!^ 7.vp; l 0 u g', p\o^_ _"bb>w_-7 _{ n" f=QL0=K9ք5VYIJfw72#dq"dnEXvw7nӹ߅&a]t<~n~FN ,9nb:y䧞*^Jf?FLWĥŌ`05eFqEUs Bͻ _ veRIzhZʯCHo^߳}UGS#Vv2P(t ~SS(h:Ԟu"bKXkiq5jϸNKKD&l:u|6i nJRZu3_TQi7LeI۞/Pe{"(u;W;jhh.&Nw)ƷYqQ pWG{_$)G=C_j̑SRyL.>bSKJ&cC%\=|rA͡SY!@֦j2 f=;9mhjkp$G䱙 Mr]OspJ>G{Щ%wl} r8Gj>U_UyO˻&7W8 &N{<,#}ww W% tMjnUgm&y$VnI H9[>KyS4:[g (u(<ހC8dbnWޓQ`wx7Nj/}0 DAD&;9Ã{~\:mS\+j5k's(XtYӄa Ս~`(;oTKv|>-f 6t 9p LׄoW؍-],U~-ltr}Oe]~՚As?*URc4i~{0vN~-ߜ[ Vo ]=]egvkwPr}{yGvaeI >x 8؜z wr8rJVVݭ8?ǕqEQY.f rz +ΥZLo]3 5oh!DzlrBg^mU]Qei{uw`yM6M,XK|p>1jS4o*i=oV,ﻚKd;ւ#~Z*}xah2ֳɄD;$6w˹̅qѦ HVc`obL)Y84X;+=9x{a6 W;ofIgp7 @$Q)BG}sRå0ήH󢉝gh7T4(3,.k 튨phz,I)40h.$*sW_2o&!Y˖tsnN3\0;`>oM.t]rs+J[Z5oƂ¾Q3Kۚ4,a}/jª`s*E>\PLK~0ThHsbzDb^#˩g8?!{!{h}櫝&LŤa3}u?$l[V3B4B&**QX iRZ-HWiжmM}7P56֪SJ*eMfKܭRzo^q EbB2L뗔Xb{iM| ]ęg[Jԓ|(-P岚L]Lh*y/c!r} -Ewzlw}Ҭ#)jG@gssYp!r5{ڑP vR;uv0Hاd/,ME83'WFcݹOa{%[MqOM:5wK6a[^X!$'@=mGp*3|2$OT'ejS S(\>*\--s Q{Z0tguV~lǢC4\!$QqFeN#.ϒк7.mYN 6Ax-z@ȹZJE4~Ό$BFy||#à4wUd{G1<#l|7K ի1w>r V"Q:Ұb6V{U-ZcD)^FC!zRRn X<ķαH,*cH5lZxW,3^%*;1 yGd͚Hu8?w( 6CBχ0-/r~i9@Ұx5A:39TEϴ%WSVs A<_Orǔ׺2@x8tlR1 (L6bõcIZfɎnNK=HpY&FQ-#*!zv|K|MrEz= rcA ۗJ$$]R+q0W ~ς.q+iӰsKie^bcrnH<(~ppukD> \'2w?ف%~̆7(-Ja |5V0;-fV0inqRψ{3"D?l>Z?W(s890+;hЦ5R!ɏ׮(=J`{\> !手ϛy&)!#pxxwK:U;BHǮ' >D& tSal>^cc3_eQj2D|U?,oO5,ۘjىҗvKcEk<䩀JuXadXew\.ۅܭ1[t NB;vLF!A`ldDؒ+Ctr)=%qO=\{nWǺƘ`M[ ә|8$pl4-'}2TyIoyw kegj6 ŕL-6рrNMծi~\>cg* vbDj(Ǯ~kRf=_J.wT ^dG:}h(4֎f+wQ UuD>9O*'6IKc褏5b'AE!y כtrUs!s&%JĔ#,X&@֯1w>Ϸح_B~w #!F 8mKQFʙù6:gdƒ{&?&җ2^E_u{(햬Gv&МaZܐ|M{܂qd{_o9މ3w'C|O;?Zw;bP=uݠ'oDzO6kEkWEq.G'~ σ GGu@+o$dF6y'6s!#-B@Z  djCm3)b?n4ܒ>,[aŏMmC- m ecL5wWXiuoH|6),!Dp)uژ(&s-45ϾL;ƉxYGys5D%۳Uᦞ.#^}ÃzHr*eˍ #g:EE^.&󬕴fo\jָD VTR%1eviZnU{۫q:pm$KzBS~ Qjlh(5+de͝޽R){M1v:*OM\d=Nk]pwpҷ:u[εcʆdZ^/Nn}~; $ՙ1 ZErm};D\dzrf$07ᔆ^ +KLc]Jx}j1߲vm7̉^%Kll 䧚ʖcG_K+zW#,JԖ[7Y,?Wn;xoU(E愒. IQ1;\-}ñ]Ov"L hO0 75;;WׅҏN:opo R*!O]Y' ¦шط  tnbRVk^rh䐳+߸B-$6)~=Bb;ݱ@ ݋m)0Fn$}[jrwim;E0u皧Ցgea9OƝ[-W⍱OYJ&x.a@ܿ85Y (G3Zݙ[JuAY纟xhM*&{tdSq-^ g@7*/8>3l ʱ?7۫9ة3#4w~HOmHuݺ]2Fr![n$"s#"uIձQ;DW? sU9~RuOsRA-6OhK$y64uXx1%n~-W9!ӏI*qA.(,9pkɧcwhV:H>%=#%1TBaf̶J#}Qb:J g*ժ//3<ٔ4[bp1 [Ջ [kVx %ؓ![ u#O &N\ xfbGٶ%NK;E8\ iVG4Vѫ)yJ$>@TD|:I*xshorL#{K<n'UƇN0Tsѻ-8 ˅%lI:>JUQʉ~s-U8#kbZqhg>+ˏbjr0.P(@TG'{'y0muwR=FF$W~MmC*w}7Mr`ڷTVv)>8h=w6—^J(&L{$QEubMqUNp})?|X[ endstream endobj 612 0 obj << /Length1 2484 /Length2 12069 /Length3 0 /Length 13494 /Filter /FlateDecode >> stream xڍP-kHgewww#1yrQ);YYe@d dx! ^_Y 1 5+=#r8hr46kF8+ P}@GY3cKEͿT@711 P!9z^]f 0ۣ>Yv6o߈ 8 "ψ s/`0?#&3b0|xF,ga~Fvga}Fv'OAOA+AAUO_An |FƖ@ b,gW4_ f $%τG>77s,ͳjg H& gjG $-$!2w5aBzhgƐX?Cc!2c? ygPCʰ}VCBB^::%V}Fm!C^z s;M`G-vu!<L:O7:a6A7KHK Ir288zH]8@b8! !ޮ@Hx? A"[pǿ/ h2?6 lwefW|rzNJeN%cYϚ3)ST96,,tp,X%p="ƛ axr/N(O"q>f}8&.$Pc^km#W PqaVFle* j՛6 a46` ǚ#|̷m҂9紅W2ޤ{fno@[+honA'Z!4^k바w!W2MoKL<^TؾsB}Y{͠.zӪ>ѷ|"Gh]OoyEɚá#.v64%RV\#(ݍʕ̶*h.T^[_IA| >D.ZhI9φO0@o=e~I4x<[GZߩ={xT.5-Kr,y3^l S{5aw4ysڃ |v~*nL*Q( Y"y-O_e2ڛlZ1*ARԳP sɑ!.h6b \h|Xxq?s lbpط fǫ-3=zyT{k>y0/ckRP#u/vS$@9=[ZbIn:{#'KJɃz7f+ׇ:^"mTj)Է_!lx%`GFkȤ3e°۠&pm[Qp:e^HZ,qh#+G[6N׬Mxތyo}J>qӾdOҬOapPr^%J P˺a*˓ye#:x>M,ݧH/Z|_ƈp+c$f|s))|G-w c3TC$vTr{OSb/Rq.*>#ċ8 kf@K4^U>[b )fT)npB4U m5o˵ijF9ʰel-]W3澆#m7Bd=-۵0d.Gݥ?E7([%O?Ht0o6D +NFM_5m~|Ӗ [lN9K ImE w$i0dipnOv޳wf3Xۭi2;4XGz@CwZ=zi|~IߞRmBI/#Ќuqeo:g<xl(B 2JÅז~kOSG[8wxW[J( 5)ҀJWjx^z5&,s_sbsSTC},2 ةEdbl@ֶ\Vz#tcQ#bh]:Gqd_cf>c4]c.[3TAUSlj)!J"TYw%!6YV^|Z BU?$G>Frkк 4 [D#/3̧!x+p'o1'<Xj`>FfXwRTg"Z++"VAImv%d˃3#g ~fҽOd NLGz4_Բd XHKڹA*ZtXRe&Ty'#cb޳! Os zX|G>Mz`:Sw ⱋy4<_{ŏr2faf讎 lŕ&4 V~KR~vH.t/hڧL dR]> ,%ZRH*_U}S)r\f]ZZd$ԁ9A/R ܓ(UdP;+wޱV)\W!ɸķgx?%@#Wd6E&zv Y-t=^+yT2WY둙VV4kzuOղye_*POl6o2QPw0(ˑZ[ځOd͚binU=̕3qCn{U.Yt<W(ݼuh ol3B#`g\{;ͺmib,e`#]"DnIS^IΘ$ֶe#7nũ:jVx׃~8=J/(t Y_qzScny(?y`^˘רKO{e7w-jSK- ^L4{M.ۿfYVW  Jl} DnMBfUַK=y/@ϠNXaoO1/ub3\TÌ.3vXýKNĦ}nhX.n@wm3zϼu<|7oS<&ڒm&;S^^ےt#ҼFtG#ZzĔr'eGF~&!Ж:i]Æe:k`/{T7Jzsik&fkf(4_鑳Rw쎮c6cyn7z*CUg:h` 5eyDHwo"[3@.>:o!U w!5;O@k`b9a,5Y1>^[nm∋;_/(LvK?~6 0J.˝xc< ȻuDvmIz'Z[ה,t ;l#xy|nNa ,3d:xKAi!V|^@:" ;6Ý>Vxt ݮMR3-Z&ٺdYջ+_)qh{JދR[x]DA[M#ݫRa}b)*'GnU~+)ՙUֽ NȾ*?aQC @b+aӦgH(lbȥ~=A΋:+4PpW]q\zәXK g[v쩘GqI!Q{uwar7[4կ,k:QፔYZ$l?+¶-,(0+f~ک}>&^pldxz(9= ƥ̮H h;7?=r^> a0,;\G܊+﹨>rd(`q=UPkYe4"u[!<5_ ~h=M舡Liҭaa/Ѡ’"kšck; 9;2_Sob\á# M#2ֈD"Xy1+?tl=Znq56!}sNJ}k8Kճx+q/ć<6*U'`Qj i[,| cXvfMeK,n4N_FM+\lStsZ$r;ŀ iYP4ر \VB&:"u˲Qtn-=F;ЙposeU$l޶Ko yl{S0iK=Ů9$&Sw`Wq(31AͶ>")ZhRճf@]=&D/`f^8^W(ޕ?ߧPbH|K~`]fCC^F]MXć6?cFzUb( uy 44{~Wiw3V6/kmo׸>JAR0W_FX$ uHk+))#oQHO||9-ݾe^ڮItzi$YF Ho=>%KUy]7`,!K'7l=ٟs[HwH&h'}m=LD/|敪*iDء/9*/2i}~:omOXrk4f1fByDrCk7X6/6JLV%zP3h }dsmZy }I6ʫH`uPu/`3*` ]'3ҩc  MqoZPE(3l:">73vzRo-h=e޴ A5IfO-]Llʋ}\=' `0]Z:ְy)SA7 qfd̍r2R˩}W? j#b#l0֠02.If+$x@ ph|xԶ8Q'ɷw !0 rʮ D' LXn^ue Az(옹ә,Q^o!P7'O\`]tSrZӫKe='0]jFW LNkqJ?EFr+2ʔZh]x-Ipxb&:nYQY M*73PfVMǟJF  m< <_-st]S8ZWxQQBQ)KWXK|'ؒ{cP@gh u3GG,@4+g>M)ºFuyePB. h[Â(jCWE/0~J,^a6VhQ.xL6MBj5k@VI s4s34/rj6kR~ʯp&h /G{ȅ{퀘c#!Ǭ:$T(vd#?Jݺ+I!شg%{ܦe.m2ߵY&{1G Y朞4fʱ><]7N=PP0 5YheTXqKE./xhhS|]Ъ_1fG냬cEF:XyRoR2N'-:Wf5+N rtuѾԬ|J.dE\mVPNvtjWmĥ,E#d'$jJxƖ-J |ԧk$r!JQF* ?;c5G 1eE>Ko4>S>NcǫMXn{= []jzl}J01ޙw ajZZx]. WEǒTWIi fӰD(6TxPhI^5ƝE Fwk|/_hJOh5F5jg>hRC]`x"3rj4fKtl>:5r"%yR2I$|@`_ڼ{X7Ah4\MV]"J5ɻG^QO9l$O9->G[>^eX~sSIaW 1K&T%[xE _WJGr_y}v_TSy8BS_y`vBP7Joub/(Pm0./z"iCy./Xq0ĩ-f9c7ƫsP? a}].c L(b!9gde7'2z $%ca-у(r=&AEc߫7|BvWhyĦXPKL?:T9z^1K' kp ;Jĭ#UҫGV.)`؍OMA_yk`vU gnPp͒K*9L@&RWK~[O|ϸWRvmpb4rS'_ÂD F3tH4*;yZ BơFhit2@|܁?qP6jzڛRQ>58ױS;OlQQ\ ^:S(Mص|Ёkai?އ=H]0`]Q-:*d*nӞz>|Id7ؘAro631^ʒ zeΝBm4o 2D e64E2iQ&٘Aݧ[V+xC@b(B JS=aU&8WK%H-KnгXF cR?WT؁*& /Hvm΃*"7hrR\{hd11TxG7^,:Tz*ǭ47PIN1,&|-cQ>8aI>?-~lЎ"3~5%L"tԠ'Dk`wc̏eq(qq M6-aR8 9=# \GEU_(,Ϲц1챱Zn輽4lh{؃WN[-06 D] HvZZkL;SpIA~Nև~5S(V܂61Hu]hxP_#V\ e"+`)n}ءZT3^؅Ռi$t>&֎N7` z1/*7&a01j?X-d=9Wޑ Ycx}&wԴ}J&e32F ̠t$*EŤ?6D,SIO8b\V{aFѩ~&e,v]۝{k9\e]F=bjIJƒ\1@IaI7UۊoÈij?ZfサAԡ.Kf? > Kfk@ U:NELz;^g_kKvNUa4Z)Ĥܺn+jn7F \I] H[' i?0 th lg!~U,*LWoT̲sHs*!(,ҁ҃`$VIx' U U ,>`0 EҐwkneُ`]!3 ]0кLx0B\a՞QEUL!K3CA5]ryskǔځ*xk-2d!?В/|}ʧ9D(/1b.'Qii_xz":qh]5f)JKו[Xe2)]rhsޢ09#IJ15Bn:T+2o~ɤٻ.SᏕv>>m fJ8aMZjkާ)p>r\z1t}R4~]gE*ٿF餮ٚw/Ctr⢐eZl\/~-ҐCo/gcDZ J ?Js2¤q+zs[y__VLb-FxT%Aqg<ޞ*-qT@.h(RrvMa >/J-]_as\R6(/ v+/U-hɡ?KK6AaQ9a/M{pܸu,7?1(Ԗ/yq=]G7FVBW0ݠ;%]pGa'';Bu_2X4ɨ?0+Vo~.loJiǀ7*$g٧Oe;gŦx WLG3@$]ŭ#Q+nx@Vw ˜x @"g;[F~=ɛ]Io^:SƮzyXH`s{1€ s7B _E8ٓ>Lx:\K0xI`V1^69wQd)@HQB J+0j5D֙VB׿4ޕR+KOFQ?%pi endstream endobj 614 0 obj << /Length1 1739 /Length2 11447 /Length3 0 /Length 12572 /Filter /FlateDecode >> stream xڍP-Lpw@nw A%X`]9ܛ{WWS|{uW)TԙD ) ( `e`feeGְ؁eG99[!B^l@ Q``qYYvH]9ZdmiyG;ƿ 'k3@ٿT4f @XX\]]`'K!:F5 r9[(AƌL аvۡ@%䃃9 R.Pv9MVؘ߉  k;@YJavx hB@*?99Y;B~;kt0ۃ ȿ'a2{y, /da`n ,?d%Ἐ,A+//r3b]@oOG#  @>= `nm,d1,/wv賾ȏ '1&?-)&vx2qعXlΣ+`N//_gvGl࿓)_ Q+g'YJI}O7[۹x(_ڠwWdn::XHo\bflk^8;k ?-3}F_ r^!`6}\e/ `avC^B/={,Nȿ`mqX "#V`^rA/9x_j'EzajA/յ:F^|?gor)sԴ}__6Y/gq'K r?,v@g?n'i/@fK`3 ڠjQbWqv#DHFzfƲ|RMՅsϭ:zw+&r]KrW $Pbd|NP*H^ok{\ipKxە:K2IiPils]aeR^9޵ s}px;ViOK UKDJ{Cv'lOSi5Z3C n+D(C[R,bN9;߲BazӬeV w_Z "?1D˨/Tu@b:kK?n"0o.m6>oUPwA~:Xh.֩, ? u<=8;;hWE:DcGy}T{w,&J~ʫmPa\]nk}c@y+"(5f_%Y9t9TRN݅?٥?@ ٱT_k˒{0ZhT5EO^uQqwȳ!u F -e +o&opUcE/Z#fjg#23ÒU'{-}T;-2Z)95 17\pߘgCЉ@.PYs0fkǤ M] [4 ?|mZ62L{0K6UcBs!'\ԲFh|ȏ_9 4a:6ﮏքq5rGjf6CA-,gH᭻$=ѽrB!;:y<~g!Aw;St!j3:I2m7[1Qc43[=fYrʫ>ۃ׶Yǩ˽s6i)`*Qt"R0GZNt5#:Zs/.Qa(+v/>h Ipܹ-qāJ'erqd6 JOyypfd=2֪oB6R3zf+6dw$/CF7zoK*2VBU_7gSܗiDxY1yvVF>r:T'*&yM8;>m$rCmm8ATįV`i eDI͒+qѶ,4U\_KnktJM9uGY? IRw+v6iT_oC4dH@3"^*ôbJ$D9M6fwS'<:4Ya,ޯ"#׺zȰS}3MMzЭqM6Tתw79n k$$g "{|HNY_ѷ V瘡n?\𾴲QˀI",@YI*T*WTEhZP5O! 1_c1p\k%uū99e$tmS7ڽTg3^gYMRkãOD{ ƾrTɽCjwq },1a1&qxmv? @i;L'#r>/:ۿnGeҖCs}tMXf/2M.d t%r|OF̞:P & Yg&[9hFM4fʉr#F|/XKcc[c^Χ}Gi%)PUQK4'.d7-u#s4.*Y@`׷bfi'*JԘ.S/|)A+4: sa)HuF4>+N!"eX K&:.g|]:19w=:*b5 ٹg&ǦKJ8iBRh؟|b?iqܬ&I^Zhk9׊iNjlZm 7.*#WDV D,ttc#2Rg%a*}FN*OQu'>ڋz,]̀5TJ!/t\kJk&.BKP̮طG8]41cA~ WYDMMlx('0EcS= f.Y,+jzth8f{!|Ȉ#ߒ Kllچʰǃ<ߢ w2{n 8>$CIdE{=ȿbjN9neb]ȃK2ʟЃ)`*눭zcL U3JpG X“md{˨,ͮ0^`0 g$)sS]$E*@D!bA9zKDs$9En0ŵU)Tv(p1"}/vkJ{:5TpG2N5hݩ/TF2R7_u8mtڠ!:ll,k)/,(r@Sِ&NOǛpe.]͗6OXP©wGDIgvK: e+;jkpT /(@ lYXLB?޳l{M%spb`6 yۿ 4)$9<~2S; (jeM,L%474hZR$/Ev`?0hpJ.giˏ-_0nQ]"P7q] Y9a\R"wGK}X%S7Ƒ VEض ݬ#Jߣƍ6W%V`;,Pzn6oݙѓpb|F gfHwbYuNۃ>uY+IBxC'F|7Zlt7hiOH)sf[4mab_~4 wyhuyN{t|Ug~2ÄH%G1ή2gsR7Iެ%z#M[բ7JvT&cp  {)u6}MwkK۷θyk! cJf N!+'{aɚcTZ^&fL GJ!Qw_?fXს,W1)"7KQ|zc U 6ge@WulRue>j;lwW$pC`p }m90K`>ך_-WmՓӸ K F i;GZ:5 b{txA"fsQ4+|>u".)_{N)TFqؘP#,mK齰;zdVFoJS&lE,Vt3Qla4^HXTZD׾`3"=ͨOoh./xVrME=9$y6Ϧ^(bFV~V1gVWA\rz)ouX`eu0 47 -¬+Ls&PejR b9>R{on"{~dVKCwt1-*X42k '|l#BR>1i"l~J\]k+[FuGu|`isB9[φsjj_Km0bb׎8Pod78AЀ8/lܥ!H"GFVJ%_I_)*bV|v؜ᪿ.0>>ψ]f'$Qg`d)-j)e^+:TOeKs3YL"Uǐ&h77]_ ;} z'vA}'mܒҪF<\6Ax4[( ah2'c^bscș˽\O#H"tN20G%U]jD`q,!m<7sݘXjTPgd`ai~Ͱa2JͶvDA݅ܗ,bӠ9V~K[Nt 9ٺ+_I͎eڧ{DcpgZlV>Id !.N6 5lo(3>cn2v2#wU]a~=ќAc 7&f<^ҳK J!7$}̥wsZD$R/M'7#+!:x,>-fnxlxce3=%{[} Cd~`x6&mVz'Ic!6q|x)1E nW?mP׭EhBC&'7Od9$J!^GmIWBha/muY"JnCU 6qP>a𖁢k{=uov]s"ᄡ?uzg֑6UcB%i㾒Qg\1s=r0zl{!+VCLgZj@Nh@QҘRb5I\Č٨K@|-Of=Xӽ:B''s~{pqxx ]yxݾHMZ?+O=MEPeLv5 J&LVyV]"ㆎt\¸DI䆜f$,kYt55"UzkjYh h l#! I`@[y`Y醆3!}%.ƾ04KR)6|Q9dҎ*3ɓU .n4CGӞ!ϐF)F3i&>Ɓ(5t%.2ObXYdAVuMf썟Tܮ n'ðb+W]XX G¬#j ܥ>$ܣ}.•GH'l+ wuQg?0_e,$+洯Q Oaxdo"/'@[S.%A$f^wwJ+Sy;âJBux=>f,| K$@MT/)6y H>da75/a:e{pTpL#ݡp8f)JO\chMX G,]/ޤs)5;@"eQ ێӅB4*5>k-Lj"vO̅5'*x("J=B% Vcog$PK(w4_Đ2G{F|=3 \n1{~W]$9R>N - ?ٝU;_a|l/o4PRJOu<3;CV%dZ@QΝH&UQt8 NJm|P_*3WV!kV֣!Ϳ63i6r"(i?LQg$$k0[2zw#F< 62$dggв~g^Լ3Y >(Ds"J^kAqP=₥G8nU*0T9 lh2@Yjқ +cYePaU@3HX/߉Vqj*qgÊl)w ps/grXn\ $)Ut>({ Fq$l|H5CP:w%k%d< Bf?)1_2z|iCŜe w.٤r>8UJl5X3)8Ғm!(͟YYǀ|.OfZ #bIyYR:!23g4 ^#"Ay>0Νp֬͝hoUʧW52ƻ^Bß>E1O۴?HJۄc[[cI۳U{J&pR(TQEPd\'Fco]^ATL>'!Ԟ ˆek,<3}@r6i|DL<0KSq#'U98mzEF%OTЃŻr܃mϸ8V.+qcʇ7l(o5qEQÅ`-tbhFH?>0/%y$Fp {E؈ž6qrɲRg} LQ36K-]SqbL Xt9w/^M.<4| l6]MҶTgK=ur]~z."ۅ^f5uՄφWHA0f_ao7ξue TW ` /Cov,2Fe9{^U:R$cD` "4P,({wt?%9Qr0ntH_õϚCLqJmmAgv`ӊm7koaM2Ϊl5jϤ5亰~ywq 엔s%>pl]7i/U2М4b_OyZE`5Enr:$Sk}-TWalۋ1óf"j.W_ێRxcGύ!ẂMWȠWϴ# d,E])2\k=HuOtAui-70I g2"ѩ r?;5+$lϲ" 9~ֶim ʪ ¾# |Г`K*"nCrlvAwJъl:"=VCoEq.FZ@Mq/zwi5MOBUԊImkneGrU38SS^K1y29a.z4LOŻk&4hQ@:]3 *u7Jm C۝YV@Oo`Vش[MF.xxRǞȰy$hDC.8h΁"9z{=ԻaiYyԼTjIVF9^6Y?^Z(u}3tfuEDjU[HiFms,41%FA> gM̮ZcP&5n^(ØԔkm\=3A U \/ 53 <_xa:(#8(TusW221~=uUqUOZw.RAs;YZL-'Lg"aj\I25ZOJg=TYji&:Xؐ&|_E~dIWmY+[,u Fa˙̅Wk+pRkvpa}رg 8C,>=>T8^?o endstream endobj 616 0 obj << /Length1 1415 /Length2 6444 /Length3 0 /Length 7410 /Filter /FlateDecode >> stream xڍtT.R 5tww 0CH ҝ"  "HHtRR19߹w{׬ͷ~~~|z26p+"44T@ ȋȨE8 nP8L\ 'B qp@@ȿpWQ<ja`7a)˯;}A9,q+Xu<2X5H?1߉-zU{%N&ٕӲՕr9;`޳ZwaJKQ|jTx8&1q@@;těxg gM/pNbK*{i=]~4yTn"JD5-UqS6۷ Fm+;ָ*&~Lp|xaDv0ܕhCaR O)Jk8s4_ȪDnC;Y/cj_ H030_Jzt)ƘDV4t GՅ;E=E?>+:ƣj͈%%$H92]_7Ώgn&vH yzEQvU )LC'ڔRrG te +>/ -"BP@%f 6RBlst1ֵ8jtww}T[7(lR|ۺf5ZRxbu}0Bk<>i"[.u_Jmo`bF\(,417 bPOKy틮`m, @eLP")@XUB]Fz:sZSVwS8kcɕcޏ/K/p0%/^GѻݑiWX֝on'YߓSA"694IxGyܰ-|C ^ngAތ jm! =}8jc93 (x` S:$oY)NL lVJ;oT?t&:27ICU̠[AP:gjܒ߼Jy]XD9m0e38)=\ҙNڝޘ0܇A}}T8,*\ "0x[CtЧSDcUcS'ߩ1k\)D +V*45nc r-U%|C!EEr|Y-zg0F (UiM~rVl^p ߷l"77=L1U=IJ) w @]XܥB?bV_͉ngU@ɕ1̽"/W-4v0YM^r5o4!zqx=O ڹjJڧtzGK_1im-9ׂ'g$W2fmH_ gh!\6ʧ.>?)?=8 aw)֔/l<_[%>T"ͥE C,=TZcEc0#,Q]F ;) RPe+k0O?&[٢|zu=7촙Yu@D1CɔX~{cY⃄QLn$jan^9ω @iJv0`a1C*>(28US"?su=#ڶ!DfxvTTC;pqTyITuZ7"ivC.-3 MǁxzG.x&4nqSln[+wc3ā&\{qNY>2ƼtOFjm#S&Wi饻lgޝ1K~E >4T}PkM3nyQvˡb>f✽DˇmtsoBo"IޒpXcxM):yoa[F}A3 +D>a )Ny qz+R e$|KM} %{Yf vrza3]M\@JTŦhXTpbdt0$xOzKnnrJ^4G+0GU)ZhyLѥkڣ"S}CU|iӯ&חѓ]կ &9/,{L$Ӿs>p[x[)!]y<:jr,Vs/`_v&aly=ObyC'O<^ :G)5yYٙ&i{ی5f(XUYq֧7RIbf8(Y8rn<g/,,mmrKZi|E&a{ fHLAg3S*.kIW>HN${Sj&ˈJ|<#i62dzK#Nc{E8O}`˷Z\olQt(&b*#춸g.FmGIk;6h`7~vT0:LS85%F4>[篬ʕ_pN;qWEiMvdAϾK MY6<)1&U.Sҧt7xd ^ RD:I D) [;Si+|Jٝ|yY;FN`SqEc]CDՇ \_3Y,RvпêB^9 ѡgTQư7ruE?*{\̀ݑľMLL(EIg(-g푦R/$+U o$R8tU}iO;W1Wǘ<vq >O2DέVt ֚]J? }"gunb\̔`l.Bݫ U$џ+ RBDd\˵lVMm@tkWR Rfm#ƳUCK:=:ƛp恣C3x^Al+D3u7"X:Ec<ʝh?W3$P6~1?ώt@ЉpM61<#Dy]g~Jר@Y[]l+V܀+E4,g.Hb(E6qN\HVw&G k =qά2h3F}g/Ⱦe=Xc..Yަ8 )2Cp*aC }nax}D!\:vB]Aoa=1r3;2+sxSK3l3MEQcT/HM0iwol/EX|wҙ] ųoQC'+bmNsQɃ1EO8._KG6d 艃<".^qpgf B"윖WTTZ;W9Kq8~,J|r9"醭hMX㸄n6p춓Mqv1Nw4g?>:(*P xSsr927!ЃKuH7|VS[ pi=+'wn+>gQYҫ+!T-hvRky[ŸH4XM@dû| ^{go~!x8!z:ͣN2'WI"te\8$TWWBp}汰t٧m|ǴJ*d| = ,zJPX}OV~U;/K;kQ f*츻ȁ@*JƉ] M.PQt3ŔadiHUr/`c澝GLI~Wb9⾇wtS6Z*搲uԞ8 ϣ Gv>ԁVA {a'O7[Rl2OxFHa=鐌Q~ٷ9`LaR#`BnW7ªXqyt}xYоq哂=;۹A V_ejɯ#c<ӣ}0GT/X8z4Mh7c -ޔ(4?-FgԔ2A#Lu G?J͠(1Aǻ8)k Z /õදIORp j^g{#os=v W?ƀ;<8nmCT*x?Ncqݜ/)zIFJx4]ɶuj8`tv))?=Z/vڤ%4NZCn-Զyn#?HPU2J1kN3H #⹉3f|bLN.Em50f~ ,VD)/~gVS*f|,@ԕ,ÅGab'ʑ7FNMkX}?E)[&~ 1ߡR&y`3J^Sxk5,%+89rΩr݄Blyk^ސ' u W"95+hs8uvjDkN"K€v叠u͹Kw J=` endstream endobj 618 0 obj << /Length1 1533 /Length2 8365 /Length3 0 /Length 9392 /Filter /FlateDecode >> stream xڍT6Lt 3t7HwJ3 0PC7tHwI7 RH+tć}>Z߷fw~tdl\!.08( SWW|@ /&[dqD@b[]`O' ^ P_.y+/ @x`2ɹC`@""Bd!P n8?r躀`]Eyx=]$8P=@q~ аr g#u{[C'(x@tc?{qo߁`gW+/f:Ajp8' f dehWVEmcv==NK 09gg ;?y;v_?uxP"l<]yaP7O&"dv8@(,'  >`{|]!)AŏlBm!V^F  !vP??qP { 'Gzٸ|1k< 2z*NVp   x(ZVп u}lӿ2/࿃i<`@ _. FMH/5_#i= 55YZu pE= #z(B} 6ZP8er~[=xo y\Cvw+ww+_?"?qAm >1 s?kغc[x6_1]bM]W 2\[SǙWF\S%aYdݼe*ť!CUs7;\h.RR!҈#hQ>{"ec7˄4LT(ѥPFB%bpo c)pN|-4:C#^U3ٻבU!PƗp|Q*y~zbۂrlgJl&l)I[9Æ(۞Bnse>7S Zju4I5 K0{]a^φT9,Zc(dȌ8=WLbG0EŊ7_~Ngh2 aMWDl!?S!)= zjD|-DM|H@2'CSr:( o$DS"qX|m :Y"Ww#Gc_``B2~cOjWH`j0ԴTfhYo ..~Cö[$6-sQ$o)ny,RxQ-TN|n~2h"9Mt &LEWà7[Vu`w7/IڎJ3ȏ3GA]D~7S;s93me5:˗t!}Do{ }K5_I4nCByңQ‰T -//‘3,Z\lB$p0,&γ3K}*CMCfѬvnmwKv=:V R:zF"^H6n;Br&f)\mQ>/I79o:rM&S~!_!7Oq*C,G N/q6# oI". )r͹+ٛ%]EL37xۺly'fqǫg.)La?|"]/ u:Ls%f|y>-,3i>5jH gN=FxSCZn^6g c^}H?Xq+#x;OH1H?Z"|B5hZ CYp5^.}Opb+8ɱ Bc>Ryhv :E6y/]Y/qLv¯@ֵ.IT PQ`vE;)t;b_n(; x) gHVVzs5 xAT+%4\1ܭ4xm$希TS}o΢B)A;~&yE6(Jxm 2yWj}{ Ncm7~zG"FZ=cnVe}m=;lQ#9hFDZGniE!&Ja_"{;ƾε6nIWG8zqtrfNч3dZN)17 ݹ_NSEH:4-rhm`1 5ff, q޻=Ck\H$K2DkHoۢelԉUUuMr]QGT"hcyE19ԋ":)83ĨNdô$˞~S~OJ٪ 2k{dHMy8''r BW,e-,h֑]\Z )x;>ei#'p1ǑYfo VTrDmh&%ӗ[7/D4|=E Ӻ1-@$@LiNTrR= Ya2d c iwЇ0R?AJɪH8 :x')߭T$Z#g(mr+ia̽u(3y4L Xr ZYf۝LF,J鿬Sn,2tZg޸6;hdа-o P1*+E ,cJ=(6}Eڊ6; [ ?Kzߍ{|8@BX"Xjdv^dMMB6zo>;&WBhB6&w;MFdWPWf!|m壻tDu^3k@?+mz]@φb{5F*+s'*R]}#WK-85UD^qnQNah+ny+Xnau MX1ߑt,IiKgF_8Q鳻OO$ r]uK$p6B('CT{ x㙹 ӎLMOVIӽ.YOsb_(Ȅ- 6k.DA1}&onEۚ`I^!oY&4 o Dj[=ipʺ}ߚ}#iYFdo+귑cr-NVPfG`qfKvqq":Ea]3)R:H#pW+#6h\ߚ_YƲߝ8Pl4e(,6o1Zʆ>^;+4ٰGV̇MOڪ(ZXQP^,+Z[,Yqdž;!\Pa ʳ7p^m2qU.~︵6fi2uRM,G 1mWhk9#[dN0pd#,u: Osx49)~ѧei W@x}IGyKhRh9`u1"#Yf,ks>S}y.W1񄙎ρYTFK6GGIx?P)e2^ĩ_i,uu]2T__[F>3odC'P,cָ'NQg:OKm9y9]׫R5C{/f:_$S<מu|GD,"avǗSO?GN>GZ2;iDŦ'dE$sQۧM_oL7}G +9bXQ}܀?6CwVgх𜔤.OT*x-au"ݸU`NV6;y @9r+i('\k==*_| M7mz&JŜtu X[G_Z݁Q#^0¢Dt#& ;oX-K wi13O.N$N.l͗}t55GrWc)e'Lk˧&?jPF琸eX 7")Tީaj4>#uq8l P'y.M)HI]'1i+#Hݦq UFi4לǷv?Qᒝ,Kv 5v>edhQ\mD,bRO8>hԘ$9!pk]DŽ S3)/%K'ƕb:JT=jYZ*ߗZLi?x]"& ]Mo-Ȫhc \`bg&zɽ[1ab{+pEeu[4TQݓL`&me4RmI zGj;˸̈́sHە)oyva,]k]CN<~'ILe gOeܔ,qf`q ܈X2#xOPdVFjV;- RjH!{ ?N\kR,B}&{âN8rLϞս_ `FnJ0pԅ .=Bm1_9)nGnHL˾gϛ)k#޺$L ໯ vi ?U_&2P%Z+(jMYfi_N)v.-c'UyYu!SvzWW&F)]8jqxcKn.q$?ܾ?ҹpY;t^@HB[IC_1s1%t:jh(*mNM{֭Ljh8bfp{J[ތ,g0HZ85~@*=⬀9&4#ӓ"Éx}ܷ4#Q\N!icnmK3){=mVg6Cnxm& Bi7$m LߗBK}5oGB ¬gAKf"Ec$Nғ!}]55~MSRKGז|ETFյ00c\"_)4ND {"$)!F}{| 0`K} 04>e@6Ox`jjOY\8u3m*]LO[B_Gq{2+8=}0W=Tw6A@g" )A>l߼}}M!<b0 6ۊ>)^ᨭ4Ahu}g7n-VJ>N(- n6'uzt rmUd!hydEO|#ȹJsjl=燼9[PմLܔ[`®r eēЭ:.aH9[c$B-Vs+{2D4N8dve F-_'R#*>*܀fXb0A4K݆ڋھôh95/nf!y^] &Sb X-r<,-+sSS(z{,UM15$(҈.g5ic4]9c$V%uey8UϋcJ|V2𞹢=0 ̟5L+Bh [Ḯp%)ݙ:T{DٍGOk'_ձQN8z<ܞW:360q {a$ɏozݛ _aqN3urqbrZMw 5+F"|^(e XZ)HOu :9Grs>ZڷxTznTg;7,&jw6%苺5,gM6*\ϖ d0%2gJ8,^Oe^ W+|yo},B|@6 %¶#eTcDȃ S6eFgqZ>ٸ6/ ''=0CEG0-P 4yKT47Ӭ90wP/\#ǃ,<L+'^uGD%j_VJp\Hѐ<.Kā~/VkƱ}7.k5}B -dŹEW< PHHǔ _j[zd\Yw썘 O@_$4/)iIW;-u#n==@SmU4b~I $,rP(- 8)_RqL5d/kG Rq0ˢ;-}TO2뮆z$* 0B8eS94Qn_\W9MOf;7bҙ*zڈ[%5jG Jok.tu-˻vس7["TQ N+ IUNs?Ü%YRՇE ,dz3Ę"ͽH7y[$x$,M/=HחK눮$%2q?[Vdn'h쨅9c~_,)&&mxooHa'9 hJG"jmXhk {P1N'<^wrESl,@<dQ^{ƾlAJqYz~a(ǧLeɍ\"ݪ̗si5ؕ G0sïc=0$ 2gd6"ϬQyQJ0W>hY]S;Mk9fbyl?NݸY߂LG 6r l?#go_۰UFvDvL)>8DBsSZ QAyI8)vH>Rc賄a޵7Ó4oYR(<l>\'8sH4g)v$\{=Ye\9B Q(3cY,SFF K z0>uQ[D\3A_(R0~VY(T'~B q]죃3ң^e))aY⩄uU `k*A. ZY b1)qAb)']Xs0]b<V;y&eVuru[dNém?~*zQ8}y ZqG^B%0vRKknnNƶgX#-h' Ғ)c.&^Uf]zang?𐛖Ŗ0To ġ I[mWA;[ n-?uA~4jn2*g5@ERӤ l&O ϳ Wpfc=_Ԇu@+J_; onXhkݣaA-CoGίo QlЫ endstream endobj 620 0 obj << /Length1 1399 /Length2 6447 /Length3 0 /Length 7411 /Filter /FlateDecode >> stream xڍt4HDM(F3Zm0 fQw E/k.HD'rνkݻfocf営AZA47$ UWW@< !3 u2@]Q0$Bd]4'AcpH@ BG * l<$"dE:{јc `fEDqNH;A]a@:aNHklh(/ Ńt`x( qgC"mW(ì& au`*4?`?.߻y.7W!w2 Axv[ TPA{/ Bb!bP@0e sFxP0yܲ7 AGтO2 lg1 $PXn YcoNQW!78wwC`p k#1:@7GPGv6sx@0jC[/aqd@Aa'AaԆ_6!F "Ĭc |@=S˃@1)̾[+5?*rx [8on`:[P'Ԛpv i-P|Z-MQg/Ԉc9>K~bnVjl'XܡUC4Z:/c v⧮oL$2N4bЋvaiEHu2rjZE~uJ$\xԧJ#*+"Qďm\|V` ]Kq8f\Ib ЬHTn aՏO5Ry[[-_˒?agۊ?>)ԀjI6Xg }8u^MWP7\KknvzBc|yy6Yg!pxeq`o`9 Q9^-+tq-wpq]ڮ1Om~Ԃ4 <tx,<kT`=OU#d}`kuKN;_ž!UugƞW$(e1,IÙLӸVnWoSm]HlP޾1M2fq>pO=II)1ȫImx;q 7xgx|F,Uƈ0,)ma`&QӛF3g&;nMs\\B];ɝ&\м"0Ѹv&{'m+KeN^VgZV}ZͬgL`JS&g`g`-v2xr {E-GشTxhܪlٲB3ü+ >e~l}ջC.)da|\͒Pcy`QVx*=blm-x@ޕ_ y:.[č(QÚ&4X/JTNU/?wFO 3T[blMWx?9Q bRU*S|LAYM擀OפK FI5.G> R@'(t\ 4,v mV섦/؝7ǖGSlX9MJ">gl ,DӳxD+R\RO=ysCfGS5ĥC;Ɍ~L 9tuI<] MH4 Imá]%Sx# rP"չ7݉(LqBG2wC6AWq ؙ{Kzr[4ZR<ẹ̑3E)\<܅zzjD1{ͱDPW.VUB8T]Ww[BU?\s,?lypocɲvĖ'޺b4q(/k"d»gm+nxթ/n:+M{n r(#uEHSm'!mIˏK3X *%Zn텗.}e릶I \|RZrU,*v5 [p ;Juܟ' j XEy6T޴I;X? 򅙘PVIl9Z+Aߣ Fo=87;י{=L5OIwp_n;qs6{bbɿpN9M cn] si)\ ,.V<#ݍ%@}˞c )S80n#YNQF=f4vAL1^/6ȍL%6kO:7_wS9$5$r8&^Hk*d[aBkI(OM Ĥ߁Va![fIBʥ#7w.nal?p3)Vd;Ȣq1X{ꅅ-5/)woWE͌r)MD~1r>"0R7Hy>\' YtZOz~hP(`in/Z7xtg:;t3,=C< /"ò*s0, ~p` aJyhDFQy^6 H Pj7 /dQYR7y`)-F%KIh+X~S1 Vc`Q0uЭK]c((I@nۜX\oA)C")r?je`O&R n &=8DgDQ2!2qª {uhikhJNM[w""H*Ý!;& OOvQ^_[YMH=k=Lhdk*w=*\Gn\l}!>(ǓR6V{ʲb+"`\DRImspw[,VW r6έUoP37殮IwgJQpʥ ny&PvǽaQ\TjCjxcyS#ϮˬٗZ6Ӑ5?Vm^Dp K+ t]㝞m]|l9)XYcS )ף-ItH41{?os8f*}=ӏa!xH=k-u3ەI@*XQcJٓ͞9?^tZEGe➧ NX1U؝4"2#0CFO?T^5vBYtgXLȄM#2n8 G,a@3wU_"lc6;.:A+MG%qG 2Ly 839oS;g ݞ5EG{gHvooܗ4GŽ?4} rX}b^.mUaJau[N ahq=@CX] َXȻnzak48G!uQGVtMK  Fy^vq1R&` R$j]m+Ԛ r<됯{.O9PDR~1grb^eF_<U*+?s_e1fij|*]uPry6B<&2g2;(Lg)A;ƅ8{ǚЬ8= b?kR$reJFA.-;5ro pF%S^KY\9vDL?M4H6jpNլ|l$pLɣ]Őךr=$-Lk5C{KcNiɢ%uQRڒotM_nfrrCdkCs<ՇS_ȹϘm4NW/q[3 އZRH~@Q+-{p4 ˃ NNd|%ca`}-t7/ ' ^ReJ+?nGcM-_9J:4*̈rޖk5T*m{/ NԴw IyK)u-9wSnJx\p7%o~lyw[tkX<"~/;UF斫 IF~[@ش\Q|>_{7\cL} ~NUx)I@kWinZVZrvJU:}H+҄T#j0~6JdtX 1E92׬SPSMYP6T f׏4n$=>q;`/'/Yw(YU(ћ/Jޟ?5SGZO̚!:Fp r#/?B F 7plI$y-C&`pz5.Qƭ BQws}Ӑnl7p H6Ӆ*C߈Cxg?Uxx+n_ Za3l_$ȵ_~ˊ^;_|{тM}A78_8E3ܳUnXZGgzBu[zl_Ĥ2GOz3P{[$æHheVQT3DO"܃r-WE.=Hf'Ov~%1%]JGN=}KƆ>'ѫM~箨%Ev2Wk#rp[k>m7C82&gXLzsE 7rLR| ,iH`rZJ;(:/7>0E'$c!*QJ Lhk_]/m7 HVJ*aۆIL<#P /,U :[ endstream endobj 622 0 obj << /Length1 2692 /Length2 23914 /Length3 0 /Length 25416 /Filter /FlateDecode >> stream xڌP\ -;wwwww]]k:3s'{EڶP3Z8YXj,v&6JJ 7;?bJ-.@S7L dsXXXXl,,c0(2N.6Vn 1r3ؘ:Mݬ FsS; ?!hܜ===L]]h6n5+hU0@weL k׿ꎖn.@H`gctpy;X] r@  z`eb7?޿8ljnhdm`ܼ M\A6vf 27HLAS+ݯuYB+? 9O`i`a w'fMgw?& o :^̿kx;R*urtXXA|]M=7w"VV he;:H bg+Ͽ Ae`2)ʪj]:11G//# kɸAF4ut-M? @qK@%7`d1b^\6WےRMm1-AgM"keLA `eom\l*6nor_WfgTqtX0Ft2=:\A+ RJ:;Z:16N.7h eݢ%0398\.& `%q70F<f߈,/f0KFf߈ ,e#obW@슿]7+x@*]7F v߈r@hF\#P.:(/w@ n G-Ê_d/3w-H88~I(f? ;FX@ܠ3u]/gwejo2r61//jN_wV9;2~gbv:a @lY* Ȝ WЙw2 gA8V9޾??y{F8ٹ$q=_%b%ttZh+f5?kпs9mlL(PO]\@VVPiAf7k{ ꣛@H=O3j,0GHGq:f#oD.$WowJ 6n| |=<"ȟSTbrJVZJHVјزjlԢ>{.vQ b$}.k2v!SƮi3tn $:[m|F;0k}X:u6Q~Ε:7f<1 C_C^ͺϋ%Mi ${" U U8Le$ [ C%}#Q!oy~hUisD34XlTTa:jݟWyF|~W٩!- QzތTC̃Ծ)U[Ca{ɓ\VdQq@PߢDI~mNϦ ;0l{ː#MZ/o5 }6ǶIMmSU|osd@%Poe^ DXF5iz/~Κ Bv` ˨J^rn<5 Vԭa4X<{Jɛ.Pu Ȍ߱K%BP`~]HKcg~ -k..B S]#gtjIQ9&:qq09r0>_nxZ}d$]E3}E$LPy vTOCD2 h!fe)xh;?o_^>_^uP[%KH&;-drƴ<ӊ<mWy_q=1b"e(y>6ej.Y Çf$ nYH8|y*6Jdr lx>kg2Jz+^y? '}48s.0qّNod ޜU<|37!RErL`1]Mxê3,&(ᔔ%G~v(kjssmбoPm,,k @ ._+ B\ٰ7mHH ;<NKO2ښ\iOGxԉ;4OJs;tVH6 Td'G"MëW(YӡL2a ֓-uPHkҪ)0 ,-Zͱ֍HC޺ H Wm1_fXL%PQsn&M}0{G+"Qǭ[. 2qsIr#71yƶ9>}kDT <]}YP5jAK,N[_6jE}x,xN3NIGTdyJATT)V:յj~ O{WD7.II8bL|UoE,qȬzLkaa Uӕ b)9Whi/E̠ cT+3)n0TY nIm()*+nhwYh COBlU~1BrĨP%hEin^ Y$+M;.orqR *cDEz'MR7S/i/MRu^3mmr# ʔ|cGfjZucu*Ar`(R_`3L7P&[E %ةv2(ݒF0Z唬[9H 3^iIn!KgT!O^G'OL.bͺUn"䷟x&mFׂu&K= .(]EolXT>'V=ӵG l`,t.hC4 :,VM*qTߚB ( bgoU=ыu}Nm]E|Ⲥ}ϧ\r2:s(D2)ۈH{5qbM s5lDhL4v*swygdՔ^) #%qIhq:Vy8z}aݟ@ H%ѷ Uƥ5% O^bgCP5멊.nQfw|ʷwAK=lۨwA8`)PG*Pm2AW^a}6l(DnٴXe)cLa}u#5"^k Vե:$]F`WןYXf" sp$ѾpLځz'g{,fNaV*S75YT N]PͨvH~-Y<L#C>ޟ5QO7(oE*JҰHo^VuP6lArƟ+Lరf Κ{0<],=*NMX@ PgCdKU\.Ŏb8l) [^X3Ikp "rrvO: . 7i&_D1KPlqT֐ H@~5ӊn2`]˸m PTѕ.>O KhU6 &>E.;A1/iDBBmWlT=^ɹ ۴%~~rK`<ԎOIPpᶬ9 U+t{(fpTr+*QRomz\s"s ŗMfKԬoCdeUԖS$:ښO7 Mͺ FAodžHe:Ϣe^F>7F Lؒk(@ 6\!GX-G]xLvB$ />Ӵ>+C4<*Q^2#ev?;jVjYϤO?gTz&H֤ ;!/X+B4v{@q^pL]JqG7w }^5@p}POg'sߩֿLSm T>$|qt?gؼ ˒3r"ٶY8uА=vD袝 wN!9/U F5Yw n1/[Y"%`,˭x)> )ޛ,MHsV{AȺ8~b YEq ]:;uA;$ydA V HuCG:f,gj5-/iޚ՟ +_CTbj$Q9T +y(J&-rn?kn-L͉x6XJ=l֮E`7?칕A%.̹S&Q5Qh&ӝEozOL |l.LbZ?"X?腺w C:/J^UtWh}S'F)G3y!I'^]#­ "Un~R2 _n\{HQgjtC{TT@΃jsy$];İ+ao<'̱¬LIgc]vUmIb$ `UC|.D*QnC_p5f6gŁ{ҠG[^YDˢ)ȭ׉GzRɓhLGݓ}dtr(K5pgoE+"Z00*RpyԩE$_ƫVH{c_8 l>P BKZa~RxÈ̹j:f^D-#3+@qAD-Ɏb{|k&pTKB_ZkGqaAaDKgd~(v&7ϷFIDo 3!2\ϓ9f^HدHGߚ.S6%2 }-P~M䢵܋eX.Du`ds؉_0)ŏƠDӦ,೒j$IߩȂ+"N*gJ6 9)&e/FSa zJ͒ؓ5jٛsiӗMN8\,PH\hjĆEP5иd H2=h?B}/奓eǙi0 ];c\qߑʶ],+d|jȓfI'mf7lY %-5+ְC%Ȍ-"qdU~!vz>~a!E 1J,˿۷e?;WGdObѴ$md /[[֩,}(Q9^y?pl,w+k=:|P!K7OV9yB{ǖg:8l_12+YxTbG$ma)TSẤZȊ0G#0s|D}ʖxC7,ψ?]=V-Z.MڏӿerzTmaRFTwG{NjCR1a0sX,r+R@eo+`hxp6h|iעs̓#KҘ7.u+AS @jǨ[egmer&ͪ7"iFoqWě)VFژ雵{1 H2bf3,$ʉ3Ҧڱw,$śdUvFvFQqĩl^^n롑5[sb?ڒRc(eޞh>:u:J-򶹘N5FGFt#)B hGGC)[Z}q] /A^5Ţv':MHSHLB2\ONBN|Yhk}lΪӹkq/9R$rD&K7XvZgؐyê"P.ZpIXp󭏋;{xqai\iI/?V()"t}9%4Ц?Dpg<S: G9&Qh34;AB%+3dÐޛN vX7|S_3c gj@A5DGl$-)f1nֶM?]o/3PV'K`Acޏ ܔEJ3#3rE,fwK{5l炘49fg{z'S?!҅5D v85s#Q!6[ w [X9 qdT"< QNfiuD=`duT8+7iI;}ٿ["hޱrׁ-KE3W->aaٸ#nŰ7\V&j qPJu"0q>L& VovE{1(y99 =m/4鸿(%t]jhviH8zQc:ebծZ %' CӚ|%T~?ˌb]$x:2jeǶ_.}DԞ4̃$R@U~xY "e]d] pEۀqTһ^hHſ{#qnv혬Ȫ:֯0Q=Xq+[t|P)1ͭ883bՠkr/˰ G \I'P4[v;8/k,V9BOj}ޯ`}ō[+"҄rrޏ2t6}<1!K+\t͹;cZfۘ,kІZ;qmo:|cYl]b7Ͳ/M}BaBƇQX:k<<@IWuhC%dUd>4)œbgXQ x|/Kڅ<\PLʌWn!*sm]?a'",ß,Q-OP 6j_Mc"ѾU#Hƶj*z׻ɖwBdv'rY W3Q͜3ZK Q >A۟⨲Q+HN)&~s2W H3UzCX\Ō|LCmOeP~P,=X2\\yuK^f t{ ^5ܑ!;QEakl#SVo/j9}z92UMmKdXT+9SzrrϥJc50Բ/_9ϱcc _kQJ9|C >5.7XF?5$~;uA7g}7ۼqdy?%1))GeHlϻzZP7: 'sĐp\&+ÍKO؊'-j@Nj0MhA!|L/yxR"0nu举^'+e _ߵ퀵 YmX͜B.! 9 ]zh޲g#HIKӻO  %@c6AY}M7 kIVxfr !}Z`_WzP| Z̻bԶsfD0udfč%q"c2h9&%}0Ȗ q؎R:x_nq[`'?ㆃ=MWPr>͉ZNkcT\s]!60g$M%G(kMHjY'{$D<_hjneEUJ)1Љ#tev1/ʟO &qTmexՀ"]#r?[Dd+-վ7O3T "/ "iNJ9'}L,J3$'pR׫2 `F^?ϭ)O Tп$9ܼ)QWN%~GeΚe²u{1& ϶C?_3Rn.2}E~1[\3w6a_S<eEB7Eob.`&xV94%)Q}e:cQ^0HNj4P7C8҄/K c1(ֹlh'M'uL$t!0M_`x6*FpCoa ulAM 7Y H] /Lw$NvÓ=ӯDX3P}ƕ#DJkBIAFRq0gQ \tKlwuX!, _kUIqʶ쳲$\B#a7 Lca_ɳ-SEa,1׊~\%\3H`Տ 6^ې d_7ΩGuh?Մ 8D$NMRlbwLQLGU]Y2=^&k #E'5,ԍr5jzM2ۘyM!#{mKv+/V~"FEWJ|( _s`K=zE"ytx맾eIzR M,˒qYv]&$u47/ _SeZq2e|@dGnk#iF@ȅFAe"LT>,fe̅{##9H톢5#i!<,.F6}ws=\YNp3X>WrH7fMrQtx\g!8bذ4$a֝.wzԘeTpc<1Z ͓eiĚׯ<< GGh|Zڹ Uv"t|~oBp!WUIOf)l8[!upMLb`5C0TmԬ;Yf+7kb$Aj耶dy8[ҍ,,085LOBQ ku~2"a,BUZ8yߒGFe"A|TWGyKJ(b<ЦI*_7't\%`v7[2f*$ec'0=tM氞Lb橽=6A'7_c?QR;p^;.盄 9;~7w)3H.wb@zNit X{ :_/pmK'e 2n\UJᪧKMGvmTU rݏgEѓLtg-l9ۀ赃uq˂S6 )!TqՕ,- !l|.F\Z*K(y`):Bzkcpi%NUƩ23oE%"2 9axX|r!f18ġ-co[4+مgUXHT#;-ä2 Faֿ3GM!U,Zꧥ$Rϩ,Llj߰1{`m{E^"-=}uxl@"jH2#x[b\7ڔsmB IlF&x=iMmlO!LNL*ϫG *PC?>U&~Xd[?gT1K9棌18.)x/xDz-ub= fȖ/}`EU潶{ӫ"I yafDmDkKC#ԓv뜔rc, ni1qK0wj7Fl#|-*cƝ-FN%_|8pjiĸ6-s?}TĈ.%REe`N>;*ݘjQmIb+=k ~5q2-XxDgP l Q0y}%q7bpҭ s3!$}P`H p,ҽR- B}l; 8{qQsۺVw)^[*h+}uQ-Elu ַ͂uךG0)4Rk@TT8BXWxm t yC+\yg;G#7UGk\^ DJUWpeHA3VKƩP[z=һo9|qsy&yk_ >eaj/)]-p ![ ؄vQT* Þ\rdHQl78Qò`0ͧشݓFBЦ#&z'd˜>W`L^ @jY+ښ5wLB] Z{D0FB;T3ޢxX\!`1b0mυ/R ‡]}:+xX"ex7G/v; iϳQD!Eo;d8MdI8|g}`2.C3Ha|3T(efhlkFAʄa:TQ}"vB@U{U <>xN~CdR]v1$bjM4:K7#.pUѤ9d<<W5`)FDswr{yM> %\VA^_ihV(] #uk2$`ykI[|C?_rUp6}Ѫvd"8\8JkC}7l"8qєPIH߹WHbHbCg2ДVcC奕QwL'~ttYEZ )^os{ea1hPK1KOW4x?DSHmJ. ⛡YwK 0OKHu"xTǻ;)sAO^єL"R _DG/-H3N#Lj,-D9b'^-6<|#p+E&R6|PiIEl(cp@PƻnXEU @ӪE3 w|!Bh-)vpxʦet7U Sķ¢dzǸrܻ).Z,A~üMgN%e'Zu / i(B3 ,5 2rE oOʜtȜC.K\`ܴTVX* ~@mJ{^NO,@i e.YRY|߬#o7ICf')'v(ִ=.)m*s*7 G9-l>-b8 )eO=?gO4K]r Rk\iv8v6CƋuJ3xuu)8]yI8;4 >7_d"fs!1b_۠;A)#_Bi]l\WY)ksW>Uvt]n3PPUBĹr"&<2"$Z^QqO?:0 $7HJ~޽ h!n Y)(cK6ϧ<:OK;C x3c|x&̋\;+.rٞ7yh&nllMnأ+ߴ A/BD 5xOmKE5?{g`2aYE:X ݭ_ұ,- q`gw0Bn B)ڰGb +⾽2\(|e?Vk`3,4sy Y]酽RXih,Tڎ { {sm!Tv\BGj4rq5 q#K (Ld/g@╾:z|;F,cT KZ31嬢Ҩ)s%<ԇ} 7^Zcܪu֞SDޜMpQien >e@/'fWƉ 8qP(BO!%8Z"-p͞K[J$$STM~sBpI㴟WW,LnNs^>i-M~$nKd9psNZVe.ۥw@"Yp3TS0RHTQKܢsMK<1pe=`:RDѠ Fo4lt=Ag D+ Wu*FlgED5~'7^0&֪⥨ȼ$a$azUq>{LGR(gnu%vp+~7~fw`V%d_`5o>V$~6f&bCv] 0Wv I&0|dUq@l[ٸX`tى>8>I0XSVG+EC0sg? QI S҆s8umlQbj ݻ`scvdx8 }xZ,pd)*HL Oœf)=^90%U:yqc ;ڕн$1W=[5@ԲIH̀R|[ AbMuA;q [ܟ3#qձ)%0S w6fl$ץ6M>ex0 *8zmALUJ}I)UBWsg:;EgTx@ gtf䋳'BE1*zQXv}/NHr6VԴU.- +HIb6Rj_f*]6ODtբaѬ(NZԎY}Ĩ31u x hhQ+\vy_0;9dK|a Q$S!*Z#jGL.iPr[G12|.,C@ޕșj_sa8)vteU.TźqR6k`CMWzY:Dv {faTQal !t;E_6?pT7CbYL |Go_"wӐJ4#XNXN-jή?4NӛћY(-vZ[2kLxTmxٛ YQ5zcg~Ym$ks$bT/ nUFqyC8C `rʩnp)-f^?juP`u%&+s~^tќ4B"SraB('w@dP1sܒ΄#hYYgLM 3* mt,<0uשt>xmm |Qm0)5!]%/;t2kZ«`y}ymn*+RUVȋ; ܊H1k$Uh @AcaR:yd30$I?_ΜY XDD+Ь@1ݙ6_믑gDaph6CӞ68Ud2Pv5T\䠵ӼGbgp%rƒ9 Qd! J;W2+)\;)2z2`خYa?/MuTA#bH>;83BO6K=iS]h"ϙeEZnx f!)TfƩ *Pn(Xl;Zxc}IJ lDaP9"OC6бǢW蜷 m5?5WЋW3?K`~e-ԡt_bq\TV#{S 8EbNƙ^d. ֻ#U( Eyr *wt}+}WOb\Ll+@8kCC0ZH Ai[!`=z!|6$U#)\ 1w0.ݙi.lr(y#CXF.ؽ6t:Y7"ȇ"w3PN~U) t3 #q(:M!T!2 z 7فf񰀩BogәqxK޼*N ,kbK.A~_x8c J)TYAoA {fr goEhuTf3uePM;DԪ'tOFd]pnR S,zhzik# ЊyVPɟ0x$ajd.Q@ 0dF`~/.JHغ'RL.n9OVߍ1cn$T)H%X~J6rɺx旤圣xmwh+M9$o$M^]޿̋wS hL,ǚqR B(S}#Rh΃.g%. YS))(":۽f@QהGwZgDǩ3Dh "ɭH f {zYaN,H1.;~뎰D }8<8m[uκA$W*y߬]lIVj:@FeP)$tow͵++2{h,UM?. v\hGS(g,9oR'nf̠i3]L1gbKάna{8OZ,W'u+ 畎zPIPÃ\t)]Cx3m$Dƥ.oq`m~%,9hۈ`nw]v4ƴHf ˢpא$]dB.R01خ3A0tĂul gzFg8--Q`4buRPira% ֤zӆT*#(ֆ  !=!5j\MȎӏ@=r%.oKmoQtBp0l9b*~]c#[_:!!(L=cWu/]lTD4i]7ld"v&i1D< |I{B!Yr榞5ǝy.%˓ϒ(v,E*IA`Y/x+ۄDآML ֹkw N|yb\0.l_?k@'E}H%Xۘ*k!ijL#Q s6'^+g>2;= D$c+&}HnOkп) vxzHTW?ڒ8O34ǙQϕ70jϩ*~4e)$Q,03x'&U^N|:r.@%{! vabګi߈>-s=95gQ&J;DdA, @$v[,"1o`9Uk*> ō0QNϵGY-"WY2u44;5BON(QcVDc6[*L#C"'x/Zm; Bw,vmzPbS!ݔX&!O2b7 ^80lR ZQhs9 .h8rAG970S3zvz6Hl&hQ=VW{x0Ѯ"{k!][d*U7#X4*$APh*9hX\n ()X:;t | ̪d$Wj}gt1$iL~H+pC7=L@("7h!WNy|𳕧VAp9q==aoKq?t+RQm^VM[u _@._ 4 #k\@˒SRObԡh:HKKbc(zm2IR7~ϣ7" ߪa/#DhhbGlA#Pduc^t$Q * ^"eO2u}P88]:YcCDtp:榝srcUZco+MdAٟM`ȞfX3!&ԁK* 8(V@%uOv>D?Lj:6{l/'zdq},ٵ񂑇C SIYDmN0d *sRG!l82.IxHk]R(_^Ů 5rv(zUncH-( icъ\w݁4=tG4WqzCSX&/iTV[[a~/խ);y9ogWXB>=y|@JpS^*P,HhcWW\)&]_ޥҕ֔OqFtISD̈́6LwFE–idufȴli`'t ⧱* dޜrQuV__BW00i)4s'=v4ItT~) ~ߛL/ }7DQ%{?X#%eXQEEf/|">!0DQ9FBLx߷ڱxCM# 5? /eXąI-$حi=V$tbFV|$vĂQO}tccסB3a (ڕ-mk&L V@a,AmC CC0g S0ƯfGNάEoG4cE/@rl FBLVl?4Umy.zehvs*QaBǃ`FYkHr8ع7T$g;뿵 P ]MNlѢ#_{bZQ@Ć)fR"x J۾]T8Xv<+'^2G|;A,!s#7P.@Љdc6Bi쀍2WJJŻ@UDY#I簧5Y&B%VlD <\ɧegl*i4aNe_ǶrM"Ϊ52Idjc?ϕ}u Bf_ 7T9FSfI'#sHp-} ދ<5,#Qhź4C$6g=J\h,5joeW iZPݬaޅܚG4f/ "mN5dSs\MS ˱vEJe ckB%4 N0XB3%A{:l/nɨSܮyʾ)5/H'0\Ie(9~$@ifj<=缨h2byR|-h}.-)Inw %0}xewv 4_xaZFi 8#ʛSwZmDsk+ 7HYƦvQOgD*g6iѝ-4 Ȥq]xP6|<]BJtJM.u;qCʭK%OYv`\bkq&Q΁ e$\>Wz7zVUO<f2rȃ%U_vJTثZdh<]"q`P};^<y C;}5b^oRzc$2{Ni덓ZR"6Yy>#kSY CnzBӈ.9%3ܪ{V;#m}m>YH9#GEVVAW!0Z Q;5Iڗ:` %N? ''t=G 隭xj24ﱳA-1@c ,F͎/MH<zQӭN<ȕ)&*HDHLR$uV?oTeE<,IϤ6P(\/mAa_!bݐg.@kD)QSR[G_A!ntܡW w JL)?_:D 1KV:D8WlFilJdL'2 Jj7%,~:,;3e}^HF"| Cé(.ȮO0T<Йahpqclh1|$MmkM+bֺrԀ9V }, ܹ\)gf\A9lӋ'dsds,b0 ĭhC>JENs)FDr]u֗"GgswZ|RkvXϋ mS>.zu"s lmph|t\־~w_kDJ4}9M9=K):Yȡgjf|d"~ɠ{ܬ|lte]pUs/xcH 1?/}1A\. É|DuZ*|cр(5lXPbE-wd&FY0vBg; /E ]6von©s}4ɓ[LbTy79l2k RP5~{c}f ※S^n؏})W6">fyx%m$H\}HhapcHme,%d]7֕}l =mn}~Jc3 r" Qb{uL, gŝpF^|OWz;ߕK;DE* 'I_[~FA)4kD˻ i@Xxb4 /S`+jP'&ld_ic&ڱE"7}ܣxϩfRlcsxiM2@'D+ÀCuQOb O#}{GB%9áʁ~EFs̾|lդbD_ӨKx,D)- FadN -w G?U> ۱8WZQ6=, h~7ٳas4~Ҵa6˾bLW#ᔹtZ:ؒKDwX_(*8^а3/RNy?b9 ?Iӯ\:(j8ť+ ڨ25yCk5Cc1:{P,cY, V]J=@ ;.6utI$|h0rkޝ 磃fK`0=,@P`2X4l3z endstream endobj 624 0 obj << /Length1 1624 /Length2 9170 /Length3 0 /Length 10227 /Filter /FlateDecode >> stream xڍT.Sn[Z( !wNw8wۣ3sg_뽕|[>>z2 EG[ ddA1 A0g#D_r0h4!U{GPGH-@G(@ hpT! gLF9G lm?Q9(>6W{00 "o)+@,@30=Y;/`XZne;T y4acB"na^ ؁ zl=`z9]A8o#L%0Y!?AVˇɣ,!q\z*jvOV >͢ U?*+Gȟ>N?u,+࿹4E _/ÆR:$xnj^vMGDYɸTF-('8M@>\P]vW9j6w~~вr[빃B\-L׆8%F[˄^iwMbqpIL:-`wQv`_eHsPb%9W-Im6˹H3%*E4}rVF^3_6V},ʝ7u<," eؘFaUQ%U{ЂΓO'&%zxؚI9Zq1"u]DZP)b?xuEK/8!Jr3&#0>7 H>T,@y }bD}^*XrtO{;}u9rJb-yRD)?w$8NeG)j:۳Fʐuq7f<SMy†YJuz6w_E $7zo5NiXC~iOӋkt^,jWvZIbb;G |HL?u.eug^e2ZME^>EYQinD/;=20N>˂ ~7%o7UhSJ'$۸{5"APDpuFE(,4FGh)}orDv7hG/=У@]͘FcZI.8=(*{%­>#}"]{kbO0ԍw1S/ҨL_ϹȞBsɮu՜Z\֜oia1Zˈ2 "$P=ΈIMMDŽL}m-S8V2?&Udن Viitɔ~1DiC]l5<'Q~P"uHT3rܓCs"!5̜gbe8Npf}~W2띙0EnP`1\\;~o5Qp&Crhn5iƚ}7#a_ati6SsT59- UPMB:G,n`A`w]`ljzio*]-(=#C??K³6N _aqfh:{ CJS|T qlLGؚ{://NHP&)ڐ\t2a Cz^wF$,тHdYe#GNR,DO+?UwNUS?ԦA=sgl AQȄe@Mh.Y+ZrT¸g= ~/$GV\B^Jk@"]1JQS)YSߋaY5-:b S4a'|\KzHRY4v!qJ~ˎp(u_5dv!=;I@<SW,v%{21L57NZꍫOAI ixӋ(ՁX i9c S-!:DRJ Db'))&_g\Tǟ$>aN![0cn`hӅ'(; BUZ>gQTLB6}IsXCh'$vXgqNGS3eJ{P'$ #l`B__ko40Zb.`I8Ʀ!SmosMh!Jj|*ѻO}I#{ۜ-h`Bά{wfLw@K݄p ,VlJ¤ A4&f_jYO>j/+-$M- p+ŸϑA~-\;J=Vop ] dKHlid3e$e~Jhjpb*>j3@D%ƫ;ay LC|{:jrQ%W2V&Zm|x&5i bbRQϝCGCC!Kbϯ;\f|lRDRx HG3p61GPݎ".gR_%oţ~N>bRP@ jxJ;uI-7ڗ.$Ta7ˀRYẌGG] Bm5=r$UFp%r6&qґI⋭%Hp!Qkĩtbb3zz=eݸVq3CjqE[诣>D*%te~F|<oD'C yQ0tSP%A)qUBZ@⧑ {@]\u՗90S)ऄҌbڀ8%T:E `qw0> w~-ZBjk^!Bܕ\F8<3µ\-Sh-Og7wf?e3U֦܊i@=Mp=LJx̰@v85˂ޘS\h)-&%+jN/.CU$R3WZ.J#XŞו_+m$U9hu ˥]8$%SD1vt 0PZ|20Hׄi)Odw( ƺ:*k]d.;~-D7DFbUA]0WZRƳq"2 m9ۅ7 }vwgDLHXbj%b˨vn/\-749 Z5oޘDQ8?虒m\]rЬrZ5L}MB-e]X{ߕT&?Es{ȹ:v5դ5Ӣ.*7x:S|1PgcBo m__ `]=[ JW*f{T%r]LOuKjq7;xMR%e]aMZ+2 )5N"L"Fbt2#_zlhU!KOJJovf>ur '8G#55E~66$ꨗ A?4XJo|y{^mKѳ8qʚ2C('\~>Η?Y}Dx"~spM FpA:%ݮr+T]D*wA#e=o=5LǗѡc!\Y^{bUfsL]߫BAr W5VBMm2F+X-x_l:UZ>Tnj=Otƚԏ'FDLXQ鯾MWeDMݰ2 nߤtE90{f"gb0\4xU_Az?zͱf$ġbWm'hSXܧoy7ϙujΝρM@xQqD5| <FR8`ʹ#,PVR Ot0qxk^7rEQTbҸ=x p)yUm66`Mn-T0r=Q %fjs^ $P9fhzr;]Yf'UGZ8-#Uab/ 93vV_@;=aD߻{̅;sH#Ro[*.BH4nyy4|'%Lb]WŋF,|%lDɽKɆ \`G*_[FЦI#=:ߜEp!~hm(ۨD؋:A`QQ~O"x߶7%?M81hd|ɔ0ru<ʠƩrNa6tۃr.aȔ̓x[a.hYrM0G-NJ oUhLI-sn iEh:ƈLGtkV kQd؜XWaq+ %h`,\8 <afkun!tub>֏M ÃCcjILz/nl ڵ '/|UN?FN0r]"\J+_&b _N=deTOޗ7i}xiL~CnH3w}+N'2p5c뽖ӽMSaGo<6]Wj4+E|tf{ #oG4$ 8ޜ)5>[Q5'éo1Q5s٩w"_o[o6q)%1y7<ٰu3hλ3xǤ !E=>2Ԟjևʒ`O-iI_R_]Fb{%1t^ Ie!xct^g_":(˦)J?ʸ<鰆Z77T1ɚ!t)+RyWCZ{vn3f`dxVɂw&d|X/a/1iKܙv#E8ZFihPc[{.S+1EuNՏrh?Ԣ:|jxo0,5oO6U~Zij'SejX{{X*'6[pscM fqv^ 5al\7t),w7ȳaVYXHϷg]QR'^]ʟBҥzD;WӇ爀AUrj&ѓm 톨JҸ٣֬cNHrW뼩RĈ/r^ A;{/U[&;>l C8'}Qʣ y5{c ӦM8Zq_<6@2sZբc`=l/v(zmc1%ҥԻjw_[I5*#Ist+ϵpkM\3^'4Mj _]@"Ia{<˃ hl[~TwSvKO0 {Nii ABYV _82uz+(Fتk ג,JQis|_U\_NJu$ =^޺'T_ԳeȠ.4Nm3rW,|6wH\ -snV?v`cM;a]F}aXr}_P4|Tm]CMYgos>GZnq |i wjp4b 6|xE}kOP%?47v|iVA|UByd, Y 0^ H.{Kikɽ,G4Xh0Wѽ`EG<)pCt"d;P;;&otZ=E~H+Rd:6dJlgoxM]@5~ij3URKJ35 @Ƭr :Ǹ0<Ne>^Rm,-'OXk9)C IҾ[>C\YXcq>zr]\cC*6գs>|`x? ]V_qE2 c0u %p vo8FI`ؚތIZC\2ۥ|@p V z; ݊EFB?repfXh,@aJscX嗅}ad'?rm endstream endobj 626 0 obj << /Length1 1597 /Length2 8923 /Length3 0 /Length 9967 /Filter /FlateDecode >> stream xڍT[- ]K"wnŊSCq(V\*-nҞq3̽sF]Mf A8BiUMN~7 AmFgaPH,ܞl2nOq0(@p q qp88'"[T% svڹ=-?&+f ?t#leZفV`V`Q0ع9 {zz-]0[1fV' rxY8 жeׂٸyZO u}pZ\OU@пU `7N MI9:Y@P[ Sy,ֿ- |  0)O9I Sjvrs![dM˲Pki#> i۽:Y(7Cm~7aĮ;ey2cx988y gʎ7菓_' ?j}o [,A`(?Of_]^#'q8~d$/kOeӔVWIIl6.^ /G,࿫'UjU.O?߳ o.5ؓhA4na?).Hf-4U@w诙UYWi$l#UVY%: a߷ &p}Rip{IYq,\\,9 |Ek ؁PS = % ~? >,Av}[ rAv O@'*_ /`w >1V..Ow?m?B_^Y ׅu\H{M2e0.tb2xr)wuGBbGkJd;{$v)hl_tZ?*1; `\{{T|XObNqpCe< -%* ,~#RKt%\7>kU\ݤ$c/|)/mr>(D/)0O(\nƼEt~ GF=n@n #AKzkxzPi4l5ѵ%IH0"џKkĶPv ڥOQ{C}V6Sg*z?Ԧzʪ]?Om9jѶA&cPaX/Fѭ*eĊG@٭z^3I8`Jcϙ#_mk?tyh~:$o[hPguHok6b5C=eKOC#՟L_Q.) +(y!ap ~:IrX?Ћp&j@bZ!qʮ }3JWXCyWAJwaE꛱_! jo(hp0T-Lvf%FJ٪|q52b[__dK1"LK 8|׷|Ck$"$uk_ClyQrOG&f=r>ee`gF˱+ƅjV$6sN0)vwBLk}Z@[镝 xgCG[jm2oE]T;]%5w!s`ͭ_Ecqtƥ+NUIJ5ȶ> QL˳w+ @MRqRhveC˥@9/W/ڏB?  n /JcyP>9FQ^#Ǖf9/d1qHMs[l\mCu[o"$dkyL5fD(bIڄ%}a.~ģ2j[kt˱Z7ѮvET&k"1y .*.bE^溷OjKQ׽'+1'`i_}`HMރ}㹛̎q3KU1T3Ĝ$e`aVO_6*4,h yv5H&Jhy<p\;lS;NXZ@fۙMGbyssIĪ`AEp-LSv0"uщY):,EgeIzjF]ӫiGQ:+E(R_e[i^I;oBHUܭŸwLx"µaSTkaXVzoJwBQWrƷ7e8-0S{sSjUyvrQi坭)j(j여k~>K=72odAfuU<LpRxit n}E/or%cpjR2> x:S~y^Rb UA>]yahV`gBVjwmWoHHJ "dtx쭫;>Po{+՛҇ ӯqVV TkBڨ/綞f :F@[rEd&cĊ6` ^ϧ.y@JL9jry.&U{%"q]爑WЮ R_5y %vXP |I4¤&4.sPwupU~J"_ vpgBWmџlI}?ӟ~,аYw"geװi `<Š"8W{[6,-غ$Kh-'"2¯_}R,jq 8sz 0edÚt~?(_¡y>JE˴qKzDr6d( 2VGZMP5  p+IۈC|>FEs]i$}p8vé/;kc۫R뵣'΄a΋@y\FXJws5Aug!TRv)x^/3PP%fK% VLw ټWk.@B-lW5PDi-!9̓BD̄ ^dX*H\k]U2MdzstW5㧨Gv *4QI5 0.3>C)r7)ly5STĢ>0Hϐ->t|L%t ; Ҧ*LHwAcee %肎:ƶ5|zONJujdyA0{G]4ŧ;//b*}ܿMd֛2^Jő#o,3fT]e>t?/.Eн` [P&ZS)yK 'H8@ҡxQ? ^y]}$2m)70ߪ$"@mm@a"0B4YՊc7eހs҂2?yɘ78kxڔ9uU|ncRz-?+z$EEc~|Xe# >*4[2rŤ;l6:X:^8t3ZP\QGHꗽBlY-)=9Cإ.ͅ뢲ϰ"j0{]Ã0vZJXCdSl.UT򭿦JžR fy60Q;aX2fVr& j*ݶ"ej^nG-]hH޺RLr_/v,/"ΣrAc Z[֦o:.fI,P^ʉI~S&NNrB֛# }s[x8[pY4 r.Pg~6Gx93*:@*$RQAX\˼˺P94GX_T6aLBbEZq'P.8ګ > &~~~T5Rկ6{ZU-j:k'|fqdǶ729rPCCT@Q7(! *:%1[]0 fc&$KUIzg"zjSxd7pɊ GHW#O0@T{gT?OuGEѥ:-^lL3)bDMѪ"egs,H*WÚ;7B'[EtX;ƽܳoJ0# *Z.^[in] iG%ҜH1v/T`UtŘЀУurMc2UIO#cVHԶ UҞB'sV7r<:>5b7䗅>Hqjѯ7&hƒH.E|nvn~'f诨 ]uou|L[@*_[xÁxor?Of SaNҟW|J=zQ1-.(a2 3ٴ1,)V㽯SCĚ'O ώ] -0sX52ddbz; U-ˉxi84 ͌ˀZZ·2XmDՂ@IؔQ8Jj]iKp!g"꼻h^ސL͡GzӧRʃq3HdeE6b5bY`MuZ9t9TbK_);[=UYJ:#) F”=NXZ-qoni} .ˮhGZlAuf*C"B%>lj=#)J/Rzsj8=v0ƳvѢ3-&s3SI#d.0 p?)OK}-t0bH,;*V&: Hݺ|ӳS'"w=ra2TuL7K(Z5DH\Gv\ઓ\ !n-=*w6|>J@PJ8ٽ-bRb{GI)\0j :MRN)[v?*g[ XVox}.єaVăsaOCv-mp]nuK\5rkk)*^݄9^&Wb`Sn+*n_.} \^bZT2ް6 $\qmo17pNn@G`Mm+::BUI%hٌrש3mMRNJQ,YI̼j c2*3| zTѻtx `Ȉ 4~iIz2X9v|2Iz@: "3)hw.]_`R1iPAd2} Vi㋌+b 9! ,+iMZ'C*=ޥޱ6Mwp[~5'~_+|ev]fPGkO_*)IQE~.ٗ`_Po[1UۡG7/GzD/?͛Z9 eJ_c!Zzm=CKǔ}YȃU4$Xq31NY푵[</5*%oF^+aw7ic+K$,G e_+T^?L(c endstream endobj 628 0 obj << /Length1 1511 /Length2 7479 /Length3 0 /Length 8495 /Filter /FlateDecode >> stream xڍT6LH(0H %% 030 )-]4HHt"4ߨsoZ3~v;5@z-]Nik%DCrpD: %HGhAB0" `$ #Qfp@  Qj PaW, Cnb v V`@8n;tVP_!XHgQnn.+a+":Wb U.@]gum`V+ f APwtc?Z;_޿AaVVp'g0 @!M5.'Y2;Q`w0l28 :W+ uU!0&ìeNNҕW~rP u/cu=`>6PͯݜaP7_(@ !a\[ F wؠJAm W;DA|[o` B,!P?Q0揌< 0}2CqspTuJF 8y@>(Z`_YU"Eu? 5}Xpc!n Yxiݿ_(9:ֲR`'_z_ݐ(Q_SCȟuUXCݜWv@fw POie*p_ A_ jP+UJyv ؋5b$A57\08@#~$p@Q$Ep7?o_ /u7+fyH;|@(OLa7WVnjSՏȿ_bE0? m;~Qbn3hw$Ma ^EJ ["rdgyk۟W :o (F ?{{kقީuq&'=U_!|f]{ZPl3V?4d g=MɀcI2*f}^">MS|rJqd^9G|1pTL9:fU)ѩ>?`IeSP+f"VQb~ÒųՍ񆏜[D|%H1Eu1CwE)?iJi]v\s?4F0!sONbm3j7 X}c8I'ݟz|=ب GVfQf%&lO ܽz%mjf5 GW >K?Kz+Æт2nuCQBw&T("x;l+e_AANZb!ORY_'FϧIK\h0Ns$+:xj|-Z/>eTBf۔ R]gzO.\Wsuhxi^)W(w#k= ̻L P4&J4=2*oTbVIn H-`aEZ6t7CRZx:=7$SL7hf_t>7?C4?+ Uy&7b1'TAw2lDWj9HzuJ*}r0"!8xzhPG[8Xw5Ғ» X|elz/_z'kkg˭f:^r{vjӬ[ނHo[457SoPeOa7yW#>6ǐi;A: ,ew?B^og \*EE{|}{3[VM[-@Og"9$x;jKXȔ8b$'Žcq6$c05!p7TzTވ|[_#=v-/H)@ݗRe{k؏` l%KXꨕq֎&UvId^ORGr,߇0&E]!Ta:υ>\[(-we5 λ2?&_@?$$_-ULNNLǓ&0l҃6oHqJi  -k2 %h+n.evkHjZ)`mz1;>mGco`K-@ [+KLuO63]4.=Yk=5'mh,y38\}C)sj+ifʯWMup6܉r\q]pFn|o1} VVe@Gg+ YmAaͷi߈l3ܙVkXv~J`P|(m~2bLMAjg o@JAPj:qtZڝT~#ծ*1VōS^Z!$$ܽ|_RhGccSpwl"a3UP löpPg)$OX]riS8k~欵}8.-ʅ?={gmpH=@pgΑ!3ځ4z{4II9!@ˋܣd{IGOTEs` FzS:5Aw mYLc;X|[2|3^( SNkqpy{l6~͜#(tK+H`@ut{5\W%?XQBE;WOWf>46z7uxZ Ao}W~oZУH*>PwH8v|#cМ -&3%_:2Ώ,Ed^sP* t,%F>ۃqn%KamT ƿLrǥVkQ\v~:II-1Il$2eb\b3V_f"cN>jK.6A.gO&fʳJ/ѩ1EfS.3lm@2ۑl>F%|6#V:ľ~%T:5NPaVO^m n>vA`+ $m|Nx2RQW-L4K*~+I6A/DsG=X!|'cۮMY~x80C\*1Yb3:0_$FM'܊[eRpȺazH)#or )=!QZ&hܱuyMUk M9DCaJlk3`u{M+?%Yal_Yr9ldެ 2/~bW@)z 8F>oяKhN;nL(s-ZeQO*/Eo[A5~Ǯ 77%DyK6>PԷ\O+~;!/t/17l%"lSF Z,J ;o/PhniV'$S.k гQ?}D}CQgqEWWE҄@Sbh`A}]vCZ]j-'>-'$ɺc%z;y^'tęG.=8m (=ݯP#cYg>蓓%.o:#-du女fI]'hf)rtD;׽+׏Y3Nq*_z>5`M,bt[]5˙~t{BĂV;|!h?pq\F8UIMl-Wt=]:ks_9tM̲Sslͼ$>g&Ubz_+C}1:NU~/GD˾,:و,/ A#"-I.a\ *gUSPZOphe#k%VR=噆}Q\674bvvKByŪe:.aϤK\eq2'84€MIUUtXװ{`22>}B47'D|/} "bq 2zlnH{plptM ՘㦞I*y L*sY~QC@E 9/5B{u"[;!7~!O'BwTx׊.i`zqyR͡oāԃČqDɒ+<[ߺ#ͿTTZ7#))1mm:y"^o@YcH8T+xfͭ0άpl >p6/] F_zFVȐFg,Q1P5ť][rW^K,El]?%;+WDܥjlyӣӛYm#X6o:)Î_rbGBYGK:L*iʵuD-Z3 W%Xr|V^=pM,Rcl$ h@qg& %&,_7Xj IїtulFbSw›U:Jy10f^w7M#;N)y&\'p132jT"}.Q'UF7xn)SM%b+t(ĊZ3Ah˭1ǭڳդv?sMi uЅX=֏ A[sx |f9)ERo#Оt ZUM-5X>κ D%m,Fedz4Q/nYQ HDOӎn?# l:݅;t }V)l hBp,$>xYS YWCz-HHz4j0]'ϟ;-]Bd+bFiٸYb'#sI:f^ڏw)Lm}YIk$Yb>s+Hݕn6t˞q*/tYN7W+$w7!4M*AaW2_2*_@˾^8:5B0rđv K'~)W*,W K# "3啕ؤ%S9C/,+qmkv,]dpʑJ;d:(ntV3:)+k\aS_sEy@zwnN|m0QKp 犂9ka S }sYgϊT=[1rєsU 9Ơzw1+JD·au$@]^g mD8|*2&m3Aj{ў\a֜\IKֲZ#maJk;UϼT^d7RYZi!|e => WV yW\ol=}Sabzщ;i/8z $o;j WyPsN%Y~;*؜LlO(&رyEϯD|2ajc̛7DY%YT`#@nLc;W4²[%?V{mߩ/y3pn7z:xtg}ph&}`POyP >ˈ/yR'ʶz~FsP--5i}uSsG ǷW Vo3 >`E!JS WY} B?L)O '2M`պOiBT=1uEOG> _%J0r=?DfWe2 =\}A:Y/gQOFb_/unJ-Mdyk\\пstJ>"~F|jOHLQ7q׺anu؁ZDV ^ TU $Þg_px`Evn5g/b~5 EZ! m$;2^ؖ[O rЇ8~)qsf+hdQnCԀ'cB?U:İ[ ."AsB3߁ rTSw>Ȳ?lI7x yD.H[m2]9wfo ۻ[4nG&9O _P1t~`-J#6?ra endstream endobj 630 0 obj << /Length1 1574 /Length2 7805 /Length3 0 /Length 8854 /Filter /FlateDecode >> stream xڍTl7NIIH $n 1؈PZBRAR:AD@Zo>9wvv߿wg\ Pp-K ā`, 2B]P}'/bJP4VMjx!"@D\  QDyH;P{Qn~G46@/")).( @H6wFA]( {h#uBy8 }hG *u.L4rDx Qh\08k{Z@]78_B #1CA~h }@("zC.P[ġ@y} [_y<nhO!O˯ A`SD‘hOp~cuF|?g{W v^n c$ 9@Q0,. ݁p_#s#?7o!?rcK" =@vh w@ xp?w=`, @'K,PHswP@8PPX @C࿽AeViJIۥ$yZ ^}頰y!X ~Aiݿ_xPW_r,_Xk_UuՆ!\Wbw@w*_ sC?sA z(Oį(G*3]TFPvKXT #Dv ྿  !Qh [^ 5OOIX7""^9XZ c?87pGB a Pw :}rF`2}=6k W`^؈ߌƶ?ߏ 'P=g\Z1IL{xQ%8Os(7v|SKޔ|pa`0 aզ|M+ j{`g7m\^z4'>U}kJf&VW_i^||jc\8ƕcr-Jw}ϗrhzF? pH>|A8tKg;#'9+ ILQ\ޘWAvKՠ%{~*[[m}HՈW+DW.Y=v0%.^+>[|qIZ;m#zƛMfM1VM\B6z/͓~ Q. HF+.Q@ê 3#ސeU.3~c(zg-<=ֲ썀dOD D$f&GѴ3 kNiV Wg]L[m4Nx׼~xlm60zEs6.;QҶ):Bt{'gj#v}wxyIJ TXwOKYks}wӞވA]H 1cu.u EDpKg7tkt-=ݐ!!șŌS&ޝ1sׇA|>M?qnZH#,a{%BϏT<"UNTYTNRYoXV.l+^+KPRv}}U!©bmTaM%Ջs|LNh)SҌ*< ih{^R?98KA5ɢ0iA/]B{^ϰLI8pu#CK߼QByI6 ] e<kl1k?d=P vTic9j0Dk)%۝Ī]bE{x> ;=ak"OySnՖI1^Z7f1"&V 8/\08"& eq̼'J5bOC$ k?JO4dwn&b<_KZ$yQX:Mʝo.{4%NT؞is_F\ύ_R%"ue&YM9$|%4asy~LJ&X-kvuB'pW;Or{iM)s Zn0%G%(ZD *0DflT_7}{eh^ 3{&Oޠl2DsۇyfrEM=EgT )<~kk!ըa/[l ;yo߱DPW9X ՟ )T#Y(;_ xc3W ZCۉZva&?ҩ6Ɔ 4[q S5~,8[NGtOody@Js3JLYzWaTͨx(nXIa+ghWZCBw/c8b>\z`_ɍN9ɡ',{oƺO|Q4W2wHj%J|-L& 'LN9΢1wl#SaIdЎvi(q[sr@uM=ƟC<*W_)NІT٭9$_SiJozo(]4VXy}8ێR>݊%+XS>Z6$b@(eHdg#9mwyzcY+.VW]i0T,Z95kQJUNVCebA#,(zzy{tuޯţ)mqBVEie:OSuMdОq5;^7ْ~?GJ;.B>C9M7,u lw&G1NN[X(ZqlZޒtæ.tm}+|,F|QR( BDTUslsf:tQICD@WsFDrGD\ SںGC-EP̍nBk'#˷hZA:l l_J!?_o2zc]Velbj0.'KT\, Pv$7Ep˗dk^QjvJ84{@23Vz u2{4#g^JaL5%b /szFEy<pLKCOоF Gr6\ew;Xhm3Io2w׶i6=B !ձ +Bp/voʢgq ,3au:wxSҨ %CQ*bt9PѪ \ڣx!:c/}Αfj TI񪜛Sud4YUfri~ν4٘31Fcѧ=7-_3zɬ{"zsWeDwVV<;CEy5qE'@-'{ƶ`ބb5ͽϸfq{ raT-\ |/M(I I<=hk",U y;uڍ:~bmر@-:9 v+buOQjUbqt׻ #ϮE?Hp{}w ީLn=k,{ Ťl13?DO6ԜӾJQ۞V|ֱ5hY/7v9]o;%稕WSg][ݢ]qi]5J1-߂NݎC} { 7 ͧ hh1lЏhYxY2*i?z1u`f˥o˸u)z V¦VǘQo;7.EGpX+td=]YE=::_Sn_l)ѨZ5ReQ`&ݒqB5oY[Hp >RKrz_z N+cPT@z:3=p=BV(ܳ,_b^s?rlr'1~}'LX~8E؀ub;Z1םJ~Y5|6p8]81OˣgB.|a "H &vG2E4,Nթ`u|7$#N$j}ԵB'.8?,\)C CX/K%|pbl}yc\S beJzM*Iĩ AvJf4ElKZ$@lk҃]GqnEbTƃ= xߨ҄Tr)1kq!7;T2:Ac-ܷdRTl'jp^2O:$["ak$\vl%`ƪO(g~X˨'4 5=ۖhߟoO̘l ~GSG[j$B ~j.CSpE·}vP{ƭM2PQ%E*b7K.|wvA9|4i,˜ ̥?]8j8c.P狊r=m[N H[Ƒf^>ɲk#WirV\-@4DWUgL!)o-x1ϷgtYp +ZotpJ1dY,:$#,͟n)*hA[<ġTlU᢭To)gO}5ND7YLfngjųfnjWV F^'\O/Yu|fZM zBLw_쵲1Sn=ŏ-2Oux Yo{3|β-5W': flGr: 9B6v]u:jp ¯:Yw]s&- ӸMZ>#q;*̀s x.+)*:Y줇gnBWq8qPʂx-'E9>Grr<FxwfVQMϲʄU㇘֜arfߺ}s;WpqqHHVV|G݌w|f_$ GF%3Kxq5RqmUv4|q'C:^pw+l#Ar=-Sv|E=58PRZF'5KB ^.K+EJX*UEW-e{(T-oYT#:UO&,l,R[kHX~i|u7[暝}IC>U܈6 VzU(-_ƽi'7Owz&:Z~۩S{Gh^4A1~qʠYc1'u Ņ'kH^yǦݛzך./zk՟6 t;Z̵O6ݕrSmpӳ9ȩ6\} vٍcs֛{\—S۱F{)YueӥYr/tN;/ /YA 96lΫ ]{|r|V(lH$˃RRQ_+8K0Ͽ H`IY:|Rc^)|j⻻U7I<.lçOGk0(p1NYjq#B}[g{G[v4fhseј8¿VoJ*=~Ҵ9f7?덄O`ā'jóB✓6+g:9-.۟>,2'mmkW$ j+7JqK8UZ)yj5qb9tw1BCWZ.X |`O},m%_X g BT)LٳM"1x#%A|4~VVZN} CzrzcIGKRq5qmMdUԁ 4qrc]+c0B endstream endobj 632 0 obj << /Length1 2198 /Length2 15568 /Length3 0 /Length 16889 /Filter /FlateDecode >> stream xڌPٺ;wwwY8 w%;݂t>;}{UU|2sR*1;%@L,|1U ; %)&/3]&nnȺX\||,,61tp[ ) 1r3:[ &V@Ljf@WEAՑÃޅRajP݁怿(N neXxYA.n s3l<@XJ`eb/"lbf`hY,%Iy&WOW /C;wwk;w7HLOv.f֎.L.ve{%@b@ _[;ޫw[mA -A`Ȭvrʈ]Gf tppN_^qtpXCq1q\݀~>Vo 06s-A@{睭=z, `_ecwsuEDN*QQO#;~,&可 OU},-s):O,@gY8YX?./HK5}^\g_}@T Ϻ*ͭV}D@v-'\QGׂY.](FV{*3K}Vߗ)2s0k8&&^-~G54z=f& ==?3_0%qE n0KAf?EzgY ;`VOP8E&; `6/|י9ؽ$IUwfAV3{64uK>\CBn2_=$? Ż_=&r b'/Կ;Q:? ;şzpG~;$ӿ{* {.tyտޏsj Wup;ۿ{߳W߽=+'w&o?G 5ss~/71=fK f6 D<&f)i}Q`Shk?o8߉H /5't<>%Nu ,:i 'dT}q l&Kƃ\ۣ_ʳqbe,taOeKb1F#Z?t24g ƕ',Ɨ7zbM؇y*u6xxDcT>}ʊ7qƷYiU;]RQN2CbDEoK'ڗ&pZn3uaPJ~Sn@1꿶nLrOUN Ikjslb..ޯ{ I`SE3IA4<g2S|xojuOi0 Sp͔[kEKooE4whխJ& Q=!RqiTwRKoD؃}:" T6^Kd:dlh}JQ& xҿ$IkGV˾LRJW.OSdYhJl<0hFf]ܻs%cWl ˅~׃A>kx*Ol,"F +`%K&N yOPJm7fx%G)>WF+1Ϛu|:>ք CN4lk HP=$:t#TLAja"j9r8G\;-QBDrȡ%TeضQ&z#r&XUOsG=tlBNkY% yNH H nӻuV nOm`)g%E!=BMpx݅mehKR/^'=}0q Y=:{f0#[%-i#aBsPB cgURڷq֨Ȑ $d%^5qh=ۓ8!s잾kz C<_?F-"[sG/"<4cnmFl(ZaT+B"%4:'о|F\=qp͸%)eǬ)7E)=8QGu@nEzgv=ФvWQF!F0zO0IҺ/rOnF Ȇ` X)s9áb]锌+-U;#U !&P]oF?97=n{&O`jp$&$Bo-qЯ{LXBԕ aDƲ~OW+'*Ħ8T|o6Bla4=| ;4R! SR;,iŒVYq]WGXRSb̍2lب`S`=t>[Τ RPLLq%4 #jy!)Ȅ_p]Q׉CaTA1NF%/f.1k+ Wi!x;⼭._۳$@Ʉ؝ .DSyW-ZQ %{U/PI/` %S2Ar;NKGLzO^$C4A"vQA0y>p|ޚm޾L 8U>ZɈ|fJc{FS}mV4AG/80F9NC=7ɶnâ^ uҧ&'?c{A{Z$u*16 ظQG@= ;cHYT 6=v"ޞ;jـ'r֛IV4i-T$C +{gI7u8iDL=Y&}/pиj/i;g7B*5X/2BYٖvU\3v[%ټ<$ k|' 19w7xecڈofXQwX^!zu@ Kۤ,dS `%P4sZ:9hFwԋtH`]>~kkLSlDP2t|D]è1S ^Lqwgsm 28iWb̂}c9"o[נ~q5RcEܓ U ;ǭ\)eҡ"i]j!QBG;u/2]=)Bd+ ^S H k f% =G~ (({uXئrlO{ZP"5*c̜lH6YC(Ms aIԹL*ls 0t]&G l~"^dprU@{&i'q]h1n07XL"85?ln'@I8ݰ5ۭKo_~yu0r RKNHl>UQaj:-ZKDL{x/.hۣK;HzX$ihY '5ѾOy6Xter}w`%kL%VXG~EU}dE+@GT ~űpog @8aat&T ek= w؎!1bVuJcįdU@x\Ip'PPBnk͋.aey,oͬd>QVNkƓ7ԢԖ!3DYCsItYG?Pu.6Lmpkpo庌+Fzcwn&1r;[/m!6?BqJ逪V{1LVXe1V0 ?jP'<iH~YǍ@l~PkuN|G~~ /PeH-hGh;qp(` a+KFMb#Of?wÚM:{:\Y|)! 1B_gՉ~6&S,`b" w ťyI]ڗ ڠh~*_PvrrQ$ Q~2O+P ͨeF GoR/B'pҚS̲׬r@H7]dDteX44}O4036bitb#e29pXOa6fiZNBz2$̷f u<2VY+vJf@mrYS..4O)ì Ig(*Ƙ|Uf(ū&5w1{Jl`%-l/|F L&>YI(\b%CzR ^PA|z "ڨYΔak⿰'Vi%$17gwc Y3 ՝Ee&oڼN\>y|CwVp;\hBt=㠧5ʏ8ol`ъqbKY#ѿ+An~9_k~Eub%'1̅Xj[cd-qUQ뫾ƈ䩵rwe.C먍"x~Y%BF7ْV._WGL8ou7*AoxJvU{E2JõSk?m6,"vh> S)IU }=v7Qdjwl1ras"23K}@2SI* QFW$ݗl&@/vƖ(Et6jCN]ol#dErotG儫kf #uV _>[txRosrx=}S!MVsi ZxZ.mo:# g)1f*'iI9ymX~6'-MfᏋKe$wr65Q#H[VlNR'?#'!=Ɏ e&8qmbb/iKX2.U# jSyV"zV 4ĵ/ٮJm p8~(}+@TpcڎGv^&Q`1uwu!l<7[.AM˲v0xW'WGOV&6Ro Eu/  琫7T|΁GGvW0L13RW?[~}["[p6Ui1r?ˍpI6XKE 5IY]&2 k: {.H#RwC/ D7NUy[eHc.oȨ弧~ꞔ!33dJtLen<FYA{Vv@FvLayNO䶐x>0=[(+NC0B6cʧIunHKm$n@ӧUx/\E-%xIjqT_QC LO'y4}onm/B}gQ 0ZJվ~DŽj֫yKk{n]^Bț3~Nh-#0Åf`t c>Rpoeܘ(ޝ?ca2xkϳN| !\NcsXԇպ{kN@E,; <` -;ݸ6t,~ +vB.Fkx?+9o㭫!VJfpl9)f9d:>wqto˹t~ F 2 W>a'%DFlBuީl [Sq0Ą !4*ऍws'xdFiEEsKTw5-iqTK\c7$S㫩t.|?7i{D>-BD ހ1 ړhl-y+bF)PM8.%Q6g/Ro}d-vIoaym4bgR˜-ˠ` p(hF-ʞZECe3)6q¢cO38k^إP 0q@K0/gXgjR-U铿k[GΊOm(eltR]Z'pTP$P8du}g^l<mȃ[ l}\@?8ͬ-fv2W>թi6.6+~ha( cؕPknL#LvWcm wW<:_ _/ BRTBŻ03~ͤ( m"'_z4. d ¼UPע/3Zb`a⚦-NT͈zl6hɐrCalhc7D!D>Q01`5@3}]]?$QCbGu"CdOb|#qP{C5O& f-buK֕#hMl>2/|!!u4SOf˕D*g`Lw|VH8pkru>7VӀNQHxjHNb +*DUY#c# _p8uҨ'z]8>X7QGM .@ O VB jx4pr8"C':b M\X1 xdAfZym;-}Х*rOW{b(٦ņPǻ0|[۴ljw' 8AD,׽'E-z+;JyԺ {0+ y Ed=W~.nI|U<@9Z \0F8cDSjyvR677y{l=rmWo"}%Xȳ3u_.Ъx[# EY]NXiA3q^Ԁ%ީBh5QM%e8u#K'V_+{̛愄.K䊗.3xyElg}yKcJsx"dM9S9 !ܓtBcn" CHEasV>t"y`8(2ߊ/vw2Ռ\^K JՆBHLM3?  8LņZEYf9 ^4V>I#:3wDəxJV=Qb?PkϩIQ~ GښqԄ)mQRNC{ZT20tD$;NJ&_)*!#3ն36Jǡ =l.t_K&кU{(:Lq~) -vV`0]DoKG* *dnZbSZL[7$xsW8-ӱiͮϼ4+'\û=VÐtőR8Iٷ܄~&jhMEP,I):Ƭ(2p+ͅnR.,.wGp!urϕq){W^ݸvD6Xt 5qwW4LFl?͛0GZ@i$ 6ŒF_!6}݌/|ӽz0w-#Hjgz ;ZDz!KVyA?b %5*D6>NdP]$'f7(ҁژ+ :J9rtͣ~6=/YMz3 vDl_vΠ B!lRyi9й+v0^lGw2> ,ɠYVc4(ÉeY;.(u `MZK ~&zOdb*`o6ێl󙴒odA>UE~᰺~'+fgzë4mtHjҲ|IL GQ T}}3;pwbOX [&!b͢EXGmS_F6cqma(Xao3foਔ~a_0U 2}LgxE)Cwv#e>,i\IjX2`DlMž Ss%A)eC ڝGSZ@%͊\@e1wM'KF`5l! tk0Fp2Zdž>H1RG al_@$t*kůn0̀l ka:Jx!ؕ""bi[G'kDr UuY \M _/`&+N:\57Y;ґfz%^Ea(t T0CPzSh#Ndm9/Atpf⧬ē L; Ꙣ-M!A)$kE:vZ>5`T be~a_Ujt XJEAc= ХQvDONw+n]u/.S(# DxֽL' kdJϬ&RPrDz뽎 52o#un] - V1-Tibj 'nq 'uT kgRs nE@A'PQ3m'QPK7*?Ӭ!29?SQfS`>3Åi4|)LÅ͖X,|oͭaCTv%JX/N߄:Mӯ*>lu F O~A:CݦRܔ1Db6NI+lm *ˆ[-6V!⣍cƨȗ!>0-%MxA"j>D1r i p2 ӊ=Jςm =F/Q3ϔ!Z{B& eg Mވ~60K5z;6M- ZY` X@~Ϳ? "ECp M>o!!؋):bP~?<}AYDv:@\9:A00ϛ#~~^+xm#JpUIH^^¯oFy,X(PT/1oՄ:J Ci Z'z_WU3,K>U 9U"_.<;z#@UM^.BWVS?$3oǽ_BVۿ5>`;yEȹKOOբܹpI! :~ 9õI3m~Ko_" N 4mF2Vq=m 4Q5bCMMkd-KAXSt Z xUhE\Ab4j_ӡiVQR 9i2^KE&>6.R_.T8xވ:t?S( q!zh{))0Z"}HMbE'sP٠bl:k-%w1TVoEG"gUiLkI PNk+(zV?LqVvtT%e4 Enc+ /tvpy=ƻw߯pńŇ YyRY![h*R`Et+_pRM<>#f$ڙ[ZY%XU71nb2%=IA4N#8 XB[H\ 3h.5{W"5BRq?VlݵnLÞ*]kD:+~~Shv{TV*[]d'$…e93,JhPMW)n*7-Rw0~*V.ߚ0hoٺAoKՌWQZsSԺ qGqA)AJ09liU[4lgg}$Od4kRCA1C\jLYDžzdQ)OVXY6R)oĵ0:?apki!_Ba\p=1X(߈DXYԜ0OL{ڌA2QL 3ԾHPadza0[G;˚RriU!s[TyA鋩qopriRs@s[+M#n2x3mz٥u jm7})z6#$WpqH}^;}QFo8 + b# !oyǃ} WԺi}j X5#my Vz/&x@_ʑ?bu>rvؠ 6@J~1oj! +dβl,{{#OC*d_ͫ^2KYN1z\,lX̘d+}>1:G>_#Da5E.D:j ~8bжL##4qZƽH9%Ggf́僁bapçbvShxġr,rtHMwYTisI)CI#eS3+Dͱ./p+;\nص;G9z%B=A| b i!)[X$СAD_v^OTJތ vrVpA('(DRIF8&z dg&.}T*ZNMQƢko/8w_gp1vL"%E#QKchFGG_J~9̓&(%GV6J % tE^݈bilC1A49D!  _ ʑ;yJ:[=ߖwhЋ[I{GDy]V,MM;iШG!ݑVoA{)hEEŒ@]mCW(dv D̟,$lo9M=ዬ~}{JmrV&GݙQ 7bW3XP#Ih'2z6ɢa6A=PMS)}> .4r"W'Wgkrw8xp0gɭKv[jWNkq.\_Mn#?8(\ΡHy9hwD.e;*zSȪ.9EX}q ūJL[ELl\(ݩpcIGj]8NdB,/-nz1AC:]c[Uo%öC <٘/-1syjxkƅ %sEPfbKf|q"C7\8Ϣc:K ΖaPy ^9 YsXy8OC+Bo=+yz`Beswޫt亇?#cKZe^՗{cg)$%x^<~?;a;]"lL犝kE>p_ɞ0pU3*+AD3:Dq3y!89(H6 kddn/' 7A wH]n&3ZUs;EMcceJ6ӓGIõ^ IM۹zP|q.\q4BjUs@<9+N3E8]8=ש,x'}Ʈ-D~&bZ9Eƥ2Q252E>bCgoTBNī!2--JVP6-"HQZ֔3}FO\] K\\W܈G,*'.cl!}TGam"p``Nz%h/[?7툫X8h"iC>cntCPLo0QDFu~t,]+imlTBÅF6ds/tF$Uڮ3%in}5bLi؜W;4LR8 N6L؜ePix(+żioЩ}tA7`]8/~oi MO>/CoMQt^rmi-fIw]fSf!YH3Hvo6\Kx$E"ǹ0+7fVts4ƕ= +m> _csrOG:daK1d뽴WUhv ~hi/. l d9Hd]7O~*H-E*G]NN c"] U-۱?8OztZy˔jIL}n?ċFz[djvTWJA-cKEif5 WsUbkm1Dc1:Ο_k"}1uJe#fk$1״~UT7[8Wz‰vgݐ3aO =\!OR Ap .먴$QT Ѳ{2~M 5({U;i^v#mѩYdmڿ뫛'qS2_~4 _ET=K ea)1%6MɭmEm 1:^XDŽ@l㒎F'TM*7Nۜ$vdzrĠ}FijưL26+!zc쐭^yͰ* nN6av{-šN|Wj 7̩Jb23?[ { mTX3'й{0E*JngŸ_rEn҉:!15Ht*:,1CQ2ӠVކeOo O.]O%y?Hö$z5ߑ:ܖ6UWpW#r nAc5tO!Hw/-QϗYH[ @HF6TĄªm0Ag-aɏALfw RQ ͜KF&_2RZwO?S`fSXs"~nKfIHS_^pCOpEt. lU"/*(S7DU8EI s"5$B>-[få!'@l!\ \שذp9Ւ{8x3gv:N+[@BFׇMSі&)/p{reJ5>yZqia&P1~Xq|"0OcA#trMֈvG( gk d"$ܥ߫F-NM,~ǯj%({"FnL-h4N4X, ~DǨA7bS}H,KtjeHz?v;yOs*0mp. VsXcB?|`J't+Ti}. (7~r2נzrUژޕAw.Zfffѱ3iZ[&0} nadRUӑWL3xlvP3\_S.O\ۋc7/hf@\ΥUn_"X8 endstream endobj 634 0 obj << /Length1 1515 /Length2 8713 /Length3 0 /Length 9730 /Filter /FlateDecode >> stream xڍT6Lt 1twIwC-)4ݝ" tÇ}>Z߷fw~^RkIZBArP ';P )ف@././uA0g0"viI'c]N>!N~!   Ș-*E(R [۸<#т )(' - 3Ӎf-_"6..BfP+b9`n Krf?chۀRkA\` l8?B,A0-e#_[d7ѿ?fPG3'b ۃjr..3oG3{gSOf9I S}Wl;8;WɲKi;?0 duO?c@!K%X:r@N =TA.^ /9@6=AOz;BVO%|V? og37 OKK d `Y%?M d-K(?吔VTPfSMRRP7 rn; ? +(@d>ߋo,UcA xp?O_ȹ26f`{ϿO|uuy i k]U@`W*=$M;ˁ=@` ^ك! u3`UvO@OKWB,`yb%S<_{|ߪ?8@!8@?'gE'$ǧqB-à 8\l`{*? ^ _*{Z?|ɿ?/ XZV7_KmNkqFMd*K\]H&t.o2K,R{7TkLh3Ӝj˜'ٗD`Ӗw CloS|*ʽ[ޣhi8tvKcO h-Z'u@34(.lh

cM^feV֌uU /r~J3|O(#4Ѩ5DTpہ(ZI4_vX&S, NV=f*} wYjs$0ƭɽv;7a=ފ|%=d ;ecj% tn"bS8bαԛ +l8Z%(^Hb vkf-ZKhl;I]}iLpL0QH.^P4:R-!q Xf=Hq蘫|roC2en'#>D$Pȸ]Mxߕ8_͏A̭ie4/lbHɒfpˊ0c,D}/h\ۡԽ!Y`CBS3O& +kKBr.LW<'TIYwǂqeBjm]j*EӆPɕ ÑƤyDyx`5gwVKw$Z:9.nTMiSr*F֑ 3xCa)Y&ÅS>@[GR,_|6^1VTij%RI1{'w[QIZ}M^BX\4/8@A*щ97m㣐$]R`8àoT>{ݍb;~ /dj(k]}sAq5 cSt1;sS"ؓ?;8/7 EP)KaI7w~G$ MhӶo'K0]I[kX|{=_l!{*VT !gxDZCPV%\,|"M\x;j8J'kQ$ [!ʴ *> R$e|zrCDSZ/P?=2An}4Ͽuw,7T p54Qt8eYЇ|SUQnSeWs CwM;eBڀo3G􌝘Ì$m>,+V>oAzRDq {UUɣG9-N9Q 0[(6qT+ݖ]s8گEx^1Pv) UvhޞzJ5"7_rI d"|$Ϭ ^ODAY2giƆT| /gTUΧ͊[nv \#jHl >_㻩_ Xm}>gU0lO׷[a܃@>Q6_?p/R͎04m񪤇Uu{?3"Ĺ"p Ȯ=ڷ&Ro9; Kہ=dӦH.J[۵?ccSzÝN*[ׁs[fу;Eܘ+Xr9M|0ǩTxӽ}IW[nػψvF~NM!e.j!nr*-GxoPYjegckŽ% c4[9oc71@Uz{y_^7ޮ:O:5 ұv_ Z~^+KRM"Le 9h %^omB 'jvz)eqH68-0~ݣP~DV2DqBQKmw09K/K<qtrc=V5<#cG 3}]jPk&ߤ׬?4aCI ^݌6B#{Wl? {z.9KbBY?ulR4& aD;^f88, z 8%Et> SOj?#lOw%b}N \K;}Vua)xh/ 0)l25MQ1feU>1œ |^%8Vk(N[ׂO;R d)@fƆ69|RF܁ DEBUi(fH-4i1JPOa H56#Ae8&A 0ʹMk;5aMt5wӉy}ى(iZ~%qEG@d k$qnk5r\TZLNžʗfYlqܦiP|M}5Y(·25ys.{#; H5}XWۀG:c~sَI{z\uFnԱW)5SQz /fkZ l|wtt6Ac³Tou< |Rz4Й _;c?g  bie@ǃ쵿mlnMyqJjIA+-R~BPhe2&"T_Np޶i#n "z:Eԟtz 觬4Sw9vyjϩgSd׉=pSFv=ϽTi>TA7 /iu{8jRD \9dtAU<\lvL HPX@evMIb=1nCVu_4ᕏw\Tr /%_F! w"128E䑢J }[Hv{z+e4)I񅧙rHn F:$aɫC11]|cW5y'Fy0^ &֫P_ +Es9 ~ S$v$_ov*ys2b{{Q@;9=bLqJ?ҼDQ{jchVAiOQh׉o^I?@êppnH@.zx6b(EK`,Cؿ(aU$W Z@ɜZ sӎR<ЗY÷ˉhp8zK.bJ-UKf-lzP ~)&U8~k.coW۔n+z\>K(]8HG|pp¥}E{î/ zA.Ac GbJ?ojuߝ^iNzQOƙ3kV8iFGJҡ3v]AKg$||y^ȱOṦ ݒ6{)yfcI!KvSg. a~\TB8l8%f[ZȵwUrPRNw5Wfվ+l;8GWN\9cx_FONgξhHDĽ0n0[LEu5qȬoliƪvD! :.?ۚ>Ep^ 9"8i::  õ"|̖;Dl?.R,}\܃XFr,x`/udA! c5^Co_N=CsiMe:{Qlݕ>h2)-裾KJ+t<ύU5lR~2;y>.:RҿC= q&ccݍIlIA&~0֪2U] l&jYy7Yh*#M״3+W=Ky˶bbhIEp%QFMH4MLǻj43U{9ڴ0%2ꭉFSXKNKO&9*ZQ]}]AiX/8u Zb5N2W} sEƚʕ?PHd`<;]V}8*JnƊ=CμRxɆ 碠ٔW~;8萭A5:cJ v# F xRދpy)T|[n\5$xQ]ɓJQfw"~>4n0=NXzwx9AB9n5 u71|S{.Сdh˞Le+dJAY7M2H^ dSԩz!1F2QL6ߝlrb*Ӽ%M?+;cG(؂uyf\^Mƅ*8T&Q7vg߼9Ar*Y9z/Mr,vAy_-J}tc9'6Ubj8K&|$4u/>Ax5vezPei2OIcuWO@Aڙ֜~`-h8IV-N>ZH,6M]ª݉oN x} 5[2B0E׋Krsˠ0S⮉gWԤ./ iʰmv?vfnۊ`#z6%7A=] "kD..}irr#S PzbWV{V@uD>¢9~UR_6jD>鎌"=.  l]MQfMbr$O{IƉ-DHek4qޝ$x]qkDQ CdSrf5K#q&}m&狣TsAoIټ7"e agN%7[S-: 3M׋`"h2lkQY;Nagc֏je' MWKxI 3˝g4ݮxVKWm`4Wo5l fE~½!2fyk].%ogd=>#{Zv[1fǞHzacQ,/HFsˢ[oEH}!9fɷLX(->GGcC&vY۲цv30.$nw@m'Jt'<}RVψJ sڎ?zѲSbj'j/" endstream endobj 636 0 obj << /Length1 1925 /Length2 9905 /Length3 0 /Length 11081 /Filter /FlateDecode >> stream xڍT[6L4H3t"  0 ݠtw*! ҍ4R JK|9=Yk׵}aQ␰YeaP7Nna377Anj vqHAnp4 <@0077[?0a4bP( `W ) ̖,!!A`% Pق+ZZ0KJ0ں9 sqyzzr]9a.6b,O-@ v[~ +`hB\kZޒ%e0_#/1 G_,Zjb'Oa.NT@%pF@o /z Rx\K7p~#xMHe/W jG!p0w?%lp +Rp-@ߐNSL~+#h r™.~{v p@8q? 75xoՈ..~ ]`,R]֫j rOQԕ8p~7ƹ<_[rSΒV$é%~߮Ƽ!mHp]SYlS#65(q̍PfoC;]w 4)n9H4kn qc]ܵt"!1QF1韖R z*B H }irH]%/JK;"Q\L4 YuӢf:i=,dvr] v~Lb}xPQ"`LSffn)A9عEټ)xjP!-)BfMJ |w*IM{}kYl/n v {$+zwA˓pǯA&B'4,G? >p$ȸZ/߻ZĦ|?U?IG/RXUdDkS/xdkt(o%ԻUka/ o is o$6HWRkO Y{WZH+/qT+O$鉃Rϒ?/w"NR;೤Um#ŗ/ݧg%s),j/%0{vIHZ1T't]ixE!! ow^NK}GΖӀ0hW3Ѕhg LqN::O'|:O3_ڝ=BR}gL 65gYv*Z;cW4ޥ,m(hNuYg-F,g409[t=^YS/wINA3{8簶nҍBv9)R-xqmik앒q [+E+_زEV ;7De]DK ?]}kkanph2q o;mb+OMK ΪH$Í xgM:i0,3> v$rۦ_1fvk?юW*]oٕ}t2{6y}Pc3R뱏:FbY%gRwU&3ˢugB\FpCJCyZ9Eom1?ӥpw1`JA\dfڟ`lD 6"S6s: ` /]O)}r^[%c6N_T _9\}ΕI|ϊT`DxYiIP|)f4`Z}qzn~qCǀe"쐶BqV%f_ 'YL7}VkURz3*RWIgdHZaF'}ҦYS*,pg8}T=.r޸Tpe'v5듰״$lSR:3R Ac ;$屄7WT;yŊbg q2[#X{[cX|n8w\(zrYzYEDi}4S}oJiL #(Gnt̔IWyY߈zS;u~B?ӆQv}󉦄~'n[|h#)nm2KNغ^Ou.yP *?.sBt*oat:ZnKl}*;/v W{|Ny?|eS$&J!C)P,oǫ:VV +lTҷ?"g3)j:xS -^ nxN3-=k]/x&ݽp*\VF ՟~N"8c3!2TO΢& Ey``#iX"8| UvWDc縊SIvrT19lw6;ȬX A0@J,q}G5?/;TkrdܢoOo7➌t =v=SOy;w>5tEG#!1d{ 'sߢK2Rf!Ne5@p΂}su56hcTEQx6%9B{nuo9Qt=P,:Ԗ372bR-DŰYDo-yÜ'nL$,$0HLsSxmى̓:)pxHfΦ'V̜e7jdj&}3񓿂s!nDmwiJai ]Tdyͼ ](z'؈]=09rB't.ͯ4X z}Q%2^>٪B|.Im#lȦN@`$̜Knl7fEb[8e((j)N ܊X@LȓOw &:U;fĝSKaPGܻ+ F IkdAT$Zo#z'+eX\'SNaT[QxElbٲ+keTUfS)qBW=k/EWW5ѫ(fjDE|`F +r^CۅzRX /"='7kvFFTMڶ IaD~2>eLU)W4n<ؗg՛BRp^12bf?Jnr8, .`H{Xy<7:ђc7G>ooPYI ">||lKD/&/y2#̕C;&ƨ{P-v?)=l|.lC!2MTϴ1y!utv^).fl$D5̽5}t^s$?tHsŎUuѴg""sNaIi&-#0fc]byȧ܏kgerAOuJ*}l>Ӏ~HƔhتK>{4_E({HG!(QbWkMmJJ @Ȱ`'׻6h9)ȁqڅ`M}HC"zyvW^:՟tf$:O& {j:ُ,eOψyZ:_w)q|Ǯ}llgi;'Y//ވ)8h9Zwꍯ g9HdtU[x2KP¨MP&Ew\w 썦_a;6j>62$uFz~ȶ4@oSw՛9O@k.S})D7%VvH6˔ͳ)?ORE ҀBcBc6Tu$k7zHPu!R 7N/Aь񣷙xUR:~ٜQmCIjz Tziuu3C{~-Iad JWBO%0jǛ.ɾ:AtܱRnd YW.1p?<|T%zh(\tm$'z/M+dYn1́> K"5v-P =ߦ9myi0au/4^~ԫ6ha)"Jx7h <`Ez(4c%ago$ZQ6Ěs%X$d`bm)8'Pݬa&X7|!" "ou[;Ĵϫ {or&i.Z5Qfi]b MɋɜWkٗstxh%nqȴ mgɦ}{ 0$\+4nce e`|Vdk|9}5nUYմM-훝w, nOD# Rk$cCwI%m&|h94ʰ)ŝlL({,hX԰e۽ qY9@*_UlxZ'_ohtkdmnKX:Epiv<Pv&В ERډҏd rt .(mShs7LftVAFױjwn*Ff<1ÊO6Ԭqik?D:!-XB/Z5}w0={U@8ğt,IILĽG(X|z^>W{G1[+#3R1ldK}1P\v)|gL,mAo\?7woE. q|6pwlOB%cW_l+&)~PTH[( B4vT#u7R̎]{Oٹ6a㔖Wu5]8v%ǟ xޫa1`*sx"x?7'N0r65ի`/ {$AmBhO.xOlgڙ Z0~KoXv{\+Dg<1Жⶎ"֔AK뭤E;̱˝m)0* # zPB.jM)_Ò/\}gB"6[U\ۮ$VɣE<5 1Xߋxgz4*QZfs6*}t`.< ]$,Hge \k•F*"KNGگxMGno{qVNɓx?Gʥ '=EL=?|8*^`)^ۯO xwptf}nz@\84VCEeXK֮U9q^&.QYh?٦Tݼ*4|3JuEggjOW&2>%.}6 J(LG4Jނ5]*ؾՍ}^qQv|-0 b ZlmbBC<"',q_ Yz6l1! 70AahJN-EU8)<Ĕ؍dwӄ3 [27`8ɷ Fxŏ;%3d1q$V2ձO"|cg] H*|#Yb "m)xCygx<]-V8wsvGѣwnt.?9ddr(5bl41}ү_ry;oyC#R}r00QغFug2t0:!꣔| ۉ} ryÏ<صEHNJ5FdECS5ݐGJt_IgB˟nѕ|9ŧz__4Wqc*Z.t(ĄeH" q[v/iuc|\^;"&:y<58~-E)&VʦCϏ4yeH͐9 Aka3Ug.?3"lv>T;?C~1sͫs[/ 6 ^a^㣒LS>}W2jKUJH&i7)SvҌƈEfA"4[@:(w'p ব kw#x_u6Y] ̒'(FKQr_R3L!ɆD|ZrƭXx1{h7tU8*Кf?~Sr|ϊ2ns2|}>7cg~̑a#*T#1KQ{|ΰ |TqZxAIHEh%AׇMԲ]绊&(@~XfldTRυʖj [н&? q{yV@0 5zc[|ÆKx/##wujWt 0ۮj&8 寡73L!7UTNR8+f#UcqLV2,%.#>^ EOAFKBУaSRm =ltMQN !wqT/Y[+@Q*@qces eV5NqQVbRr6R;lm=F;xO(,7_I-ZoһڣLn㑩.TD Bӝ#ϖlpJ]7փ=Mֈ/(Wz]f*mxBvːl6ŗ 섻ɟ'N m-;=M=&c@X1_NzŀB+{lI#{妜[`k.dv.dRl|9K ̥+OXtHPgœ*0wϲ9=3VDF#|]rB endstream endobj 638 0 obj << /Length1 1417 /Length2 6418 /Length3 0 /Length 7370 /Filter /FlateDecode >> stream xڍTuX%t!ғFp#6HwwIJ(H#- %!}߱x\u^u<ᒶBZ4$ |#cA >n¢ G;Xa(8!'(}퓅jHPEB  De!np+7PX kH0ӐDFS@ΞYvP_e7] 4X,?‹BR-)ib9-0m9Ȅd[>|3MLHbC>˭1~. l=`/ u&6XS:,*s*~^򈠋c3LS}4Dt7V7?ɇ fǟ~ ,35sSށ6IxLԏ\sϴ8vH{^ZFl%{"O2R;n@DW@ !a.*Q=πC;Y5xhG EU_kɯG3L{ +hon`"E Aߨ[yS_+p=ynMv-B .FTBvyonIJ]%[;_VHO".f?uJ2: I~uUYjW˥ k5~)eyj -ԠL xؐo}Vt jkLɂߛiw+2vۇ/RV8LP))' "`Z9nIJU+~hG ƈՓrz}NN"Ir"wx\)E(CۏJ7=!>vY2v"1TUn)dB.}#CsY_>|?w9"cR%¬t*:6"w$$.^A:QNNkbxo?֨M~7YfT<)5kFlyt^2z]uLZf-5RULLJfZWzawZ8]X|- kPw'ZXa*\34H#%e84 f̅栆L\7X`j]'>$bƸ mZ9Bfd=}C!J^gk uZ7 =)G:l?5&wkS0N:dvߓ^EBDZ+< aJ'.!^濃{Z+@fڐU }[ICxh7dK `2oλOQE_VG+K{z|Đ`T70CXbX*A}ĔfD",闬YI)VtXճn噭~'<6ZP2+vl&"Gj#lb=n^2k^Vy/lY< UcJCvtʗt[&W 1I<@`pnp}mqgPoBy)at5 FmCSb^-9>E5q_P>MGcR'5\vi==yűR_kh-OJ~w-p9 2.XG{AComGlh:i/$01.+ڝze|:eӂ!1q>|L=-1ᥴ1ӊc8næjcNY6kp+㣇g|vcSƅsrr2  Zq˺ fչw%"ԢFJ$u)z'ꀏ9獧=VUzhE/=ӣnMr5q Z''8}(D8g *M>0I$^[fA¤DW5q{1^ oDY}=dTmVE vh%m1pj-7*ڧ!?aʗ,ً/*duQCX]Mɤe~ٜ "\62W3Ick%zFo+P呸bLRfw{`4PJO wwď?4!KR0XzJ Ŕ=JO-%SE9;05kUD.p<ꪤ'l]r| -(6LW1e&P.ēуOJ"[c>p'c<;iިiV<ލœZ\ra̹A {<>'A zo>8'UɊw^ [. ^{=JzQj^Pz6/0- Mq`)e]؝ oix xITMxT*6\Nl[ T1NKj<ὂJf+ j:ݕ\9Ki4o $TXߨ6'$f]JB-;͉A(ڛsZ.>?[=\d+07`h)if[Jq#h>pC!$!=Zh\Fw!FpQ\3y٬-(tm= Ð6}rMq86rZJ8 -7'xX`3}k-3-"mg!s$YfrqrڦʓB>;ڎP ]t:?͒Xs׽1>s:(.`LK=%5S`.6-}zx~iuϣ_Eꉚ{@TY?TC O+ǧF[gW9fRُ,! tV%Y0mÒi= [ߞ'2Spxu2MUG? IǔK7r&Ȧ.K_4 9t;88IlЯzh`0~aZHquIӈjgDY@n=OX ](ytЭ4L3zT`Im+4኶dUo=œ7oP\o⾛Y7 zTkWɺ1 [ɫ!gh9)$UN|f֨cLЇX>T3<%pX8:I 箊{C9̜#[8c{cF]ubL=p㘸p^F8[b_ /%/|V0kj@TMG_P!VOr:ZrR`Il)zK'>LJ P ל.)1s7ryxk,4v8.={sivh-a2iBF:S^u g{c#H(^5FdԱJ,1BCdёՕ§}!hsa"V'D]JѲMD9ld&4*3/Nk^cC7!D5^Ⴧ}P9&=}_>C~' 5R# ".CkQZsɞ# ?z-rn}Αꨓ*ޢHs޵ 98 (Z&:r}j[rV4p&j]^ߥRJSCA4Tʫcۄ/qf-*lFw~RnH^,ob!Ui?5M;7ZEv^*" 6ϹV7̍wY%*%%w4)+vX9Ȫz"nZ&ed9q pH(p}TgԝLl_ݸgRK"|xM&\8)#; TxSEQ7ecyGp4ߌ'yE$Rʵln) v iXobP1t ]L;lsf$lk4ݺG 'IgzWn4I$IO_6kǔ݋״UlGAs2Fݐ)1Ӥ>{Ɲ6>[1GvaNV/t |U7(-*Kwr-dla4/mQddyg(pt>y?LK; ҃> ^g*aL[fǟFa$GܧԮ ̪D~]@I\4}~t ΛÖx8~wt,t_u3>3xeԧ"?Ņj¡o7( m[ :7ΆrQfr@Sg+B??8b~Q<3JY߉^mjrhJQxFPtۃx#54즂N=UᳰE40]k."Oq(?H&#*bu sUnul^ LܹAY; x^SfѕIYM;)FWd֔׵˛ L25n_P:ߚ`I/Z={9,%T D^*w&U."{,o!5v;tk1G~JDh꒥\@ԟqbAO_ PWhOŨ [ݽB\tӐ/FaVE|.}U5^2Ւz&eY|[^sJ%ok-־^*M|[O,NiU%w Old:}};}Fu૷gi>n v sw58HS1 *%%sEOm'?輻V OɎ|s\j0dKkR-))h77;ލht^da/I|ւў>@$mm/{{=ߜQ)3S?`4)=2V?Mɋv*șXU?ZXث75RhAE)O<[~AYGy^EKk֕=sKф3UyO"^nWבteS$Z#KOFqW }9A%C}]t I6nhT=uXnc臭(鹞~RnlQd? @L{tpnLS}R kHaܣjQ)]^:^qNAr[/e 5#?1U3)/'_+f9Kҟ.E٧'1X̧kxz\ܸKd%N-lp؁7cHPi.H~dU]+ +M%6{[ ݄8O_ߧv EnT@Z9SxbUXW\-> & cR:\k*c@MdO*qAgN!!i\Ydm}]a!+ՠwWR4c #l+'-fXڢ-㜄+%w,(&'ֽtA^6^H*8"p:Cë(Th 6ñaU,U~41H[JQ wR@UMn+T;kGÖaܲAN9Z}Ch,z762r$kiWM{("Ƣ e8}Uvo7O6`=@K \'PP2&XQ\X;OSV.ǯ:<`UYQbs+Ǭ endstream endobj 640 0 obj << /Length1 1616 /Length2 7614 /Length3 0 /Length 8692 /Filter /FlateDecode >> stream xڍ4\k6,^ D3 c1zoQw!%zoDA- ZIN{Z߷f\wyr{Y v`U(kBB"BBP 7Nn FCpPFmQh- 4<`(& I@JzB NpABQ}p@IIqE0 mQ` [Q>r%BJ zyy غ r|/(FzRغ&@0t%0@@P^H0 0m#Z]W0/e /GPoc[j ( U@yp_0w C+Eg'?wrp~( *p{e r'( BGOs/+nVG 9QQ!I11Q {m` - ssE 4PCn {E  !GBBhB>D3łjZFRPI K @@\\o?z?q:H.N\o_:4sXHT?y#R~˹Rm]0?hzS@UM6Ru-zhF  BzP/7CC0h+! >E-gA_&,*E"m}нFD~@Tڃ (G&tI¢A[WW$nZFH$ AE5o($E6Cڂ2 Q a @ב F;0a ]tNd]l]Q@Bh p xPPWt~a*#D) ,"@Nu5 ^8+]щfQ(~ZN)Wюd͗=g98~&'d9]-ҿtȟ?A0.N5Iۂ3d*BGd.-s] %۵Ԍm5l,"Ph)4L#ϺE|ۜ j't,#ɃtTI9JeitE.O8lė/ ړE<tCu5y#Dges{Zod$mIv`Sb8Z-Ŧfw-ԞFia~h tcdMQ8w}kb}9,:{*h;N'z;9Ӷ/N)J *D۔,l:NZ= M'?g7B\u^)=!圏Y&2z@]Dc,\D@]k%i%ڻ3PUKO=Mu("kw,MjD_)9~B^`0TOPZxZ G񞢣oG^03>zzT՚0yrɑ cVI8H#jݷH%h /'d g8,GTY=lYd=$\\wh*E0nr#kԴtVo \QҌ+KS0kH.[C<]>D  EcD{[3G%,-aٽ#<`ښ3;`[cʃUN|++ hEѣ,1%YOb:^UVBxkY (P_Bt:ӻϠ$@cO3~:e_9`έRnlV -{fy-?Gdʇd:%ԍ.y]d KoE M"Y+RtQyv<3y0gBQeRCeyVPRvmXH#mͭR?;suƫܛ.fom"z+!`;e$@DbBhqAYVMqS.ww\\޹EOZh{Q^- kw6 Bwq8Ork'NPCKT\afa}g:&fUeӿ8/<03n% *#>˵l+$Mߪh5& a0]FY^>d,?TPMۛV+)^pXRwm77h9V\ċJ7ߢSη0 v.`Ћix%C0?D2Jq_CلoB*zwDoI wt1|h29cέ䝃0]qmnIn4R^C%g>]`ϐ@Ϩ?G8UkRx; ጞkfgh}7 9k#N::9e&:Wp{@sryEQ6F=ỐI7)ٟ3=z8$0ll0:*w%W >C51c.YARtԣնLֻ^`"GznYףHDBMZzOũ 5}/JL'l*mԉNO-nW[8LwXZqgӭnpyFĉ+={cY{3!hg\ u]js).ѬTPMPYڪ7[<#8[^8}я$ڸ>%t*pd.3ey60&1:e}\3LmbTܝ~((pYe$79iPAf F8Rx$+GXaY$~͝; 弩 &@N=w,/!,]%opd?lz;SteFƒ`Y[vpb8?7;3.9 ]CΥ>"爵sh^k疅D T/Tzz,Эf Qn9ңrN}ٜ,VSa -d& G#Ǘ"O3\a/R?gEcۭR j<~q0ŭKuRfF<KgfLw.NѺT8unoKQCHN u*9=Yw(lMqz+7v)M53֋FBm6pbO3M&-sz/C=$jsܧϫfԪ~7:cm{򄷢cʬ ,N!/Tvg`x +:b,iϊxjR ^1SZYM>##xZ>]‘1dF+2l{hR$iCy=@la띨`eCCj)tzjU-軲 pVzu&>i(ǥk:ΐc #՝ Ee2#R&tҔ̉dMפo+1'GR$譪KFݩN,7^%N^'6 Fk|d)cuZw0Φ⴫[Oj`pP}58cnfR/} -dzsr`|FX`*@ GMp!:Iݐo_üs,NtvGD Fy+ɋ+^_1>{5[6೵9ܗk,zfG&-!2#ÿ&;/0maƼDnq~5  $l}@mm?r̲_ m|} @CG]4{n8C:U$I#k=rI̓jO#SggҌr..)"P1Lpڞhk|*x~INJ4I1GulI7HWhH8&ybڪ'7w8WWݒA'鎁H],>U˅8\8ubVs0SR,Jh!HD"刲1KDqSIleJ[ź>#F>OUdOI Y •KKJsqUyxQkc z'JV e)"?yE>MS^c1SESPȮU8d1}d*հ1^gMaI9Hz@Y3l⼯DT-ߩIq?kI瓃Y! ihk(3aSJNO!{F^?ǚО5'jOb^Gbud4tqDB,!Y#=;A@_HE<+t=6 $"4lYZXw}"g3UnىvE:k5+(9^I˄G >3so ><_^x]J+AN=[q/1?ǣm-i.Jv'ϩHGIA.@X2?&|jZeol,e Zaqq~~"aoGvWNmV>EQjL`ջUbK[ww@9|%BuW<wq^syyw?=YS3|J\,rzsք1>- _Y~q [:y/0J9{IˇZ{Rc',0<+1!)q^i0ݯN3| @P:l]w\zziUG7){3f:N%ͳھߕ*ۘejbJ]dA]ߢabHV+9ۤ'`c5Bq^iոoNwX:%I!P>1Kn)rڍYjPd{tiPVmTlSc_el YUxEH5=oca+t/Bq]ጙ,s?fdR *P{oRc,XN>YA[\Q$}Mj.XL顥畇\tR6ۘWZRf$244=rśz,5 ^CwDZҩ-pb, sǑi;c[qE~S8\Yx`CM|U^@о\I0h$W^tV|].yz@ or3k+e6^5O <ﴆTϙ+8#(Zbk~maM7B;eق~|y8P8ò@KiTl YuQTn%D#܄ZS` hҖzDJ=W :\zmLLh4F!TC^`YC ԩB5YMHS^QLXV;MF0rW[zL҃x[ _Mz-(Q\{[zZ*`,fw]SzՕsK' onXّbiџ kH5X6q -Ǿ qߕ>c=KRZ, Q(8UF=$c=M9`5j T;/Đki wΥXEXnQ5~u 甲LJ&i n -e#hY(։eI^LAwgB87|F%JN 'kʲzҊ}uXgoo\랏>Ϸ M|T+1n&KT˯W|Կ1r8/bP0 tpa}U fZZߜt<f]1vLʟȷ!~n0/v߽at7iY{ "EyP/3 +CelGVr36-w:dO l&hwtpM9 h36[sMtdW-Fk> stream xڍt4\ނ蝌Q "H1e1z"z"z]H$r}umٜZ2H " Ju B$zp=쯙Sr# A<#U{HI  DI!pK@9p!=Ppk4̿~P}{20 A!h"bEB0?RpKڠю@Ypm:0gf 50@3 '@ǮBAP0`Θ% 誨4a?`?{{ oDp`tp <kTT@ _@3q!!Em3(#YnkD4]V@X!`3ɯ(?'k@!.K_CX8 N.0?6k/**"9`PzN/3f/G# 3n|H!0ߎH@ %XdǘaV֘GƂ=TԌLo,A%&.g-oUAX!t٦ug. $0Hn"b^o1W!E{n q{`H@⿡OaD8W Aa!3?HD@PwYjP?cKjpL u`ˇs8cxuP/ E A" ai sdPDcB}VHɯcֿ.- 9.(Fi)oY`0(ɗI$AmepÏrf7 KMc ;h^6jJcN% IZBG'y~ '*g:Wd{ώ[alH&a+qڬ7͍|:3DMxt-y6=-ٴiα34B'<ĉ|mbzL$S=?6.]KM(!6cC(b~Xەh҉©_.M@}zc͛ %`<^^"nzcso$$e#ś:#P!?!8몓M U[Ȑ_ rk17D{lKT2s^}7#4::0Lv~".e}J][4_9 jTWxQkDG{ 2Uid)|xGGc$VÉe{>dH(ŵ3v*{bzQ)p֠# *ѧᐱ"0o#}El>ZWm^W?9+^P7:/g{w㣿3_tݞ! ha4/pWr-ág/FbP׍N]xzgxi#˻̪!᪐$-įNb~j~Hɫ$^R5H0mrB~8?3U'2*]P WLqot2a|~ri}OaAbPEvso]2-ztnX7eɃh~} Z5iO9g"s(5cߧ}L–a9pzYhy޵oP3(+"n ?f<+?OE/4ݠA?QaI!'[JHi`{CS2 $QoYŠ #aNvѲĐa+IMˠ:i8Xw )nF_[UH:I_<9@QK;.}5靇]kֆ\$' 3d5(Rgᐴ3o>o !F=:dXEJ)4rԿ唫x>B~HW>u"WM.9p@]i_hOL&Vxĺ|oz-oBNQ>ky=;\Ó8Smr-Җ}ҭYz]ێgRY7WV<<_gcJl dMP2Pʉ&[$T-ڷha|sXFּtYir'n˩Iҵjp=TeJ)JM` |I)wO+Wߘ1tnd^=#+6QslPo<1_GKGqE,L?`R5LB>$z"ZS} `|yPAF'*m* md$k[u$MJ۶5%xjFH6/vޖ LbL=ۚg*tK3?EΧ4YxcȉS]7iʷJ-C3:*fFw|/٠vz&FhAEg;|Zl/P%x2|:,:!KaiIKQv 9bkj,[&BU l|>Wl,kzbi6}eٵ7(C4Y+]yi-Rg1зEOhhwӎX&h+13Kp'u#[m3ֆi_4?T>X hJl]P%;N1n۸dSYm3eH3>>r(nI|~d0`i~:)f*Ԫk:2Zj]ʹO=yyya[W/}&dvxff?7!o~D){*)>U|+k>eNǙPVD0YT8j=}G] xa!h:l>X"\Ӷvye(nPUuôXX 9BmF B>!UOn&HOኞ[M!3'fPT VJw.}z9"P,")lv7]%&#ҪR+ݱ<=S}NKybN8 _}B\SkBs7oл~A);xCzL5n-?&×ⷂGqyaaBQȂ:C7i.x&M%ƕX);܎x:ͳ<g_Yѱ&6f!Ue%7>ЇxMוW1^/ 'q 1bV_\|_ԴјмsUx#'̤nU|l>נJ⍺zab0yB+8}Ǡ]~xhJ}̢Pg&RSgWK[#TstA> $S}|)+.ӗl~$ ն;1,'켊,1!< xV\xWSOqr!dM{_ISe{&u`gVX5 3i~qzEx:p1]0?MJv#hFң~3,J:fh7MUZת9SЍfy73jrGa2P%]m/Ň'7ᚗp!  Ql;?F>k$Y ?QדY; "֜j=*ϾIŢRW*xI|0_|pjꏄf>ZDRUWW"n{6[7`_rڊ-9zy 2g2t"[T+vn){- ZWpjjS|m4IsVƌHvu)̝lˉ US\D{CQ!fG PD?DQk(m%xMXӽRLm[Z>XC[_[?V1bDx? +̽u:vkaF7 u׷jOX42*G_ڻv|0ARe+ҢZudXgzK>=_ 3VF)g4bǤijhLG~ٺ2ȫG8J|OtnFmS{b Vd :㎆{ }rtܮ\#-]j}G[L땊"^݄vbi!" YLs H* =d'{Tdc!he;d>(㓇zAΝ40I#yQS CQL\+٫;f95I7W0ʱzM&3({a uL.}DćS䜙͢F{ƣMd{Ŀ!Lo-1?ljA޸onGBUa{8]ֹ$G ^yUo' j 3O2- cj7/ \lfmR?3ϒ ?WgɝMU( PwytTx2/t'In-Ƌ)"*猥>lp'%~%uR8޽/ͺvH[}k1FPh x/^tJsj!-Ea3 WxԽF㖛 x'pd46l.r endstream endobj 644 0 obj << /Length1 2064 /Length2 16091 /Length3 0 /Length 17342 /Filter /FlateDecode >> stream xڌP\ =8=84N<Cp8r9=\ QR63J؁xjL&&V&&x 5Kg@G'K;Ͽ,DF21#wCy;@ `aab01qΑ fji g؁Nvy6` l t41䍜-Mlv&@g Agl`d`h.flP:]?(lScYX:P3sv3r6&@ӻ xP(Ae@9f@?LLl@ s (!L0ahddojdicdngF ae;ÿ98Z;;18Y0mAN'f4y߇k syYLabϨtpJm.Gft311qp <*s񲷳X߿ཛྷ\gG׿&c%b_-ǏOzfj#fTVҔV""v/zVv= ; q,_ 3;_?%=/'R{\ Aebg2yyt7D[͟z F6[O۽jZ]yJ;o0}[:IXM,M,웍%dd tKfb~8*o^q0rt4?wb~JS `d998XvQ `/d0J V?O> n{L QS^?={>л俈]gbgH)0 3 i|w73dz'a/nnOwN@п,e/_?}~0 bO_;Q{1:Ӫw{;g Vm5kWG[I9 os|st|'50qqt|'y_/ h8gglUzS~w 2NsGuO9P5/jAswK[6wZbm+6$:㑓 oَCV9Yʠ۵':|wgT 1Q\\m*Dgڥɝ.O?J/MXqsbZ'+;UiwY$ޅS YhrUCS jNit#JW^S&Fc-D>neZN8WH!PD^<jzsLJr[`>1R16 '0U >f1_jDHwGdV!ʑA>Շ8_l yQ&LjOdFHy/:MYM4mAz>W[dk|T5D(q7 ^3{.)Q][zCTsmYCJ~Q 8KcQ9^lu+GK>xȸzo^u+Qv^ MnO٘<11`6_ $4bQN@ ,qcViH:MW}\9{W I|`VF3)H uTBUψSk;/|T?B&JUa?No0rO>Sۘ{tJ(*7 |nɽ}Q3HewRĥu_H 3å0 ڰXӋ%"o<ŭV]vwyf0D7ӟgQiIiɩgTNc $?aO,s;Qp:P_S$dy&ɰJS }ΎEq˔L/7 evO)b=4I"Nk4OCV2=CV˃Lh d)U_ś:}$× -'IaҚm=T)X NN d24XgD(9Qp"fey˔4ɒJ]F)ޜ{ 7x%OdCN/@ǭiɤƦ^E}Iv9MQtx:Ĉ!9=#XgAn&p1 [9bnE|qT//n׌Sts98ybUQXnC>^靴 b^?ZzCTGٌ,_޳B~hZؖ z_h +-χ7u6o`sq,HH)1jGB`iSXX0s;!,̳IAnF׆#^iWq'"pieKGU~@$ rJ\Mb0K2,s 7Nb\`ObΗOF;Ry|Q۸NZ`m.%ƽ=aKPd,\Uo-%Ӧ"pehQI_JgaX4z4\DS.d܍q.#/1P3/;Zwy6<ӯxRrrx=JpYӧz0vX^D<=}JX^/lV`Qe}sY@ p T`<^P&C[QhȚTyh 9?>l'xjqP0`8f94{Zwi|\-2cO \oY֕Ng=fWϑGvMø OmN*QmKjWRQ(}MW>/o%oFv cn%ў#&ƪYR s {rٓA%q4$$gzZQߍǡO2Mߦ7dXy10?mQstۜy;%hmX簫Kh0d \T 刳PG DfwbGlQ3z-"6;ˍTJ[(d7[czrD"锰\N(HәzѮeѤ=w6:J$,$)Y Pn&: ]g/RB/q f(7I\Y/r7tXΛl"}^Z XW |GF6o P wѰb>\ć]Š^ /qݫn2F>Dtp |y{idA5n@+~0 (g3k媬uJ߆zX z;kTM.߀זoH2iv,Gح :(擀z#y7FО|ڰC7m(m8XvM@!8<7Cmujfq.~?d:ko?1-6Rj , !ޕe,l.8AZ'{{mB,βM&|~JҶkGn(vӤPa*Z AJYDhPYet}Ƙ"uN!5ggH >[|:Z/hoXŦϡ5H΅6jX; g@1\!e_n <܏Lwr_ VT&0a(Zic@Ův~A Nx0}ߍW'b˥~,Go%>a^Oฑ9KPKP^l`X~E*XB;ɴS/[ LC5P\BC!?RW Ď"DJ].;=Q&=4 %Ħ0v&LU0ج|Y&Bwp碕fw֓rXnU'J|۵tv:J {肋qgG+G1IR:S3NyDJw LAD# I3u/qhwB(+ŦfZidsM3<[aV&Y)eŋ;ʉ;!YZ-D?(C~JV- 3 8˷N69B[p[5,GO  VKHP aC\R)>˿y݋qL "nze UK:{T. t\#"~-Nتc{j? }aI*^\Z@1SHZԷ5>j L͌d)D"޶(FDF}!#w.r+W6 à"6& . ?`I /˛Z[2@tRD; ?&|6[y0l͊ԞȲ>F ԭmIZwf,n_Ja\|>9T5 cW $qW8xNx{g]7Qh6N,)B`LPyRa،HQ˫OrbK+UU#>~Ug\|~w$w.-Q2)6a$Zda\̸p4FъqJLV%2=f-e/QY=ۏG@o ÂVn7jRSGF VᙨT^}Xˏ\n7 '1ޥ*\)L?4dCv%1,9KĩdpJt.a$Yy [;I{̙l8ہq_J ߞ I8IՋ-dxxNfcLٙGDnaY) o|귊 `ݩ$p*lb<˰N o6*6$s"9HTp@_Nu2L"e>'S3K4^c4ޔ&6J=DmŘC\ Ai@iz !9o,ִO\guG z_;5bZ~yFNy= -:owśF"1Ƒ[IBo*hb}$Y^(Uy;1:"HZ+1ϣiނ!EUtO }=bC>&(&D%nMj֩ ~?y)#|0GOvnUy!*Z@ M*Tja?^t4m}2;{ȕ_˅_/î1BϹz& -ljw_hoh?},VEz aAsɳPdFkN05pHavX5BڗfNbP5sԩ7雥T(I)gАQ¸JⒸ`~VX0Gw/:Ģ4dI^X=յBYWF`۲ q2uAJ}8W򦰐%A'LhK*̻NK۾-eq2f&wW=*9x//qa~|tkzá)^6lM]i]!r,.oK:4:|7gHcXd%h??8oQWdN"☢ FvQ~p$IB?{Ǔ֞@NK8Ee_ *:t;Z\ s,?ͅ*hj@#:@6e=}Eqa?jF[Q(Չך-m%,.PQou-U$䆵N:c%[4t1!O3n7TAB }zH.^%"` Y <Kz90`y,gBG<.<{ R<n`ҿh9a2ݧɏg~T̔LnM|di75y iF+a'Kt\s>.p5w 2љOZkp,Vv$m6B*/C[R0 Yy4I]!,6fq\]P&;)9pʮpϓڍ54X4D,$Lѧktj-~+1ca(g(+N`e( l{V K L;[|Y2?چ i}g!t8Xmupv24j:ZwsUOܗƑ Uב 9^kً> _mN{9 _MLc] ӅG.IAn!<%MG JR(-jsVk|m,*IJ[߮87nC^CE,+8n4k[C8]# lKH ۗӥ̬+ɢLn_2"uW9.4ez#՜ۆH, o$mxtވv3j~vn Xosj !}B?aJ0DC. .h>T9+<D0œxrhTq#}Q *x BT(vK ծ;r 8 ,7$7 .0^N};qh­xo%>Eǿlžڎ/S YeQ̍ rꛣB 'I΅*-]M,ۑ~(Nlē4:8˞8aT`.yP\%%hXTg 5繅=$ۑ[TY㇣fozl瘋t7ϑ~&GSyTk½?P+NwU3Kls(1ֽOf#cw.Ջ Aw/Ys HYY O ʶJfibh9GoEu;؈,ij(:tik23u˂2Ry,L{1Ga\,DoRx |9Vg{d _ T8v9Kv 9]1NId41.WLY`GdI+KѷV\ٯ^1aF쁔rJF&*@ƪ*4ck:8_[ V6IAy[vRoT#y۲,?ZB /9zWN['b۫Đ57eghdq @(p#NY1AKmAqV0dvk zu98H7X]HK#g$JH^@۩0ҵ z)g8Ccdk0ƼNr?s{)dh[3!Rv&81ڒPb%n`|\bEW̶abctr2|ٮqdrHM &V !JRAtt6(FJ#.N5n\AdPKDyۮnFш[ZR^Mke,uj` m o邞O CJScmI%Tk)&Z+pT (#K7x+!"vB,OOL,G*Y ~c YR3%؛Eˍ(16GE&k:&KN {֦r|[2!D6Aw!!`y&ŧF#3/y4v~xFyHzoWe;h(\- r"OF}r:JNL9Ŧ]z7.Ju)j/8_݆OQ:ϟS^C4upi}CVS0xto-1zһ{U6ɼ) nkE +oi}H[OsƑœ ͘|o69:Qs[l舻32/:TwjelW<Mn킘D©Ftp [,lmcZv:ЈVwgѬhF{Vk"9o &-Ig8B)U ~JIEPN?ا||E1!its/NLBx[3zMTci0(#h]6jqGiLb+jv.Nb-]BcrHM|0[{(|8cj~Qew FwW#q9*6@yr8&UP"ũ4dڝE<cC4 #ʐ3.8M]Fɟю;,Tý& jY8Di YhqD$:aS 2m:H#X1N\`5}-+DKJ΄67&ݣ)ntˤrbqs/11x˟CYj j$g ,j!%m&brrܧJ)M#ݗ\p)jt?ֺof;OVz%WD邢f3]ўq}t ;2]s^? ƔOn_~s2` $J)`gU4-I? {1t| W+n͢s!LV`* 0%7(+ٲS)M*ޯ1/)&&dapP۝ڨC:529gj&eV"8D{~UzSrҜZf ;83M}wS{j5VA V\c᧨<#AKh$o`'xEf,WK}[m PkPv6aM6viP $o[`maz/l՟48mUt>hf{3҅?{@뺥h.-3ȽQ{ 3=m6)D=IVVo({G {-͏1ܝ칓{NXsB.pq 叞 }> 0wJ\flٟs n*iK:/R)aYXBwѸ̭=!yYz3ς ]c1Yӊaic&=o/;Cb] l80׮ӽNqݤU oXbRMZMl&m8B$L,_MqYxaiu6o9:.F|]9u^LW5Fmu*y wPS%~zEC(MTL nw1,|W()lnàϜ,1(e")X4&}Z_ƣ׺X՘h@R~K|3#"㴭7d1sl!E>A "UN6֗yfzcxpG)Vf3{/,~m)wCگ)r^҈G)HbU۝")7i[/RP]'˹Nk_P18ZQ‡RPVA;[n OL ;<{e̋drغڑ`\לGf@%DR}p"$wV%AP k>?|h|)\S;X ,vFkQ\ Ed۱ԆEY$+\ qdr HO+ lkd+⨍]sV {;H!gkDX|ߠ5őBYw@ P;bCÅb1G5)M51u!8B0@b+~4c(@PGʸoߔԲVQCȩae8B7_g&| ߨcM^*vg|i7QqMj%1g6nN#zÍWLlr鎍fh%LqM=[𖴸|𹩝|i魰i􀺝H-yX\w :(H??C8b^8 糴zaᧉ02L!C6=A"?WDzG)(8b'|gk[P^d>kSHeWG+uɬDT$SSOH@8G{|Pe0b$,ٌvq Zrl^^yҞzWn0&2lUxp Wa#׾$ vq!H <{jEɪfX"}$bA;62 .=O6n`O b<X4Y;1ŴoYlj< MKϔ0& nzOYeWi? B1^bBl҃K69rDd5L$c@(򉢂vt5Y *u|ͧbC`sf/@QZYiM~* IPhVjAFԌo= p?1*1Emü5_4fWEv/h+?m-mJ4"55H8{`I11qt7o X>lYˣ[G<ˌG\a|= h<iH& D9*%Z[;И0D]ʗANR> saqFWיfP)3  ٩ruI^]s؁,"o>@)gOʆY}ϦFm7uNۗZV&oNj (ٛ\}kGm&@qu_}Dq7W⢄-cWt8OyXˡmabئI_a@r$ıe./~iwAԝ2^΋@^;#33L)q]b 6WOkQRo)jKBKkAmҞbJSA4_7$R>K)4|sD6-cwA&BhQ/;iZ>0-zENk}[Хsr>*1nSO,vmvI-)(*ǐ|񬤸gb5c <`EB5/ʖc&/Kҹ ]x&)TOa|P(PN_BjD\ƘT۪+0Cl /l@*|y2r]N铉C}XU4={ ˈ#羅Lq\䓗5+՘B-9.?4H$򁉪#q;n#V4ф5^f+>$$24="ouYbBT;m&z)ė>65 Mv\~&-я=KPc˝ϣMӽ,$%"=86` qZ3g'&Ԕ7iHjMʢŐ@Eis۱V=}j8Ebۄk9lΔsy.*I 4oԡ U}fSv? OvNiJhvCġOcN9't0IDaN:NϞgg_p.lus%WeIǍ OU39B)͈yJ3I{DZ`כ[D!" ]5Q;Ѱb0%4-M}g'{U{\­5O?(Bޖ6u**= E|po^J{ 1DArmCU)2H˭ j,;OV/ЮtP` š^Nӭ`vysUm9̲!{а N"_1!!RUhr;o(̜&gC,1I̞E.lsN'SaZG$B&W t%?an)Z'-O3AȢup,T!r'i_tؖ6G +Ww~9c rc>㉮G_W{0>~i)Rɰ!0g7~J@wL&+ u4ʰŔPAo+bʬFVR/x㜱8='I3dpc uU Շ,X@#:nh"`:ޫ4 N \WjýMd_:&z&(8륔l{tV3r˔ITeK1)̾^>6P?O*HF-'ղ@>:$*l\hR*eP1 )#6/xTydx[ГͰ8mE]= 03þw?pZˎ<齪q1W5a*39ꭓh0onMd2_EKJ!&PH>2y<\ڔָ{Q+7w-:Hnfk endstream endobj 646 0 obj << /Length1 1456 /Length2 7174 /Length3 0 /Length 8162 /Filter /FlateDecode >> stream xڍtT־4"P3t4CwI C0tIH4tHJw# R* "H {{}ky~>{ɪk/o h~P e@'!Li Cy (`M i!uOHK! P_D$J H̃S拂;8o $%~]a(8hAЎ0כ }[vz Qyp#@CyhC\a: := ho 10M'l0kt`?d?>_g _(A{ )A _D&@lo+Tj=<.Z攕vHWWA>%8 9v_?7@z#p&<pwOX/ʍo Bb;u e iy@`4"`087f|s(!F{ +y!.߯?ۧ DHY3.Wc{$@O7_g.mjaEnBo>+&.H巛qC\.nD뉾- j3Z0;{h #n=T>0;]8G2FF"=ޖ( |7uy?o _78RӞHQ/Ե~ =an#so>z0Cm}&sSUnx\;cqOh%$ 13^+TOksx>}Ƥy7,$ưEqu?k`s[";5>1۱;.׶K}WQV2f(Ƃݯ&|_7ƑVbێٯ%RY . vMŤP)XBBkB!{f~M^s`;CnV˘Y H\EHݠvA0QDu<|rְ֊@j%U'G+|-'~xg1";q.*jT}㊜(0`<1I2~N NG*[>B nV% Vo3M#>`5}Djxۈaذũ¬5q!iSߩ6*%Yg+p}֢' l]h3dl<ȥ10|4&w~:t3v)ڠRN qkRg2Մly9"(\C WsY3̂l.[wy9[8EStÂ[XCM/ MIJЪ.Fz3=]6W,c^7کNl)ڟ]=!{@syK~/[W" yIJk=ϊJ X e 7orvɵh :6,b eQ%ci]!~yNzvc8Uu /)&/ 먂z,B*pm\;u)C<&FSjvktdCfih _*\jpM}Q1N4RNJݱV}-1X#:'z„3xK {  ժn)~m2#=8Z:PC*#h!]D'[mJ61?g0Cn ɴ$娑 l6dlx3?^Ru}Wڰ<.?OP{IBp4GVf2םF""4ùZSf_Z*s -Or5pȊWgkv(dArld&VZY4lQ7} 5(~--t&g?;N $Ρ_1,.yw_g6Z;0p ;3ח4=8%8QvcWBB9z΂|'w[>o}IJ<8 v<%M,=fKr*2KTz2[\~STɂZsb6HAk\N60ńH"{@`^-U{ELEUai 嗦oH$N1r 񙅫H'P 7ARf+zO-(TG.蜈IOwc0}pC;*]@Lm6O F-CkdH\ә:q+iʫ lf-Mde=ݳǙ4&n\I 4A}><p27%"rI 6g{ʋZ#!]ƾgohU?ƒA M<֝Ixx] G _.v?#Cr)? Vk}ka#Y\Y#0^PpL2yāuiG2:/m,595:K S7fs޽NlX/$Vk_fVo;YPTd8ռh{Rto|K'm+ `$۶߾ZVۺ rj)HלIţː׻53ewd[_ciL<7K?"0bn#n`i4l!eZw'FG+9}qD1w&/||KW(VT|/2.2jR)sep0xw_=] YB=E\)wbV{ZI12isHϕW4sgD_&u_HBULŹ=(Jfa|PG[6_U~Y6չJȪKm$HeY,= CRg$T\$YPee珆R|~EϠyv[T$~7%gMOmmf7I|n|4~1oיTyBHm%gX`'q75bTZ7^z,bk] Ҍa}ζFj$dzU֋FO^خ0]1b7"_`;%Jlft`j>~C|N4tfXb=n0 TT l"kr7 ҧWax>3e69qgQs $m^l 2Aqx]ҲJÿi=E͞@r7RV=5_B~ndomRS ʖGQvv7L /bjOܥ-2ʙHSdJo:1qJܜO%`OxAG2i..-, }#iP|Eg!ˑ/ r^BAswxhwe^y,0Qnа\XP|vwRP% r1M" b4?Wb c,ŠS|v4}"'GZ-r+{'ng+ߤVe"9A88%$Z3%u\OO qnJ_ex7cO8$]1:F@(]]ɭkyZ~O;Qۯ DX?#¾-Jo_' ѤR^_bgg/LmT3s+mM)oUp̨ 6! Rꏣ2um]+TQZir+jfd5NW>gV3e%c4Y-#st?:3U"z* D2 i4l/ۥ55y^HzYJ¾TW#l-CjVEX*9ɵs3sfe[5_F ĚoAHPahSҷ4ğ͒|Ԫ0-Ay;!e֦Sv[O/+S 7oYϐ1󚕚 ".WP;d͑¨+E5ŒeM 3 XlK%IF9WEc*v#@]!la Y_u7qBz6sYcQ/ڟx>Aw`Mg5W 3sDaY<\$d=K!iFygC=ՙϒub\F>XF A2/)ӣlg\er{͈Ќ1`B ˙ powJqE܈R;UO jpb#Vu Uk B.؜h[L=q(/~A ];.Js"uRMh7yݝGs[t+UtcCGd(pݓ陝uu{lhMY="K];4ױf.JƲS&X_4SA#aJ^`Ri[ߒUZ#iN)c#t 89Kxtȑo_iw4A%+ Aɮ$)w~`h@SrM;cya 6xo\̴d_N-VƸL&F?'L!B˹ݹvdAGtGME#^j e*^R6c'lwΙI8˹yN&Z9[-az\mE–?vMC]Yb݋ύmor/ I[nq~No(&l|SIQh苫W٣XwMO^Kl`p+}:}R{كMC~Fݰ O\(/2,1#c!l^l5UqvwK $1[bY6x&S,Кb|MUiS$9aŗ? BFAy5.JX^p^Xc*A@/rfEoΞ͊_:Q'6H(Nx.J&$~:c3IK 3ɀMtJT*qiTK#ݪ[5@?lR ?"jlE?1 :T #HmyYL㼒#('W]K|[Ϭ%~6M=M`9u&!$:ܱUc €\DK U'߫9_@Ԏ'^Ю[ Wh{RC8CW(ujO"g}H5;lEā 0gwW{`.BwwL?tkk\/Myz[ɻHz(nKC5l~n  kkxܺ =*Tøӡ`'7Mz 3ȚGVG˅i&mh"ړ=jVKxX}:Ǘ=b%/&Y1Ec7 qF\ElnR-?\xq5Oug9}[[Üq 8~<$B.{#|c`GF>g_aX6ta|\Pnm5,st%'yʡO;x O|Ngl4?0+i8;18|2Md5ú쵡sUW\cɯa/xD݋_$ޒ֓/9qXԴtrڇu]1ԚoV56E0½~@!xռiӀ6&qx':mbN ,v$P71wi˪!megˏ5ٲL0sƝK?Sg$4[`Otɖue| U1hݻ M>+O4Aů Tf8[q_[WoGPL'ڵ'|AT^'(]-_g%;e Ǿ[:xL4{0a9-X }RA>>!үWIT#~9+E<.Kvi7=gyn-^3MqR4Ux5uf/ǪOv!{ɂ9CuVg/^?@Wf1G)o?Thq78R^q"N&b}`wonK?L$oue_}54-CT{\=`0^c%-OI\'^onk}p=sN$CӞi#']b.lO2TSU)tg`x&ʧx$ls\$lѿb Ѣ23 b~y2ȩZ婋C4H#-a# &ZGsnyԨ`p\ G{ZG2brqL{Zq3%j1{Pw3WW>6-†M !Gɚ۷P .}x~(kl?r@tJBJaea*d_䦃>Nts>ꭡ)K,>$*ÔL܈u 00>%I@8됰'?ޘVX!\, -NriST˱)A[L:~XqxfinQr9Q cئ{}Gs EOn)bk endstream endobj 648 0 obj << /Length1 2778 /Length2 19975 /Length3 0 /Length 21561 /Filter /FlateDecode >> stream xڌP  6Cp@ 6C ]]wBpw79ܓszY{w=PiHX̀`{VvA;F b F  B hI@ W[WO._C @ dPf((4R`O'{H~Л38rH@eS@;HFsS[&t]\YMYNV w{.bj4V{`KwS' "!.@'$;@S^  7Oljns0[,A@ 3ⷡ3of 55E +0Tw}N gVg~YB lgwqFO4ݓõ{Y-,amrtKmY]<\#aw-O_JbH `% /l8}_̀V {?!b0@Cvq2a`{[?1%{Y,\\.>/f/Oy{K0@?d!]/a'`o,0dn?coGH/= v [Ͽ- se0d.? _)d$li$YPk6#g {fpdm 3d$R!e`d$N7d- 1q@XP~)/M?&ؤ ~$`d  `{q Hv?]dW U Hv?$ɮAkA7Mp \t ?Ep @,M HSgsdj2d g?-m њ:QC4s25B.q#4@m!c';?#?  Hp@$6 $wtH\ t-An[ vw՟7o*{O@Y@dASAȩ B1/ w?lIJARۻڙ5E ˋR5$䭶[G N7_@? i |asSo tv'6o!haE\ _~A݈@8 Âhmtי@8CʁTlk_!Ey~\;5lge 9>lv7}| BkS!A=!Y=!3$? b7wu_o//4GY Z~ vgMe`^pjwGGLbt-4ԃ%C%H}RҚ.Ncj e~"HEK|G'S&ѕ]3έ{G]_hn,Qڑo~eR""1b{`Ιx!WcB=*6X猾Z)t"&2 $OV ..\fg`Z#84T&`uZmc iXp>Gd.+Yxw0&ԉB-Q{wcJJvkreoIrE)W.OuF u7ci]2SWt4 LF>UmfPo eX/vOF RIp}R+k!̒uShI a~ٕV)^kp!u=g@G}қCPY"3Q\f9F!ݞ͵ѣhy%ˇ ;ŖV4y  rU2z:tg]M`fx׮/ٍlL1x:5SwʩAa«؊`thU)԰oB<+jv?ru憽Vg㓇pk䨹о>,3(*3y%q$6 yDwzcq۔y,t'&w1A%<Y@ͬr"Lǯnl0?dN H i/_>[ 5aė2 b"G[2㼄u,O6<"y #>ńYnRUj@-YըO ]e^y߾Ȱ2t1aJ.&"UV5٥yPaX9f*Ecs(#;lk^ ހ6rW?`:FK!O9;y~ĵ$TGi'& >shr /^lMo: ףc 0GCּV0`o /s} $^ě %W:M=wbD)_N3g  $mpd( z^-;/ֆI[P I x$S}K $0pت5H'UР5db<Ȍԇ2Snx}UTu2S&AoEiII 'Iy+8zeT̩ z!vMurA+{hù fp_;W;5/5^Chw]]_+QavrU\\6ԏoi&tVEs2xDsXaL_͈}_9q?U9rP,#+!zL2gb|qgyIn"{uPT sе*rq\ 1u L|*ob4-S7IV᚝@CT/`x=Cʽ1*5}R`j ەk~'>dGy6S !v Qb;Q,4CM _P;ԧA& =WSsIXFn5&e+l<3̲$X toeFN¤;8vP.j|m?fn䔱 l nh\-wy,41HRt 0⿣ln +9{Xv5Sdo|>1Fcߗ޻X8ϐV2olK/z#7a98 aQF edE9(3a+Q_&MȄS&@uy_&X ߾gr%=Rw_D^E uIs$KoM\W^ B~pu<q@p1q+[aS^Cպ3u.S=.̤sNJCmjuR } .' *%|1A1ʾ QntKWV~Qg)qJen̓"tVKf2QJ` S-O;KFU2+9:*cDA# ͉ԸbM|d)kX-z#=LM& ߕ_ǭlG,p e,5 E@4>Hc+|yl[Zjp ><; CzGk.8/ }k]yM7é E ~/4EfXb.r4CZd*xQf TF$нWI.ZQo=gCwt/|Gd&@I*& ďvbVh ~Hyh+9椗QeO_La1e՟/|(%ۣmXQaWd 3n(Mbc¥_/20ghz@E4c&&d ua;(O+vѢzJ2“rFTMg!Iyșn oO֭S£E!GV)mPUzYq3Y 6x-tNL.O!| kvJ?ж*ꬮ~ƍ5Rv}}bd<1n`T(_#~m ZJ@W׉K,l8.; %:]1Nsn1NQ& ׄ;E3?""KuT뼄ۃg4"4w= e255J3 \ujV%jmɶHZJGhlfc9KxS yIXܽ a'zKԟ4r (&)ZdRu% B1ng3:Lo*5`zrΫ2Tu4Lޜ=pFXj>,P^Y7H !cWF:eV_ڬ  i5v Kc#CA\<O~V6vl?#6ۯ;P2tLJHЉZ&K,NsH9UΨ`ql=kW2%V3Y> i_m5HƏY=E+f`钝Z e.j=Zj2\,lɂY- 9iFjgPKlu _s ΰR5 \:,{¤Ky4WQzqd]\;MNJ <(jHt+τ]ăhy ]K3zpIj9]q{m#6>4;2[Gn5Vv-Kұcҁsl_n r(2=,]+ݤd- .,8p9UKNYffC[t>1C0HY % #o^r[bu;}FvQiucWKB'fz|۶L31>1׍*ZcsVV*mSdCkT&-(*S7bbA4qH>Ml}Mٴ$0 o4v, `]cdڡ/b=^ =>9y{s1VnK(x:ݝFDk|F#^^$} FDo'?M \?r$#y4`2v}abxC~DVw&Vp0c-6 DoF2)ީ)yU4,(>f'q(ίmMoqns}RFRJFкu]܄pISC:~t!y_qʲ,Lb Uk=5ݮ-@N֤@M*x<ӱA62Qɉi.ثJ75cr:)v|nDRRh4as{_\Q^#jB]tO E<'gOCc{79om)zN IG&`|V23FDm>"\k[2'gQ>r{Fv?{b)LN}j.ԯƻ=cu'V('Gnsƛ/@G83ٌi4U~FN^_BӺuc1/R)A ~ 6`\)c!j\0rw)fyC "C#N48n. O!0ڇum8+㈉>%}XYʌvoJy aC)^ouE˦Fau&5 !ƛ|_ӱ 1_%.4B3wFEC` 9Sw|*YWy{[1wGvD>JHԛ&SH)e Ժ5H:t};N-M{v6-9jUf.:JRەkntwٰ ̚l%V{/Y*{^OfML+0 e2'|/=N+RW*<Ж3 r|4_-k9aA*AMf~7>ui6j[&Fޟ7ܯ,25Z]nv^^Ɲ2ᵽG'YIͧmGC$pɭuhP{l++ FiNߦ'.L#Yd]LyqW _ IAB1exh;!\t|~6HUTy'Sjϫ d-*xhS=G=QDJW68š8&<}.lϳ7Ye7tF6vwd '*꯭jg@&4" T׶bSxhIԢ`^ 3By03*7H,NɤMgAuXt{{ zxт>+7Iծz ͦcyƨ ` :%y>^(ĝ^&kG$H-B橢/ԙ:sLˮ ,Z$z/9vs2seC(.o40[Og{ܼl e݅ &_Az5z?Nj֦<Fۖ/,5 9c[K0k~@IԻ#RcH ,Ƃ%u˗jfG ov>Vp(06 S}Ga^s.ڗ (w}&צvyAdKYBG0b'XF 0ti5imqօwn]:alIHTKNkUd$ C6NND7 I܂8n/~x>ZLSu/S"Z5`g\nN,^?_j r!mtGam7ma:D?}?>VbO]3?t$"t y?7U-%Ef7ϔZ{kT4xu0"K;>eJo4_w^_r *:g)kn+~z̼jDozdvm, %xh͔PY2.%v$% T9hju 1G.&FtoV,GTÖkQDQ +pi爐IGa0-q ?Vq^_XnH4G3},e\Ўuަw{u)hii&4݁\;Ͷ;۩vq)N!5r-1E Iߒd(LЖUYR(8)PgO%GSFMwٓ-CH ӻ7<` R.V#2ܷ߀u_xfc ꮟҩX|)Uhvj]%/!^r6!|C,FgZWe.g'hЋ[:rT.r?k-.83;B Of_TK;g3̫>dԱZ-d/.ƘNiHtH䠬weaV Ϫ!V 2s u^5g*O+5 { }ߤ!`>H6E6>)ˉ}aGT^c!۶K2"!i5pTϴĎ_һz&iCid]`I~7ZT|ʑku༧u)wfxb٢)E/ƌ?zOpb)j# V6ї@[gƘe;)’: _lբlu,45':]awU0).͉vX6gn Ĵ xOHJlIA@W#T|b"[߫B^ rjkv60K][aaCȆ{=/ ǹ70tŠ`qu\䡧zȅ}ްn9 \vaαF{Ay* ZXG 泽 8/ DX+?񂯟` z{'zhW<CYi*Io.յ =6/= fp>[=_ =C[~ `r&UNWVQ-yD\"}Ř/Bg?{}xiWW?szۺ-1Ur5CG«sSE,%V(UZ&n fZ"}ѓszVSBvXZm:-W7K}M'M;O=FCU`a ̤d4{[Wb2+jmB (,"+|bKbr){%iߪ ebqc?{I=xF3vp7EM@|›rhz$%'tlPZ%tH{UɅH@xI^MLK: %hGTmKZuU;"& ຟjM\ eM->6l[r kN."¯62={=ܼ+Mk/8H^`RaC1ghT}6SNX[(&Vwi`͵ʹÌio ^A4Mh'4_>6{dcI8$n%HoAX}tO^{ShH)&F|}9P1X&5a$>_%ĄBx ¡ ^%'(3s #9(?|ne2 }2W?6bB>G`Itk쉜aKtgM/JC\41[n vw,T zؙC|4 (-<1~]׹+L[K.;oۤͯG}Kz}9G!Ɂ`R#}̨)%p˯aV NmJ(E4V튺$uRf-Bg&n.!Lq2gz@UW^ A{#`yAvUG~#Ɨ- n@|7vJșo /ol 4HWUV]xS:S"ŰD GspdoCWRUn $t/~Ζ8Q\BsL([\#s0zqZզGJKVYphBt}"X7PT98ɨj7=Jg&'U,z:}C1י#DJo(; bVOi5 x"|ݨFp< $l5bS`!6cGX6 X3 Izxyā{x6|>|޿xK<az޳Y#:.\gv|Yk%ho-ߞ3䲩$J~YNL{\VcT.`kqgNjByywFVi'pa$Rn# p'j<͠g#FbY9M!?U#K83 ad u _NCL]@*ǹA{e[:C䐹@C={|ބ(VCVhD\t4Ig|^ uf húokq}p0a)kQ^a,ctB~:O˄Xӿ" $ſd ^"gP}yB7L{b`1]K@OvE sr;AJhD/Pn;/ꀩRcR4ӈm:(' ˛s:H_Aw n?~h@ϵ!Įh F0ֲ eO®Ï)bٛdΏ6Vrnrh-n[ɘ̸'U3\/xsSւ3۶'l"Q2'L,bҙ0Yh?63; {yFUe +&nXW=XA ,FU.}UbMW h S1"2&ҟh(E[s^GP2_|E$ xmĦ@{~ez;7 6k'] 7Rd*I/Ri엒)4Ac4GWMj]g iaXWhȎRzjhj4 Wi.m Z }BO%L w#-.aI䅬_XVZagbҒVql 2zk`^>uI(F_ f\[*@t aEeJkSl fœ<׍t!UMi65oi! C[E7jth[tH[YEã5!'p2iĻfG㶏s먗_HW==m ^]!+W啒iL |8oEQUh>iWLNpD䪡Cy|YBAwNT/\i@3J6&qlˆǀu ewyCTlu\RρgOD׌Yzx˺lmG+"zAfl%A'#U).r0lZ=LDm_J?t|4f]b)YDD~~1EռBAS2ihr8 Ri lgu8-:75GT7];Vl 5TPuEެФ RiM -A:JC5_tf&%ME=f%VM}i)TZ=nI2;xU_g_#f`-z*+fvkmm7Z-3_ȽY} xu=Gm$\)w0x2/^y˚-;Muo_ [ *+y4/ئE]OJ:kywr9OFfP٧)O* g32U6HL5Α^+dw/2wkkm,4K C/!}hVrXvf+~t#U&`uG$ UW%6Lt3;bK' kN/Jn#ޭ3Mȹq~ Ik^1"3ߣK9}P-;{;˂:ױYxy(ZjpýNd׈? m%"Y^WoRur$Pt&Y}I>, i̹U6$)j=DG}rse1V#kq4hڈݟu]Úa %>)<%o`U9%uLb(?,U~ywpߠZyQhXԸThY~mX|b??^қp{{ς -ooSD(P0r)p7*n/ S/?K=TQNQ5H?K-}F~RRVoR;iEħ/%]}o$(chj/u'uy>؋D6΢c rTd܃y n6CKSޥմ[2N*x\qs1 7vN'DbߌYA(D}a0%*}oХ']P]XT)1;+I&U{PPdT5sA ]jJ_U03SB]8%M^WEwIUB \8 BvpE>j'8!ƷߢW REUMNU(,!y sx4V+0t-xG%`((im(+UHDkr}nG}@z#1UT҅ ch|TTT=S*t qsXڑ =n<9(3PճإJq:;uh?/{GBaK) FM=# AvdŒ5lT ClrhӭF`^qw,!y#F,xzer@\뇬{?ab4(c˃ߤ%LSƊN ޹./MzrDJWM8{pD"$"RDn ҼM?1^o@yH_T'sR6@k {Oo0c?rm2~*'kZ;EwڲCqVd~}c$\Xig gxQodu>Y|4f}iS'4j\rX*w<_kKTZO݈鯬 L>%\{/l~?B/E>!܊;4Mk&¹)IJ)j1PImV2@|Į@{pxā - ֲAm2'065[)tKET Z'Gu2RSKJY1$Hs-7 a4}:\Fൄjwu6F'խϳW3iu\h%CO& tKp+% w]=B#t;Yԅ_s6Vn`U(ًN]v>VƎV@8yIVbݛ6J 9\L"n xn *'RZ aMF1 _\|w٬z_Pg-*'蹾쁲B[-@B`,XO Pێ;fnqM*g~t+a40T]%<0SOܕ8_DJ|RΟ.#+Q_"H)w't>Kk`->U+mJnAJK1hu_G{~d.IBAtJ% {q/qum$͝}9^t*δǶ\Zt,T[X_7VrK5Ta 楻!]!d!&g~sρ:d;Z띻RKϨӝh:ii#k1E/t]W2iݵ"t#?*"׬3 dTy«#2JzVg$zRk.$2nBHdܸd;0*GK'^|aCP~~YD$e&e6[^{KMc?[ i_ms uu0ٲ1 :GWT5 S65Q>>p/k'&e0K"j^iJ eSiCʺPCaS^5 J1V'6^OBRAW0- AQ>(ZR1a? I=RWܼO^:y=>af xA0)h0Kzu$잣E?Fi᱉K`w4T>Tısbi;0[:NݺX;yusVLipڸ?[poQQNͧ7ުLJhpb]P$>tF+1=c-lDLk D-pO0ڕ;L٨.,Yu|!Z%hoiU%\ (;}k %@Nd %F_YeqrU'dyg> Q eKxf6iAZ4u6ڸ@k:Pބd0؈J3)ڏRN?]t&`8Y4DN|ZOBhOO( Oɹ-Á}W/C]~ii/"(m3"@ԅ,N#yTk^G eב>7Y)"=T4hkH Qo}D[8½cTX(xx&hAƕ6(l6a$YeX gC.Yl(7g5L`{&<y3S]9-yFvKZl^2T|&jx=acg̹"O,[RkT`_:­=>J`b,|X ҫ4<"P(!3V㔶ofJ[h)6lmiܕ)덅$GM=HU{tU ^1K1܏>gA=>[~NnYFlfW Y-&Awgn9 'R\|&(= q o<o07ILy|͂lm:T#N0†X2BфQ.x=xPcrr+Č?Ϗqr Y,ھ@jmdl=mOHwg}FJ b|9YF&/#5}cRպ@%+VWRi5n܎Z9s+#ԣ)%,. C'|JD]+>J(Ђ>aGfTNii^M v噅Sk"TC]6ƿɡ5Tq(o;3RDQ*_y"-蕗mo[Z bw {G咿CyNQfGtZ WȟtK"lgtgDkt,-p-# xk"[ \UFme>BneU0Ԭ|]1C% =42f>s $FV<;0WPrU|I @ܜz<`bbE3pk)> stream xڍu4۶7UJ+KQHXU*:(EjUQ{WQKmj훎O;}OΑ\s\ MQv0 #TLM@0(Je `h20@ `C!ڞH IQ P?( @  QH* wt`'IKK r(p( Ѓ``،P`as`dDD!( q<`h/=g}+wgd\S'oa,CaH'`Ltn0oc?A3\ H_8G iAx/P6@ ia<=- :^ Cb<~֧GàرY$8?t1C=aZjL@ PJ `>P'M}`A?il~n(7 ?"x'#2`bv0G8":9G}V@@?nceB"|/ݯ9SQA!Q1QX ;!]+vHY >c飰x/$n ?3&.Hu9 `寇.MaWVftS- HG?c{h}`p 0~f?W G Qo @v.w_G0;:_` 2~ "|~) "Da.lwJ"?H+$ q$"H w" )_ 1?Hr١ 06&?OD 좛+bbkq@yĦpH Sq br b{a+AŹ]ںae$怹`A[ KYa;qDa`vI!a ۣEplO_|c}~I`u e%,B}┝$ ^Fr'{b/bƔbNfD ~TX0]l0W: ?]]ijC;2nwy+},辳8)]ij]/.IJJ/A9JF-N]+'=ΉWTUJ7UWtlZՙׇy&ᝰ DϙӰuS/|CexAD\_$dT:!~T@=lΤ־7 +c6O +HT nWrZ[5LPh_=|.SOu> p4:)mjfA3)ʺt,Z EvC v8 0*?{j V PC&zx ߠնhB 0+`'aЙ4=|u|kHm%MՈm@Bn K < +Ԑo1ΌsCve 0;!Z4=o2V;![ƗZYL6n9Z+FUT$Hk5V[W2]Gaz(\(Nh{)h3[c손ty[sauw#+ )TxQ]=6._mV?ȫUWOJp i Y>Vfe5_# @|XFY0˸5=J JzQIo>X y 3S^\a 3mh:zZ, - ݎH.כb%w*~6ZiçfҥO;Ix:r43/l᧴|CI`Tyĸ]Pt۵+Gˑ:G P{7F\R4ԂSJl^l?6+?i]QywSvgԆU\Cϝ}P4i,Iўyx8^gC+4_([n:ԇy%;!sYr# , Yn4C~4nc 4uKy3slq1F\V28uS֌TBlz5Rk Sz=7\i"Kw|SM hbHVkI?#+o,ćRF丗%#=r,b[$/s;i\sn0:$[.ֽ,g覬Ak60B&xa܏E+q \Xjˍ9h ,%~^E=@F$m"p0{f{=0ggej܊+) &ڼ5D|vJH<>ue^ @fkLDjyuvޭ5!CwUk==r v*8li= _oHdPP4iZO>e`s=T\_9;CyoyL\Ztۂɵɨso.mu7a.eY7X n<:4-Zu<8%>7ʱ&aS=}dֹO8q(-ލ|D>򭒰T;3&^)\gиtSq{St^C6=gPWa8HOb$vkH;޳Y;5٭qIgjnAz|}3 gp3,fj0AIt dkVJ 8)Dn,cDq{kw=8+2sE=k'ޔ|)C!S}dGny܄/dl&8|]Rug,fMJ:1㰖ԊQa׉dYrz5P'! ]_ {kff)NKp$6n|RP.QSG YZILQ |:1ƑRBhr] (8bIS8p89B _ NyWS3m;3דNl oMNR@d&! qk^|S1914=_ܰ,0V:|>A`@?L%3'`yI+ߴqV]ŤJG}nAR([[u䞟 ڍ}#*ec;N`= E`R#']HUˋET?rCC=>q8AMIG \֯ Gj4aeS奲qț>GxԐ؅ x6ER%]j5E2kPL̗"nd+D;n1-]gmQi9|:P&fn nU>_~/ rg FW( )P=*{Q=)Hj ,# =yVH"(T5zSϥŎ),rH:cspJaЇHdwtՋ$wA 6}]\9r̛+5L씻GKψP1lb,O2aTV'l1x^mW-ۅs.$8OlLFa|=cV[syB/"f!S'~E+Aq߳2܊Uc=[\Ԏl=skq0)~qrX;k,>cW&lF4 /uim}6ypsjK3pi]Ȟ(Y*a_:)g_ZȲ AEpEM2ءB`1coq.(t7D\b_ Mu ܭ(>:,0DDsS U_R?V RXnS l2D j1:kȥ=tG]15ʰ/|;E@I*n\֑BZѼUJ0NyqA:!=4j g7EBA_"9LXxէ$(_-g1n#1eNnyQ ׶ibԖ ^n;Q#Jӭ27hՌi\ə;"ՑY3ˤHN[%WƠ:hVbE|n$ .Sh1X&Z܈$[_K,k2-2M+l7o+^v*vdQɎwo ~:θx6m"zEv`%[b)e\<={wj_kB=:n΅7^edwnw{FVߤ*@13T' Ĺ"U5m8l`.%fz4Gn!^9pdjS?S=*wuìCTo kVB22Fn_VNViP/ulKҊ0~Bl:%e{zہA{Ҝ,m:ÊEͳqH j"!y̰-M\uo݄Mni~qgν";1䧡|tJLhI!NGign9xx-~YV7 'yFt^)pd.}Ϟx&':_jĮx{7P*5R#aë`e Q0-̈́i;uֻמk<#jxiC>( 0ldǺhMeS?;;>3AiΨ >GhhGjLl_dzR }yV#&ݕ#)MŜLϸ5DC^|_q*}JZr֡;t*`ڙ#&둓&dq琐aNJ 9'ԸYI9!5@7*cU)> stream xڍT\.Lw42 ݈tw7 0 1t twww Hw !H(͝7{k :g49$ 9(- Vpsqrsb12aXz W70"/+d08O (;xYann`?F# e!P''G2`WؽY7CmݙKvq)M~lA077K>#3Of~>Pg |` ?;aV0% n sõopyYC!޿/$_㓒z|8^8xB/~D ߑ(@^~=~W߹Ԡp͂̿%n-m!#Mېn?7 7.Yw\P@keUA`w*€5:s`79Z SuX1G0u8x++.|m, j~ ]]Xp |xh S.N@]PA o$R^~#!?%7K7piF|.߈.7WA ?ި5/|o,|nGӿl 3 ̀-߱|Pc (<_nxοK>2\ܡ0:[5(wofx]ؿ pux G]]/O \\@^ +EH}}hU$'ǷI9o,>ˮ]7xI,5I#[>5G3ךցith@aCՎk^rbcK<|+yo;5/g9bucLJ,`4D^sD9SJlX~|E>Fo^u`0"A>'aKV"[)-Y[fD?6*_b?)Al]19ni58Eu?mԁ; ݬ(t)6wO'K޷R1とxQq.rxQ_eŦXg9 4j+>1OXQokDU|6D&SGpiňS)mŻFIZ5#2Yc#--qLjeq=]J "(R!0WuFQ otZk8+WL2}iuFp2pF]@j ZI) v]E$S=Z3O'4ϱupP}0i^؋Q {F*16(Nq5zߍ6rHza%Q ~$M˷[kVPr#\QBy^<`lmW[_>N>9dP ML(83鎄gM-_VBUMZ& Kϫ"w-_5]j= j~m;̙6_v;0:he/QR ;f]V.B&/]F KTDUA ,P`m$͜TÜoK< J&.9&+be㽀ᳵq*01hD}99sZ> v#Sw~GZoi|ډ]|P@ ';ە|g9P Li^T.ԍaK,Db7NsPPB&k F2U`.x }fW3Ԏ]Q++xRcH0c4ٱiwG+/>h9"|vQǵmc5{_KZ!1|K{5 %" N87'g|!1V*Xը'HOP<6J[Z'0AXAac;9~Bz.zڒnTz47 ޑ hcɣP/(RV\'mW/Ct9 еXŻdNဳmXΑ:k>[ 㔔L[#v2 aѭؙ-޶ 1h8;4[DPx57$ UUaJmBր>Boӂᗧ4d-uZ(W#hڙ َlxo;Z_|ӭ6lY<ߙX흩/`ܲo!$..Ł.=zus;&fP<+nj߼k>O'\C5 9Q=6dً|+bf-mY z3ExrE ٲ z9U&" 8+-|ߵ>T! ׽JCdIM7_rNҡp]ðjhfHiGV&5KUr_{cсTSܑ4Z#[xjsA cfaQ/_u^,2:^Nb}e20<d:e\LSH#0`jlȉgS8h֖݂N,SEYx=LBE8<"׿_rY"SunG;~um0jWN$Ƌa/zU3|a̎[4;3AKrɻ3ZY2OEdn7dP[3LeUPiP ) x^U[h1"bi> *Eœ9<2@;u]9b9{ɂ5!G Ok|w&^F9 /qMҵg_? \;]ojd@)9xfg>_~曞ԏ_?޿m 2vk}ct?@?+Ð4PoA⎨GBg8|;7K~|e wHlA\8~(BYiB$Z rN![¢E^<}4*BHA;\uhclW53#G%0bIEx$[!I'J=dhgסGM/Mȗ}nQ8`Ѯ&4XPndƔq0Y :ʽwLL~Svz:KT5qϷ E(YT,]zQ3jyݟ?G yXDWyXD8c FڵާxԤ7v>]1DᏓ3䩢FOI%]}Ft/J2 Mp(3QW6;Ve*ҪbZ9U:z=k"@^UF$cV|6ʃuvroR' S g h' h}Jڣ C/~d5,ڵݥlHس~.$ͩ\ox;5p;QaLv JÎQ( ^,lQ_t~yk=&$A3nQؒ \"tMoZ\zL2i%-tvF;ӅpⱲYÅZAM|9,zϣ_'A[څW9+Ht"8ؼgL[@_KoݻC]u97(˹)ڭμK!^Hc!^`kgD1KPhw-lAo&X IߏM0~pl=nIr^;PD7|x[oM޴ݕ1I80rx)VUL])Hl3#'hG@B\WR ]GeTu ./ޛ =7-SW~ǬL^"gy/VgFBp xgTblMNjZU.s4~2OPO- c'յ3Z; 9ʛ\mUODvRzY)VEUmB.hj=NI2)Wm#Au+FZz! Qnmb)ۚ@!ѩ^=Q*UZ` UA/#q~w[㌦X3m/71 Ŭ0 \iX(Р?3cS(j>stY¨Z)XZ7 ]b\c`XIe'¨lг0m*L*^c2ӝqǾД!*y)~^z/?Ű!3pFkٱMTMEpZR_˸.~@ mVdug.FzXΨ/ɕjct~pJ/jٖ$ W&f;7>|~h0a^+o uGBϴP2p -] {`z+7 *C N陃_36f..Kbig&H'r{ M )Cr׺'Ƈ 1U+ZA~S!Z$Ɏ Yd32OT{+Ž Dhs!È[)rEaW͌tW0lK/ _eBiXg4+"b /Ƥg* jk](R\3 UְW`#FBڌB1/'[/'(5&N⚖gs{e{és EFgҞIGIQ&(BRB`i?%eE98uq߷ > /Jͳ@+@P>yH`76/"ktf oCȱnS 6U$.fQyɨ n9a{$Mn,^..0Ygqee1q%|E|\`xC`eX}ORj"a!)+n~A.r_׶kD@mӖo͛/1x:ێ: }Z`+ >&1 r_LRl RE$}ٜx]}u p5cȪf@I$A[_"Qx_ZYʥ#$__ec-니~LJwQyŪ~K7Z`c{8{m|I9 6>>L%e7*#ʑMdR&K~Ygz֐H!83@^g|w޷'L+Ri1 rף6 }4`O%hz{EeVMַ= \$(4OQO˵ .cg_*t8;~H\ya7dLy908#X1k~z>1ѰWx/Fɏl"gho`wֽ݄7rGXas#Eȉt'fW0N מ;>Dg9Up/1af58L/.A] Aɘ7.+ybut-go\ybR~?}I9uCwm[ {yHlWIh@ &yve_9RS^mT.Slϱ='Tl|ʤp|8sv^t|FS+?!/S-QXK.|#5ݱ9 $>:&*-?vPt'<4@_w`yȹt9fh~Tq['@m:ī*>LGJE-(hy%"[mw瑛Dr"d6R.I~y>^zmd^ ({ZެЃ6˪LU6GH*FH>DdȡJ"hF4#g﬏>geaһ:ϑQո>W)axstf.ez^6t _@Iٴ_UZ7W'(~L $IGZp)cGL3q0P+D(?gdy2יm{eJ 7\p.iԱCڥoO7 Q1 % >k XmfFP*v z•9V[,Ya?vT,VLZ\NXON_$-mb׾ߵ/#LU]MHqP[<2(1\0pZHօȴ_i&\^xM ŷנsκ/Xq͵M\, *lZ8/}a*)g| j;lBC !QF9;lRv3;9ccҩT.Lu(TFveu=v̺馉?u&OfrHJ:Lm#UTNG }kpU? ?%[3Q!p[./l@y&3  uodzN'49ԘGgnV´URPXJy,]Jr%,}M/gl'`` !vol\coV(#R~+;c{kD[[t~s#+wdKtqZp!!!e88wU6|}׌qlRw璏^p 79HBytʢ3F@h4Vz"^R h-r+UA㕓$d*_><5qUS ǘJ4r~X&() 2j7"%Nlc7Xw'<~ab6wB-P PyUJ = v3r-aڨZ/1s0&Ik{k"mWs;,w6%$q+֨Nb8 1+yF,sͼOӭA-P]P2wo013.l{`lM'vwMHͧ̚{PVԶx2ilkCiCc=~?KrpD'CC iLsaP9wV% 3u>U=8) Ybzϟ_^^sɒ I-gHuE8U"{Γ?ĚQN J[uN)mIڏOAy2Q/~PlݪhckѬ!['uW)6ćD5PRuN2 Ҽ">Шjzi֝pvi#j2SL`03ec1HEF7 ʐ@ٞҥƯ=肯(XOȘ߮ __@"U tIJFo"˖>1tf$t_>0~&,~.|&(1'Q9~Fd-@hn=<yQ&es^s\֒ Cw,璌) D&t'zXN5U3_㘥>b>~Z~) l[h !_w[(mF:VD Dˆ»`c@gXox,qS_)* ~>>aG/UW+dKԱ,S,'Bh919Fyb~F#Fy;"=o;"Vybj%]X(uC=P^=M!%Z y\*v$^3. m0ɠ0OT=Dѻ܂(eEPBK n %1$4}fhT6KJ? b|EbBwgt fنƳN2"wVnt0ȍِ dQSFh%-رA'/hwa4^k =eQ #x\_wB/$%P'32ڲSxCV4_.t6pfw[qb-"*_jt6zȅZ~EV{9TC滋bLXB#󫜖7qJ!j-yOo5EceX,#l F#r:c3>gNA؟Hu ܱ4NWͥgK)Iٲ_.2dq9EY`͈gx Oo&nqeRH%pFo`."lMִqה{[3i2uHta5 j9e}RuߏLuiD@V4& ݚl{7}|9(NͿb:W [J{do2л#ch][Tp,p94? )©L% Gh `CwFEH6޻.CMSi sTGTRNa2b>L99q@͕SR}]ʻSn bEDqRj*w֋erG5z.SW"?R6 ]5׏a4Dyur$*>[p3TqŦnHtuCzYMlVsnm:Fm|phj`ٖUmz&qvMyTA}Fd'Oj$# ASImwWhaXhu8q w;1 S%Û|D=d SۯaKz,1knieD_' P^fKZCײ_t4Gߛ1X2Wrl1ޙ퓭5\:}ނHΤl ,ǻ[p Ipr1vf42iVMfFҍǧhBkS@HGly|j}iEen _Fw.qҙfT8%r@HYdL-fjh*=+Shd"dzU-@]!N@V0:R"Աt5i endstream endobj 540 0 obj << /Type /ObjStm /N 100 /First 917 /Length 5729 /Filter /FlateDecode >> stream x\Ys9~ׯ0q_ÇlhKe;@Sevϯ@S彄%Yy$*.**xr` uhAO8NrU4|bW2-=(a8M%hKKaJ[ K k0c=~0@,w@ ' (@FHuXZQVV(zULz ^øzҠ4cР*Вm$< Дr*EC I4&&&5`>ZY3h`Ņ;`²z]*K/FO;tF tsSb{II[h2=jXFU>cX\?aaÎx4ϫ#Ч#U x}Ws^ Nrx5 yz'ye8w@g,vd13rk>&߫#`>ZՋ֟ L;OG88^|pێ؛L) ?'PTs0JGۇV=V@Ǖx<9TԿ/dS=bS5+!HgNJ24>zuOpkM(* ؂\*-|&t-ԙ H#NҒv3KD;qʘn+Gx H# ^K20`LOx̪o⪨*qyrz8 {Qw{s5m_?[K(TBpu;Guxڿ:̪baqS| *y 66-ĽخQ |ajҍxRᠿ#ND7`id`o2:Gq]ΠS1VӃ.wx6\>~RPm+pUɋy=!JlfCq>Eb0_Fw1jqL'cyZ t`q6!fÙAtVS[ 8Fb|ޟ..F\L'71#.O>Je~_~9y?p-O@CۖbOE]k_1Gv){x&~#ZpiÌ?S3RXFo{>hјf1]<{Y@t |Uʨ=L%813InI{츪pvԹ4̇gO&%ɜiYz!,tYE&| +J'q]COEeN\$2p\ck6Aid¬jX'8YsEh:CB-\9Z#K5hE4Ќ, sUc `K6:6v)5B,[+[j7F.oP [RM_?8QAQ&UU%Ҵv4 (N$QXsğ)T};T!c:d-b{1e Ӕe_n" xB3g"Z{ GhŰ#(+EKe@Cpq\rKhdpD6k(yIJPvFΓ`\IKԵAj#  8ZQfe`\iF,:S2p\:ˈCl܊%I+$rT<%o dWǓ'ƸA34"R-ܤIRdw1 UBxpJWx_6[x.Yĕm+LAsO131JpeEC$`r"uKi=G"mhL"WDWqwi,)iY,Xlk 7TliC;8pek@kIyUg 57逧a ̃x!8.$"QA-8M,WgӴhFxwobftcIZ SN1]@Δvh%ͻ݀)W6㭭u?^IL) LA6e1H:4X; 7@ aGg>xq(`$ڄȤ(ӆ G\#Tls榁~WU.1qJ4JE7X_lmi=a-]A!nFQϮ#\Jܝ~(P+e+@[6L.RVV+TP_L1q 潌Nv>|pP㬣轡rD9\ {qFR$~U4pcނg e}jUǶT3Uj,߲@\8:e*ViwTHe)8 1JX}܈N HIŘl$ecCJ`{U7 XH͵Z%XrCׯ]OE-и.*J}\Gs:,&:c&ςJN}Q_ʹF\JZKla]Kj5JM:.{|2_g$jBW1+3Ϣ蒇|/>P( c칣̇w|tO8x|G 8O3E(E\Jxȕʷ!}ޤ<*i%XX f@VB@Vt&SwH'>Q<&% ʔ(Tx \KTJuFz[>emeleͱ?cөE<(=SA}nU"'xEG:ln }t2s[M^QwӁ POM;oã÷kީb?U)#W#}ewzx8 {?v磺-wi߶`k6~4??<}Ќ`lڸSN0ܴ|'_|; `¿#MR@YφB:J>O|xy6c[d˦edqjoxԎO,eNl8\5JnN''8*0ߕ[R-3%z7FҧN$+ϽVϗ`Bh}9~_OK/..>Xz ݬ\$}U~y~I_[QTmtzp a0:}i־nJ J}Dnx>8e?JZjNFcmg 2Ʀ{"N,l=8yGÅu${'NqPLVc|Vۃ㷏Ixg'/2>Z<_Pabw2@y'ayyڟoj]]ї*D]^z/GzyPc;oVpS2XpSc ~_f+'7$ {1IᵌE,6Xr!3䲽Ń$9E(%\,ڂn={ ;\WU2h:UO%˘75{- ޡ`lpLCl;&f+Ĥc3M Ay?xGu9U|YLNw{'kr'Ozs]}mU?}pkdta-+>ۄ#>l)٬0hjiPm̏\nZ<[/l&t RwFKr=XµPњkK}r)J3 N90GV2.TݧtF:Aj9 ҟrc/͛c s?̾X l>?|x91<}~tp:DIih,D}uWmr}/_;ĜI-wa{O''<.)zڱs°\!nVwG{;vGv{-1k˵[<{Bg3.ٿl: 5@>-Xѡl2fsd!Ձu!?8^Lu1> &n<)MZhӧtg6E{)#~w#; 2rn]³#hyTS`ԿxnF|,nm4M⿙YmO'N0's_>ӻSto3ׇ[ ~o}>x+ڌ_έ Ԇ%+Z㛍M_Y~\ }- -,}>2/3 endstream endobj 685 0 obj << /Author()/Title()/Subject()/Creator(LaTeX with hyperref package)/Producer(pdfTeX-1.40.18)/Keywords() /CreationDate (D:20200731120849+02'00') /ModDate (D:20200731120849+02'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) kpathsea version 6.2.3) >> endobj 654 0 obj << /Type /ObjStm /N 96 /First 836 /Length 2737 /Filter /FlateDecode >> stream xڝZYsܸ~_G+)pU[[Ç˶Gڇ=UrQ*IG<$>9wNv\;kq:i-Lv+묦g߹0Njq.BjHqx'f P@9 . c4Se IRN(HǍgMC7Fc 7x@'(H@ )$!T@+(k>2@*P ;@ ({!eoeE8(a+e(*DhX/ ĜQ gn  )ZRBC0`/~uufuyI5t/?.-B~}{9su@/Vspu2o=OѭНNސo9k)8BͣѯNO_t@St t>?=:y"7Ҏ]fǣ??}5xUse::6g=a7U)a<ؗO/ Q.8Q\dGW'gu":8{<}uП/ߜÏ.}.92٧wˏ./:4,;x|u~v)7J^Ue'}.h%q'MW. fyQoԝn=㧃|Uu\ΫܳwWἮN-e;8<>=yuNs?F{[ I{]ӝNuz'CpHw]F8J}  52=K;J6T/uz6"^ J팡xxP7ūcѦCu CX+EhXI3R".S/+]cʽKU{x OR37+ooi6v/8۸q.fQoC d.D6=SH6@j[RAoYl9ٚfU~QDwnRcjKZԘ۶le[Ljʙ&l_'/65cR)%_# ~0!QLd2Ye|E+&b̎[&l%GV1C"5XYQ|L /S[1o̊[+1gzbA4Y1ϛEXؐ o臸z%Xbw^D-ns4Ȃ3p 1w|~CwCb&~oeLi pfK u/Y-dmo'VX;vI-Fl/E)7Ilct|ec viEmٱNl_S'-PeKTAfI-ak]Tԕhc4_F&:D,tL +yFZ\F:飹!Z{^|{͓xRl^?ttjCiK'yf C]%e{XUbAb]%+_כ R)lT)[!h kWVj-vZ)SDHp6sWn A⊨$ӕ U+p (u0-V~"T)K_͏_xH9lneA1k'F\ CH"T @/=Gι1Vӈ+4|!2շ@B@/(V Z- W,H[\BkT:T9С1*PTAk- .IhVYвS"gJ+NB,mڼDKBr 1kuК!i}` Aؕr6"ْ"RDyQK*r6y!IlL>V+ߐ7Yɡ )M[STHETx5qMiDHfSM c!֯bA$WLSH BzfR˹,KrffmbML ?VܒrrJav>lސSnj"Bѐ^rxg}l:e+|CE.ry#(j7"Cn_qifdpRS]4BilAڤU{0׈-yM/K%Ls(֪A̴nn917|LMΊws.]&iYvp67J #2QJޗf{|E?:/$t~S0cp$g&Lj^#+muNϤӯIC&- cqgiئdlb_!ƫz{o[ &6q[GsOۤ%JGͷEwոq6ڸKзKP,|> endstream endobj 686 0 obj << /Type /XRef /Index [0 687] /Size 687 /W [1 3 1] /Root 684 0 R /Info 685 0 R /ID [ ] /Length 1670 /Filter /FlateDecode >> stream x%mhUu?/smN77滛n7T*(*",_$(N}SB@tH/RBP?s9}(*ȹ3Wˍ=f B;MICh])FA;FV%Ўh+h Yb a XD;@X Rh z5h`Z)ZFPVP% X-`+&݄@5v`V]5ԣա6FfM-B(DCk& 3=MH߃GA'ԂVҼY>%FBSG A idnAjB-m%>?vhڞ~<*AbyC[; FЊbQ0V&[hh`LYsJM34&8Xg)A RsQ T P<[)$q/Hݠ$aXShz=CYK*Tr02/[ȲZ1eG`;pI>_ pbLp{ } n`JIⰗc5"࿀1CʤgqJ\.Qb0k'rM0pX7a0,FsV$zp'qS & '|y[X/,.k0Jj %s7SV| 17񗦜EKg.7x_m~$ xLqtb_g%M_>oa *xu!&4x``;<,zB7T\qWKH^}':'=f^GW(+<Ԕ90cQհB,q(yjЅEi@V,j~tgT"kӀbWkA+K_UT-X8ƧqEc/J|:u IS5-Z#m=hWx4WfPd3*6.iJ5vjI\kѵt p"[N .vN<>N@c9{cNa@cc t}Dop¢G5Į75ӷXw5ħ4įs X ZFChؒ+4䭷4YF'-FzX K]ѤhRh4mcf,SYK?FsF{NK.}_7páλ:Tc#}5 /; 8eONs 0 'm@gyΩcΝu},odX endstream endobj startxref 478728 %%EOF NMF/inst/doc/heatmaps.R0000644000176200001440000000773013710766742014365 0ustar liggesusers## ----pkgmaker_preamble, echo=FALSE, results='asis'----------------------- pkgmaker::latex_preamble() if(!requireNamespace("Biobase")) BiocManager::install("Biobase") ## ----bibliofile, echo=FALSE, results='asis'------------------------------ pkgmaker::latex_bibliography('NMF') ## ----options, include=FALSE, verbose=TRUE-------------------------------- #options(prompt=' ') #options(continue=' ') set.seed(123456) library(NMF) ## ----data---------------------------------------------------------------- # random data that follow an 3-rank NMF model (with quite some noise: sd=2) X <- syntheticNMF(100, 3, 20, noise=2) # row annotations and covariates n <- nrow(X) d <- rnorm(n) e <- unlist(mapply(rep, c('X', 'Y', 'Z'), 10)) e <- c(e, rep(NA, n-length(e))) rdata <- data.frame(Var=d, Type=e) # column annotations and covariates p <- ncol(X) a <- sample(c('alpha', 'beta', 'gamma'), p, replace=TRUE) # define covariates: true groups and some numeric variable c <- rnorm(p) # gather them in a data.frame covariates <- data.frame(a, X$pData, c) ## ----figoptions, include=FALSE------------------------------------------- library(knitr) opts_chunk$set(fig.width=14, fig.height=7) ## ----heatmap_data-------------------------------------------------------- par(mfrow=c(1,2)) aheatmap(X, annCol=covariates, annRow=X$fData) aheatmap(X) ## ----model, cache=TRUE--------------------------------------------------- res <- nmf(X, 3, nrun=10) res ## ----coefmap_res, fig.keep='last'---------------------------------------- opar <- par(mfrow=c(1,2)) # coefmap from multiple run fit: includes a consensus track coefmap(res) # coefmap of a single run fit: no consensus track coefmap(minfit(res)) par(opar) ## ----coefmap_default, eval=FALSE----------------------------------------- # Rowv = NA # Colv = TRUE # scale = 'c1' # color = 'YlOrRd:50' # annCol = predict(object) + predict(object, 'consensus') ## ----coefmap_custom, fig.keep='last', tidy=FALSE------------------------- opar <- par(mfrow=c(1,2)) # removing all automatic annotation tracks coefmap(res, tracks=NA) # customized plot coefmap(res, Colv = 'euclidean' , main = "Metagene contributions in each sample", labCol = NULL , annRow = list(Metagene=':basis'), annCol = list(':basis', Class=a, Index=c) , annColors = list(Metagene='Set2') , info = TRUE) par(opar) ## ----basismap_res, fig.keep='last'--------------------------------------- opar <- par(mfrow=c(1,2)) # default plot basismap(res) # customized plot: only use row special annotation track. basismap(res, main="Metagenes", annRow=list(d, e), tracks=c(Metagene=':basis')) par(opar) ## ----basismap_default, eval=FALSE---------------------------------------- # Colv = NA # scale = 'r1' # color = 'YlOrRd:50' # annRow = predict(object, 'features') ## ----consensusmap_res, fig.keep='last'----------------------------------- opar <- par(mfrow=c(1,2)) # default plot consensusmap(res) # customized plot consensusmap(res, annCol=covariates, annColors=list(c='blue') , labCol='sample ', main='Cluster stability' , sub='Consensus matrix and all covariates') par(opar) ## ----cmap_default, eval=FALSE-------------------------------------------- # distfun = function(x) as.dist(1-x) # x being the consensus matrix # hclustfun = 'average' # Rowv = TRUE # Colv = "Rowv" # color = '-RdYlBu' ## ----estimate, cache=TRUE------------------------------------------------ res2_7 <- nmf(X, 2:7, nrun=10, .options='v') class(res2_7) ## ----consensusmap_estimate, fig.keep='last'------------------------------ consensusmap(res2_7) ## ----fit_methods, cache=TRUE--------------------------------------------- res_methods <- nmf(X, 3, list('lee', 'brunet', 'nsNMF'), nrun=10) class(res_methods) ## ----consensusmap_methods, fig.width=10, fig.height=7, fig.keep='last'---- consensusmap(res_methods) ## ----demo_hm, eval=FALSE------------------------------------------------- # demo('aheatmap') # # or # demo('heatmaps') ## ----sessionInfo, echo=FALSE, results='asis'----------------------------- toLatex(sessionInfo()) NMF/inst/doc/consensus.pdf0000644000176200001440000002751313711174456015150 0ustar liggesusers%PDF-1.5 % 1 0 obj << /Type /ObjStm /Length 490 /Filter /FlateDecode /N 9 /First 53 >> stream xQn1+2Y=B!"EԅLѿLlP5^T5R@e%` eS݈ L ͻ{`s2jg2",>s^yk޵Iafr <> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~endstream endobj 12 0 obj << /Length 8424 /Filter /FlateDecode >> stream x͎a}_E-h,-al@u/hdֱa_DEjH&3#dfUw >o~Scǥi\mn>>ӷG}__o}-~>~oC߆icޖn>~Xn_bg׽9umc98bm܋ {zMã]_}7rM?nνKח2~4v(<#z=ƺCޗ{Ǘ0$Y]ӧ_U_?'W7<_U_?SzY"UZ.T< /amix _gxˬ[Y(fڗn/+ek=ݫ*ok}Fo?^yu| Si]>ux)q:^ zņϾOԵᓗ^l[(Եq_ yeTlx\׆]oN]}vO>}D|Gx;RL~mx9=mP qoNa w)L=Lôcڎ.~ćӑLLJʉ+-< arnCxHʣ5<\CxP&+ذ~鲈W1<.}iol\B?ܭ߸~\aV:oކ'R$B\yHq.)>Ԉ⢄(!.J⢄(!.JOqqC\7 qqC\7 qO7qqC\7 qqC\7 qs qqC\7 qqC\&\”Sf\5EqQc\5EqQc\t#.z5EqQc\5EqQc\\5EqQc\5EqQc\Zp97E qQB\%E qQB\%ŧ!.n↸!.n↸䧛p󍸸!.n↸!.n↸ހ↸!.n↸!.n[ .~uqC^7 qCb7D |N&'G'G$'G4'GD'GT'Gd'Gt'Gs4ȓ ғ#ړ###### cA RcԘ 5&H RcԘ 5&H 3AL#92AL#92Ay AL#92AL#92ALP^-y0AL#92AL#96AjL1AjL1A>w&ȑ rd G&ȑ rd G&(O$3 rd G&ȑ rd G&ȑ ˠ /&ȑ rd G&ȑ rd wk|HC(CM5Ԙ 5&H RcԘ 5&H Rc|L#92AL#mk`OKxDZxQ#H{s€郀y0` #Ȁ92` #Ȁ9"`kox5Ⱦ9o#Ⱦ9o#戾 su%LSc05LSc05΀92` #Ȁ92`>,i{;py,À92` #Ȁ92` #0|yk0Ȃsd0G̑sd0 `G'RZ*V, `UKXjt+)VSXLb`Va4*)VSXLb0 `kLkb0 `+)VSXL j1`j ƀ1`j ƀ0G̑sd0G̱ 0mc<}0̑sd0G̑sd0&kksd0G̑sd0G̑[05LSc05LSc0;Ȁ92` #Ȁ96My,À92` #Ȁ92` #X^c-y 2` #Ȁ92` #Ȁ92`% ƀ1`j ƀ1`j ϝsd0G̑sd&<a0G̑sd0G̑sl,0G̑sd0G̑sd}̀1`j ƀ1`j ƀ0G̑sd0G̱ 0mc<}0̑sd0G̑sd0&kksd0G̑sd0G̑;Hƀ1`j ƀ1`j sg0G̑sd0&< 0eA<0G̑sd0G̑sdk0Ȃsd0G̑sd0Gl8/07 sC07 sC|XXXXXX--!ArDrDrDrDrDrDrDrls5 #######c#RZ*V, `UKXjt+)VSXLb`Va4*)VSXLb0 `kLkb0 `+)VSXL '8-05LSc05LSc| #Ȁ92` #6 X>Ȁ92` #Ȁ92` c`y5Ȁ92` #Ȁ92` #Ȁ[LSc05LSc05΀92` #Ȁ92`Myl`ˀ郀y0` #Ȁ92` #Ȁ96X `^ #Ȁ92` #Ȁ92`odx ֝f_ɾojƾojƾ7G͑}sd7GohǾ mX-O}sd7G͑}sd7GoX?Xo^#Ⱦ9o#Ⱦ9oo}MN75M}Sc75M}Sc7;Ⱦ9o#Ⱦ9^Ⱦ9o#Ⱦ9ocoy5Ⱦ9o#Ⱦ9o#:UȾojƾojƾ7G͑}sd7Gͱ7mcٷ<}7͑}sd7G͑}sd7&kk}sd7G͑}sd7Gͱͷ#NoU|K[jRZŷ*V-o:o+)V|SXM7mMc+yo?+)V|SXMb7Ŋomy55XMb7Ŋo+)V|S[ťƾojƾojϝ}sd7G͑}sd|&<}}a7G͑}sd7G͑}sl-߼7G͑}sd7G͑}sd|۷75M}Sc75M}Sc|#Ⱦ9o#6[>Ⱦ9o#Ⱦ9ocoy5Ⱦ9o#Ⱦ9o#۱tTM}Sc75M}Sc75ξ9o#Ⱦ9oMylo˾郾yo#Ⱦ9o#Ⱦ96Xo^#Ⱦ9o#Ⱦ9o-MM77 }sC77 }sCo9o9o9o9o9o9ǶǢoӇ|}}}}}}}}˱ŷ5[^[[[[[[[[M cw|Sc75M}Sc75M}o#Ⱦ9ocoDzoyo?#Ⱦ9o#Ⱦ9oM5 Ⱦ9o#Ⱦ9oco ƾojƾojsg7G͑}sd7&<7eA<7G͑}sd7G͑}sd|k7A͑}sd7G͑}sd7&ߦ>LM}Sc75M}Sc75ξ9o#Ⱦ9oMylo˾郾yo#Ⱦ9o#Ⱦ96Xo^#Ⱦ9o#Ⱦ9om[jRZŷ*V-ozٷ*+)V|SXMb7647þiT|SXMb7Ŋo+)5`7Ŋo+)V|SXMboM575M}Sc75M}Sc|#Ⱦ9o#6[>Ⱦ9o#Ⱦ9ocoy5Ⱦ9o#Ⱦ9o#۲tG75M}Sc75M}Sc7;Ⱦ9o#Ⱦ96My,þ9o#Ⱦ9o#[^c-y o#Ⱦ9o#Ⱦ962T|Sc75M}Sc75M}o#Ⱦ9ocoDzoyo?#Ⱦ9o#Ⱦ9oM5 Ⱦ9o#Ⱦ9oco 575M}Sc75M}Sc|#Ⱦ9o#6[>Ⱦ9o#Ⱦ9ocoy5Ⱦ9o#Ⱦ9o#戾~r_Ƈ!_Ҧ-Gzٺ2>?7?!ubxCCyx;clXaء>v\q?;Ns>nyi>vϧkz9}>!mb9k7ccJΉϧOS2`܍j*sF|U~9/jn-~q[N^}>m<|닱 9^ yxr|>Oϟiigwn=}>!y_C?SOmC?Ӻuyw<9ҭ硟shٟ&!kxW|gru7t>wC+ep>wC1TgTwCnVc6S7vp|o08(x;8LqvpVv0vpZCJw;8L~yW?޼ [9!! n.Ezv7K\^ʷ!5monuyWr;8̰uy;tf_a^Oa%ݿ ae/4Nvp`svpW כn WHvp`G)G8a?&]v/t5秺{p,n'x˼ 7kQ=5fl<ukQ=Gj;kiQ=Cx46m:nx8;m:8uۃzlqÅzUߨ~q㷕סנg57S5S09_^5+@9{fYͿYc[fS2MئW=4cv#mئ,m:6`q='-Nqئh:6Ut\ҦzlqWڎM_h:6? t\m:Dt\Pq==Oz905! 3W׿o6ߜbτ7[M^x%߼5w-׻kt׾#?_sّO[Es.\._wܧ-xM<ˠt>xg0_NC9xN?p38۴ Ƌ[V%ӝ_n~b}ps:_S੏P,}|YM '>缅ˮ-}&3xoSs^.3lzj>ʻ=@7].C9<~(@}Ky+c?_n o m;<z_2v$%t)3׍pQ~|@bO`O aLp\`_K+}ఔ^h= +mMwxi; W-4<a;ϟgo}xi<~4 ;c(mK<^z?qg~endstream endobj 13 0 obj << /Type /XRef /Length 38 /Filter /FlateDecode /DecodeParms << /Columns 4 /Predictor 12 >> /W [ 1 2 1 ] /Info 2 0 R /Root 3 0 R /Size 14 /ID [] >> stream xcb&F~ c$󃉁Cɐa?b endstream endobj startxref 11798 %%EOF NMF/inst/doc/heatmaps.Rnw0000644000176200001440000004465313620530505014721 0ustar liggesusers%\VignetteIndexEntry{NMF: generating heatmaps} %\VignetteDepends{utils,NMF,RColorBrewer,knitr,bibtex} %\VignetteKeyword{aplot} %\VignetteCompiler{knitr} %\VignetteEngine{knitr::knitr} \documentclass[a4paper]{article} %\usepackage[OT1]{fontenc} \usepackage[colorlinks]{hyperref} \usepackage{a4wide} \usepackage{xspace} \usepackage[all]{hypcap} % for linking to the top of the figures or tables % add preamble from pkgmaker <>= pkgmaker::latex_preamble() if(!requireNamespace("Biobase")) BiocManager::install("Biobase") @ \newcommand{\nmfpack}{\pkgname{NMF}} \newcommand{\MATLAB}{MATLAB\textsuperscript{\textregistered}\xspace} \newcommand{\refeqn}[1]{(\ref{#1})} % REFERENCES \usepackage[citestyle=authoryear-icomp , doi=true , url=true , maxnames=1 , maxbibnames=15 , backref=true , backend=bibtex]{biblatex} \AtEveryCitekey{\clearfield{url}} <>= pkgmaker::latex_bibliography('NMF') @ \newcommand{\citet}[1]{\textcite{#1}} \renewcommand{\cite}[1]{\parencite{#1}} \DefineBibliographyStrings{english}{% backrefpage = {see p.}, % for single page number backrefpages = {see pp.} % for multiple page numbers } % % boxed figures \usepackage{float} \floatstyle{boxed} \restylefloat{figure} \usepackage{array} \usepackage{tabularx} \usepackage{mathabx} \usepackage{url} \urlstyle{rm} % use cleveref for automatic reference label formatting \usepackage[capitalise, noabbrev]{cleveref} % define commands for notes \usepackage{todonotes} \newcommand{\nbnote}[1]{\ \bigskip\todo[inline, backgroundcolor=blue!20!white]{\scriptsize\textsf{\textbf{NB:} #1}}\ \\} % put table of contents on two columns \usepackage[toc]{multitoc} \setkeys{Gin}{width=0.95\textwidth} \begin{document} <>= #options(prompt=' ') #options(continue=' ') set.seed(123456) library(NMF) @ \title{Generating heatmaps for Nonnegative Matrix Factorization\\ \small Package \nmfpack\ - Version \Sexpr{utils::packageVersion('NMF')}} \author{Renaud Gaujoux} \maketitle \begin{abstract} This vignette describes how to produce different informative heatmaps from NMF objects, such as returned by the function \code{nmf} in the \citeCRANpkg{NMF}. The main drawing engine is implemented by the function \code{aheatmap}, which is a highly enhanced modification of the function \code{pheatmap} from the \CRANpkg{pheatmap}, and provides convenient and quick ways of producing high quality and customizable annotated heatmaps. Currently this function is part of the package \nmfpack, but may eventually compose a separate package on its own. \end{abstract} {\small \tableofcontents} \section{Preliminaries} \subsection{Quick reminder on NMF models} Given a nonnegative target matrix $X$ of dimension $n\times p$, NMF algorithms aim at finding a rank $k$ approximation of the form: $$ X \approx W H, $$ where $W$ and $H$ are nonnegative matrices of dimensions $n\times k$ and $k\times p$ respectively. The matrix $W$ is the basis matrix, whose columns are the basis components. The matrix $H$ is the mixture coefficient or weight matrix, whose columns contain the contribution of each basis component to the corresponding column of $X$. We call the rows of $H$ the basis profiles. \subsection{Heatmaps for NMF} Because NMF objects essentially wrap up a pair of matrices, heatmaps are convenient to visualise the results of NMF runs. The package \nmfpack provides several specialised heatmap functions, designed to produce heatmaps with sensible default configurations according to the data being drawn. Being all based on a common drawing engine, they share almost identical interfaces and capabilities. The following specialised functions are currently implemented: \begin{description} \item[\code{basismap}] draws heatmaps of the basis matrix \item[\code{coefmap}] draws heatmaps of the mixture coefficient matrix \item[\code{consensusmap}] draws heatmaps of the consensus matrix, for results of multiple NMF runs. \end{description} \subsection{Heatmap engine} All the above functions eventually call a common heatmap engine, with different default parameters, chosen to be relevant for the given underlying data. The engine is implemented by the function \code{aheatmap}. Its development started as modification of the function \code{pheatmap} from the \pkgname{pheatmap} package. The initial objective was to improve and increase its capabilities, as well as defining a simplified interface, more consistent with the R core function \code{heatmap}. We eventually aim at providing a general, flexible, powerful and easy to use engine for drawing annotated heatmaps. The function \code{aheatmap} has many advantages compared to other heatmap functions such as \code{heatmap}, \code{gplots::heatmap2}, \code{heatmap.plus::heatmap.plus} , or even \code{pheatmap}: \begin{itemize} \item Annotations: unlimited number of annotation tracks can be added to \emph{both} columns and rows, with automated colouring for categorical and numeric variables. \item Compatibility with both base and grid graphics: the function can be directly called in drawing contexts such as grid, mfrow or layout. This is a feature many R users were looking for, and that was strictly impossible with base heatmaps. \item Legends: default automatic legend and colouring; \item Customisation: clustering methods, annotations, colours and legend can all be customised, even separately for rows and columns; \item Convenient interface: many arguments provide multiple ways of specifying their value(s), which speeds up developping/writing and reduce the amount of code required to generate customised plots (e.g. see \cref{sec:colour_spec}). \item Aesthetics: the heatmaps look globally cleaner, the image and text components are by default well proportioned relatively to each other, and all fit within the graphic device. \end{itemize} \subsection{Data and model} \label{sec:data} For the purpose of illustrating the use of each heatmap function, we generate a random target matrix, as well as some annotations or covariates: <>= # random data that follow an 3-rank NMF model (with quite some noise: sd=2) X <- syntheticNMF(100, 3, 20, noise=2) # row annotations and covariates n <- nrow(X) d <- rnorm(n) e <- unlist(mapply(rep, c('X', 'Y', 'Z'), 10)) e <- c(e, rep(NA, n-length(e))) rdata <- data.frame(Var=d, Type=e) # column annotations and covariates p <- ncol(X) a <- sample(c('alpha', 'beta', 'gamma'), p, replace=TRUE) # define covariates: true groups and some numeric variable c <- rnorm(p) # gather them in a data.frame covariates <- data.frame(a, X$pData, c) @ %\SweaveOpts{fig.width=14,fig.height=7} <>= library(knitr) opts_chunk$set(fig.width=14, fig.height=7) @ Note that in the code above, the object \code{X} returned by \code{syntheticNMF} \emph{really is} a matrix object, but wrapped through the function \code{ExposedAttribute} object, which exposes its attributes via a more friendly and access controlled interface \code{\$}. Of particular interests are attributes \code{'pData'} and \code{'fData'}, which are lists that contain a factor named \code{'Group'} that indicates the true underlying clusters. These are respectively defined as each sample's most contrbuting basis component and the basis component to which each feature contributes the most. They are useful to annotate heatmaps and assess the ability of NMF methods to recover the true clusters. As an example, one can conveniently visualize the target matrix as a heatmap, with or without the relevant sample and feature annotations, using simple calls to the \code{aheatmap} function: <>= par(mfrow=c(1,2)) aheatmap(X, annCol=covariates, annRow=X$fData) aheatmap(X) @ Then, we fit an NMF model using multiple runs, that will be used throughtout this vignette to illustrate the use of NMF heatmaps: <>= res <- nmf(X, 3, nrun=10) res @ \nbnote{To keep the vignette simple, we always use the default NMF method (i.e. \code{'brunet'}), but all steps could be performed using a different method, or multiple methods in order to compare their perfromances.} \section{Mixture Coefficient matrix: \texttt{coefmap}} The coefficient matrix of the result can be plotted using the function \code{coefmap}. The default behaviour for multiple NMF runs is to add two annotation tracks that show the clusters obtained by the best fit and the hierarchical clustering of the consensus matrix\footnote{The hierarchical clustering is computed using the consensus matrix itself as a similarity measure, and average linkage. See \code{?consensushc}.}. In the legend, these tracks are named \emph{basis} and \emph{consensus} respectively. For single NMF run or NMF model objects, no consensus data are available, and only the clusters from the fit are displayed. <>= opar <- par(mfrow=c(1,2)) # coefmap from multiple run fit: includes a consensus track coefmap(res) # coefmap of a single run fit: no consensus track coefmap(minfit(res)) par(opar) @ \nbnote{Note how both heatmaps were drawn on the same plot, simply using the standard call to \code{par(mfrow=c(1,2)}. This is impossible to achieve with the R core function \code{heatmap}. See \cref{sec:aheatmap} for more details about compatibility with base and grid graphics.} By default: \begin{itemize} \item the rows are not ordered; \item the columns use the default ordering of \code{aheatmap}, but may easily be ordered according to the clusters defined by the dominant basis component for each column with \code{Colv="basis"}, or according to those implied by the consensus matrix, i.e. as in \code{consensusmap}, with \code{Colv="consensus"}; \item each column is scaled to sum up to one; \item the color palette used is \code{'YlOrRd'} from the \citeCRANpkg{RColorBrewer}, with 50 breaks. \end{itemize} In term of arguments passed to the heatmap engine \code{aheatmap}, these default settings translate as: <>= Rowv = NA Colv = TRUE scale = 'c1' color = 'YlOrRd:50' annCol = predict(object) + predict(object, 'consensus') @ If the ordering does not come from a hierarchical clustering (e.g., if \code{Colv='basis'}), then no dendrogram is displayed. The default behaviour of \code{aheatmap} can be obtained by setting arguments \code{Rowv=TRUE, Colv=TRUE, scale='none'}. \medskip The automatic annotation tracks can be hidden all together by setting argument \code{tracks=NA}, displayed separately by passing only one of the given names (e.g. \code{tracks=':basis'} or \code{tracks='basis:'} for the row or column respectively), and their legend names may be changed by specifying e.g. \code{tracks=c(Metagene=':basis', 'consensus')}. Beside this, they are handled by the heatmap engine function \code{aheatmap} and can be customised as any other annotation tracks -- that can be added via the same argument \code{annCol} (see \cref{sec:aheatmap} or \code{?aheatmap} for more details). <>= opar <- par(mfrow=c(1,2)) # removing all automatic annotation tracks coefmap(res, tracks=NA) # customized plot coefmap(res, Colv = 'euclidean' , main = "Metagene contributions in each sample", labCol = NULL , annRow = list(Metagene=':basis'), annCol = list(':basis', Class=a, Index=c) , annColors = list(Metagene='Set2') , info = TRUE) par(opar) @ \nbnote{The feature that allows to display some information about the fit at the bottom of the plot via argument \code{info=TRUE} is still experimental. It is helpful mostly when developing algorithms or doing an analysis, but would seldom be used in publications.} \section{Basis matrix: \texttt{basismap}} The basis matrix can be plotted using the function \code{basismap}. The default behaviour is to add an annotation track that shows for each row the dominant basis component. That is, for each row, the index of the basis component with the highest loading. This track can be disabled by setting \code{tracks=NA}, and extra row annotations can be added using the same argument \code{annRow}. <>= opar <- par(mfrow=c(1,2)) # default plot basismap(res) # customized plot: only use row special annotation track. basismap(res, main="Metagenes", annRow=list(d, e), tracks=c(Metagene=':basis')) par(opar) @ By default: \begin{itemize} \item the columns are not ordered; \item the rows are ordered by hierarchical clustering using default distance and linkage methods (\code{'eculidean'} and \code{'complete'}); \item each row is scaled to sum up to one; \item the color palette used is \code{'YlOrRd'} from the \citeCRANpkg{RColorBrewer}, with 50 breaks. \end{itemize} In term of arguments passed to the heatmap engine \code{aheatmap}, these default settings translate as: <>= Colv = NA scale = 'r1' color = 'YlOrRd:50' annRow = predict(object, 'features') @ \section{Consensus matrix: \texttt{consensusmap}} When doing clustering with NMF, a common way of assessing the stability of the clusters obtained for a given rank is to consider the consensus matrix computed over multiple independent NMF runs, which is the average of the connectivity matrices of each separate run \footnote{Hence, stability here means robustness with regards to the initial starting point, and shall not be interpreted as in e.g. cross-validation/bootstrap analysis. However, one can argue that having very consistent clusters across runs somehow supports for a certain regularity or the presence of an underlying pattern in the data.}. This procedure is usually repeated over a certain range of factorization ranks, and the results are compared to identify which rank gives the best clusters, possibly in the light of some extra knowledge one could have about the samples (e.g. covariates). The functions \code{nmf} and \code{consensusmap} make it easy to implement this whole process. \nbnote{The consensus plots can also be generated for fits obtained from single NMF runs, in which case the consensus matrix simply reduces to a single connectivity matrix. This is a binary matrix (i.e. entries are either 0 or 1), that will always produce a bi-colour heatmap, and by default clear blocks for each cluster.} \subsection{Single fit} In section \cref{sec:data}, the NMF fit \code{res} was computed with argument \code{nrun=10}, and therefore contains the best fit over 10 runs, as well as the consensus matrix computed over all the runs \footnote{If one were interested in keeping the fits from all the runs, the function \code{nmf} should have been called with argument \code{.options='k'}. See section \emph{Options} in \code{?nmf}. The downstream hanlding of the result would remain identical.}. This can be ploted using the function \code{consensusmap}, which allows for the same kind of customization as the other NMF heatmap functions: <>= opar <- par(mfrow=c(1,2)) # default plot consensusmap(res) # customized plot consensusmap(res, annCol=covariates, annColors=list(c='blue') , labCol='sample ', main='Cluster stability' , sub='Consensus matrix and all covariates') par(opar) @ By default: \begin{itemize} \item the rows and columns of the consensus heatmap are symmetrically ordered by hierarchical clustering using the consensus matrix as a similarity measure and average linkage, and the associated dendrogram is displayed; \item the color palette used is the reverse of \code{'RdYlBu'} from the \citeCRANpkg{RColorBrewer}. \end{itemize} In term of arguments passed to the heatmap engine \code{aheatmap}, these default settings translate as: <>= distfun = function(x) as.dist(1-x) # x being the consensus matrix hclustfun = 'average' Rowv = TRUE Colv = "Rowv" color = '-RdYlBu' @ \subsection{Single method over a range of ranks} The function \code{nmf} accepts a range of value for the rank (argument \code{rank}), making it fit NMF models for each value in the given range \footnote{Before version 0.6, this feature was provided by the function \code{nmfEstimateRank}. From version 0.6, the function \code{nmf} accepts ranges of ranks, and internally calls the function \code{nmfEstimateRank} -- that remains exported and can still be called directly. See documentation \code{?nmfEstimateRank} for more details on the returned value.}: <>= res2_7 <- nmf(X, 2:7, nrun=10, .options='v') class(res2_7) @ The result \code{res2\_7} is an S3 object of class \code{'NMF.rank'}, that contains -- amongst other data -- a list of the best fits obtained for each value of the rank in range $\ldbrack 2, 7\rdbrack]$. The method of \code{consensusmap} defined for class \code{'NMF.rank'}, which plots all the consensus matrices on the same plot: <>= consensusmap(res2_7) @ \nbnote{ The main title of each consensus heatmap can be customized by passing to argument \code{main} a character vector or a list whose elements specify each title. All other arguments are used in each internal call to consensusmap, and will therefore affect all the plots simultaneously. The layout can be specified via argument \code{layout} as a numeric vector giving the number of rows and columns in a \code{mfrow}-like way, or as a matrix that will be passed to R core function \code{layout}. See \code{?consensusmap} for more details and example code. } \subsection{Single rank over a range of methods} If one is interested in comparing methods, for a given factorization rank, then on can fit an NMF model for each method by providing the function \code{nmf} with a \code{list} in argument \code{method}: <>= res_methods <- nmf(X, 3, list('lee', 'brunet', 'nsNMF'), nrun=10) class(res_methods) @ The result \code{res\_methods} is an S4 object of class \code{NMFList}, which is essentially a named list, that contains each fits and the CPU time required by the whole computation. As previously, the sequence of consensus matrices is plotted with \code{consensusmap}: <>= consensusmap(res_methods) @ \section{Generic heatmap engine: \texttt{aheatmap}} \label{sec:aheatmap} This section still needs to be written, but many examples of annotated heatmaps can be found in the demos \code{'aheatmap'} and \code{'heatmaps'}: <>= demo('aheatmap') # or demo('heatmaps') @ These demos and the plots they generate can also be browsed online at \url{http://nmf.r-forge.r-project.org/_DEMOS.html}. \section{Session Info} <>= toLatex(sessionInfo()) @ \printbibliography[heading=bibintoc] \end{document} NMF/inst/doc/heatmaps.pdf0000644000176200001440000143660513710766760014745 0ustar liggesusers%PDF-1.5 % 79 0 obj << /Length 2812 /Filter /FlateDecode >> stream xYK۸ϯ0x!vb;֮=*8GZ$eFص\D^_G/n_( "]`cEF0)ep >*oҮ2iW^~*¼+9mڭ"l/4*auuS80ƱAh"-Fi%qJ*Jtd܊߾Z .#a*M 9qB# ֯d*I4XAdUzRuz>~e1MJx/I(t[sJE$5iEvv "L'10i5Q`IixVy۬)n#]VnV"옉`@_:TEEWRB24C'ũ8 j[T6!bNJKd`Wz'X`aACxA.O @Oxx+"W-ڥUֳYzW+֤ Tqr..a)*-xbi{O 0P; <iћ@t͌x!cƭ}$Cl87 }{i Ƚ]Dn=k)=O T]6P0SPy1UR3ѷmD ^]% ^ѣp m$k&eQM'>8ɋdmD 5akҔ,EW- BlƲ,}9?'gMN! Z Z^*#O{ĵzgX^bC~ڋK @|?&IeiμmI%g"}JqNUhNuK uaRO63vpVzTq*!eu~WR ?fX' >7G/En`p͐oq# qbeW<@c 1 ͫ(bz1 V%O kaxPxKz,ںY\S<3trA2v) Ԙy?czӁ&(>P 73}cXϝܩA4T3i7(0" dWFw"fcpbxlxS;^y!tk!3m Gյ22zpǐjZJW@N"_8S×T !%ׯL`AG2{(ab#;sD_D)^ ZC2Dfr(a4>y\S&`E&?UŠ GWpm! V=AzZ ;Ll?W&yZWŶnpCU=dWvZk:ϸp|sV#ah@GЅelfp$JQj-xV.}"#AZ6]mtA#P/yԒ{wq8`HqHgd<ޱ!]ܴIIW6Nb/ͩY̑b{ܣ8P9~?'14 }8v `\*[Ţ8i|zXd &Tbqpmht(;I@X$ͩ`;"q:3h Z4H~}H3 . j*BN7&{5UԩՉq 5|o%^3,B0r=J\1ȴ;J nn;.VXkW.Y/}MsR3yKm/}EJ"w~_O0$+о|8 ѝ_*=?9?7A0%xW~FA8B)[(f~ Vg-޼XKlux!dznxu9q &Πfz7Ⱥ0f{8:Eݵ1fw-fKgI^]Zfpg V/Q# endstream endobj 101 0 obj << /Length 2778 /Filter /FlateDecode >> stream xn_A /`qx,&AvN2&m%3k=ecꮻIrܝ}+s؏VGUal_k-}iٻ֒0vR? ZP(S_, o`9]sM/~ Iu>9wVb %vP ?j(JǏ<Ʀw>yNE>O'M3>nϏ?qIٲYd, UQb[\(Zd-HXM>@2w%ϱFQU< @[mr0]9pQ.Rmtr% %%RHD(9H07- k-?JT 3pdHkn4ܺnA76T}٬_A8eYºFw>A5ܗ9π X3K2'2BC%Iy HZ!YU$I0Bi,^Q\hYQtGNN_fܚ_6A:Yj&EdpaL'92"l.geSӂ^GJAZYwYb ES7t\0N\)I`{7ahiR7lX{aUdIj %T+E~Ъ8ؒq8n:uWCqU-5g: !vsAb'™Hu$ObؚпgF^M=UY(#cX'0SL?({BOr򾴪(|I9O [DNVcdvCnȟ"3"{ (QN8!uʜBk 227I+U)oxT,#NK{e7"([XH "phʩ%2GHbOAm1C(DTIV>/:~~JG/qd|,x\ E !P^Q =gR#Xu?qfzSZJ:8Ik?#VhpC09z ]P0p bmY5rW{Y{*+\r]aU>199"ƾ9U=YQM%"12/*!! pSnQh$v\ 5IИ4d45[hZK{,RUzl? &lzo)H9M~%ClS聶5F絛tP2(>aӋ(.l9AoҨF#oiA,Ǝ[\NA@4t6k8[TA4554kKhuO΍EFX(qbAvf{ix;ׁD6ɦCNçDND : ʨul!e7fu^sy% 9 o (YƦ@)AHk/n^(N/ qbH]U`da*a\c${7Ťb~\f_(ֱ@\!q}CPߐ^ tEգwe"|:LnToT p Ӻq|1ݔ90Z`KI p\SdfPCx/B{'>Ϯ,EZNf^c.CguadYZ[/ /~ vwE~9ȵ<+M,p C?u$u"?Vgya^F!c H`j3fK"8=P4i !lqvU[U1gUMWب[44j{?PɚqGo}(llNۿ^,X˧WMJDfF1~&*|I5PIv]u JΏ!͊Ë/'O!C}Ÿ= ]*z?Nپ7Q<2xS'ه.Iqߡծd(ƛݣ(8=B囵_=Q.Y7qk\O|{2v[)\)D.FLz-!f-Z}dP(U"yuu'j;d+Gyʢ?}Ԩ֪g{ έ༬ff25-?\ ^w endstream endobj 110 0 obj << /Length 2333 /Filter /FlateDecode >> stream x]oܸݿB@2Hbp-p]w98C>Ȼ\Zv_~$] lKp8_gHs&">~57+F1hGa~: DƅROd4[!\%&hԸhqv(A 6w7WgjY`"<22!k&{٥)/m?}#!}KSIjL\5<Ӭ™Z+ͶLl4e< 3&6V**z]Mr'̞bmyoמɬ ̏Oa"ݩ^ RϞ\N<Þx ŬR4IV sWQ A|Lg~? v&6D@Pj f4\&r̵0ZjZA+גwâ-(]s;i aqWҝUvi7 krz-eV.hx;$yGjd܊H C|u=%F42A#xX!<}{ӺŦ . x5ZB/%,+4>EYth{ V[ͪ%(] TSN-KC7(z{C1 9i>{?A"L|{[,Fy͍o4kϝ1[*!tPi3Xqh!J>&Jn&O5QA6-< i~89/˽AADBKnt<E4sbtuX\TX\ ]kHLNa~i<$^-hxM}wjE|(1xYE$J\ZA<}?e~jxqOSU׺<<=V!q|m/vTJ Ni$ sOgL Nя5$ZÑY2iOG}Ʋs, Y F?CQ2rDu}6w%uy3v!M=CgC/b_1(p:s=vϕ07AnIh MPBn.Ciݸ2S}e&R%BIn3r!9S`?tN?]%dښ8Vo<<'ȗ>2 J1E<a H(pe;܆]6lI(/%Fǿy< tW? V}/K8^qsaDT MSzHHLaL&@iЀ 4We;3 R]=! h] `ІeЖx_!=wJ>6% 9FڤbM)7)ƞ;,^$sI 9^>g `ad`5}4l5f'̨t7B PI׸;;V ;p#\)"a. h^:䶓PA^э8CO{oqxP'H["uh X endstream endobj 107 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (/tmp/Rtmpei80N8/Rbuild1eea6baa0860/NMF/vignettes/figure/heatmap_data-1.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 116 0 R /BBox [0 0 1008 504] /Resources << /ProcSet [ /PDF /Text ] /Font << /F2 117 0 R/F3 118 0 R>> /ExtGState << >>/ColorSpace << /sRGB 119 0 R >>>> /Length 34711 /Filter /FlateDecode >> stream xK>r7ߟ?yĀufl'J௟&Y^) eq~?__}~>ϯ7J=___^oS6ߟkMg˯mLwQƼ?ܗ1t9b\~Ȑp??0_j)]KQ ױ[!ML~o~[},K_1,?619;cHstxN?<192q?cxmϱ~?:1x{S+ʾK1q|VZCM;GPumRwUAgjzgi5fhk+?ֿAv4ey[a2o(j/z۟eps~}\5Yڗ/uwX|?/=}2pn+ %_"(eO/먽^A;kWP۟:h1҉m=>eyWIKo{A{L?YVjuYK2;;k ˬvey[u~ZG;k۵juM^Q;]7[Bqqo—w7kwO(ֿO렽Y~\y2-i}̭&|:{_{o{şQ-sZvm+rknHt xg^PW2c/.dL:P]5_{M(32"Q_Z L}(.l&{mpo{_!56n%25}x |֏^i>2\;%au;z{_PHSoCBCˤ>;.5\[{p?RЯ>nۇg](]'?5{MhgCwcٙ斣&R+/_vp߿YGh}oP~{ᘽ&}or;{Kpm|֟5ֿ~_0}m^j^_=9kBmnzc]z{KߌАW^z^n>GD}B!mLw! 5?+0p>ts|;%嚥*x]?;Zq'>W5?w5` C?}ׄ[L-:{Kpm?ǐPNΥj>kTP2m4kB>ש/e}ׄݐpn|xWTXqo<6?o:39>$ ! H5ֻ}x!XcPG}Yz%T5KC^5j*[/*~:P72_{{KkkPVymPۧm/ C?oCOG7H;k~͐p><>5C­ڟwYPۧk8ه^ߚ^oC-^<{pwcꎖ{5Ohp'4=ۗ>> kCB?ۇ_?!}B߮y&cs'@/z[]YǗ}B(H}Hܧo?yׄ[wB{f?X~Miz 5$ڟ7wBwv epVL?M`nǧ}½?[p> 0>+Vھ]_m3CB]>xu>>Ko 1p߿YC]~;C­rM|KE><>WkאPNN?qt;OGPW_y-` 5}_Y/<-|oo _Z;$eKCByRo9}|'ڟϷC[ߐ '}ϳ 㽽OOׄ]e׎ ~~.>?wH6؇?'pCb}nHӱ@/>>$iWߎp_Q~>:$ğϺa}qH;]׆,={_]>Xnm+to*^\?1 ;'ǧ}}:_nۇk|{{KWq{>[Bko _||'΀/C`}pHn*;N ]C[Bmp/X9bp|=z{Kh8{o ˟;${ C­w~tfz{KOm }'Vp>t{wBzn;8O{{p;ž>oϨ[­_[}B}?yjonOhoeQ_܁q {{_]>w}C­}MX_\?>ֻ9w*"$h=_r/Xg)Ooe,􊽼oqW_2c86R,Vko-}~?o-[-W+}[Eq֍$t sȽ_~׾Vݛ][voŶ/U\K;Q1lv[Q5iϕw`k8zk)#4{OJg(}ަyGsFy=c)ysxKΩt4s'ZLk{OW^e&[,ƲZ_:/ƿе=Z}}':nʫ} yu*wyfl7fV?=4= 94 X}1}5__p'MSȖiz mBJ}-!s LRWiNV|.=(ԛGS~ v-G^z OVUk,uZ* W'ՉA GØWÝʮ_x8d埋\vceVReCͺ{Yƶ+{a< v־fOr]n{YU~׺׺-qcٜ)eCO_ե6[KTW ث#oS| o'2K==mFI'S$>y$;{Êi tGY:~ E*Vf6èh]km-X޷nɼeîԺch_#l>(JɨO)V k]FRU(:U?[03JAhc/sLKyz1f\Pk*9}am.eٜi3o}0 m>=OB6FNW(A H)azJSM!YǛ=5bzbV5b.J}l'Sqn/5uk_Xr :P}MY(h6/8DP FJ4&T#uI " H-G•W{&Q$' fF:-Q}^O6*0"l  "f$5"fT\DwZߨ"AanH(Bia&P.^kCAKVR0d%-vJ&Q%-l-U8ovrZ9 kL8 99ǁrǑ8lqV\!'a 5yENR#=( F0R>dFE֥<)IK IPp8 t2 P L!A($CIH ~΃Dv羼Z]ɇ\~ABЕ?(PjM(K + SI"0(Tps]?I>_V8, (DHId2S%J@u`l 6^$iOU(2BMlb `MA$l:v$MgD&xSitx[!Iq BNkJ bNlY$5\(L"cϮ*gRy[zb}+4=1(.N`ɭ)ʡ[U"I8@RtO8X3$*\DI@0r: /KѳU x޸"& ߛJvc-)Fau,F&6 Ml5 h8krJ FpcO9&XZb5-1e՜%R _-Nq% dpQ 3CKD*I'&@4JYHK)Mɭ-$Qߕp)KA-R^(Q)GH]#St3>H)o2E@a*Q)$i tqSIKA,LܕDRQd5&%bMvJT~e2ո^d+XB(2Eh2E;e(5 U0 2EM[D)BD*2=$LWd Fʠ`)M^+$I S:j>/۠IlFDo$SV) E&4*C=kmߏ4bwH-:&2 g[nˬb6wJQ'L9CFP=W+X|/|oXM N*\ AZHb]"(E94bX=PQ+ܡ;+2PkbA)Y i))<"|r e[rVV*H^E*W) 7~Qt U*2<9רsl4bEבH;M7L{MEQ$^B#=(5>>0RxQ ^A ]F VŶd[Q- 4ɶSn҉*xSlir+Xn1 S /q]9|LQjľ<(2EoI߬IS&Qd&d B!SM"Ȕ)2#gP$*L"<4bP]P N7$e&(&QcMxUP1ūS伣*>S{\CyLJɔ[Yʬ @$ k ӨvA#hSI<ɡI&hm+pVvM+Fp%XWy+@r%AJ]s%f$A";\#Ri AkK$vAù[žM64;C#% D#()=>SћTHߩPL B(^|>Sp)X92+gPn%A#hS4>Su2ɭ; VA($Q4y(D4 QfEXdZ(X4g1]Ӥ|Q>S\I9$Wƹ " A`MtPMi^CQP!:l}&T:R$I814v&&nj*>#pM<(3BqDP! ͛$ELB)176jIo0ham94o#ӈo ".cL%o#~[ۙymd\>mDצFt$mŊ{XQ1Ko#s۬5\ЫF$ A̗6>R2LA*4 (D4 iRJPVJ`V(&ÊlY[)UX#ih4c"/4Kdb. "҃͡OLOks4h20` (ՂR+7nVEdh(N+4fVi^rƭȰY(%Io(zC&z䎷Kz{MMoX4 h ff:IyA =W5+C#" Nz +U0 !SY?$QiAz*(C&Q?UR?&Q1.&Q f1rIT̤6cFi>}(I4֖DLbMf( DIFDxu(DIW ~J$[+& (D"DD瘦vJt%4+٧XxKWb Fq%kTgGk%ˣn+ѴiRIPVA.& %H o#X9S!JAs g 0_#-dL#+$UevAvi+ T+DnQM# hA Aĸ-4iШ:A ҽJA#<*4ƒ(oAY4X4X) YAGMFF1 UΊMs?範 MXܔ5FFǧɭ+ 3Bئ\#P4% f ]EGǦI+ X] 6I$*MX]s5`Etc&Q DLs(T$+&IA3`EtJ$+L"|D$)-6(76Hh21'UW2`E*_!`=ḣ4+QR Jt>g+lA#}Fq%8Ae(D#t@#RT7Ε`U) ttl64mFǷЈ(FAcx ZɠtA2( 04XWLR4j~N6eh2FѦན h24"<Y9Ar+gE "J0ĶF[LLUEf^|9|CXrmBt;p$ $Xgb[vb[CéYUP!JT(6} =B.P0 T( ߯Lb^`d&I1pcFtI-&Qg۳yȝ^7b:+1 a?$vz)Ӂ3ʝ^)N/zVi* eM&ҏk81+4nDƭ5 h{541{b@^hM5)P"b/]Mh8{1+g/ny]AAY AY(1a1]q %29{q+,*:3qu)lsTL9Q~I]-?hecheD4jkO+x$3Dhaq\#mxaD1tdQȽm $W$ L"-dM76mFmA&Im3 '=f䤧~5 mAh"Q'ӳh4 AEqT6h8mEwNP!XS F)CTh2L +xd̹bIL '=nU? @CIDuTL'hrl(!"#sR"V^(=H)^~:@9 *^SQ;Q-N2Εw %=nLWt'(&s*kӡ4Ĺ( :FxETVJJ2O<ڴY FHh2L 2CB#|z#&~|OVmڬ#ȃ&q4㧜G4#ǬPM`-&ꖊ6 M_Xy!X7kNkx3!VMMP#7f@"8MP?i_IaM&$D&pHOTqQs7&Fm(יhC&QHIUDF|\#MP5"BޡOGRNk&2kTdjB̠rMӸF"kGdt\C9 S\C[GNT?,\U 4n i,I 7ns]C9 \l{:5ˆ[] Iz(dHkԩ`d ؞h25I["s+vIĠRVtO1`$[^&sk(qa^ >#Ih24AC+#M-h ۅD|bP#%S;aZ,5Ф^~/f~/RrVHj $'$k9ar(r8iRunq$9tШSȾz >{]z?ERѐ]C *K0.%BaDD i9oh817rzcV+p{Uf=X9*`ZDLhzs L"Nm k{WlԾ'P#L"ah$&dHDVD푢Scui-W$J(vCR!3 cFB' &{jN`T͕Ш4UIE-FR!6ڂ&yAN ~dV4C(C}WQmwMFnqi oMGGCM95ۡQA!HDwz&5 BAO 𧁕IFU$*6&CzL=ѕhLhԾ'65?W-l(1 Tcר׃2skA i&Fk)X+4)V`Et-4!k+odEH"ʗL#t`U1MF鰸ФvNAwtۘށFNU+UY\ɬ+ʹ'#"'FЦTSA#}Шt&yUԶ =(^F𪠿aι(ːsi2A\$<-I$W FJ(|}Q Q$:K6IfGVs2AS&Q;I ~KjGV(%:(6Uh# VEFakd,; alF+1 SF+Q|ӤvoA#xUfI^]G*:F*:J4U(4M&k hxӤx4W*} F1+gPreŠ@2WKϤy&A#EuKFEуI6RR,R+FL#^ףGW H44 X>Tʭ|Y[dtF`4eA# SԏsLhԙI/=HEv:K9^U<&ū`M|rb I_&XI_3_lhQ]#(VkIEy)4b5tPJ' 4b?+H@XAd SqR^VAh,`Jۿo?UFK?k.c5@nT<_ןOXz=2BvK\ګ՜N{$WuvJh_wk.r~4-x:1M{=O5!Ã> } mܮkx kgwk8f]glڷ3^e|N?d6??[XD맭H)rF1Rn?>F5 >@zM+vՍvvW&WwW?U]uK_[9juTGk:(PeN4Et-K&LjgYP oevѽkK\_ET2t-k3u5eM} y n˪%"Tкs +5=|Mb~( eѧtM?;OCVf6Prn7LuqMOn> zkjG4]ukku4]L?k:8-}2o͚~5|4]wKGt?W[g;*k̭9d`i˪sGYo2tksv6'͟:!9}i.KW~GsYnm7>mN^u)dy>Xm겯\ֶ[3;=-kݓ ҵ,կ7fv#]LZsQY o-Β~#6}ݲ4nͶt~9ӯ1\uq~\}FVkqt-ΞWeZؼv+|mtY&6R.mΖe]^F`5ٮ{htiQ[ J66Ko]tcN^_kt R]7]`쇴|cu iΙWذ.^ i˺eW9}BqI?qNI Ʊ{, 59QH`t-V`Ml)Y f)X\p2۴f1ze}?j!W2YK3t_YHl-߳LB*v.%Wb&Q;3N5(sT͠QUۗS~KQuk>e'Lw+fYA" 4,e!=W7Ө}>Q,/ Mn@nY7t$5n-Oۙ 4n{bnWG5_iTzVuQ̿\ru3V{Mn~ Z7Ө+|nhh2u+Ng-A#ڼm{ei䷰ߞN Tֳ ru3jf>8E4ەkߠio{7[&ӾO-4?Эl 4_>P7dڷnQu[ܿwMn8-S7hdZ?)r Q3֝Iho3,U7o7mM41Y S)<Su3[/mu{LK}/[Xjj_&v~} tھ^Xz?NXZVMug,7v*^D}_v&ne㚹B mkJmwkJr*l_-įݫmڞSakn{H:<බ3s̽h>Na8̼mf时mfm&/ӶG3so3q ˙cwmǦ՝_c23o/3o{/3o;/So#m%mw%]_PҾ{N JwNf̽h;yimgc}!3l׷kk}| 'fv'.LD~Cam{ 303ڮC~0sBQZC5ma״k݃m`=m'`fffmo`_Dv >%ڰ^ʭgw+`IWzZW_nz״ /jjr /]Vh"ؠ"G+ -?aIy/_篿ן$nC|5/ZnK8E+tEƿA_ʿp-ZMD֣,ꟺ@L0_=1(-O-lm$W>:I'9'"!`kx:}b6,D'Yη¦YxNr 1ߒa%>Ow'`䒩Lޒbni>d[;I sppIee MuJ$/BLN2W4|sU=PS$y/~-H=~ZɷPWO买EN8'–]r5K;{<%yH'9si7*e׾CNOQ<}<ɗ<?Ç/=H>%yv{ɱ?%'y.{W-Dx-IV\Z0 ^'9˿8Ex$u-iE ~=~I6Dg˫b'y.m8њWu0-}JV\\@2?%y:9, Z^Ӻy.٥+/Bi>K;ɞK "Z+ϹLш -3t<6G ?]K$_hG_y.e-9NRZ;,乬hG_yIv\CN'9In}\s-QޒNfd%υyu5Pd+ oIk[j_daϥ}u8?KX>%'y;&{ӏArd~JV\\4'G_Y0-y[lvQh[R_~hwJ\\Ԯ|{d'eX5ɗ;%gY"-n카rSr\\\r3Ar1G_ֽߒca+ϊI執F2: !^d%ecVo{ҋIm.xi{߫#XXEۣ$ {.u\цXc*U/To{-}^y̷U>K2.G_yI\է>R%g+ %ejDLր܏|?`G_]H;w'so]CrָzR7%'BN,|W+cAҾ>b_\>W~fNէmL>Kէ:]?(KoI}m>KIj)yZnxX{L&%qKJ;,kjLuͷ2{kjK`StP9Z鷆t\C5;kZ wVh!!Z]s0/ ;5g ֐C9?50[CBf*@hH\,%a]נoM]((ѐ@ሆ@Mwu\Í5$Hktax]hh1G|![]󭁷4KqdKᚹ 蚵֐#Q]SDSrQݾ5ל5[CfvTykH`״`o 95k ֐ K#5mwY Q̟iAmS 9R5KLszwZyk֜սDCvQå5 Q| iG@K3 9n5sִ'svԕ֎~TE44ͣ^zkK9QoM”?.%~GyrhV;}w t.Jq=Xz^ If")pLkjX欁f8*YAtTCpKM8(vSp]糵,f8b5{X4 ҷw}kڱbJ3Mk|ƣR Go@a#4;L@:>k>A뽷GuV~@gtiJX쨋@Rs ~s;a JSgf}\s֓I{`xՌsh8*M'>,{4J(f MSGˣ*.= GG4)Mm}g #5u,<ƣ{]:[}w*k5߮ˣjQ4`7v[wK[~\}Vv^^GUH}G_Lpmt޶j7[xSLzܻ>xԃ_ydե1ɔgS2]sE2[ߟu[Թ\i>^7ͭ\(iJW-L7N[=N4lw,Tf'sӇ#yF|_gϋJ 'lx[Ᶎ*@?~~?֮oۯ^}r￶7拿˯mLW\Yq8]cJa]JrԽUƔ.G]ees]kk:t9悑R4R_eHsluqV!q1駮u5SA׌y2;GqsrǶ2rs )w2zB_Ɛrߘ+ϱ-f1sz]UƔ.GGae\BT)]oJ-^WRsyqxCʝ{.Cͮ~\W1~ٗ1t95QNC!QN^e_^_Ɛr8*!U;Gw?e,B2;GWP+vQ__!q5NWƐr닚jSKY~be|˖/cLrT5\z[29(㜿eftHrXK7_[9;GWgWk9}2;G#s+Dz2R]=elK)e )wI]9ιRgYSce)]½J+QwWSx!ݏfWM[)w}9!q`ژo_lu@ +`)%Ƿqv92K}콽&\.[wP]vp_i?k;pGճ.էu^mRJs]r5G ^d_:{K(oY]w}BKQ_Q~/hzQn[go C|ڟ[xׄbKS5?[u5wx }Y>K8Vw@/>U%ڟ[KKk;{_!5>%CkBk$uw 5?>Unm?H>[B)m|M[B_ Eg ˟0oCWpWwQ%k-,&{uO]^~YKY{{K?z'w7}ORJ sݺse%e'<* ^^.ek_mw[;ݡCRrm=QT-zC/TM5 ,c+ Cҕkv󐣥ter~RŐet9=x}rwgLvH)9!G_jw/cJS]!9eqjQSg49R䀹=!GK+ǫ5֐e9u51S29e,uR2ÓVQ[{xQΨ[Fkїz_жeRJ+U?wg@c?->PjwztQZc9ZJyruscHs,8-+*KqrԔ.p;VfːCxxQ 9Zݱ}1]u9jJ*_,|]z. 9R﫬uh)=}xմ0)w}f]MwowG{?r.'רG}ҕz!GC>1y+5Ey$ǣskڧCU1h)xxt)!GMs5kږ-+kW1;}Jc.?Cr׃xQ9jJWƝcq*SR~+rz{P+Λ!Pjw1jQS_yrlz9,}u9R1GM),cZҘҕk5G̬}KxxJ5s O~rsF)NxQ}Ki9q/ʧL5M>,FrԔe_Vc(57;!GK+ǫձlQS2}ͺ}ttes<ʸn99ZJi]C!G(>cr׃x4)]w7s;vQg%9^oz$dzk|sV9RLXSJr<˨!GMi-їz_󐣥t+ǫ )w}f]M7)]2}]FR[h)]*ܐZk:S_Rz2v6R*}J:)]=9eCҕq.*!GKYr<(qs!GKi=19R,87ܿxߏ;ݱes)]}JWwG< C]FcC;`;D2J *-w>Pjw-s-.grk5-q@9te+cqtҕ/)w]IOr+*swGe>9R,l+$dz>ה1]s?>QF9mgRړC*Rgr)s}!1 r׃xyk9te9;ݱϽ:ۧe2SZtϐOr oYy)]]ͽ()mV,dzR=rvW|͐R~Qr<ؗ{EOi=9R^!徧$dzWx;Pӽۧe2{OrO{r),ǣlR~WJ!-$dzWV;Py)]_+#}J\gcHr̾: 9e8te9{)zrXJ*fի_#1h)mwC*ӽ٧ܿxҕqjvIcbr<(1GKi-nٷJ2wOi!9eL}?VwsU&s_S{z)^#]^S2^9^v'7s)]^YSz2S2~~c7hW5:'(Mj-g1]e7yH ,c!+1W#Wg^STv9;Dz*bve9^WYoݧ7r vWY}euH,!+1Ԭ|)w$dz^IS2<{OQbm[=Q^΃> i^$ec$$("$uG[KA![^N$m["IBrԠ5;Fd.䷐8x5EDSu8C( 9M;pS>( JR\ R&LM(ܤJMzJkb'Fbj ;5B#B(~k( $Xu+"%܊c09=f+-D܄Da,QDa,v$c!Bv7I[$nWp FI 19cIƢݱIb5gA1ԐEMB1hD1@^pF!  U`,B#02X VєYU)>M4 MEDxC(*&¦I@# "AC!T|V\J4EHdL#`*M#}p"gFuhzZB#@#]3M`U"h zg4ܳV(V0P $X)n BſX7Y+ 5i(`E4X@G`@(Ӥ 4BU@(Xϓ~r !7*pSJQᦠ喸)z/nN #$n)9'hn M=)x7%}(@7,CR鹤 %R4 M5ݯ"M?$1\OLW)^<ߐ4E7BS썆D))D]  ̨C"[D)BD*40I є9Qঠ~7{-qS$nºL}y˫74hUJ4pk(UA(1]#|;$}jeM+C nk~WpV`˭jZp ”/kƊs(uw|RV VKY+X;+1ۤFaC+ cI 4 cEuto=D5n2\C1[ i)_*|$R-X9rVw+k$b ݮ*X)XTЛH)6s V*4<=h=Lȭ{z}7LRQ[QD^ B=Qk>}*`h$bH5y=D^lʧyEG^n+M j!VЧInEuTO-!nb,X9r@S/q-ghPHLM&h~&hN՛D):D)Bh2 GSBqw3HT$5Pt` n$e&(&Qc&*V)UdRQ*@S[]C[92+GSn VJ!@"$ k5bE Q(F9&NLq[=l ,AYX y,A!%XO(d4 !m(a!#54Mz>"`ɭb^ЦMzeE\]#EQ`NHQ^Sz$#7KAQ*hS1\# Q^SNyMAÁ[)r+Pfʭ71$hn~ J !Q@H77O$6AE;D14!>UVh6YxMFyMvB%=`)KrV Dp,n~n~GH592rXBtt$2&xM"u2Ib3I81 5vM@&e'L"|(3Br4B$c!R3 /$3R77F oXcM4߸UxѕhQF64:x+5߈NA#P4 E@kFYk[5BHlkB2w>P4! h @Hy4!hB 4)%h(XB[9,2EQ@EPXYCih8誯i2Q\ @n'hEC#@#Pk4hȭPdazZpV u|M[)U!5d iQpO P5{/AM PdXt- 5(% KDQ.&K T77̷4KQ~A+R1\QG=r *C~A)^SkP6 @fT:wD nIPpPȠd!$"RP1dF7DMj3ƭ*A6Id$dD֯IyjdsI_QD%MDHO,UyɒiR@ШCF%:1Kt`KtiLPpDׂM}ND%vkX߼KuzVd<N& K*19/$h(BUʱ *b,Q*k,)("Z0xAY9Xr+EHnl83pƆ|Xt[.@Pc hB CiRӠQuʇC{} (FQOThQ4X V*67U!U $X)B"r,Bt* g1 }BA%P6Im#KÖvQOb!To A#P4VX(䶋FaXHA ZpXn F2Fx ѥxXH -C#}אU>DfT>Dj}Z&Q[ؘ$,$ise|S6HgQ}Mu&`$D%3 DDGM`it7IKRQLQQa!)XVyVF`!b`_+Ulo3:FAIhuhW:A I uĩp(C4[F{ Ih;haVV(:5bhV1Q14(E*"67ZeM` N2Ä$NsM4b!XSbi!hTm U$#MdtZlMG9%h8nU䦰42~⹨G V0ʭ| B$ >MB(oi#1Pc, T4W*=eP+Mn`ɭ|cY0X \#P4% f Sk (El5$*&Q-B"`$ אI4>C"RP1̨`X@؆fؔ&Q^Hl"B ~ Mf` ]#4L"P+Knɭ*t/hXb5, 4[lrz,([T$#רmhdaXՍ% t:D6NB6kD$5'CC(݉\C![)n!KlkX ްXr<'B43\#pSIĐkǔ7%R+En>D A po195!U&~|>tW|F"ݧ`k(,Ua!t ;Er,V `Uق3Z b! ,4 EuObX(z F E@aI5 $L9r+@*#$D!PX+En9BnMpFk2h @0 C۟&OC ɠX$ gPcFu$X(Zn8cCC oihPcFu(V3:lL$ VPB*U@FQPm$A#P*H@44 nLC{Wf?Ǻ)4$,4sϠȭ*huP z 1@= C!)4( әծVuJ@epp"PB#POtzD}'G=nP'z }m`ȡ*(3Mfk(R+7nVj)5ʑ[UlOP$V_* *SA]C~8|UlKJ$Ci2爹F `&Lt8-`_5A|LHg&}) r XЋ-`H&s$r X{OO|}\F~+05kQ@:m0ML:&Q;k1 :ͨWDy0${637>{(þ&asV;I2 njH P&Imr 7rPVdNCh2~\A 'ǬɸUja_hs M _;d@FeAaDd`U%)b_`ŭP+dB8F`:Sas5,<4)h(q+pb 4:4F5*5򝡫qb5 24PتbFlՊ0d4 ɰX@>9FhONtԁUEO5ϡhON0>9A-0BPUzDuۺؤ4j[WTX2M ,A#|}VI, MB(=AC!2h5FxEO\̱h*VS4V 0Y^=rrV =u*-:gRΣnqM"Odu5DpXV`ȭ RVuRP5* kĖ5E$PXq JUMFo@u9bewhgDpiUQ,V JQUѺuN# :n&j Wo*1<^ϺS~AШD  `qRU.Cq z(C>D$v $QB4D@PI"Io7 H!nG&A1$"HA"eZM 9 E=f! %$$3R> hc!b4zu@=rFa!!C#۶AN~ 1 G֠QX(x5 h@S&V8P,+@nM.pVİU@-[j#k( 7nVd* ŇdiN43:PYFy' ZpVu̪;5 ꐭds:C8^/N~Aub!X9r+E=nPVoч7tE4xAE2öFxDG X)Ra\85㧔Ok(DUA [@(ü]q 9 GVz`Pǭ ߬嫟;;) ԡMM&PFo̘15AaPC&!$jL@~ Џ$4c$Uġ^g"A" M|go(17Qd jh0H&(xq+5fSFf4m]ɀhԶ.zd4SǬ\ÑYONT?-QAP35[>,fNs 7*PCPP)`> Q~6HWh)`z@!QOtz 1< ¡mkqbAýz`VCȠV^ 5@pVz*NI$ԡ lz̪|}4c]#|}'"E(XĢƸ&yUeF6&L#ɜZ̚925 EH>U@r,dbH \bW ,V(1@A@_B&?r,#iFz:I2L"w$I&X2&Ib!3r#E=0fNoLBN뻪PDM"|rJ D :1@oTi \fǭ ]qFx 4",hdxj} 4Ƭ /l@oXm}úFhMT7xBɁV/4VШB^VFa0zzi Ҝ4S+1ԁUy.*cM VMʬ 4P|@2WC' M# wo4 DX+e mP/" 爹FfK:ɜ#+ꁕ{ʽzA[pI@CϔSw\7eUXOYOUl ?`+l hĖ哘%xBʁ[9:o2+$nUQnOXlQ[V\] HbF%ÖDJf,$tb;ӌ(p]n1 !(p91:*ȚD *L"p%$bҌb3 &#sDpFF &pYerk]pC(ѧubԘFbԘFAv`ki]lI 4HbI=_*DžN{Q'p1غ#4П*< L!]9/ 8'pFxЍX)v ,n@%C&,fU:C0 _+|1$s4r cNHxo(t–Q脁^ 3unq$:+uШz(/ >]zKER!Hƭ`%K0.%CaL# &r0p|cVo`V)-qؤVTs[L# ʑYJ0 pt 17t^Cy[` ujkXzEd>AB_ăDð$&HۤL"k'mRljl!7ᨏ jg0kMϦF#]RL3qr\C[,b҇ Xb4 !=4Q¢hz`LpXV(4h ))6w\#R `w KrVuRnWS'e%Rj *|}1b*|kn HEjKkD5,݆I`-)"'XE#Q*/rj/y&2$vt$L"DE|LUP1B&)XQ $:Kn̦,HW5BS#MY_$ M%) ĠI$M4X*cШ;d5jsn2ޠ?SP6jC̦Qhvv%Mfk sّ~>Kl"@KTJ2bz8u#T=`%4(&NF((QNrbJt,& VόEbF$I(e7Q⧊Oe*""r.&Ĕ6K 3 >WX=Rģfr1$G+uQ/ f4K'ce,~* V(MSMe f E:CHSwTƌ$-F28f4iJJ_ᶟh_~7-2LWC3~nmGv`%JX+fDz*Pt[1bFS]^v!{ՆXX\DE'#XJ. 3ut d/h1{2z* VT#JS|_?W_?3~?Ə{}V}|?ؾ]lq6wX lliO_l1Z]k [2,~..A~ }]BľG‰/1ݟ lu Unlikx"ު遠X{uqW/ذ3hl-d7?^o^l}8R17P/ AwMF>Y{Am %~C#1h8nhk}psZߚ 6h=!ln-{O Z:,R7)ѷZR@&Yj]/dho5^uCk` Qls+}˽=oNWЖb@lS0(zFb].T ˢEѓ ֺEXgnaQX12O({a%WQt57W zcT<+{K0y|#y|kZyFj("~6V?Ax:+ۺ|wKi{1Wmvсk]G;u+a#qǛHX VcSAluiqlv6g; Bohtk9#FB`CT",%}lH-i0jP6"8䗜pbYOF:?f&"dsjf2rlNF efGLC&iדqNNFef1y'#e2Y}2RN 3=@Z,a&ӡɈy9T圌GLCHy'#x1ew2R3q8?1ׅ2.=)Cܱʈ&ӽZ'CHpdĬof"JNF*Nf&#od+鞮4Gr%f = d {f<F̟c& [d<fFHUdVs8)wx2RNd0_k1Sy󷹓OFf&I`Pf2NFp1DA.a|"0RdC0x䤝TLD/&!KY`λSw2 *'#U7Mžd1%3=B!3#LcǽE Wk13J4fg!Oe\T2O:3㑗1ݹSGfʠmo>EMVQ1T!k vՆ|Sy>^jl'4fbf(صژ:y71 g+lV:ݿ`1UYUfc*4;6+R~oko'Xcʈv_,>U$6+> yϾZ͊OE=՛qJTد 0cS`0n2 ̾~-?o16+ 5yefԩk nt7?baG#}Ux.6ϋ8G1;Aomu0&rkQ٠.AmI[^kXfEP&oe4҇1ڷ9kdwﷀ$  mQ٧6ؿ:(,P4c BEEcd2g=R_ϏO;{mBۚ 563okfפVok.46FCL`L0T.X Sdn` w_2WadonH_g8>>+ri endstream endobj 121 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 127 0 obj << /Length 2284 /Filter /FlateDecode >> stream x]o6ݿh!)s%hpⷤ\֫>IkfHJ+ɛ1ഇ9Ig&`k.i" W{biF"W;!s_߆cWl$Uя-|yT|d\el3]ۋͯW?? +VBSlVb_C1f{l.mb6΀CYܣ{y]!a|jα/Y?6PY3!2hy2Ml>+i$N,@ ,}[U} fz#j(oCtǦ2|nq}k#M1:#Χ>v:Cх `d5k=tɑ~pPmv \ G]򨃆2HqzE|4O mM}7zXFkހߤDS&mDiv;8z $ڀO` Y_E{} EDjՌũg/f+M( F0b<,(SjN~y uvPT,AdBD_KͼH폍]Q~ tرJãRQV7$I,2KnlYmU< p4Tl 0 o؞9컶qºR] *p䶣U,>`` :50}u9 'YJD%pd) ŃeD j &bɧƞNw]\p*kol(:$*_= xCBG=$}x屬Qi>2Gjh]5S3G_U:e U=S39A @m&?j~g60uՆzOlP/|lVq.F@eV4[S>џOe}bmsxA M3ׁ1WԀO g;4 #!9?N&13 3O+"uϭL;4HZ'Me@:`3J̨4V r`C`S1 =eB Tx"I'p<ƴWc嶪R|9 ^/sl XDlNe`|:2͖1 p֥`vV60uW|?(e{&fɤ3 7\qL_ i_%ZdQk^<]1ز[LFGqJl;۲P ׂ>v] Mӥ;{s(f!i.O>2>%  #aNs}p}4yBpz^݇\*X%^0Ux5픨Z@aV_+l80ͫ=2q{ Zl7 qa5s -B-gsx@8p(*|O%l>m5ZLEǛy&xBr> ,t9u|@`DI04!,_Z+ endstream endobj 123 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (/tmp/Rtmpei80N8/Rbuild1eea6baa0860/NMF/vignettes/figure/coefmap_res-1.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 133 0 R /BBox [0 0 1008 504] /Resources << /ProcSet [ /PDF /Text ] /Font << /F2 134 0 R/F3 135 0 R>> /ExtGState << >>/ColorSpace << /sRGB 136 0 R >>>> /Length 3886 /Filter /FlateDecode >> stream x\K _qf2zKm)ƻƵ&h~I=9Ҝ׾?#Q%Voo_/?_O_߾e}џ?|gw_voxQoO/ n߽(o6̈́[V~xa4(@[yC%p QQ~YCu0Bo7aKqW;28ڔ*LmSgxP3h7nY{&|lIvFC3Box#o1nu#vлf4)&SQ-jF{>L0G'0B嶠28NdXyd0 {O1 pQ(é< FC"28dh$ N Ǵ%Z8ep0+Q[2LK Z|\dZepq9ʈas`AƅYnZ)D`}Ol2F!Î XLb2p cpuNԃ8epe6 N!ෑsd hIz)X c5(Xx"jA jf)  (ZiG98p (ia1d[=)D={d2pM1LB1FĎ 328']Qj ,W'jX ,8 xUD90 &s5m;;(= JօkX:hq fi&tb]!7 wSAuߡ"wiywOYn\][$<YStb]釐5{uˣn)tlGj _`_ " W%FD@BWtg{Ӿr X@JX3Uޯ98<10w~jμm=30whrK]S%AfzpUpkVVhJIM8puZ>Wa+G .*衋k㘪.n@k7ЉeO#5U dkc= @k\5A \+Hj`U%4NZ}`A'֢Jh , iVU+;zL8^2o>ʽQ~D>~{ePb_Qy(w>NJXD*w`+Gu]N o >Ͽp`㒻gX >m, Vj6::Tx?Trc\C!2RRDZ*D\TDKirE!R!-A6#S dz*F\TJO QS=LSVr? 5È'")'leOI"=^Sߥ}Y7LOňcLOHӤHOÈCLOňLOň#LO<"wXŽR>22ty6@3GqҏbY a5Q9f}:R"y6+|ꍭ(BY9fSLomEz7?5&;<×_ɰDj7"0Y ڊ"0Y;L{c+P6lY\z|mKV9nR.z0Afa@c1fao- / +k8 vb ʎea G0cl`Zab׎cB~]agD5&_Ĝ:İǁuaO31S191 Դ1t߶;f::&csn0j::Fgۼ霛Gtrݿwr?a5^͹IBEǬ䑾cVs`Lژ՜UwjL<Ǭ\dUY͹3u՜$tjY{dY9grР43pu*1sɎY̹ߎ<~0N s%|]>}.={^f\ B+>}xlŜ++O s, 'ŜsycV,csr}Z!UDa7gו7.xkpOǰO^"kFF^;_~?/G?W&_H*&5=0eHGۃ N!ZK-؉S)F:TKepj.T -28HQK6b`1 6J8ep;2|D !p@H 1F>(?uNKE/Jp qc%25+[)DsT^)c>W~FĎ T28 >afAZ*NB(Ιk1XTmǴpJGej) Q]ĥh,Z8epq9xܙ28ȸ0ˀ+"Slj w18 muQQǰ(kLc2pJ1աSZF9b#28p*'NAO.{`1*d]r`1zT60qf!QǨrں F!Î TB8 CAzQlj2p*'N+;`A2RN2ep\rZJLqJG>I xjBC|τ8ДZ2oN+0$ R hiࢦZ81s!k zЃwUU=2_ >4ߟV!f#MՉP`S.*tﯟ*[ҪF 'id>@TRAKЕ)G5'֓e~{ZA4U'Q zml5UgvA-ɝu WJփ7csblϫ T ДRU-M^Ohjjȃ`HŏT TSiTMN =d4U dkc ЙJ6#Vjoꓯ* U@IiՓ!b8a* HڃU7)rdV9Wyyl\kҥ*_1R_[|>[\^_=_{{*`֨X_WbZ] KUU,f]\Z}U 6GWbRJfU $Ct5[a}Uj/HU $AL+Ⱦ7"rj-wr_SAWTh'0׃Jb9%-ZeZV@lf{%kZ뢵DYZK|h}Mgv>SC hֻMQ@oB. 2?Zsl"e"<߬o߬O'p ww %a %c)#H AJxDAJxR;F0I1w %b.H AJxRF R;F1тw %c) c)#H AJxXAJxR;F1qw %a<%c)#H o>7-AJxR;F0}n17j]v %c)#H f%) +NR.7p^;^I op{-%õ o]@?6 endstream endobj 138 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 146 0 obj << /Length 2221 /Filter /FlateDecode >> stream xr۸_>Z0.tmu>$$EKPv|@JT47۹ܣwgt ۙh@ b.B6?SO~~w&%pWl1 YS/}L*]`VqCS?l-=wj$ltL9i:4`&|#~1I??7j #]YTvVDʠzW; 7 Ұf#[U; T`sia[ -3^,/H $|n=% ?Ӑ:FT$1(@ĮnTXTcf .7 XDx r &EukVFv[aN-53|R_U N[Rj4f@㞨gXX(Uri+;4,p~Y$8sibfrUvAzVu);e #dcfI}eLDzʲ C7}WK;y!LC/Ęc Z~|*#н77΅'^$"Tp kD(?l-OC-DP!Z婼>¹ۛ^wf?U汔C5H @0f > ٘*۶e] *c6pȲUyue Ӛ0j8q#/KG@MUW&d|ӧ]yÓH-8` MDs !Gi[rClf&"K XMj ܀r;MȜ`3䊁r@..|"xیL =6lqJjfJ3Elo|^yĜEdIoM"t+w] 3hV71ш?!c Cz[ᮓEmrodrҧ0aHL>i#Ax 1$QlMr}^7!$.kd $I\6Uٹ\uM%"sTMm됈e( O0+"ϕke6s:aG[eo0jn ,E[ƒ+H7m)b+|' lыVPijŜ9l}jb ]ێж]G$=-&ݛv9#4g4^7!A T@s`i R`}p^Z:NJy}ԹSn|;!r2ÂzܨAˠ<_p':6e(NZj3a;1+9MAs5N(_)]}ExDP@ty$V$Vڏ?qˍ187g:311`)1$IWvf*6P؈Жضz z _vIm{׵,dgne>l=x:lB}!aQg<敕+m2'5 8~(Mjx)8ل  G(vwB9x?}Zv!6IU9(zQjTfrkB$`m1?ȗ0p[d.8=J')L[C QdpkT?C㞪(~Cqqh 1RIK\ɧ8UUso7ynXA!d֨ۇm.j_{})Oža;s/4hd8.Ff-A59eZe%d>I{1`GX%ET™zSwmqO[P6B%;;ղTߜfLAn2bXHNc?Qp""n,tqd4h+{{kcI{d2'iljZKɣտNKwBw;yY8_7øw'fIhQϚӵ͓{^{};\^/1(ex} XjQ4M8|[ /ُxnם;geHj9x rh{0]+E)+Rz`cg IWEl=wg>b`7$̽wR_M̿/keMd 6i6#iA)~s}3mJ so}tڗF$- 6P؅ endstream endobj 143 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (/tmp/Rtmpei80N8/Rbuild1eea6baa0860/NMF/vignettes/figure/coefmap_custom-1.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 149 0 R /BBox [0 0 1008 504] /Resources << /ProcSet [ /PDF /Text ] /Font << /F2 150 0 R/F3 151 0 R>> /ExtGState << >>/ColorSpace << /sRGB 152 0 R >>>> /Length 5625 /Filter /FlateDecode >> stream xK+z)-n,JRUNUKe!X.IN' 4l~$ єؾ?_>ھ؏}vl?<oӻvfWn9K]~}@s# XȾ8v):R7%vg:HB}< w$b"3}$>f۽yО9&&]$>o=Oc P+@({ٗ@SZ(s}8T_Ihk#^bWIRP1i`4(hxԗARG,sh Grc,ÚZHJʠWƉz)-V{lJ1}4%% 5BSZ%w2fCYi}-XwvД y] ")Ii9q!A4窤4(Ê=hRIi9-ESb#vHhre8[C )]s rPVa- 8`$90%Q$Ii9ރ rDRK*LkGj!)-Gcq1ARZv%H8$ˈ.G+lq HJ1/C {՗ASɁ)1G܁$9HJcq2r2z2 =ESBݧW, *m\@2$"#>l?qVT`\c=ܘQc5)3dgbͥs.GA X_jQLr .= R `Ϥ` V'c7FߍMݒd mtV EKcnBk$]J<% ˱F@UZM9|-VhhUkpS@oA@X:h>¹Й(u8l|ie0aPr'CJhy-Cy% vy ;l#*Tm) Eޕҁ¸Z\JUk=(Id;njW fUQrUSQLbXQsfUXDC-JBnNq[TTus+P4[U| C&=O#n HF0-+ }3Y̾5C?Cea.R՚h_Ebf7at2C)_)-ۏ{lBX|^ۛKFRƀ~95Tߏ.Q:-:RGNJJ8'6.>R}/XbOc桫9,D&qz^(=0/&a%FRm :$YER$Yf-,(QquaCV~v8~Jx+`&N\:|j2_%` DOrઌ?!x0e\/chf?œEU8 (LΙ3S|1cѬ2N2!N9C͌Ĩt;=gP&.s )!RF&rs)du2T0nʰ:WӨ0auw F:WYEVxOde:wvFOu|ZLeef:*3ӹ(dz{0;Pef:.%稰LPef:*39m G! I9Xu :>+39Ǵu[:}nr+D<8Lj>hρo}}oR9/ut9ܱ)2K+(>ǝ+39sLtθ93 <fs2E+Va-cp(php8_8tyΝCMƉF],j 4߾‿߿{Xw$Zaz5`8KdRHSZ7H`#IrԨ$Zj%ҔeXp")Ii9Z-5ʹ@ iJʈ3RIrBul9HJ1a#G )-G+Pdj=FRZ 8aSFWMi9Z-*UД.G돡 {1$%)]D2A$)-EѤ ф4z6 )-G'XARZVcrHSq Z&)-GFIfl_ Mi9Z$X$hQ|XK4m1BSZn\2B ke"zu%)-EʦHVIi9Z4!MCARM @ )]V11Gޕ&32ʠ)]MHS`&khn9hJ"2Fs-kb(ZhJ-GZJ")Ir9G4)-Gc,GZrte9c_c4(#E'eEWҔèAARM 'Rm+t9(5vl7Qs x NT~ŕʟqb(8SrV0r_%h* ԅ8qydn H^>dꦀԥF O+x"Cs~Ԫҧ`kG=C7']jo H_dh*`~>f Qiny€8qWgl#Zv%J@g%9S=脇s=/C JVCwIUCWDI\XJ~|1@Z 'JY[2t! Ԫ@W>n2( ȫ=EnD3ts3u{#/=|) ] *S6'&hu5EzNx2S F4/zWb ]G4T{.(x׻!g(׻f^='ZYx艹PFшRϊFt*eYq8xW蜁 Գb]z!;`3CVR ⭁"j(cfaP h"+.oOH!{Hɻ6njDa cxQ12C) ö'ړY{<=g=YjO.-'3 I6|Ϭ'3KΨY{[C8}kOffXZ4P虵df=w5kO YOffQɏЕH=kJ{ke| 3]qUp_f=3?8@mֳ֞,';V[afI7;YkOffilx'3uG 3>OZ{ni|23\=za{f=YiOOa|=Ԟ,'WƧ0=Y3kR{qe.̬=)D7('̬'3Ġ6{erief֞ +IfIa`MYkOt.OfA{'i͟df=YjOv.'3S YkOfVS+)T0옥f=f4PBzf=YjO.OfLr֞,';uړ=-]%aړ=8D6$@6d5q>Aܬ(uSNo}u| ooo /j)En@A @J|>|/)t H??pgxi)Rg]'Jγ?}zj|/܌T(H?z +2dId0p}J Iy_L""K AǷˣ+ovPQߝr;v j Zc4*r#9.`NhES?Hzg"x61"J3cuU {t=qIp> mQƿ! njSGtA4lgDV`)ހXn\PiҀuf\\( OĀzFWbqq. ZjD3.+"BhL~΀ˌCxr #bqIG3#\${ {eD47.y-2^ w0_]L,3E@"p :Q̸܋ӷ%ܶC,3."품p3H[~z+ wFf2"tN2WF1_m-rXE YsFs@{A Vz+sn\rF`( xj b#GVck7"#-a4Aha4ʈ(f\\!}vG\q :$0 lr=ƳAsT~D8Ձx=P^NT΂ j*R8WI %,~._?ZG+~2G DG+~2G cL8y1_?ZV*C-gu2U\?ZV/ X ~2Wfs+39Ĩcs!{ef:\?Z19{t~23cLO #g:|@_1׏Vfs~2c?,DG s/3 39Lt~2f)lDG+399KLt~2DG 3o+ܳmWK~h~Y׏f*Y׏T^~tœ_:W}~Ow)OۏТۆ?_O$\n-oʤ;&݁T:xQ_~ö}+My϶m_.weL.L;y!= endstream endobj 154 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 161 0 obj << /Length 1830 /Filter /FlateDecode >> stream x]o6ݿj+~aV ]AX>\Inݑ,+Fdp<yGR!#BWfQ4Z)Nf 8 #ILCHB!\8!,0݉ xLX؍jp@<==5!"+CPL@oIofow;H :eu$ y}y>#JqF@H@'"iB!9I0>CtU-cb$`M9%ہ C⊆\0A.'N/&`DlkRc~-q!pR{amP ;R)Y{`e J% ZPWy L$DZA9 a,T9_E c5_6ӭεҢ\ϯZ?R2o7E#7֥Wz!yS]^W~Ez<ɞX<9@t#j2{S~]^s&G҂.kh56fʺ ɫ(K&.{c\:ݺX4({7mޞE+Vo)Yf*^yVWCԀnΣ|8M߳{9#"cϪ(8蔐;S$4Le84mQ"M~cff3.ƍMIeqh zLU(Di-u3ENqƑmk͈nV*ts.`fY<gǬm1O mz0 7]zEWp,r?:Ҫ; I3\lv]# vWu]qHdyl_74X8?1h +P>9 UK+ c=Kb`),Ŗk@ޛ%k˵zN&6Ѓmjnɽ"|@@BO/ vFn\5]yDtB:o~>z[8<5s SsmB7^ìD}85ڦH 6Ddƻ8z[_ݖO0* Orی,)5`N_uK/3RK%}ƪfrm&`K)/y*t5 *eyÝrո; /lyhM5~ t4ر89]P1^Dz  `BA ñ.'yuhz hL@ ۶pUoYWx\\mi:4I~;<9bZy87hIbsKSw4UEv' 1}?=q_u{[iǹ ,tE ul_fT2"1jڡ~<{]JK #?\A;Iw F;Xu`:\uW59xONo\_k;!LNά )%akW Y]lʗlCWafiSlnK3> /ExtGState << >>/ColorSpace << /sRGB 167 0 R >>>> /Length 15087 /Filter /FlateDecode >> stream x}ͮ-;n>^*H4`N߁ni䴓~DRHnwp"I*R_Q_O[՟o~ٶKoHھ~o_?~|/JuO:GF9/?OzVRϙ~'Vc7E{~Mj=ϳ -LVcs>BK 6}CheZr9睴 [~R+7|%C>RJ+~-;i5*eXzoKJo|G縄~wiqʥ6߭̓VcϱK-.Ԋ߬>ow*}!aV>k,(9}^ ң?5ݒ r5W@ d1|qMI@Rz _\KI~,%.?+L)(7۶J]/ ˗?ǡ(`JJz >PkYI_|֏+ t\ gJ.'J^^uG`8^5YQmz0ߜ,-U1Gg=?oz٤^%wnq2q1= ~^ oq^ X)ӿ4>KAחܕ'QW \z`^c/[M諠HU=B)z/>i_Z/Ϟ=Q?>(?vߛql %x)efؾ/o)Q^&>Vz%(,GR/]=I?Q]/+֚ԣ^P)R/]#?K?_/2AN!$?@I= /@|IE\zOz/z0Ղs1'/ݬy$*ANuoϷ|HJ6GAIeIԓC饠v+= 8Y?/i'C=I?H= :e9ٿ_n5ʟ4>N KunJ.eݕ@|ORY?/x#(x֫QܩƯHA_ϝ~r);ْ9gk^^SЫ9ԓ뷌Y?bO p}LIe=폔o^uJ= :ezS_^ROy9eT zGX0^e|^*AǏN淤?7oYi`g⎤GI#eW 3WR@`H _{9GA?C?/x.(xԣ~*MQ]/O.?^ˬ)8.R/v"_RO^Q?Q~J/]>֣ң^VHH= ?^ &Rxx׶K= !`=Ы9}5 = zGX)@~t\S_3<>{ң^oF]@韖^?ROZ r}ԏK-M @\fJ :~ԏw*z5 H/*=q|n> ?HW rŝ'o0Lz{V2[zvΏqz _PL'AoQ?/[wGOI률wOd~P :e84K ?`]饠s ISIAw?)r>OKKL8|Q Z 9K :ez$Fc/FQԓ]&D#)]Y?)NI W OR^I?VzY ._/܆~亥?ܵ^&J,(z?II>=IAԏ0^epzt7q|xjtp~_IAdzozN-KGزSqlh! a icƙ Uys(ӆ#vf|gHKdF0O:Ҁb]2O.U Qx^EiGIQ۞Cxg^>RSlP yKe 5{:)Qym!?sjF*v3USs$xcN'zC>fz+`4` }{1AU1}i be;3>M5"άrIu@ԿyV 30ؑa$VKQk,a Q200K}C>eWxmZ޹U]?`>/3٠Goz?"bmni]H^)^8k܌UZozVc,m1O{*̉ajN)zbl_\I)t߹CM|O8zj8 5dhc|$QAg5u`}ce"7G׳XQ[]JbRq`k'z{繀Bw2Njo6|z)]7xuu M]JeqT6wbeզQx}aIer-98wb٦+j)7NY*0WTtrV m}Bp;E-A Yg{( TX=+KvrhNemJܭjϫ!7+.iSW^5r5=X5R*+쏜y]E0a69*P[gQZKasw서9rVj CkQ]b."U8lv,{ũw c3\alkYմ+kܓVrtcײVN^ j24̚,\|ÖYtJ5͗Ad8mGk!X2iz J36' si^Af i N[<ϧlB^aUA6n\S[!jot p-#ܯNnh(D'G>'\ <7be2Xp߸vaZ=HBfx]ǘzeFjp _G[q¦mF* 5Uk0TTQD[SE hME* Uj(uPٝ$iQhUSUCUƩ:: Guu0Q R[GqAmE}:: tti(T[唗٩Q,KG(I:: u0ўz(a3apD[GQ Fq 6 ɃB#yPNckםj {FF (jap`)20R6 )5k4`$ Wwu0~QC:>B5q[dwt;k9Gwf٩Q[.(I l9:Qs|? Estq5)a𘭣ȱ@ְH)fΟB"S\P#j(ѝ t;ЬA1"i~2wWןچͮ"?X1$9%JQ܄2¥DUtZ)v/\ Jt ]>Q0ÇE;GR<`2ٱ S|<l0cQ'QsQN<4̱a(c=QGscw`̃F7 0s͌xèkxè҆F`L8Fx3sF2^ffctmÀ'ׁẻg=1昳O4e9;,1昳K0svz昳ό0sv8c17_w! xcθm1ޘ39Lüޘ33sxcθ1ޘ3-T аǜq1ޘ33sU csy_,c1g]gYW4WH0s9F8cZ0}u>l8z:g]g jcuκ1Θ#W8cκނ1Θ;q1:g]ss}W,10&5 |>Kk+fXo'n\Ons8-װirGwFimh4x F~|e8CJPe^I?&{O8H (c|Г;~'_'_%^X_&}z=|/b0Г7B/ k<*E3B/JO珡*7:Vz)z|E&(g,^6O>՗Гo^L = {GpgD/$B/]3VsBOKhVП@N.RIT rprlz@ww ?Gϔ^&?Ljߤx z0^Ez28RmUz >7NBOJI~l^K= !]/1w%)\^>_J= :ՊWmZlY imU$lSJsyUg%S1UHʂ$ĎVLqnNQ9)0,4 f$#*) %wnR"1YLWP--;3 JBd1~uJJ bJRvGR$2YL%=!^Y襤ðxxG$THZHSJD9f ƱmݻNc~IxjXi@8,HB#*oB*r9~U(2[ iNf) )( rLS.,Pzʂ$"bL#mI+ y(-[YYQe1咻7.%TcU*UK0,40OYD!,dDI1UYaXi̪+H[(O=2JB1fɻ8w~Z%?.-T=b*Iȷe1q])-HR=G[TE.{%IISbLeoKIx( %Dc(չ,d,Mð8)it RvpJh.ne1q듒:NeR|%Y&JzBz4&i%uIOCXGvJ$P븭7,4oڒPr.pH*!lfwR4WI e7JB^e1QIhENIYT{.e/deAѶT* 4*Y/vOZJ.,}L@ * @*_BJEZ%XIbHI*՞ʷ<]"cJ.IJzBLh \NbLHYzP\D)xb*;-%ih^2QқwJ"Ҙ-4)vk]juKIh4ɰ(J`<_*UeʢJh{+ Tb,42CqhP\.x,P"vҀIh%LwkJaXi$" a!kH=HI8 bL; )>a!FYDcxR(it s{Oޯ0[LQ7RB=8/B*rIVCIcW(i Y2QRHea1JK,leQbH#[SP3lq޸h.bLS[-1 T$-T"pDKYD1YLޣVsa`#,PaXie.%" OMӡ7^ jK*ib4%;G63o(h<2B BS>L h0%AVݠBHE ~.2\ANȃ!䅰PCH^:)QsOa-F#O.ho*Uˇ.r1rX>bPC4>} c02Ũ]#89 C`m1202091.YhP5c2%3:Ϙ)cIcu1"6w9 sY+uWfu2Jn\s305 Sf]jd#W8^?ח0 |mbP1p1X6 qcnDCcFC`Zb(3- 2}c 0k\weZ/@b빺Ԋ1xynȻo/CR}Z3ɐ>7ㅽx ]l*Xw2oD/ѬGb @wɄF|3c[|Za]GhD4wYP]0e1=+ "bݼZ=A!/r]nRWᕙ? *&x]SV6IcM䖽nܲ7 b[Ƿ)\[^BlrfĤia kzjo14Ȓ\|CYx*5IUMٻ1I68mҌif,M f61ifl#ͬM?cr%f[4I3{!aI&eIuؤTeƘruH$ffall]64[LWi&Xkcm0֚Dx>Z;6e1c16Kn5cXødwΣnj{ccFa\l<6it[|*_EIy4s[bA6ƬI3all9lYf֙1Kߐffo43oH3ssH3֚غ.ifa4I3ohfMcƞOkpѡXDkMʫimr&մ6aZbIM5MB-Z `PK_!w)Nҫ]I*ޤtԔ}'{8ԔB\j7welĤlʢBh,gl_WMXH8+%s7GXb1 +{\6"k\&*&ALª Ybm ʫaQ^za2K/ cV˪p]s-.cLjʦ1QS0.5e)nZͦv;x[sNjefx H"VȦ8kMv0i\ecX˖phu 4kM&aմ65ZjZnbI,eRH5ɢudѺ^mh6Y&cqW /Ƙ+s\EE YEqɢE>.Y4Y E E" ݾ5 IN ]5:JSU ]1fM,Y~wj/jUnܕqW~]qW~>~b]$5c+XQ]-JMܕ53qW~w{Yn4y֔9HڱHڕnؿelϰo;WYj΄INx3T}9LI'?oY\ikK^7RXf=. f(м yDYLyFx#o_JaX`+B >h5wWoYԗ 55B)BOX QmRb!dwG;ž'XA1踃4h?ޏVT,% 9wʼh<Ͱť|X ;\M2;fWWtvoԙ5\pS'\2Anԯp(3Yp+_y̑3eܩ_0 o'\wT3y2_a7l{,st9oSIeLԭr8IƮ.s&u=:m{f2Mow)0}# ҈bC2=璹йP.$N/q_Or!^ώrq g&\<!W Z @#sbCs}=ņ҄rq 4o?8[>n}:}=Ȳ`6$m˘#>\ubؐUE\Ho< K#;RlʳD)%@J `S 9s`"T,B;@3o[B8ŞĊbC.N~p!<ņ,Y=|,`}h;U\.ܺ4ht ˂y`te@(Z^钁@E 4:elH~ּdP%;UbؐTR_l+)zśz#C,,߻! rw8Drq!,J*! YDD$[7KD&X, Ytz}E(pz5\l* .Έcd<Ȣ'U̅,j́څ́ fCKRg$.$Oȩ\fX.6dwQ2 }*\^+ Y̆]^[YB8ūzK, ޒ,=TSÇQvgC$x)uvX=0C&5 xO\ יcG- e:An)mk6B.֖R@.] yv75>J33\8@N] D@.[v5 S` 38" .tMke<7 <vM9οomvƘ`B9b)zV1@Bu`p1 xn?˜ /.F}1HمO99#Ɯx!<ŨU4̵öU0&Ìbg$sbԗ6cv&Ƙcc1!!Ƙcn3?09>sٟTa;y17R1ޘ^Cq~1g|1ޘ#T@=j_Qe7sʏ0s>_ s~9{Ɯن׹/c1GO;84sw9 csad8cܷmse`7o#׌lcxGYߓf3h1:g}1ΘqƜ9z`1g}1E_Sݣkp%p%Tn^滆'+b.h O}sw-k_7 endstream endobj 5 0 obj << /Type /ObjStm /N 100 /First 803 /Length 2331 /Filter /FlateDecode >> stream xZ[o~ׯasN4i~`F&]J,"icXt39s ,0-Xd1XLj&1x6IcJ'_`0eHY<^ ibaLG9fόT0%3Ya182Y9k"sV0 4=L NJTŽh@h@9PzI N Ȕσ7qbI1TfCh&Xb5$:n (ux\#l !fFGb4"D p0FT@KLB*&xcZJ?PLC:3 5q쁨i%2+ %e[R8?AO-#BxBd$y )"[ltfI-0&a%+!P!"ILc%hZt8مDS# X&d&Ɋ$Q`KNBy!7H > iF,;gٛSͲSMEWGG/>4iZ^Uޔ=z=ܰYY|eM®qjX]ߟz0۔wM&uCkX*mc=y=)9an$vRɤ,Tu:%+1'uޖrgzڸmNI]jgʁǷqk9/ibS&'4nAюh7JMY/kj~ܜԶxdIQW,;+K?>esX!J nHcjYSBZ>u}.l\Q<4d/qj@O';1 j0apa ~؎?`s,7\xe.ܳ#.ٻѱ 0iȍJpW};}3sY}^'dzd_?_}e 7?4?q&/BH*#t~Oyn?,Vsi6rV~I]w8.;%GZD+ @U^7 ؛Мh]0 l.J3e4 7IB*w:VfZ7obCˀ6RS~ޜwy/3tBI b[0y]0n1ϕ`a~<(h`OLdqqRh\=>4uq``emw͹I!v1~Gow6"y#sU߷_U˚QŃPC;I聘sJ8}kT"Xywn7ٛ&R-e̺H y$Q$ Z)ۼM:i-ueU((x]OǏޝЖ1V{_1`]VV{}ZN& J ]ˮjrٿfhiEe6!׶-&u)+ʦ]O68ey Y|n`%/ 78-ی#U$kpvV6|eV]zϺ5+rג{X_j| \ma<78Ԛk#}, t/|-3ewx'\U쳮辝(nl7q;nɍYo?"3}1(rǵGW*1wGpme ӻ7>)Q堨~ sj]ժZ'TGj44kQvpzZ-f+KDg\\cs 68l_ 01j w. DWFL Kŵu{B؇Mwja$z\2`^6e^i O!tIJxɅ~I!W@O2;eUލ愧jYKHO4һgGe.Dxm1\8LƸ_$ endstream endobj 170 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 177 0 obj << /Length 3342 /Filter /FlateDecode >> stream xk p2ǡwIw@Q$e[]YrDy<(Y$@nEp^0頋R{H#y Hb 2C ُ`+qljZnn:-<\ZD'HIտګ+`[͆"L[SӣTkgFžhȈ@tn|ff^]CE0CBɈ/'$#8F]$æPaF\acw9]L*%k`%yP\H 'd_v< C("簂%|u pR]n,Ymzb<,) Mh "6ٖ;E*|OhR1x_HЗz֭(ٮcAzeUmV* ޸J@FN7,fjy(˩aPK_=|>_Tz pGJ/F0k9@HE6ynvyʼn;}Ds$kB4bУ1eO["jzOZ塼(,xNG!CH4Ŀ>>T ٿT6}q!2S!gM6r)f` a)0ȲL=~I0$R;/sD$lኆSTa>)@@8P*iaj/|8x1PZ].0>[N006;b;#ɫ+( UaJ|Md@O0E$G0q@= i\x6,mG۪\3[*p M٫&҂xwF`B܁`W9t)z0Ea)̇fB/ >OxW.D!0ܹsMgbTVxxMpjߎ07:#1.1.:RZ (u/ $-/( ˪ꥻGhBykoHȑY'&昸ѭLJ=q=bpH ތVa '=Cb3>2 0rv9?GW{cy#NntG#.m%.&cB ȍIz4a qe'\th )%΀TfB򽮎z6&hhQ(C@Hv`.+X"#akƣĪa$L25[_"7ǺQh fJD0T.hPEeQsG_瓔 8D(Ш2rJ&gxxX4p Qa%~X fU˜)Loc$K0qB`1Jy~8ta)@!pPP!=.wqgxٵnQMelMp|U?&H! fόcd8t`B%œ$w'@9^<'nO[#Dzh6 /ȹoŊ59_{ށz' DT9 GSO32!L'_gYUȯ+ NsBwa_Sѯ&3֬7u7ϸӯ*m{E)կv}]]~ ^%pbUvܚev+uɼQMs=/ ?p!χ&Sx`a'Ļ CD j(u}w9*Hxxd Se0;|w*`D"]+]of mVv>GlR]>O5+o؄ڱwz#&Q*GL~u^ j^$X Njvnm /hjԅ{//U.+K`K*Y`51Tj#Md&@u5#m\3Bh|er<„~6oIU׼ƚv-]¼Ϊ;[Z7 .Ovdqv[w爡]D|");ZUVpf*n_THevl;÷ߓ'(A1(]/"\?eo]g_*qjՋxfXG(A@ O A h_㓦ذt}g{7Xac4E[GW6bO\GeS$xaugK >F5a8,y,0Y9W=c~ SPI͖^}uˡ敆5ANu(|4rj.T'˃Z&RojT<'> =B(z endstream endobj 191 0 obj << /Length 2088 /Filter /FlateDecode >> stream xZmo_!}8 ]3"ERTElwC[ԛ-f5zq%IpfHʑ7j#€EH9TL. >v?<xE(И@0J<4¶'[_Lf߲Q[>2ДdIL"_pf  _(ɋL}ibU$IvLnn2ph #e8 R7OWv-ŏkOz#ϯj`88u6|&IssWEwt|?Gdfu1mwcaSGLwj X䏝fu՚]7Fw^!ߨ}(K5LŃh"|F73`TêGI&yV4y>" WjK0߻n]m^n͍nWJW }h?sa\"n# /"@mA gyS/]zr&ꅂ% ]LiiE ܈~ WoGo+JWưTu%Lq;CvqB1L@TW1UKI,7zөB9 z>J "Fjt Li׋#!|5GX2$I:NG$4GwIwS:ȋ8hD9@"|{ XMq<\1:E ueҦPvmGDJ {{> v\Oqފ~Aaɱqw%H\V[cD2awa;ב{|26Ţ0Cce'[ڍ͡_s@.w#}<"=2VDZx97Jpo\MtLp|uI-42VWixIAKBM9Gp" B`# o ykf)=z,|'} pp((,V.8Bv窼ށajlfL8(!YGBXW4Z &,|nʋ9I*<ַvEgJyHG+%$X%1#q"O_vP6dʑ"wB{זּ^N8Lɠk4Ic\q/,e4%ƴc&A>)"߂Al鱍n4i=:v_zYE:_i!8c:^kL5g_wu\upFXmބB !r9_u7I_M/Ivw.roB!)sі =98`QU~:T iYJ¹eEwXQMFwicJW^sqX7$mߴfa|_UP;;a~}֕,h|ym{q3BDn>~RG'tkNE*v B&^]ʐ`ƮϠ%J[EC$0 endstream endobj 174 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (/tmp/Rtmpei80N8/Rbuild1eea6baa0860/NMF/vignettes/figure/consensusmap_res-1.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 195 0 R /BBox [0 0 1008 504] /Resources << /ProcSet [ /PDF /Text ] /Font << /F2 196 0 R/F3 197 0 R>> /ExtGState << >>/ColorSpace << /sRGB 198 0 R >>>> /Length 11706 /Filter /FlateDecode >> stream x}M%'>]J|Ŷd3mVf3f=][/]*SJ5LϿ8@-2]'ā/,g?=ǿXa-gsϏmvWnإ0t1vD??^c8?SYc3>GNÜ Bs~~ %crC!D1u[Jj Qcic1)۟kb !j]6cd?:!W|silջ\i!W ?6ERwɒUrvJc[s9B٠6Đ1t1N5ilS!?KjK# BtQc 9+%v<ˎ!Wi ^Ɛ+FRJ4!j9 Cb0ǘ9_c !Zydo InT){M=tCG]ypXK:ԡ'@޶Tz=Cjgkh9jOlT?sw]v`K؞;g7tΜާRK.T~g=톎u%aQ3:rS(.YБ+ҧ,:\ԡSrqhKVmcq PAp1*|'~nFCDʛKD|W*c L%/%@ ˃<\p 8O}uCS{yp 8sCgKjoMCɿ?ׯp;O,ln=s(> v ض!?ah@>wO>6K Z>K?Re\T,}cOuEY) Ye5K~/,ϐ_S3#~)o g" G_,kJ*#AkX[Mnͱ5s9{y=Fghnի&Ϲw)og&@J!C{X]|{C@~ш$H@@"d+8TZ P" 4F3@^͝R\isr^U"D@*,8D@4Qc@1XdH@@"$&pL$p ,$p %&p$I벗8D@ %:ʅ ^*Ajбq d&5B^h䴓E$DUF"2i0IPshm$"s(#Fe=)L,2"(Fd H6&$lb0 `&bM, IJEP&AXebEPX@$X@bbl d H60F&lbɷV*AXebEP&Ab&lbM,0KI,  `&L, `X@bQs0AXebEP&AXK,T-dLKIMENi'*ރŢ) $&L,Xfbi0`. ?H6Y3Yd!(xEP&Ab0ZGAbbLŢ60Z\=ˋbbE`*L,X^C&jeBmd!(FYdBAbLepBR[dHL,3YLe0DMKugbJ$pX4};ѪF#Q iս̇a,yLðӦLB ^.PL$VMj$d!LXڄ%&,A5 $@ $&,6a<6a5MX 1af̄%QX 1a $*,Uis|Ą%(TXÄ%(\Xj- KHFbL A$tIK>`& LXk#`M#qYh Z99A,Pkp,eB u/<67s#1Y6yXXX3fbE 1byQ\,K3AXb)"R߅$$g4SH|ȦFAbzǴ)8h$&,LXfj0`"RnݨXeb&"&Ab&lb0 cKI,  `&L,Sfb$}]'D,@X~CŢ "64I,dK#4$Ӧ4I,D`"fbi0KXLŢV-Xebk P& A_BSHDeQ,3YTj oe`@b @`*Lj1 Ma @P@ $@bCЬG&3f 0`&6ZL &A5 $@ $&L0MzDm 0`&3fLP>RwBz|6ok\~|>ۇ}M57MuO]sCe箎ȹ/|~7^ p/I} 弻[y6{p4m,fjCք6n~TXSi+jFKZ@^Q₼<&*/{eK{#VkWil({Z[vuHk65vn ca{[>,]cGeyX;)o.egS E-7&$LYE!z/~1lbXP1{pŔ1laXۯÐ1l`Xs.çtSG떼s`S)oaK)oҔI%o؍ߕu|&c(NJb[j ő)oȔk|(Լy7plywoAX\mtC W[֖[v.'}+~hMyJdSeKް֓m+.oXv;, J};SްΔ ɷMa)omxS݆Aa)om%oM[vNԖ-;߶a6Ԕ6J8վhۅc&Lyk[a7!,yW[=$'=Q}H8K}ֶG˴\`}s!n.\'޹DN74no_~̪ G1W4Q$ɬ@bϴw.$ɬ\/sDY?(ˏ?>}{e(Ҙn1[8ň9=ΔcU(Gdzҝ"˴^).)%Gn3u0SgV7R/eNԞ%% ґfʡ%Y({i:.uإJ:m3%*vI(n[zl)qwo:J q.U1bRzP2UK_7=Q{::SbX_S'ϔE{>))~(LٕkC!3mq.J8Ձq1D9ĪGie,swʚ` kvj/n$.ApUMZg#-.AjW}/gJ嗁-4Sb#͔CKڌq*Ht6SǗ)^iB} YSDI]4a~ۻ(5TǙ+_K1SfCN89mi.d3?T~_Wf1Sb|GʙwJHX6Qv{Q e)Ai3WfʡorRba+^f;r S_YέS*XfJRjs0^8SGkW:=|H;0QbWX 2S.6e0UK_SjsQU?SRڄ̔I Va+}(g)[ӣ4PiveeDJDu82S.;Xe_(L}ex1tm$.454L}OuE: S_)N^JBG)Q4+S_.gl] 3+_-2#3QfP[jZFjdc EN ]C uNQ'e}.]47rGh0+v&G:JRiOJlLq] VO}eDou\!C,?5~+3eW־8r6SbUzJq-cIidWWQQ3qS_iew,wS_)QtuT-})h^_S_)f7Qfi.jqTui t%PLq]i.d̔T>O@QJt]"Q7!ER%WL9ʿ )cr7!E\u84S4R.x7y;>."͔[JKW\œO/A_p~Z۞D$W=M_G#_1cQT]t2\1c4º|u9BacH˱!Wc b\Q^s !WYTKu[}cN#7/ePb9+%<}cNU1)F.8<Ƙ𡪼1)erKƘL>1䊑`nHe:1\1r"&IreJ#ntbG} )OJB*oix4+F,}i?\1x!CKcY)T>Ƙ(scN5Pcr94\1^q2B冐#c !W.S|i!]x:5$X2YBW>nicCHAc"bi(b!j!g|rxF-cCKc1!j9 17(Ć5ƜFlM-ƻ 0}n̖E=*߲ΰB#N1Q> E1eacwYfK!9F) ĸte$ǐF,}!.~dǐy\si+ Bcx1ze/^!~f}1QUCB|1TuYCBJJVbii!!W>}]r!\icilu&!!W]!3Ʊ}-k1$EyeNU}!!biFz!!jAXfarJ@1Q#C 9ReTCB414rV>ƐUbyCJȋ=Đ5Ɛ+>J 5Jw3WrE1uv?Đ3C>Yr}x ҨC Rl[ ycJcC%1".cHȫs+֐W1滔4ĐW14rzq1n2ǐW1n֏glbGtJc5EB=֕ z]0ׄ=tl9.JpyU`}Vbۣi&iGR,#u.݁gm3w 5eFR&]W!}T]o .;\)$UQN&)CP L<"ZU` R@zQHT//ʍ%iD/ e*CYl$ L2Pk1HL2TdSFpT2eJ)0S`2@)$2@b*0S`2@)$ L26*$ 6edRFXbGWP L &h0QF2O#QehY4j$ L/m`^ED`zHD/ zQFdKޕ2J0 `^@$^@bzب^M/ 1fz"Q/ 1$^~,C"(ӋT/^3$^@$zM/  `L/ `^@,Z^ez0 `6d H6"Q/ 1$^@bzE`^@bzl HT/V?0!zEPL/^S5$Ser)C` m}#Q~H\/ZEHL/^3` \撚l(g𑗠L2enFbzئ^S$^$Tx^^# `L/SfzL&2et((W5QF2lRF#e4+C(0WV*CS#qe(}V#HD &h0Q`e4*C$w:"Q/ 1fzlzSi$S/c2A2HM6Uы¥*TH`rT[mɦŴ%(ӖFmĴi M[ ٴM[ 1m lH6mdH6mĴi 0ӖFmĴئ-צe-AR `-T[jEjK HĴ%0VL`>ϭA)0S/V IH\Z^3Lre$ L2J_%"&s#1el=7 `$L/^zQ6HL/^^,׋™Y"L/2B/@A"z!5$"$ N#60H`ڿ6Zn$:JSe͏`-L[S?`"DE 6~LG^@^H&4KMzi$^ɤF"z`^ɤF2饑^L`L/ &zi0Kmzf(" /VAzQE `^@$^@bzM/ M/ 1fz"0 `L/^[T/2^(HD/ f̕ $*FP)HLSeP+9HD i$L4iM~y4 Q 1 flH6 dHLS i$6 4i0ӀT3 ilhM2 jHL4M 4ӀTmiM 1 f4 0`L޺(.,a-<\il?OU%[",JnDzO[R){"%r8%nQ7e0(O9-'.$XvXD,e7'Zq-u_DLNYLsˈ%BEsݤ%7mzNҒ,V-iĥ`4|˲Mgʒ|NejY6˞w}#s]R2+jS?@c;0TW~-Z_Q/G r]_Q6Z[\}m-yTڬlZVSsoYG;߷rsu2-#:9\ G;#q{do[Mc+7pL+71\a+7pX;r=ƍSl18ܴ#ܱb(ŵ,8rkZnQCMN*fu&_ Vnr\q[c*7pLEVnrqr0y _N Sc*7pL&_ SS|Ak*7pL1T{vx9i[CS9wtNvGNy#嶢\6mVnc7[Mޓ m7Ʊ2[}e)Ʊ[rSN-mN勭i=PVeHDZ8rvpoX|`+7p{zk[c*7pLd;-$_槿shV{E~gV=uZ{+7ݹqL훬}8=kD޳kSc*7p,cjd a&'yl߰Tn Znԍc)74[T?[Xʭq,8 klOkzm8 l+7_zai18ܴTnVntN^/u Sc*7pL&5lT\qՑ18rG/7I4 endstream endobj 200 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 204 0 obj << /Length 2110 /Filter /FlateDecode >> stream xێܶ}Bpe3^t-#ܯ$"cw7_.^E|-CNI(S$4uOYF".LhGՓ8y<­WKw',* &I&6zKӶW7_ovN`EOv+Fg-W%}~Ls~&E6MoE%l*Mt/;ʩ&ΞG[z3$aTER 2&/_%-zP=ڣU_2X5̱SQE%#[cY4aZ`jټ{zH?f@0N^^nk2fS~/&hݷ?&+usO6GCYp8pI ܔK?n`o$r_6Hؔu[3qg*5ۧњ*wL5083/<=e`N~X KfykyӗUW!%qӂTKaOUϾd몯=j "QJpB2G 2#(RȄ?ASqT)܋ `,I$IEAH$$I*Z|Z| X՘9sBRhX-3 ۩c?W^T FtuMݤGPz ƻDƻ;ؘIRɸTʄd]遮PpldT\pMŽ345<=횪u1[C,;OqOvw*} gqj)3BFxNYw|؇mlqHЃmZ/i^ȳWM{ﴰ.6^AVм(*?a }wiI#KNX+Ҿ F @@ڔ&}P\zܵX|Ty8樌dJ'ߥXM*Λ}[]fYlEO+s 컉7)"MV Zx(Wy䎽07{C ȡ }ԇpGnZ2٫a"% ryMvjy3#\BdkcD Ӫ,,CBӉkoLǗ31Ջ3bnQI?/8zt}3&VoA^ף]:PBF}`XP6c.q@RYUf=gSݗy t[H~E CI*9\E:P.Rhk <7 \N)e"OGn~hbSp  |i2h0 @8DY\`̺v4sѠR?-ƈ1vU>7`TỳsyL8qa7s÷N{H){(nmnC|pL u96 <;fEzK K{|Zo=f%}b5q{Sc{iWgW01ɸb }j)f'Wh:IJHw;sr!LrԹmr !ğ/SQuR|6y?9=P׻}w3-! ~x;M"F (-Μb|{|"?~} vޓC>SB[|iDFߤ#O3#E#ߛN}_C#w暦v(ŽY1wE]N6?uh9QN`O E endstream endobj 201 0 obj << /Type /XObject /Subtype /Form /FormType 1 /PTEX.FileName (/tmp/Rtmpei80N8/Rbuild1eea6baa0860/NMF/vignettes/figure/consensusmap_estimate-1.pdf) /PTEX.PageNumber 1 /PTEX.InfoDict 207 0 R /BBox [0 0 1008 504] /Resources << /ProcSet [ /PDF /Text ] /Font << /F2 208 0 R/F3 209 0 R>> /ExtGState << >>/ColorSpace << /sRGB 210 0 R >>>> /Length 18582 /Filter /FlateDecode >> stream xˮJv{heId4,`v ֑LXdwQ;η+5o_??o_v-{?#4*m^7w3жM6Nk5¾Cהw'uB,M!v(֊Cň{Qڰ| Y8}-!־|~)Dc?w߳ҖG~bz=4-44ȈioֿOֵZ!e7l08Dp)Rhr ́pHq:@!rCnO@,ᎇ-|7n[\y"f9*rU UPe_POQ9MTR9r@ Fz T"@U9 F UxPsEc[! U=1zd xflW̽`ovW@~ P#dPoMw@9H To:#s s󃱎@9*r*Tz4@9j`}UA<z9c0"@r"PER9j /`D@rY|iU@9H T/`rF UVX9#rUA<PTV T"0Vs+`D@9H`9*rAesLLLRj '89HFk*d$#7*^8*8Ir+ɈNNi}H_O5}L{Cǯ??|GwlW)x~!):[ktxNܾSt7yOѲ{px6w:۵m/}W50=~>޶8~/|ӶXk5Oe>n;=6cn;?6c͋ˀ3;n/'W^z=;ti;ݡ~./9Ew;KBWnfl;yt0ءގݲK5ww4m}7;;v˴wҗJO3w~vˮe'vˤ;/wwl7ga^.˚S[7n,Qq /y+'4ʕbnzawg`xg`@Xg`@aY!B30 j`~psїZ]48 9 30 30 30 MہSyp,5r~>]ʰ56O~~;h<//{3 ~_RbT.|xGA4.@O˿ϕ9yNmW._ [⪵0/~A q'> /SOCo":|k+25!,,X-|Av&4台vYCuA -ݎ/zDS]lo-⧸ .]~Wd۱D|~XT~d:쉉 Z~ 22 2^\Or+ғ2s`b]ly+VbyQ9nd{aY/@˼1vilp X?f"=.SxxOroDK +1Vك6/yfl q?Z+22ϻgs+22oOŢ/H[nDo[^l)v`+2e?ܚD45/]W.)D8|+7d$e/sdwMeru'2_|٭W'e D8_,q +18p\}Ap AB3\!#.c_ Yvne춉'3OksEz]}w1OMrC&]ʝt+˅q]yARb?L+Hʆ\n!w1eLSE]}w1\*O$m\!=.C2WnĶKyYv}>G^D2WnHK<_B Fu k9}StW'ۅaDKv̕O o1}*/se_v@ٷܼ<v" ._/!H}K\I .}0NvaW+d%ۥۏ]e췉ۯ<,/sey·lx9mڏ`\! .8~8\v"LK7؃".]/o̕28^/se޽}Ae3R4P?E @=$;^}'\z;{=M;ѷ;WD߹hۉHo' ގЇ;|z>*w Wt鳅\[|;3:qg?)7g}a}r׿O__W}.-(|hI׏*^J*FԊJ_oKON2'_IF@E$&U(⥧TM|g^ml˿U8p|*Cj9p|Xc9x0PS厇*rUx*CuProo򟕋 C.NCU~* +T_S{lg\LM` ] U8TbPZGxSu6TTCUކ* iKFx;UmS0NM2(o%0PSކ*6TmP-oo?/KUxvn` Cu Oٛq;O:# Ux;Umۡ*oCކ* U}LMXPSu\r1Tbb0P-.*\*w1TP C.+ |S(6TT!Z0P*o,NUx 8?4 'cy6TT o U]BP-/MXPSurTxCu0f) K;>UCg#*w|Sp*u|۩:8N{;UCN{vNv* Ux;U(oCކjyvPZcy6TT o 6m+Um۩:Fy6T`Sކ* 6` oʽ Ux;TmoCNy6TTo Uxm0۩roCކjym0PSކ*6T`ҭF8զӏvqQ_JJʛ֯[e*y~/C[LVi_E۹,NKMujCYa[0.jr-zla^ʒAo k{izSkY-kY!וu)+)ޫN>P5nCV[= Qz(ٳqPgbdeXV%\[Jx%\ɞ%\|'|Ǟ%\.{ \.Uy.QUkeEj7E.dd/YF- _klx}'klxkl`9kl`Ɔן[- 56~` 560VWcXʽ8tXcCѓƆ:Qcc'c Yc#kxmm?kl`h!Qc[X5q(rQccy&c /Xcc 56hgly[2rKMnXcc 56\klj 560kjrJ\S+G~Ū1Y?< џO6iEZ!_ʯrsUM99 >7⚣f{cv}tAJ0UV!}uHK+XWY#UV!uHGwXWY#UV!uUka]eӊ#UV!uU8b]e2UV!uU8b]ewXWفge*U aG@&ھú*U aGBVVFd;BX*VqNd;BX*Vqĺ*UYi*Vqĺ*U̚ﰮ aGBX*Vqĺ;BX*Vqĺ;BX*Vqĺ*Ui*Vqĺ*UʭG]ewXWY#UV!uHwXWY#UV!]"!jH]"u K+u`eQ"S2ś(:0"us[JT+u`DRu`ԁQcLy`T)S,c{0:Tk^#*b*u7nKU *WTHEux7 q"TT;~XIDV1)q*ΒƹϞ/0"_|AU{`v:"uPv75|ƺƒ W*_P /0*_4+|/0VX#*T+_xG`D|AUⳔwAemUoΜ[s[}Aw30W9ySuKy{`祿>}J{K3]hN͗?ewzmgۼ>2^kzq ;=˼ Yg>nL}ԙY;nh:3@M6=;g&ZUκh85ٳqy/u6>oMl}ΛY98ٳsyϋV =kg]kT{zgzOVh}rl}_0NW.c ,fd`aakl`d [kx%#K#8[n .fl9XcË8YH^ax6_rq]_Άx~7J` H560odydaUkl`b8ųz/v]%%Xc#Nȱd[>b H3C2"P@{2p ;kld Y?>klo Ɔ Xc gq,Wل>Z-M+d%j%Y oNV[!&27Vd%º;Y Vd%º;Y oNV{ d%j%Y oNV{ =d%j%Y oNV[!@;Y oZIv BXw'+=vw X1)V+Jx+uwhw'+V+Jx+uwiw'+Jx+J aݝ@Jx+J aݝVd%j%Y oNV›NV[!VVdݝBXw'+V+Jxd/yT[!VȺnI oZIV{ =d7B*uwwj%Y Vd%º;Y oZIv@FJx+uw a@&nUd%º;Y oZIV{ 3d%º;Y oZIV{ d*uwKº;Y oZIV{ +d*d`]BfuKJxYZ+nUd%º;Y olDNV[!Vd%ҕVΆVĺnI o떔H_>*᭐uݒ v!%2'kVHOJx+db]BVuKXn>*᭐uݒ Y-r rJx+'g%®\ aWX R!#.y-᭐W~u˗ݙ>HvOox6 kMjMjMjMjMjMjMj5&d5&5knoo6&f/wx he6v{nE^ۥaboҶ_'m6.V\<;?m>{m 읶J*Zy=Ij {oA3{-ado6Ԋ\Ve%7y oCF-0 Apkߍ;>4nyhݡq{Ɲ2De<J?@d$ȤDe9< # N*cRJKSO4T*ئIE<T@"4D* T}fDʊL"+U*Łș) 2qRO{,MEh<h*B?4иӡQoئ?4XB@mDD*'O31>/gSHDd8Y)VU@Y 9 ™A Uh*r ?4A r`Oj?+n"?D   y :!" #@ Cd0 MC<:yH D2<J#DM<!L@48GK@x~ IĹD$%D=lzKdq͗J'UTũ7PתgEBgLi2U~hg@K@D~BNГq'c''?`*U'U'?`d~? (QJ"!5+Q"ƲZ2k,@D֨/QY6Dd-D례d3d%Ïꠊ:"?PE~ʏԠZI۽R+AUF_E YE8q D@D~BęR")!Y r*?*f»Pw ʽ nYW Qx@wCnw8 Dx7Dw BM{.T]»PwSޅxkőx+ ] ܻoFx7û@ċT!7BTRAM{PwSMU{7UCM{Meyv/ull3֟˯}|n{ᶫƲI>0ew-+ԣ}ٗ;ɱ^rwv9v*YdM &ܺ=@s8>nY̶x .qv#ȵ9*w]Gm3 m6m ikj4<(pa> ]]4dd2YX /T;/֖YD )%LVA+XFEOaa 5. g`Xw#KIQc}3x_ wi50.g`x[u,^uuW2@@g`xJ˲@h䲿ĥYJi D g`x@ k^Opǽd ,wFO:CKƱY>'kF=IB2jl*ƆXL2_۾[NJ|ܦd=dklx"klx69,56ܡƆ{v56Ɔ{2jllqd>56\s[^wƖT%ƽ,1 M ˊ|9s9 sQ}PИ?WИ?ry(Xus?#{ &}Mo^ 0ҐvF!cv3b 7/f_CiX&&ec֣tcctgJoZUjN!V? c~X0;H 0w5j I_nswu+b ~zH~`_Xkh&*vy,AX0*veX;VU|k)wދ+b-}9)w؋5b|JX;_ж)wЋ5b|yt*Wsi;2:yF`5^8PS$,HNux2$ZZźNK*`^I.Vwf)w+b-|Ib%|'ZXI.Vw5"`]$Xkor.־wNbN5vbvy$X+; Vz6g{'XI.zB `q$X<J YI~yVw^kgxInyVwZ5%fyVwXg5{'jY^mg%{'JYINyVDO+YIFyVwP^>'HvxʖӨ6 v=0b끙Z[z`}cb=0z uїMDߤ$+KHo$ ގŀ^7zN'^+h]~U/?ߪW^׏2Ka*fBU3yLNgr &DgMDxeM"hY 2 ZҡגRu̒Ql{ SN~XEMc?DEYS)D "Z L,D%f|^TE&R噀*hXn8ɈL@u FdW^N?խ~UY LVw~mUxn?*7f*2<`2*)wciKFV|g>yLDc@c@T1+k@xBY њ0β58IwҿlM2V 9CuJv.';!B J` VgE!:6h"#|*9Tl2ΕdD&ZsJ2NRXI&]Q$"OJ")!( <)!ZI JJ ^RXI#*O T+)`Ij%%')`TR=ϺP\ DA|S )~"J++ , ƺƺ [-l /TyVE4 "!!cF NM#q <)!y-D)X ޼FkPż*9Tp?T5W}?T'q fх& F ĹyDh :5"U:gfT*MU*|U._Kaڽ~/f~2rh}QS9tӥ4qOO}Lkga\.iymD|T9,kikicMZ8ocYȱ.?c^K#G|ȥ^RwFZcYY5/I4Y\Y/Y Yh 1ѣ5y{#к5EH׃uiCSygik45q]隱.7QU醽.Wb6er*df\Zi6JJXc'ذls" &c GXcKFD7XﴮƲu[$r"8cD yi,e56g /FF8l2Vg)^r56V+|%툤D"w|l-6^ ʲT,Ӻ7[cãNZ2856Z+1W"Gk%1VT[Gk%1V?Y&K<1DgpVdٚ2]U횬$X#65YvMv5X&5ɝc՚kd&5YvMiہkd&]wzn$Gq>#1v# Hj$c5Ix$ Y$ Y$ Y$ Y$ }dm$f# 7I@H$FvJYf%T!RAA䏩At <}L \68L?*PD᏿Ky&g1Gq/6? jj")yRBI Q$%De VRФ,S HJ"PE&RH)IFd[2NL2κ|Dc( Q@D~B,ݞH uK VRI **IFTAuMF SI $S5;= "O4?yR L@䙀3"u?DsSM{PKSuj.MUt' q2ξ.x.DwC Qx7D]ܻ! (.T]»rB Fx]0wq nr »wCtTqΐC-7Cp^ :IF0 tsw2FG2΢KɈ\BL'PV|PACYȳ@"sD"; LƩIƩ8U\2"?PE~RIU.(2g"Dq8v QTqQ80")PQTq`r2Y ")yRB3M*Dlg N @L*23{2FdTTdL.(2Ѹ;Ĺ;ĩ<b$q],!H`3TTE;y,`;ycKF%}tob rдBTd,7 Ĺ& ĩHDE,'@\&2uu9<:3c2<é:U8UC3\ m+ݠ>V]@5 rqr@* UD+@x!BL'0UC5BFd *Z*U7V %#g&򗮿+ҠR *k3cnczad {yXk^?;q#]mOۼڄ1on 7)"d 76,+c ƆpU-{; HcGH#iۆ4r-Q<.Q|H#GH#G""G4r4r-Q" ~|޻"ϫxO~Vh:vU"gF{u&* u$* 8r|0Vi` gƏ:ףaUYQeYVp%~gdYÖVY\(dצXM `0ViմkZHjZHjZHjZHjZHM k;ݴͦo6-~i!7M v˛gƵtmmKy6/xW> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 217 0 obj << /Length 1565 /Filter /FlateDecode >> stream xXS8_{>lILr`]%gץQme[ZS|ǾW{^?ԤdIYn0`2IZVOgqB q~NtF'U**M*?͓6 zOrj͗URE@}{|Ųs%"}kX*2Y@HrY,Df1yVTіi5[j~9ߩ* Z0ŭB9kLle^vS' 6&r)/2͝\Ԓ|a0Ѵg5ۼ"5ǍQ-s[5bH?"&U!ֶMУUE02e7jahȦt\T@4l>H~}o>#m| 0{O6l{;N1'IԭX۪:9> /ExtGState << >>/ColorSpace << /sRGB 223 0 R >>>> /Length 9881 /Filter /FlateDecode >> stream x]AHRϯ#\v.ו $Ha5 oc"m?wDNfoa{5_t犨*}?#W?o?u~-ݫi?~ןW}y|_uX_> R^({Iakͯ ^eX^"aUakQظN`S} tU\aY_k]qڬ~ekqZnX MIaE a+s pmu-!.\] j@0\b "U @.4\i& Zrr[Y^è כTo z כTorz@zk@7\o3z;g@ z@7\orzf`Bop5P @7M3O O[ŜwSb;)@1o1wx 0:8)@7\oR5P @7\oBU W!@B\ *+T@B\ r-F כTo z `FoFz @7\o& [rzl@7\oR5P @7 @7\or(\o z ۦ7\o zk@7\o3z3 @7\orz307P( @PT,ညU8bhFw'/s@PBT,kt&gJPLT u@7d *4h(`C Ҡq *4ThР #4PAB@ 3*4ThP* *44Ap *4Th PAC3GhJ@ U+,MHe*i(WfC2 eI[8\@2 e* T(PLC4Th ДK5 PABr 6Ti PA) k PAB@ 5ThJ@ MizƆjwlhJջE6T$n U{ dCnFPs)6Tn MipX ThР\ U*4ThhJ*4Th PABr l PAB@S\{o,*4ThP* *44Ap *4Th PAC 6Ti PA nrP He(IP KTl᛬]V@6+bP ie(ՠbWUCն*b_P hJ[3w PABr 6Ti PA) k PAB@ 5ThJ@ Mixņ Dw_l` Mixņ P*4XbCw_l` _5{kl_ZǏRO?t?c˫wjF۷)uR2MoNó9Jӳ)J1J]+\ިCqon2[sf gq-n}ֶaA{TVE3=a-(8,~.Â*PTW82tO)&;WFy4{au_󤿓G]ny%skjXvXGߖ~PŰ?CY KA&N_֬lnZo*>Miz-wWY]}GجexMͩ?l-zy.CҾƱ>m0;l'e LF~%~8?LOʸJW_^IeYǏ~|9gRwJ #}z^RB]e]2(}7ʰwן)v]̤]Ƿh2g)žN韔L9.ARYi _e%2wCT3e?L?)iy.e\'\FoHpO@?E>$<(|Xx=)+ic?d9>S-ҔL]w㓲vMÇk]i5_σ2&h<)KE#NMoJڥ{xsyW <OD:z|%,]j.`އ׮ϔ/s'e ⯭8~T.)1~+wJejAGa 3um>V/?,]0JyWOe:= D8A;{R*i$>s~+wJe*S+VGan})t gk,]Ƶݣ"˸v֙yyRF.NF]ƣu~RV.с!և,=])Gwk:'e"[?yȦNv[vLG(z.:edc,̤Sh-󓲒v뗇,igJs1}}R&"a7JyR.C.ݣe߭Һc[we$2RMk}Rf"uP>-}g"sw-x.틯_2}eIYX%գ,{K9~1Xv"cZcwYشdz}?L9z`XQ.e>l&32v)l8'e!R9R=ʛce_0)GwKc(#k v|1Vn6ŚoE~ב?Qh{XQ.1M]yR*ie\?NoC1ce |]63>ʍ2vrc(3߆c;Y )w r ]Zz`XQ*k]O-D}gxj(\S]]c+7L v||Q=r ]fc::>ʍVc鞔;crյc::v˓V4~|\u݂M(h'՟nz:%c]p' NtWlHe_dy6 o&nrf\l#-%5]kLwR'_⧽Ҡb0&ljz#UzNk3;i\HЖ 1vj m߯ިۉChԹsmt?K>6_toOotEӧ7:-ҷ{N7LN!h<>oi/OyXwx_e։7{[ m.8mm2e(o8os:Q6m.i#297 UoR5^x)^ʉf?{a+ ^܊P쥰(W*?{I,BbP\"?{!-?{-fN?0yC 6jReTL}x.'JU^9mŋ1T)M#x1'xy0BcR<ˏY*As/'*2d:gbJܙ SqTLT+RqTMj⍣Bj:gVsTf Y SqTMjh_S*T(PJ@⁊$Bۆ$JKU*GP\@گ)*&31Mx8 /DcPqq *6xY8Bŋ14E*Tl(WqrrchJŋ14ar **Tl(WqC **Tl(W1Д*R18B@ *Rq(Mb)^Bţs\ţcPE*Tl(W^)^̏Bņr^PERERlP|b]*bNE6k[{MY*O '*䝠,h)8f|NMrMq|3q齹PL04 ДgQc>83 P[>-zO*8ZvrNT:krzQ|zR }zQn(w^gV?i8unah3&<'lzC1N3Q@31*2 14pT=Ќg=c m;P6#03T[wZ3Twh;YDgh{'*{' y w{U>+|\Tc[2T9xkV'*|@U[5+CoڡfYQgoPNy@xS'}P)_>(:)y9Д>04Q>8)#|`h>8@ >*|P)#|4僩E@:S-8 Pg5P:wG^{p UPd8^j >0AC*)T/)ДvT/UД&|Pz le3Tzl }P\6e>^Z%(U>GPJ@♇I#oh*T(P\@I345G(hJ2+CT2x+C@ *~-_XZ5 ]Z!Cw hq׀w hq׀/-gNqW;/ex ky4q?ylR׸x^9aJr8,9,7SHI ''o?r­3ΟjylqE{972Fe+ccKKc~՗r=XMiOV|TW`7DU >#y\ƽe҅nj{ųit{sό{҅㸌z{c{K GJ㝒˽±gƽEӅڌ{Eӥq%ӥqӥ|e-.۴TotƹeҽtvD&28*N&̎H6t9Mg/nj[~#|%Kq2'U[\malIV:Z[chmϩ%Tmj'UgF?6ŁLqTmsjfTmj'U6ئjGNmGƎ5N608ڌ ²M< ,񔄫'U[G^7rqRaw6pR  S#zrpqR|`qR|`-ߧjG6N >]|ږ?{`]_3ҷ9XgU|k`UCX*XguUuVŷY:[묊/YE:03u7J4nh`ݍ ;)%XwDnh`I(%XwRu7 Qu7J4nh`ݍ ;)%XwDnh`I(%XwRu7J4F(Qu7J4D`ݍ Qj0F֝x`ݍ Qu'%XwDnh`ݍ ;)%XwDNJ\lRuFJ)[gTlRuFJ)[R.H:#}sl6|=uFbl_33ngWlݿ:VKwMa/:{:cx!ͽ8b{V˜8.b't5Ür0nұx7 -]|c 3{̫j;h6 4k?7&޳<?o1vp=ZǞ#ck6w Sӵqy/PL_Om3"A( (h̠pX 59F7`=궂X=6`W.إ?1}հptmmJgr"<PSƲ @+O(r cyP_̌Wc' &9-3^5U2W<Λ(ꫢ|?CJ_+Q(W<Н.@AgPbj62Ebl JƃS `cd<^  6Fƃ`c<  ("z&x( `Lz& ,:d2nk ep6Pnӟr(m2$"$Onq(mle|pbpgEPU(0g*N%}{cd<܃ S׆{a5Fmr(mۘu@$pn#%"i62ۖ$Iݶ F2+2A5򠮊{a08:Pd ܙ dE_2F8 @6m LAjm1W+#o:}W }0+`A!D:j~F|FLJAF;`AJ+N?BOvjz03?4Sxԣ4PVAcOdE>"GpGq4ry8IPeJa+OPz03^%1`"\pUS%FQi*jFɹ Qr.rP*GYU@ U#5[ Ly ׆A(k T^ό(kԞ½P9*}Xe* * 1V*\Bi8X 5PyMW#N墮jrSW5P.\0JfQv8Д#%\eP֙;3NUSK|சϗ0nq9QH J5(܁3+Nj#8叓5@F0pRE{0)Ug!`k]JtJHytS02RWg`k7u ɽƓýVFCbo VEꍥsf EfPl3H9$3bW5aPC[QԦ|b^T>Zf3RTaUNjNr;3r37;DH<J)"SE4N ?@N4pĘTxh ԘbP,&j@Q#)⽕f\e~CPRik*TfOɸS*WQ,*;3!~Wd*?圌QSp uExHc X3p P)Y;9)@SN)ZSVP8o)Y(Rܕ6z틳0⯴95(Mxkj~)[i34⯴y5 oRd3 \|fĻ1~J#1{tgƃ3\3YG=opDrޘ ׾O-Sѱ}d^xk_Ga1(8ۏ~fn~>2c/fex601w PSM knjNbAGˉ G\9K j9v4j TM0a`=q`΃Nsh$sF],6G jU˅gww:ָ[o\xqd1|rWK+w-6l=%l:Z8l<ٸ{_H6ҝݽu^4ήK<ر ˏiE1HU8)SNQuAm2U;La8/$SNDPACR ݦd) og 0znJ) /ߠXTWT0.%O0@2]DP23m)rqRὃTm]qRav6QMJjcᅶUlDv'UvjΨLaTmw)6Uߧ6>IՆg6_`Tmxޟ OKUm@zdj'왓nTmxN 6rȺqROm]5U[|-{B̼&`Tܗ?=s?Bh ֪o * Zo *M&ZEeJ1־JXbX@,XoEo~Q> Ⰴ_/F4k8bD_/F47z1#z1Ù#z1(׋1^/F4bD^18?3Hňf^hԫ1pň^hňƼc ^hňf#^/F4bD^/F4s8bD]/F4K8bD]/F &^h1pňƺ^h+SuL׋tDWc@׋s8Wcpň^r(Wc $׋ rWc׋Mq׋p5p׋ p5F ^h|ňP #z1ѭ#j%z1#j5z1#z1`.z1#j>z1i#z1Y(֋MjѠVc ֋iє֋ i5x,bD#Z/F4S8bDY/F4bDYQ#z1ѬƘɬYz4/R.-!;r6SlaPVpNrhxSC"E}8>,}s4V/'XmZKc?߈0cZ;,՝aRv}ǯ?OC/ endstream endobj 225 0 obj << /Alternate /DeviceRGB /N 3 /Length 2596 /Filter /FlateDecode >> stream xwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~ endstream endobj 228 0 obj << /Length 981 /Filter /FlateDecode >> stream xڵVێ6}߯УDxyMMl-2$PEc$m>I gΜ)(eWχh *.kBayi?O,ϛ_¹cuvƼa?~JcbAIj5MRBHܰ w!S <<8 #{Q߮UWAR阶ݴm<MDG&?L*'wNJPMHe@Mo:2WpZ'_ilxX*P['kfD7; J*n9*$[OLe[l%[?~!1́Ӗ9=i ~״;lh}׋^&0~0f^ 0EoL.o !X箠[ء'H@pb 6GQnBo]/%b1P\7^:lDn^}Y2$f}=*咵|T> )te*v/Fx՜:X2麟!ּ$n`멧 keZԮ;z`)35֥ݥJF{@׆υ"z.\[Uy%JUŵ؋50,,h,1]Pʺ? \x`P`Ԗ|Su*yn3SbZ=unn1i3UKz!|=W'g}U/ܶ zk[†ٌH fwú`oL\Hm}m  gX}E`7< US72!B<ĂDŽu!o)B{r)ٸ-4RPGXS4al!K>P zC_exsҔZ}}S2%1M^(*\k%ǯ}1 wS3,Dw!ys7I& endstream endobj 253 0 obj << /Length1 1584 /Length2 10280 /Length3 0 /Length 11313 /Filter /FlateDecode >> stream xڍP\-Kph݃784Ҹ ,hHy=ޫڽƴ5eEK*i6;BX98*Rzn6.ZZ-=o9 (/ i"B^ UE7{'7O_!!E t WZi5垿sF ?I9X^n446 `@=<<؀l`+1F r,K@ƆB вqS x]@9x xrXO_pq'_޿8 47;8l6 lO hh ~mf/T_*>Ws'+yi4qE ^ 7q]HA/?2+'9@/r-~ |)gc zCq7ϿP896/be.6q8~s2za?F.$WQJI=>|V.^''qԀ6/_GK0@t_wqaw,U sAna.,J赛z? ?z_/ul eTJ-W삤hdf1.ʵ/#H jirrpe^N,_)he\/P^x>/hv6G0R{||vߢ??]?H $A/lRV/_/|_"=dž/efVx@ O9,\8Ķ:Rău{Ttv[7g1cК˵d@',Õ"ţaC-bXczӽIvޱCɚ2dRV-Gg_wv mnj7r5=%Kgw*PJ&Yc NeR!@Xɐp<1prƞ)Qb?sxiq^ OH}P$)&:׿ҝ8CDFR*܆KӞabc> A -L*(2" }&Lԃ`n o?:ncM#u l{E`/N)çXBR/y59'1S[c-mP7轙Z^xJ+q,VqwR=yqi@b +6%D8cVq 1H#J>h*-)d|T'B}on2I址YT:KęWe: ݌2eRz`hv[ S#*g PxI;ٷGx}5י"d3I'Tu*M&lj7Q Ooݕ)Z@nHgα": fDUz2u>JE@ɒzd gVQ`#:C*\o*E90!4nϋچ5;PMrʧ.#Sh!wڥĈC/ 9NBpZ ^h; L`U`hȱL3fxP&ŦtXjnrr٠z9VFLN2)+4}aK/v%u`\̮ue6*;ղBK[X qx{*fRN+8+lՓT T\M\_VG al4t9r w0j>;g9d_,l؀l$uh7G cl2%WL7_Sq.#D33\fVʬ=Wä1 $٥a9U`@L7g 5.J}G5mg\DQtsKt_{S>:|CKABW_݀vgB˛R&h@2z4E&6I-sr`kKa@'G/kj57E` Y&dmێN;[ij 4N}~?06V"nMߕt| bQbWu2趎 T^xza$h27ty]Wvb{6Y͝ GXPxчf n$_OT%XOu\S%ȧ{E{Oɽ5YV)e}S,%esY?m[[m2ElgvAiܬɑ).%ݴ7k r])PFyNsxijܱSڙZ˦ Fwf)X\tq ;koJx-,I%ZlV"xQp35v=~V" tqz.ĖGekKˆ7; <5)??-뼞1՗ïc}@I;=eBh%PU Vw/nz(H&uffԷ,r:`}5P`+5᱀pیc u芕PKҧD4>8̠k/շKz ~ALMSȭ4ΚA5v\LK} @Z[ '7Ł/q$Rv67avhdlTS )rve]nEe3_zeJ[Pk᪭p#zjt7;`xcDT+֚jwW#r3SQѱ+km+Я2 QJۧ.l3 =tʖ886(NiQ}.h?V8fve XTO}޶bQkw-=+kh䯻$A GBj>fNJ4UZeVHk]eQv&ؼ[+ lfp{np+:дtD?Yj Xu/u&gM)>`2D#P>,OTkDSxc?9{X)3y0~=+8ګl L=}`gr1F"4e,oZhHy#D#is,9(&77){Y>JRž!CxW\7e G|s1qяkDh=ޑ^~:]]Fe`#`JPaz'-Bij8wߡ[/үWf"$*' \눃Ʊ'Jwx5ѩr0 w.{r/E2-(ສ6{%I$5O=.#l(cE'ZD,JWoN еZYUČس[>8All\Qj>[RJHF!9@^2鼉#W Ye<2A21,?`ԛcMDʳ4It1KXZ:/}/t@1SqP)S^{lV5gyUxI.S\إe' j}轍p(q7hΎ K/]eFLJʇyk/?%cD z~hzNRq1/z k\1kX<O$CۨJ1f*[\~- }ҁWzӡ1,ovǨ8(7aU(Aס4+*J~t)c~E NBOAvLBrl L=(zfbȔt9&)¢7i.| q'kY䭵E]]ϵv#s]?g$yZnCiG,sQ9ju,$_ٜmꊼO_gq WJWqDܡRmTfch1!-MpK2rҐ׊)]g'tn*:S`1 4F_Xy`B4:^Z ^i'DT%nm/>Loӈ[ Tq)mխ< sJ߮:]{%`/sz1C5"zs$?"[.Z)8Ns,e!\?)60A9{2E;jA^9Բo6uNnu> 󚒨"6|Sת ;3Tmw=0<ڙ޶}=;"^7DJC! *k"+>:W";UKi'hRwr$ pӷ+?ie.DWؠr]o|| a S93& Vfd˶:Op.5M*͆g)% C2L3@Bgݴ΁'^90f TRn'M-jT/s {3z2R(>%݂e,40QD2}fHH"t: 5䲼inzSh:gF`f56\//8YV{fD{/^]/Yn K*tEs5b{?+.u*d ʟi+@uNΔC J /|,jm(n~Wx[9*qP y).~[V QN.Gtvܢ-B*MUko<`|pdƫ(:ߝIUղP3SVIw,fHLt:&^kΪkvRsldb0I'iоVQf&f:3iw}#rk 1'T_J1`ʶTGo"Z3$9P 9R(E[]AΉqd G,|d=Lp|C.Sp4 AYgAW{"*>K2pF?,"!DXN{z̭"$mBy>~<͸uI~6}㑧b^k0P?#iXxA[4uf'4s,9R` k2_aGXWAbL DtOmâsXsYIxmy m1 M| Jb4!! OwIF)M_:%CFhϏի֭lƔ]%A?~s͠ FK(oN0Kf@)-ȥYjx)0~z|KP:ӄi|0/p֥l:t8:D Mgma)qSX!b'j^"Z̖wҦ}B5Q'N{a( T6ߙ~} װ{1Y"]s#tdG&HJq{䒔VtۏhFbe~>Cu^[(' O˗IsۿfDav0ggIzZQ/.,y+$z1^yvkpι=%ĵ/lH$NA-XmLQ|B!8o~>'s 6D0ajvkg? (16C۠9 aZ$\,eYwedK~Dȋ}1xBr`vL<ڝ =A@(0=Q@_*6KL$irclP] z)(2wuj9>8!Ź$7?޶&p4ٷi8.N#Yۭ sbel}}➑`Od9znZ|#WVw]b=eu2J6 TAo;GjADvnqt:sK1E'pZiQ44*k1[n\OX)ƿ=dŖZ*6@p?#;[m3#2 o bX`[.n0j=Sptj&=) w}tx۠P@3`6I P+!~ھIWqCÜ/魇«9Y #6>Iw-6/zFyunT` e$+:V qV)"@kY/- ZʗI1u3GS9Xl5ՎNe7n;+zT-1%OcެiIfךvH ֖K֛< TH,/FmhPŰ'[\!-91dw7i5j ~atn~uNW }̱2-Be>T)s-N OV賬[.r ޻V ]#rl';;SOP%#[qBp205*.B v]GSCdhc2xJp9Ga9 fg?ȍ銭9Sv0%Kb?:a`EzF}1ْKƇC.Q\nSbޫ.'RzѴoc.Ր=$B*4OCh߲X/$QDWQI䈋.ܞپ&]5 \dN,X 8]%g&KSeu=$^D0u:AxLL0[n dms@ĝO~fP> (W>oԮNh3';Kݨ?"Wǥ>zrĴр' ]q-$4Ώ\C-FD73ț2[Awg"ȷӕDtThOn@xSS?'O@DmVGڷ6 G?r~䧪c]Yqꉂ{߄Sc?MY%vR'8#ġ^)y|8 %aTDR:a)6@NғlP|p"qiא;.A+(m{H3}-A&ct pi3֨ hMÖyEFeg9uˮeZ5\5=2#Pp,wI<֟=d}:E4L3|>:L}jrtpJvdwOL Ykɛ Oau:w/ӁܹA pLkIv05Mz,a*VLqk0Q}IrgְH}T2hA* j5W2Ō2&" ~qOjјΉ*s$f9> C}gʼf v{FQ;LDu]^-O$jU-CceH}UH`5Zhch@7^I޿%3V2@}u^,0  $$ JUA7g%% =Lۅt5Ǖc:='\*Cp3)YQn$aA&xn]* X|a}Zj.-u!ۙ(VV2<JLX SK8)Ncb/\YV( endstream endobj 255 0 obj << /Length1 1989 /Length2 13629 /Length3 0 /Length 14843 /Filter /FlateDecode >> stream xڍP <3;mp !H5Hpwyso꽢jղv{(HDL. ,̼1QmV33#33+<-?vx M _b ˛M-P j `app23XyY@gx 1GO9 6pL .@MmjV@tqqebrwwg4sftYܭ\,@g h-hlG#<@o1x3ZR\̀ 5y#`4_Yllj`hlieo0$]<\fm݌lM* )0~S>gS3oLi,ao&`gwq]hwO.? 3WG& {+'W?1o&?6 t=L-~`&ke|l\v/gaYLV@<o`oof`o'+fWGo6+  _ec`+coܷ>df/]oH/??~c;+["&m vj^]Q[q1~{ YhlbjmhV@egO ޶p~ɿ\#%M~o+'2g~%V7:=b[ M/F99LM#. `/b0IA,&?$d7N?SSzTXxX [ cI&7f[ 2{yn9~\A{ |#0MͿ[o@7t09žV:mÿ[ gy+7۳v%NU7mo׷gM]AGm=s|!!w" ;c;Z4 ދ6dd/Anԕm k%gæZDG'xɝ Cԅw}|4m ;d)\s{R^h B;Sl =0E0?cěl\f=gsy TrkRqLFY' 8$x.q.`'|SϸEC}z(E!cPSOg$+<5T\:_V8iC`i׷;7 04u*RfR*Ors]okX0KI 3(?ZK/L7\)mr!1m 枈=\ܺqN]PÍbg?LYNnh)\أOP|%75ye~/h F6"Iy *|$ovSѓSaw[ pt86q kVG[Nb0  zgn{?J.>!ddMh鞧MRP3.L.pیc?wM$ Q[,RH&{{ŒvNs QmyӢ-m>:AAFb~5ץh-fi,yL z~d13X6Mo@{JܡTy] 1s)dChwnL٥/:ʨ4stJچ]):bkm"I&lV!Au(vG^ɪwO衽^LZ޵@%Y&/Zt2 3Ę] 4ro (KvE&߹3ұא N5 h Z%"*\ dےLǾ1=+ OP<.]s܎u,lXTʷ̚v>.[;ӳ#&}E !QDOu鶝-9ݽEPL|3FPh#}E?]E|OON^vːp,@`ޖѓV&JJݸ{ܮ:̍KG@~vY@)dkU^uLpUݳX?y>4:saQB@I;)Wiȓ۽aܞJ(F\ bHxttð5^ѫT9<N]Fm )tV]&m3R"鉼 S7$ԱCQ-k 6 ҵ$U4{!CQ ,XƼ)*F¿VSyq/JgW ϠdirQVQןgL^(ˬm2f̀[JRXg)³xWi)54ӓ0}{Ie*v`SݰO!'amى*S>GKzF;|eفwzjݹ[D' Qjk!FHwW7R?rœS*[]eWk1psd@ YBs5󐜔OYhAu<-!KEI[!@x GO$G8t$"CBlh\)d$]Lrץ:jVʷzzF&۳7 vQ8[ tQ ԃErlmmU@ZM>1)& 6%{RTdO ְUiƐjnS߬~ջMy=BtR8eA#cNyw.Y&WVCw Wؖ>p6_ljҌ4o}ߚ~?"ot trpy#FNdx8_oJuꞁ\3L 5dͱ)Wv[Tl\S(]p3Vr^~Kٽ }0i$I1^H֚9HM^( <\Z˚>Pa/y4pV*ir֙m,?ϰ9kZ5v>!Kx襉B 5?+-2Wwah-Kf(Z/ j&~wk$o~ i8fjs1uGlo69KÚ~Vy۵Ƒ!BG <E q5_> iBX'J  1}֬%&0 42A=K%b+@,a Z%{)4WVY.:)`3v;`KJ׭4`V/M }ٺ\3LxXQpPo-NrynlZ؁ܮ3bȐL<.Z o[ {{<$W^sL 3y~7["doyΚnd͡8Ca΀>ӐiYratt)b2`zFwX6 a9,rg5? yo'=0)Kr(^/E5YUOkI uq}D4S~r:"бA0X~Ԃ~<[=kn][p4m{SAƊ ,[sn0,+nB{5\7X$/uW]ֹ[JEm&;yM.95;甧ޗ*t: NtMv 2vERPe+CfʡF扒V Ō<ݣaW,CdMaZiJE}ÃIjyRHLyB S4,t{c.#EE3V=+Y~D*1O8qvq( G2K~!Z;(j1{^]mר1_Cl ܨ!̰z`^٦Nh:&[I? H[lvE/ZZ- _؟5 J]U;)0ď'䳜?xGג#{Y5磚 eQI P9& Oݬg3CF~z2@^a'(=, XhN ab*+t4MFPa)PBye7^0pVXT =2'υ~.k]ήa ApfvR_Hn}#advESj~v=;sɕ c;iྣg.legxs-g]2t'&ow5sVQaB߭Q9!T!uD I@6k/%MgڮJ&SDE?R| sm:וOʠJՊYp`xڔ9.6fX p*VScߟ= ^I-nD9E̴O0c%|Z lmU9aB$kfҞt _V)T` pVz3(%>P"~៧U5'g.eT[xbOl5~v6A-U)KKO:;%P/]ع$4# s]iە0!p>Lυ-)fzѩ3 Mx`RS浅\ںوQిд8[dEeNcHj|jDtxoqۻ|1F%Urx>a%6([b.5=k|tZ˩opt&3ZJߵ~UOzy7vGYX. L_FOx8a>rhSb8>K;mAΏe#uX\{}ޱ HOKyLKړ;6;T~5Y , 5iL$Er &yꛔ69"b)Rd:qȖDMDȊPt)$m0C1)%# F60]@1`4!'V;1̷^_emlA{m&Rшr_hz57"v%;M sqHZ<=vٔϥ`F 42"ån-X7(U}I!^w}& ]{0B`uE pAqW:+QA9) QazL8 e51́X28!<u4_.{\FJ5} Lj/ LO=H6/9aJs4 U-)V/ܸUiAw'07+?qc~sUSEw`1i9h+rT)@ dyogخ1ecҘKdCpJE4e|p^Ջ$'*%'.a:]ƍx6Μ%v2.̀D*ot7HUS-ds'*2x0,kUۇAP8Ц@jymyȀ<#()fHEרqΊ/wR W#Gg?EWl5(1a;>jTkSɢA5bM_zmK)괊 ! }﯄H%rf]شy)3^z-DcpKaͻã eD!dĭ!RF==y:HKAEW|KX` L :jnlK^${RŎwB|5:Oc.Γte< ӘN9hǃC=NW`|B'%bCZ.vr `H G|ob25;Cfaà )*:>IsBg?CǦFa/%<of+NwyhON#i[Lv=Y/ ,ZOFe>AzI'cX?Zk'6-_XZ턯 !v%@1?%,r0q; `QK1Y.J${-jf[W\!^GClšwbO' qd",OZJkOJX`c ɲ_{@T9 Rd ZsVCI `񣁜4ZsaXt׊(<՚qq io6_Y>4b$O9q Z 5vJ`,^L #uaw$lYuViCZJqDz*`@Y=N&]䘊w߭;µa-Y<%#F2lZnK%ΰ/lp)< \E EA&9tȁʼhLSS"lǐAt_,hEa s%#EM%ў>*.%NeѨڈmϝ}c!ɄN,e:ғ0 \3M_=x aVxR T91RN/}G.#KZZ[m1qnҏDr/asaZ,9vy :]Mm :qppʒj /<[gF 1&#հhj7' ܈hj}8h۳~Ӡm*0 ,T1벥goZ̭4,um}N)Pωpļ$Ձ2"Uɹj˜!VEv7Vy"2th%RQjiX =jL= p{>M2٠rjq],)jGҙ96xfd5#!+P_ORKϯ{P 2=+lv'[:#,i2HJ_Tx>G1{׉և_n<~͢R'cOQqCID»l !b?.MA|JI0NxXF J-սUJXAFHOY4 yFtl E) ,SyRNl 2 Yg>%itx@$%4'-H/!A:WQaF]7[-Kj¹&CĕmhI{č:1= Af\^S&w–R ?Р\ۂa*h#- ~Ƈ-C7O:&pj:GTXBgw'\Y hd<3 Oۊ(AďBC?U7[sY4hoC@٪8y|\܄inb\z+ tJ~ YA giYvfuV_ : %G^.Uei[; wSc$Fb >QH.G>8i1=~ X{Mf-Eg} $Kժk!'_,10r>D-G{ gBQ(kgN}U'*m1f>itwh5kAnft;p.*ѨB|OJnHz@z,n+c׌Hwҕ &Q7NXVïa%bNzr#$zǦLt䃪D3SE19G#"_!50"= f o:5'&" $w˯nqH Bgt]"<> O0%sM~(J:6DY DΙԸ/9ik2Df?1ZCnMR:g- &sG`z UbsWRT z㌟=kOm]BBD ʪbyVymޭJUcBHhBKj|nnq_)/ Xyǽԑ].=3hm$~ fc6;ܝe?G5 €t:-b"Tc@Kft6_q>dsZܹ<ٺ5U2˴:ap Mozk*hd#;Ai-0H=&#B'upRML7}zSM@ Vlќ߻&"Lmc'xq^-z.GBO6g`KBJ2Ws8H$eQU%TtqLMi,+b"E bc<]2aˀ ԔE3\|+=*-ۣ f;FV8ήG%fJiS<G(W0oa« qk2$6[4Ji `n[# @JG|!I;A9uiNآQmgwTx`Q6 Y*R(Gy;LpjSkq4qoAxbOr"[+bP![ÊG %G7qv]+a)o uqQSQqQ0IizFf/Əd_Q%aVA0[8 z-B|D cN(lƲL U )BǞw /U+&6vP̓+Ǣ_:jF 2k%d%Oo$ *Ҥ*,FciҒ %s/E+^6.[q`4t\m[RMȜܽ?{@5|G, wՀwX#Bcf%NW_B;Kש-<=os0mv\g~Z+V #?e 5WDU\[jwƒlҐg 0t "A\8 ,y6Y& ?A{vfb/}#RJơ-I4Ƃ$Y/(tܜ_/˥I1MۗI?!d9W/18 M8,B˒XOzŘB1Ȯ:~$b㠎x|*Hz]i:]Sp񑓮a:V13aOkrC.W7r&k4Ed<l Ȧ$!F?q`ky[=SlfM#]ϧ/`2'ldA:rnl[ǖw?u]%^PqCțMĹ;rmWLQ^~Crv~vADAH_\. ?50F_?KyGrb,-Ar/@Bxi[V$RT[dv]RT5z7s 6QU¬3:DŒDEЊѹm.F<3@4NlX?;v|UE 2B94GĂx@z("ƝU>Cdh6 ݆d 82C4k`)taj]2?Cъ.I7w9\|:Nb~<_xvkU 0<=Y;] +x 9k ՛^Xr$Cӵg~ z$Qn>ZWܘB.3ؾIbu*^ ~KPRU>vAVFmrzz&`#Pw׊޳/TǭâXFarg沺ClW>|Tn]$! )#*o!SZqCUTR_5)br$%=qjG#\W}~#cûv?O1sW 4BҀJ5!?e&5cu&UQ9g34WsدO|:.$O #vŊ_L41!a]QN]օ@u(PMPch`9,JecW 8pyd9:@<.-߭޻}E2͉DM?r؉nD+YJʝp~mI& l NXr);!$ : [%-{CV="U(NWZ1Qf%QvyRBW4ؘVy^Y%~ q Wv+ulc js$P6f$x")%w}6,peM3,~32sRY?< rORV'Aи@` KP%5n?w"Z۲Qkfmy*d膄]TE'sH "3[4*/Bjao jJ0V|X_u2œo&O7ڃ/r4QS@^j"]3_IegS9܀%%ۿGTDYTZ_xG>.xGzzrS-bjypf@r1"߉p ;(~FOc`,Wh>22ת6$zTڝ$U,Woj=3U50+ % *yN<|qؙyVBGWl4LU1 fQ(~M#Mc,Xohˋj$ يn*qyغjS|I8A8z2p%3*Bap")r} &Hd(q/8:icmoUYĶ;!.ݗtX-%T5An&?l+uyM]{ 1CFG?_9p.OCZۻpCXU?M'0T oTaӚ=2yG8d@&{ѵQԦ%D8DXI1z)+MZu~Pߵl^7՝klmԧ G@ }319Vs{0cI \=z_d.$YhMXƋ=Du9I'&p ޺cf8Bzjvkn縚t548O"|t'zl v0_b 7P(!S dTz"RwMpA f?|HjWe}O?iܱN[E GӆդQp\ZCP.%ixVuH1Ūhd07? {gn?TwԲbe37K\ZRQ}7-9,y%ӽ*-k»ٱM|[~AF~7v)ie]6mތG["AZJet19Ar vۦr0.ϪZrIefYg\kU+Bi<1(23' '|F`>wLzf٠؆Le }` FVeEEa3gwͼ񅹆x2#&'L7# }e$ŰT)k!q](Et[?>z!W6Bz8Fyh 5pC^k^)%4zJ]+vnz W©(pR&vR%@u5Bq%Jo`ԋjpjӆTȢ:=, w媣d_㌒ .VCk9n,7DLTLiYW\7SC$SAcE9[]x ZP[[m+#և^+&`vj b=DQiQ$!|n͒DČb5> xxMʑaFt89ne%^&|T-J_sA*$"X K`?;R,0Yz?a?AlD$lw阫.=`,<>55-NCl K_KNxC JoYv8!o=ƫ}*UfېB^Qb=ÏL=$%rW}=N=c>]8O endstream endobj 257 0 obj << /Length1 1952 /Length2 13355 /Length3 0 /Length 14566 /Filter /FlateDecode >> stream xڍT۲ An]hq.݂kp @<{FAj\UkU$URe1%v ,̼1yQ-33#33+"%3" Ahf7v~Ƀ2.66 '/ /33`G^+ ) Ke@cJ `; b t䍝-o+T tvebrssc4ub;Z@Ζh`-)j `sg7cG `29EؙoT@r #`cSS`%ݝvfmoƮ c7ߙ$Eo<'SGye ;31- A@ӷm`dnv^ ;30sgR9Cy3!Y\\nj=o'_ |"> s+#D`!Q3or2  lg2I(jIS}`w7z{2JƠ'V '۷m]4ZRu-@9MnCu_*oMtMml i]@6v ghE6f'l6"v6D$hr6W5c6 ; ץ``af?2~8omnv``hvo6f@[hv~ 0;"u&L N`/b0IA,&? $A&"7?Ao*&譱L6o[?Aa c!sПuA.xX )X m"_-slrY6_ LB7ǿo ,YpS_ FwzTΖm[rnȱ tyk&@S)_շ5"n  ^ˎ.O(Iՙ7E{v%hWH~{Ç~Qnl2߆430Yx*RO@Ƞ&|[#S2E)Oʽlu,da_Sl!Z=J/x2$k ΙG+wԹYWx:Dh^:[1jN](>AaMSy%.zʄD,4 ;גv.NnNĚ*Pۨtaqf1,@tR8Y|!XU'mU/[rԧ K9˛A6g^PIqc9Ȍ@NWcf`B|׺.zR@ e "+T<u ħIH?qj#-A75SMeN. K J!+ͧNNzKܗG.N/|Œй`EŔ6 "pY/Lp3 Se i!c!O .y$XBy/EQ^;zl! /Z%'p6Abв.1s6qھ8 I/T:xO%U[ԜnܐEҚK5X ? ʔVd; h7_ij^զfEr_aBʋLp<ύ,~e78"ͯF]F;9 5b_TN Fb|?|pdNݓlu(̀l !@j[gs#T ǁ>eݔdbv}%<{ypP:ZD5=fz`ߑ t)ڎ+f|+NO qf j?iUq>^Sa0BEXXn1V2$^@eI7'VT8dU{m=,ΏAգ("섚B[Jb_SISq숮=̍A2`L,Wm87:`hnxG|>lMWbGBuȢWui*_6|D#AuhWTn|OxnUn + |{HkFtm%|NtViP|AlCfB~?h3ފ3uɀސ8;N^UzKhTYxX"ۘYt'eG>m? vuXl,+T/7# ҳL U GFb,WujFMTfšXkKh\(c,2wqC@qj#5(FsJiGrvt30U֒ـan zPXI)Uy'GMOӒŬ~+|)4evegE\lQN&r"|4աJl^d);IɊ`xGn.V ^>]Iѳ_o-;}g̨tN-NrdHkfJLy&+ `jS&Tq@Ţ![o)Cf*l#־;NVĺ#QD}2\|b |s#2_E0 4>Qڼh@xXxU| e3mCl(+F W46vZGg ~~U"})C O= r3UlL5«0wKC5!6!SyRuweOfvҶ0nm!pBV[9:%} "$ʷlP'뎩T#QITg83 [![U]c Lm.xVp_aP)!`ؔ}V ۑrXՃ]rh}!XJ/a .r—(]P<ėz ȑ{m(t\ف JL{ڂ[LPLEvPtR؆t~v:uK˜" =ISoN%EiU'+{ EsX9bAt&w5_d\O\eeA[H 8 Z"FUD7mZwo;s`-$X/M Ev] `P.*:,SZ]mhztwm]zn鳹aӂ֘ls" q('fL GC*tH>Kukᆻ.Cf*3d`V鼸f+M)=8MP{5ƒ̃wAͥeTcl+ D M~dpVS/ ίtmQ$^~w椚=2vxfRK}+%J_ζ»{H7 4#0aRX2}K̙,N+>a<~Zؔr 7g ~pX aVCƉw]{ƾq >ַn~!w>Hcby ~[t$#refFk&_i&{˘֬\ggwJ WB D)3UQE3u2m5+ݎ% )z\B=/҈IQFKP!P*KpWVΛ2Vkqc|2T|dz&^ $4V s);87o^{Vn5 g1!j=fIY`UФE:["igԽmk>U2vH;z*xi=o!紅¤EF<ඹy* nyfeI Cy@ҖT 3j,|Q1!ځ ȂYy>ijd[\d`E~ye(lI){;5*:$wm "ˀؽN=\:1Zy^=ib|C~JAAg84T?`y-+iK?o0 [v"8&?&Ц夵e{%T+dc4,*D\gmmqs ɮ}"i[1|3Uy$[j>S,lFSk6g;{I<¼lh6>Sk< ->8;OpO!kM6Q~A}BwvI&Cd1]Psx{ C;1;MeGŁX.}[P=EKO_C){53S(Wh.T8D0\+$ݧy Rr[/tsݝw%p)f܀qzL5utnH:rv>?9y^F&jQwVw,W؀Xj8椂'GlOvT2v@_QO3{U*~qdͽ:ƢIu9q4,q'yՃ;tO[*s\6W6;+h|0c_,K&Ip\͸2hz}[.MXmJd+|8́c0EA})az(%?U8+WA3i,&FytJe>Ja/Cfemթn A =fI &F 1g2(8Cʋ+/5]M>T h@ #*ˎg"պљ>\;StDDR`OCsb( .z<)'8dRm|47ݯ嗉~/fBb\b;uBap1W'ZumF@nCGW&DAH:OF>Qov+T&H:"UR YJ*/ڹtWE51?etS!iI~;vO w[S MP?1TB9Z ͻKGcm;SUOѽG U25}ul-WW@C.6 ŠipZP_?7- "\J^wi!bτuM]\Ah4vN;LFD}2T3` OOq/S#2T]qk1W> 6p0 qMY{A_]2Y+ؖ'hzcm> Y|y-0-r.\.damA A/iLζ57Cϑx0 >2 o!fhS:Bgmg|;a^^P)y}0;kt"_xM_/H+ ~|=I?!NF.EV>;vsM #\1:rg=YVN' e-wG AQDgkƯ㹍hйO6{clI[g͂M#Tkg0O$bcyukkT ̿ #kGv{|k<όeGQ5i޵'J7/4r1.Exi ֻyH׎S~%U.bqW4MGbٵ dbq,N^j\~{qҶ8^KDIR5:UkYdSM/UP Q>pSMTP}WȖсxdei3=\ֻVN)`pv IȺbc³eH@XMFCgkJ3BgV*մJS M)ᩥ`$?|GSxůaw ϕ=Sp ԑ#޽ 9Wuq2H7D욐+>/@5TReCZʄńk,S+MH>ǦFZ(lHrxW &:KuPs31Q'jp\ilk: CNkR@8Ϭ/1%9rGG.vM A%\iBɤ{75u엑X6^N6q U4A-QP%Yb Aq"gΜK>OƮl%#>W_ĠFF^Oa%6FӮ@$Npv-^GG(c2&)g>5eE`ms5EL\KenzeViN?Yz18j1ђ6xH"F9'`'ް66i hnFԀ[[`dD g,O`jS-RS%Ud ́A)Dbpt=馏% ܔ#FG[t<LOcy"W.$ؘ^\gZ RT%9⅌e]DkQ$stƞ*JOGYtv]f8+9 {uNsN$I(<~P(R*B+Џ/(B&9Di + ;qZy`LTԬ!HO1A #1d/MF!5ggzNx~G&c;$"RhH*_2voW9P[ W.}v1ddhlA Kl1%XhϘd15׭љi>"t);=, y4ZSݓ_OˬnmfgV}OQ+"$zY>q]ˋ E<#T;Ae]\=@&R8e[m!A췳nPPPHleԧkP#9[c>P{Uj诣z/$<+TP  ha xd cA9v)u+ 8 :QH 3N2ήhgf%>G6M< ewEMW9G5# ˑYJu9Nl>ܻ!!k, qQأ9~cz`T1m|GKX^ǖX~ jV }ZO7{MB<FJPՙx.ui؂y"VRj>nbj|IWw%b^3J:F1 zShIN V I>h[畐O2JIh`AF5 $氵[?Xe[$ YueZxĝ\ 0 Or5XO}y2UJҧyx9 KS.8=hcKբc C{e! Yu03ty]!^W8a7]6 ͽC|Oy!i]l0s ,RRu-ɿ'~ >x*XJMʠEلh[گm3o !P cv |) Q#Vk%OL ,<*/{xh\F_GI}e( o Ytob>"R_uf`NuB{1)/kBvPyUlW4)wVLlyf@1N8DJ.ZTDS6U Ƽ ŵ=<75,yH>-L]jrR0(+Ne}J\ܶu ku2T\j_2,9:c:6ק|:dkAraq+9Clha et)#ptiO7ư!)zfV+=6d]烻 ;89Ts{6/=[b (,Dq&Ǜ}CūSժ<N6v L wv&"zXRd++nΕzF%c ̫^3 F=m.LI?~\s[>qȅݙʾ9;!k8RޭDK S 2PFI;ӣet \WSo^(IY:} ̌烖*߽zc qSLeಥyZ. wfMg']>b&r*]L/h>}*ڑIT UITP\/T4? >a-E;] u $)RRd!МRe`D,~;:E}Dw&nIw)?S%߾M _/UNwN, _zhg9[pGwx@yI*)65{F1ζ~R9ƞ\7gL qV  ~;K`)񹯠_Qd͘1xu <& 1-0[[(U^%k0"g ㋤r֕l(\';'DrJnD%rcK0~!L0"RTjyGٰ0됨m}K#EM0Lj SF~>:oNF%G5Ž;47{8nN @tKGg?㿑+_4Ӛ^Zr{@اSsV@MP:'IL0@RK jFR*r0K:3AO.gznmbkX$ECa1{ g@;,ǚ=Bet \gtjq$hԽ wEx]a\6ﮏ|WO[-wm?Ցj~HEg%doiZusw( jDj̃*OG2Gj$?ћO;p *՟;G QֻD? );cc AzF#oIO K<]וYXzZmC ,C}[?e遖Z ZF?7Q<~Hu3{e/M}NlPPv +)iVy)pXzA]Jڢ^Gg8,N҈H,,-?V:f!Mq![D6.k=ޯxsW$%2T8SNRqv0|)4Gé6N Ոb 5bVDCG\_c913X)SXOcg #1BŘC}&åN}z C6U򆷁>@`C1Fk (SܖjbJ  pҷdBlœS˨벥'OHD2 5n=_{@j(JU7ŰASg&}cj8։N\3 f t~2ameZF`v5+)* cS%DδÈ$ə=6{1F;Vf? q{Ná8ndE 0sOPb1rF0GqE Z珺z_K5ν(ӳø[ yб <ג}Wf{q3Z p|m&?K 4aYҭr@D5S‰[<_eWb} ?ΈP:m\&:gb>lKe-`TƈTiK 37QQKq~iNP# p?9~-ƒAat#5mT`׎|,Ebƈcg͠?܊&Ke]N AD*_K( xjNC6RNX|c1O?r[lTRbNEؚE.6~N+;tltȀ.-%2u\Aَx‘;`Fͤy}Hlq0M'\pB+՝q e4i̇cr#*v/YZzc%G#JæmMqjit렀ݴCk:Iu9)2K5Si׆Q67!)Յb1 z֨#H L׌Of]ݍvwis? MnͧʛF"`8 W(:0b̂a`oSS=|K8Cԡ"wN0(cG9hv0Ht֗NTtj|B3 ::#u}cʴoƐ0cԘ:V}2?kUj epGY",]wP1v DfD.!]>?ej!zbQp;鉥\VNvU?|Gb-Y4GX j;]wt,.o.uoW{cz l]8t3f rlRC3&YJw!֗/$5Ƙh=]KwYP&5jRJ bFɫ\XƝ@/rug_pU I@o.MLfU*6٬.,O>qmo)X KR4J}x4<9 n?e`m$Z)nd$SC^K!XƏ)>zZ 6 zr|ʁDwK9!`NX \u'q.Uvx2vx8.>"gK탠-g8&bv!GuS*WwP@,{Aiն/!˨'#:1E֝˄>o yErR3&eI_ T1Be]25\^p@E ܂ CŮ!0;]%4BהOv > 6tx.mdƇ4|&u&@f d 1Wf.K2ҝ?\>iP)Hkizx/氊Qe"Ŗ__,;ݜRj"$ k-J2,6/Pa%Ȋ@,[7?]z`n aPKF,)?۵rpx[(DYcnBz"0<= ?H3:<{(ƴ5o* 50ӑ s/ 5#uEjNO]+T-c<ꤒ?~#bLTn5)/[.`g4"Q,R$jRxt'!;n2B&p4$w_=dʿoH~7:*r睥In:D\ gVpQ;z%Nw *j ~t2%77!_܂( 9XS+?'[cڴk<s=~+%HZmAŖ'Ҷq7  AZǴyW"97S{)fK(O]1%jKOd{tʠ]&Qɺ/qٞIdg*K7~ !pOBX <~\r0K+0&5O&g=) 1[]C&hgJSҖN@Dta;Ϸ4c:^nOT2bwYВegmGNRPT 8L S!ُ/mp?4= endstream endobj 259 0 obj << /Length1 1957 /Length2 7340 /Length3 0 /Length 8518 /Filter /FlateDecode >> stream xڍT6LI fK[BR16)]R!H" 4HJ;}BsƮ_3[!-*H_P$PR70@ aH f7:?B`ƦFa" ( B ?Hg)nh PbvE3iW ())+ uCeutHO 6(&vp@:[Q6= j4@g7bv />r;C=E`r+3~E|>ߧ? #0=r_V?.HL> [b~?1+ rp\ 朕VH(Bs>%39x?k@!08 s+#wBCՕ˜۬((@Pw g G/O3f o/G#Y A1\P _D,(CPK5A: 1pLA @?C2+$w[*j7g Hw+0@LL &)o]01TGɿ?M% o-m$PoDȦ3g;w$#?`!Q!h!1r@o1/jAhA#1eݡVpG2T=E>2$|A0 \PVAZ a%$*z. (L 7t&y P/$jFHFo$ BEiỲV@A/a@a( ED1g? n g3!FBߥ1h˟ԷFV@30%1o, C mK0f"7,]5 |1s? 8\p 0@Ly? fo_?1{~= $@P8"d[xR)Oƿ< !fxt#Bm(ӮMcWB/s(ksW ;$^9ZNRIv09Z8ݦ@]m뻄\C{(j;̥G) )}OLVלtJʈtƹ%u}Hv7+lBSH4Z~:0!yy|bl QUbTծ%q3&MfVl} >D aߴbsZ+7n%vMC/x|zIM̈́Kִ422Oʌ3tJ:tß~^xMr! w'3h.b'op!em"4\MK%$=Eju/ud$ *ҟu/hSw 1s*@@ik``ͩ4_Ktz]\Rw@F`0v˧oU;-ܔC|0yF- =@ ^E V&|VThwezH 9u߄8v'3ynSf%e nC32#NJg1]Eu4z2<"i#=xoǸk'}e.!vDž'c܏; ׵W>}\8}n|Z}ܵ{JxޮzziZ~Q5),< : V}#YcӀO[`TA0L 6jzMʯF3inrTGddutaODo}I}s@j>vKE" ejpչkGE*Ҫ`8`]ʚg2[D5*Z?exR!ƷM6ˆKJmȰ-E'x߿NFwfkn;i]xi=G.?/{D۔=\ELNhD扻Y`ZcF>aBvX~,Ax8_ЯȪk_*spvr~51bn౗swuh|d;(.zt0i+KܚlS<񜢏bK8HĘutϾ]>!0± k)$@:m73>r\E0iOS_!Lj]wAx9w'0 s˨a asު ~ORsawqIQt2g ߗ~|Sse&;|f[[~~Zq.߆(Tuԛ?8bT~J^p^>SrT=Y4Ez,b[j} KAК:Ƀ>en.h WYΚ",89wHjbmaL|D,0w4(VGkU+@>Jl7+u`vC}5sձ|939(ͤ ޚ1%#;ܒO(lZWاΌ2M{]fxc;Ԡ ]pY`y7 4ψwlByjzU <%Q.sB!q!(1ԙyb -Z% wD|&Q7H+I3J楶 W\'_ yhg4<ʆZ,gCZdz:8߃<TrzI@R-&~"]Vv&)!P]?ڛ0$N%@}NQ}(dN9Nsf2iNfWmoK[kdXFRO(Stf_PdPN纅`ӎPvSGFzΦݜ,%cL^~S,GDWL1L[YVՏw3,.艪J0}/i'OS @4 WK"y<-YdwMYڊ as@19'}]PNGt'ijz# Hpz=C\% FT*+IEq1^9Tj=:xЈj_׃ )jEʠy9t1쏅E]/%sO[DJMܨFO.${7?& B8E 룶y E]K)3#ϭG ݭrVr3-Tl?ݣ `_H6F/%iK &rMΉL>Ջ}8cCxN~6^Kr@կ;oZrW@x8E =j%<=CqpݾF'f yJㅣ5l'w&!Ugw)}fhGPVe;{p\TE͍Lx w* ߖkWOZ}t/!3R%V)|tɦD8IUM)@kkل -Tqj*$ _y͑nx+)xoj\Ye#3Dueh'D梑AKPVɵVꪐA l%BG 7*c蒵gF O^W)<Z#5: Ccf,r,4L FlU6:˳-5gO?P pFƛ& ^ Yu$\ۥjͩ|pb_2J}g1.3YPL:52R΍4WHc=OR&:ڿf@ YvPu,+5~IW_olV$hIRy,~1QoG?\+ R7Rj%ݳhF mDKzz$&;'QSo{r,ǚijLk\ v <4rf2̮-+`W\ah笱Ɔfg+AbiKF"[qDeRaQc>D9zzWyl پ9$$ӿ&#η0T+&4o1}AVDE)z סc3_#"޴i 4p͠CH#f4k0۫{)D+M,gzKm@NkTl^A-8(CptȩD["\鸧Q-)`a[? Oi6&;&3XʩvQUF. k^$}hUqչ@2^ѵpL -žJ2M\ldP9'">Cp \ieboCKcL;wFFNt;T198Ia33-/:q*(} 1AM G9z}w1$q@X(6N<ΐgJ8$.Y~OȤݐ!ʋ H(g@sĚޘ{et>ؕp%?/%Pe{lgH׫=ܶOmʌi>,ÙهfJ!*hQry |$xzC&2d?2L9U gu2FT(u-E9kIA AN[)r-kﴞ)|Q󭗿Sr\u' Ո+-zq:Kao6KD]}-J:}Ki)+zߵv9cCISk]~z vr8R Y(C_a3pnMtfڔ2x5)bI3iܖ`0P\KAc^H6&ZulΣǣN81(YXNv<Ջܙ rtjQ} _ʊ^Mv|Zjf)PoZ)(LUjKrGe}US%˔Y#{jPgnA}0{97o~!0P O#V(B@{$e5FY0<P[Ɍd*kߗG̢LE]XdjW%2s; /ޣiG輗yr2 mf(gC!Pu.u:,2_!Fw)Vd2m3VÈ4fɋaNVa:5N1Ǵn"!C=cKTmRœ*؋j$|PD뇽M`,|ںٰ^+I'QS+]?ݡ*lnʮXz8nGQS~S'sNы*:;8`aXl(c5DQ=Nrfs0eIsKdT3ìR5m #I?ԿTSj~ Ǎ29BڮWqFs[J+oZ;'~HN1CΈaΙ˰ò C1v'v Pq:=3\\ mܺc"Ww HG޸Y^J [7/( 'E=ZbtR '䆔j+[zΒv c˼{u/f'.jSyk,˺Ѣ^1t#Sl"C`+Z$;X&$-f.1I.=1u9riOkgH:-Kig";=/!2Qoed ~arh~hԃcKzI?Iow}T_9ܷbU$n#}%|7WRl{}~BnrsI{iy *Ip0 'L{3`NyIa"I5ui'MLJSM;)v" 3#б63aB]æfرT]lo>آ. V/6sZඊ?%-l`#) f aŹP9|_eϩzh*y=y^fi*6\Qni?acIhxqxʍrfeq'; Zwkl:zMwǠ,h͹TE/Hb?9o%/4Yze5q$#jL'$~)H@2%1*ަo ;}:M;mPAQќ\'w endstream endobj 261 0 obj << /Length1 1484 /Length2 7838 /Length3 0 /Length 8844 /Filter /FlateDecode >> stream xڍT]6t)!1R -  004H7*! %ݍҍ"](|κs]NXErpEr*<@21ANL`+ BA{ P}^ P ! Al\U${" vuyXxDD8d5 !+Z0k_%XH(7;ٕ dC+ r=.@W@ft!{ uOy #tUZp0/_ߛW@$apB-Eu.\a 7 du:( O| q=#2۬9;HWC`}p0w?-jc{ pn}(%XEo̽ >;0 ]`k{ y<3zap`_- /o `Fv(߻珀xLdv0?G-om$ <ޜ|N^ / :A\- ~7꟞& aw1M=u3(?evO/'r8yK 4`b/v56UA 4'?/?UyAZE\!, b*vF\Wz/9䟃mE}(@a6+ ! Oÿ<2{a7 CާgZpppߋ/o@0KձK~?T{qMìł+d9ׇ1R΍x9ͱ= qju(;iʺg-y}?Sܴz;}w`2~a,h9Ma;].ϳ$ŊB!͚"+:zϲWFF$kѫ^ƹ.# Pu \$RL-ϏUn0:m'JMO"kySv!{h] "l "'o#{VZ]dAW*Ds jXdKd`8ҵ]7PgIDk ۾ANs,!&)zs"FD|| )ek~az{2ϺGAc>忻1EVX;܍M ?PzG0e.],JJ<]6,Fn8>ĉhܤ 'ګܘ5 j?tA/^o%V L%zgV:$j7/52l Y򖜌)-/ [\3'mO/Ie._S}lm13_n†CĨd{dhWW}'hcլ6,pq%\$٨ɘ?L *}'JYGYXi̛@MA kf -I !R<ۛu(}1=jRc';MCH!YE6 x+-{^_ ྅ApS4A"vࡣl\xӾ{܂ѶqLE/qc_5ENtt+,5^:VHO}34)N?N`_/~saKPYRCl B>]jsHZKEŬh{rPj<hq\E4nm0Z]8W\.pef+N@%確VFY& UUOz߶֞O8M_h9])Szw{{]Cd2"a!|t>ftrԵ *y2D)}P.nT1#^ _Pќfl=\rhpS9BrLHmS*glUMl)$P5I&X/#wPVłfi4m֎j2+5Bm i+=r+u_naG]3 m\[!Rp*\} RR5}dreSzG80veo oF1Nf=S\ܚUxkXۘe+9A`q3Wh~3.i?~g kb Qo]oZկyäv-Ek~H?ؘU+sWlpJ Ʃ{޲*m#%EetƂ5EѤP.aܲtT GC]~M&%Mf!ͨ6@=j/öcW%)pS5;2ad}îo1A+\ 4#h7u'6Z 0 d?੣rG!Z/Kz^aWy)4s ;NIkg$^,b)[uIԐ՛X||դF[5/`/lcED;16yRcL{ og.6Pb [8FߴD/Bⅴ[b:F57 *)O2oJ= =^*L\z3V% v ʟQu鋅r*QÚVڮ[xZ%Kv<\ X h>o3a  Jl(J7&p/!>f(K7g+?7.0cWƲ̰۬o7]$ܨYmZq %NQ JVNAiu, UOaЗܹE՟8Hd_ LmpQ)󖅴TI9UWYݢ8P/nQ|No/щKM'qYȕY鐧I~k8 bVdoWH̍ܙ0oFT/F^um ʽR{ݐrJBVBW9c:[`g{ :p#qb3[B0J4ZmA" ~`}yIGi|fӦs.RY\_ j/OżON C۳;iL.a:,u,yt+Hř!zqUy)nT$NYl p\B{{FRAU2l|2Ǡekalۭu&-{W5!|j eqr3=sC |~V6KYJ&+wez^fNx$ځ_E1S G[ ,;b*._.^Tۅ*iEZN^7 BW^,GmVH#ގY&d8|d5oKSem&?Jy~Ǥ>"yBdONBmNJbC*=QD”d&c׊b% 2x&1^_ O}PɱJ~4( ͊ -' VK۴U@/Dr9:+ ޟ5F`MU%rQgKB[n1YbFyeoϨ5~ .褽=očGC|Kq&q#n d֜Ji ?4Lk7~@鷗泏us.}TXMѩ0I|9Yo~ƕ,l)ucԥ1nh.{F skTԵ1dKX Rbb">v짅nb[]$fd7<]&gx+/aB!]91dmHz0% ޙDF_J9*_MG U}F&[hY]F 8IZ٨yU=QZlHް'k3[4'(hzӾNP ;AvvUX*D*Ǻ}K<:a$e=P8?&qso{@e GCкθx1g[8oqþuq'+"KzZҪ]a J6fE=__|t5=cp.V cqdܟ឵?f)8"1o5-(S\Ii{:,f/'KᬰV'D>C'LDqG~I*4YQE@ltbd۶D=6`P߫ )KmNtOjzZU ;rsYbH%|T@SAEA;Jix;:67Aɜ]?] }.+dSF*$+foQ+.)`<ꬮ>J X/-HܪGV-W ,Csiy0zy ܨ"tNXMbi4ث0DӰ=YSs.azCxfQCX݇u0T5W|SH١o.bK-MX1]¢S6%#vhᖼ3rXqL-fS0r/*/tdQ5N` 1-:9,l×8Ży7}T^vXj?;d>Y[v?^]G&R]_W?=7[zDžv' !-y͡睐\]]r&Ҁq :Ӂ"d/HB 0_“OP%pM|\誙-u*1f+G}r1q +˒YCͣ⌳#'QG*WjpSI:qc_Ό9ڸ+xH!(aeq'Lŏ-wP)v;^RKNX*|إbf ]Vㅎnƍͫl!N },~|_ݞ^ifE)b;}j|Ts,hМj ʳG9{EFu-b:F"`*ߣ4$eT2:YA'"K:i*K!*=b;r%w uT%:0f-l=1 @)lQߒ';m$cꔴ^dl+{UcfR f~k6ső A%#$!d\ir]sUڑNLqS.R>c ]xh#,^jQ-NX&hwMTrutK p؆ľ'~}}KOAQrۮ0з A,&aad`E/W 3Y!*'ya%{_eDN 1f4hҋj-- z.btl UET3 QVĉ6+D7H#D¢,teJô$|~(ydriPۖ}_0Մ8$'eKJK98K6.<0y홞h/;gW%-3tb|w Dٲ~ ބSHm&H:壙6K+ϣpٯ>?A \Mʁ( 8Wg8Si[Ag(τ"P~5$)~gl!6nTbIAO'J2mCqWd`CkߑA=b\-3E[b丬)(E1KhZC_]<\C~}2s`W/F֫1~^yc{!$ I/V,Z9(4 ԧ*KeMP-3dz:Q[M}ҞTUq`Ib=,^W⚍͝!88{ub|%̏Q+hVI d"i(YZ9>ɞ˶O+zgS3%0^Y>: V>0zZ}/:MmQtFNa0MQN̎x:*CNn}4Yq n)-BA%gKj4+/Ѫ+֪Ï6`rS3@,OI-p>Z]}TH:> ͦ ?sߒ|xXIojN$o<  > stream xڌt6=mnԘĶi6il۶|woe}:N]=CM,nao0 $߲8Y85m\l#QkmAH:M]2)S=j ` 8I efcPf(؃HԒN6V.`yЙy:٘ʦ.@;0-@_!脬]\XYYLY읬D6.ր@g`X65-]Mr{,N09@C^ c߽'ܿiX2J,..LSoCS[g{_d]3YwYd!iog8#O hn'?wyXڀ,,aqtK,B#x9@GܚwxMO࿔ |"6@?$ogS7 ;;`!-;x`'CzY؃l=k 򊒒2T `dfj4Yܦ @AX*,79yΰߖ'$jk/5ݿ K>cߦ:2k]LAde68x-l\̭ٖZO flZ0Gte|u8W_* 74:9z" FovYzk, { \/ DyE  `R?U? *Xe NS|JOA`> >0`}4 .f|:A`d 07Xgno H~K%_YpO/'pa 8_7+ܜN?N rw, \ 4pD\dG ~l:o5 kX[ fst =}n8) =mt ?Kw-`g|X;Z&p.9cw 3skkApxϿ ^G:C_א.zQ^@ia\0}uH}8;ΨN"=S#*|<}EjКӭx`4ݍ"ٳQc-X_>Oo'vqrkzI5v}}?@7B+Pg:ecݻz/ Q߭QD~*dԊ0,2Ksa&A`<@{%SfD=[0RA@EO}9_}U֧1l1 _yT^ڿXsJ~JE.X .f=_/e*$S䞿㒗ޙ+ 3|r];G|8yd.p@"8bD^ Mސ/mo2:Udáf(O4%t4F:ܙ奨i{>,KuT~nUx:uw Ҹu^PHC>k{nrdmAWO@;" 6|n+ jM."2aU]v*ev<0 #A Ɇ-2z,-M]Uc~M&H \lP}#Z"lС I&%Yq|'>cC6K_BFmSA=N>7WqOOa.|#bKh{:*-ty@-˸8Z BO(K1M7/7*$@񎣙Yثp֢!T9+w ( T>B{ߑ'L~ $ 5؝?̧Zf UV^&"gǷ]y qϐ\ A,$56:)pJ֒Iƭs)'}3'6Xs< 0hjRr#O1oʮoE%Zw)s6U\cm6^ݽ\-(N0MG6["'fa,KK\.o&ɓR?ӣqsŅ]ܵ8퍃>W<{9(THoM,5\KDN]"`A)d-]>Iaܘ [wq~@G*ƶkdϕʕtkJDkvɾb-z-9cvS]ctz.:Cnc"{e'JB' /T8]2Ib 1ʻB>*+!3s:cme#T!7,^J~@Ɣ?1 gob`C;w FHJjd[Pǎ٫lN`SklS?֋tIkUFr촨 w<vpOL*r Ls {>cm)-7fs^'Rc9ى:cw]]֠oSļ=g%%½Xt~ၐB-' n+FSha0l4? $`,t &0*CmfS;Hr>\;OV H;2b~PIed inla2qAoVOL?Yޫypȅf< .ɒbbQpJ\s/\=oPLݳ'&s;p*4K<_9B.QgBu/um {KoȃMγަ m}׭NaR[ #G5ƙsi0kKgaX_  ]]"]5;=!(ޞUH'V6"[ւ?0òK|!6)qJ@7>-v!>m2+¿.YaE FV*J|Z=\&n@۷mWwHX?EGC]%k mb%5tlCM8KP_>h6 }s$o*IǾp~Grq b` Qc#Bip s($(/]LQ!6wXTy!ua>Kar ezS\Z={ׅpZ LJӐӺ;Sy ǤR% 4WSM֨uw4^RmVps2a*ܱӏyp&U %~F@2M5gx@"8+R꺿,+y+8=4ENπ:JmekD'ufNawG-v 1S!w$6[srb-ǂ?4e9Jyv cj^|"~{3+YSkwPgB֕X6n= k4z9:ח*2g_R"pVT$bg&F%pG0Ng⅜Ak6]+ݗgǓN$R7QoW.o1ao66YoO"h+`h*v N #O[Xüǚ*KjKk{n$cs^Xno*{e^  gv~mr")k/,00tɶa.wNcSR7Y^6J!x*~{O}rb@F+  i;!s`V}h"0-2I>]it.mrgd!gY}hP2qm5$ݞ1r@ kjt7Z̉$nw+ ťP -PV\8 q%d]&u;*SNČ^%ɁC5RxK}|+༓˜ ױ_X 羅 X7;ƻ)4xQ;uI+a/L.+a,،)|CVAr \+hh ] ,3NKkCcm7-uD/ZÃiWT; frJƾ{z!km"sك~m)MM|' $4-sb|a(Zo(G53Qe cI;| zP\y^{i9=(q^i 78YnpQgR}=M%q^ "0lk<3 M{oCDgR)Ų) 3RQ'89SyEx׹V%TJl3 e(#Uo2_ Eo;L r}fB 3. y]qX5}{2icEw$ O8ţ32Z+']L]Y14Ytyd['Am_P"3 lSu8gZ^}2 7jkC>=i>ӗ[aqXxOXޱ9/Q,IZ7jG}n@'47GiV+,O xƮK,j\G zVƢ~>I!`x?K{=0a.&E2HбxoRRCjW: -.3RW!4/Khp,NIIPM!> K ~-&HQ>)ӂqma`+5 )Y/ǩAfFǕAAћarIBӺ[np- &j|:$pnPz5 íu+O$u0Diz/T :u4tAb-K 7mb nLeipSCdùsQ?/(ci u8s=DZP! (ц Q^\EwJ\TK2Up%GB&% o2ǯm-+V O\NQZhr^79S^qfu·ܤwx=~ٞyƠMF.뀭\M{'9S0Gw5RTr iԥ5Җ{τ.Z#σpjS_$iAo,P3(PO~" gܵoW੶$pYu)ȧpUOɒ:[q tr0:*lؑʊ!wg)|M`JN~%x\'fp@@Dr> X9B~~$Ś:ER'TbV..:@%Ȼ_>jT7{j`9Ԇ9T' h񥕐3."8tvn]BdwxUT(iIa"f&7tmzx}ۘ5XY`ҷAYv813S}0NPw->g#Nؿ,-)ݜaeLUw#ED/P3݊he`]42oww vN~a&P\!W|EW~]M9E>J;c#Z+*[Bh8[ȔV Ȱ[S*A0n}GӚGU/$>ZSX*FL.կ_D'xB p-'Isq3J1Ji\Za~m_.}Z߆K!Gi>.snN| ۩Kt0& 2[JJ\0V tNֹ  VᓹU$sFknsalGz=;f[K$A;UsR#QnW2~+&5/ڭŽ,Y0%"M$qhhp0T:9}M9+D1 5IuF6kv=C5\:B%)T UiG9ȗ>NF6ndiRzau6)HiA D`2b,-2%?0VX="ſ7N!G*GNM:biq>fTMo?V ְkVe9BZN@+̞Ҝ7$qXЋVZİU]iM]5ݠ>jt_6\D*ЊdL(t^ڵfa-2_lm4&U(\p??P'-b=TpQFEXwNI^jt]Mׯ~#qTGfz}KyLslm9f1嘩s4=gX*AUZ痕˱R2b⿴*ֆ˨Ǘ$7X)6`Mf[iN ċ9-Lu؎c^Rne  o@zIVz)a=z 3+RQOL{R]?jl9Α3=}^$,-(+I1IqԷdA|(\F(kJ.R*?9I;{\Rp;ޅ\^zp#юsI(uCmPq6#Tȏ5Y9xJ YTLn BKH`p%&Rcm_R{^0C,;WsngUC7.A?Oۤ1VBDwj& zC8X~豆+76<z~ꆩ3t!$e@w;j߃WG똅3}%ؾrMsJB8N]jځ^ZzĈB<чB&[+CtqNrE>F"DCu8-D &"E;ds贲+wO63,3>4>2~r6D@u]㾰3-Zm!׊BE׻9!xHkƇ+ś+ ճqҰ6M`8~cQ22.҅%@~26]-\d9>2vGM$5nRW9 - #' Ryiaʍ5@#)`E3)G:ib54ʠ \UQT&z9Jvn>{_zH tcm?0 G$qO4@8*pPSuXO᧋j{Cb,A}:>HޤxyVû%ݿEQ;š~ 7Jɓ `DX(TX'2=@˗)1Q,xQ1 &_x>.% Pbw z̊9 Ew(DߡSE+߱?b- N5Nh`w_  IԽjw2wliЏmH 2o:[KH h "77hR0y&1co0C[#EQ WU"=UVc1KPA7$LQnhT`~ߏI%mq=،wJN"I~N$&i}]qhΗGV+n*;Ȭ[ݛX+Z3G XI.Fxߧu̗y:\^TT8d+Uddw֦0Ɩ&PiCdsatQ=27> eȘ]aފj9GT|>)˜~2~(cyuA8l1\3\G{6dqy-hMx>./JG،GxPgJUXMPIVr*w(qn4L"a%9[y>OЙzԩZoq XOVB~"uѭv*koL= LGG:Ù0'&P w|uO65(C=}+M$1/9e[#1t#p"gtm6@/3{S!Q/N tmdzVX3?xmkMdM ׵20C~Ufż2Dg"w)) /609=G~ՂeasBlHjWNcKo]@Jג"Vo\q3= ̫YD\˜yr_tKqL[^ Y!O,K4)".7uv&Iջ}~$_[m4f.Fcj2]KcmbU{jEnnAl=[^*,Ҳ,)y~)wR1jiN0x |}nJloy'5hi{[}x_^ș8mէRẁ#pOf uWkx|Psn(cѡyY_htY!pHˎ](P:VXnCAӚ+>: zƾ/+'ԯznH!D)nZ F*!ǬD9Ygm49zChTlrlvhL~@TݵD)q>NecPĞ zeٜ[LnstZKGRcp_B' s9wz |5.XgÏM1qxD!ByWvUSbxt\YRX;ǐOSJ>- ݈:~ؔV X{Eh e:}AE!v^.Le+Mr(ILtud #AKR:vbz-|J ɼWM:ʼn࣓y̷G aTtYȣxɟ-^[8PS"$@1ɘ#&񥦶 Yަ0 f^EC[MSw6y8݈OG:ӹO+8I@z~~ʄehR$6Qjo}r߭e.9@:x٩`.ϊlKD~k מq}`L+7P'޶R,YdZ$L dbU/Z08hRJ.5krJ2#cK/yw<9 ngtY+yaLbd4=Z~ oN$yݐUgAFHSD(ݣIt gAƱ}\Β%pA v[22X仡 'LZA#Xz/P8!p[?>3Ӷ,lu}XE:iR&_zh3.9_H߾F8Rp` C𳵻2O^oDafqѰ7 K9isfN*fv{e p~> )|XaMXOFutt YĴC׈k Q])#M)/4缹:}V y@dh `_ ~lVF4bSr=I$b!'f=%~U-8F]-@ۋc$ELt(㆕'<Ћ8?>xjC[_r[s eGgT.y׺%imv#=^ѵ=? f-!q^Y\8lWNN)XAxXI ÷՟\3sXDeBPkdS^[1;,1xIKJ1`)[l+:xW qbti*vY ȟth]|.և.ĉ ?sJ ,pQ9&Lr\weWgm^CIYŲ_f+ B/>*QPʶCTx, ޓ*vd9 NtvmAbv]:b;D^s%XQjƎ˭Ja #(Qs✡-9zmq= kWo([D EэT(BX8bL {_ xXSÂq8/!wtBJGm,H( -΢m<#Nt;|NkZ~EY^bcPY(:ȸ |ETL&Q>{ w~=Ƴ.(Od+D,bScX4M#Z+z-%vH["jLB29deV ^/-#jf#*yv^ ܂R}N?AܪR#2 sP>e A"~[U7{]+J V6X%-bL ^C,?sۗag#wNNބb߲.7JMJA[1# 5t$HD#Z}T!UuJ^kߗ޺1u)jJӎkO+^ȦURh9 %qAG}J=?cdbqV[^= +EN *>QG(4}Wϔ3!80(vy((Jk[y*n^;c<7 .7kmSVQJz[HڇDVP2 ''׆FLC~z2`$iOFIfkvlpgJVNfYa묗t'YYZ>y5*B%veC@I^͠i+b?ZvM$lj!H{F/-d/nO8[,20ɩ2|M/n \]Y{L}Hm:@-dႢ>4ڽ;Qz̗ok,N*Վ[DFjV}{=gٽ Q+IA!o Ilbh$\=PmsΝS#2S[T%z,.1ng&ɏ).G&#߮kV>oUi%߱oΝ`!v~qT%wJ7i͗Ev3I#3:/%~`HvVμ~n>U=fyPjh"s/^Iĩͬi j= r_.1n"9Ӭ\rJt mv~L:SNZZi&>:QYW6Wy{x S)4133y_Ɋan.2%WdNG@bLJ$Î 8|nO~"XPNAqԋc߷ܐvT٪dl7ˆC"MԇVRZc8hA.:a.A| ?=1jdBeԔ^̷|P8yEL#3{@a9ݵv5xKb!uis@;s?7mqT&sMlGQZ,Z:QmLRMx/8./#*Y1<ڨ=$g-!7* _*LJg˨X*,@̴H<GN넧u m#yc\&\ ojWiKQ'R+Z6h#7@$ԁV6.K{ٿWeSu9ةfЫy?ꈯOD$%S+uPFl1VO!1W˗gⴜ7GqNp)Of\5 y9wie +anX -<,:uNg@d<*[8N+94;Y,c_Qj*Jxa\KS\C=jy*f\hP%-\ .eH&jѺOf8ֻ }z*7GAܞlruQS؁sG벙-4u^pfrg NQ0#$jiR@z#;:A)@ă=o\iK5Bw*uE #7ymhu8[QT;jِv յ =z%MIݗ$c&˕L ?㍯Sx'~ f]FE^na2~ +\7 pY.N<;D?Dlӧ1砥0Q5-R ⺤h@a 1Yh5?X_oq Ё1ގL+kppPJMϓ0*}|L $T"peeu1-:Z4 z&^EV#SBqwI{:9~&\93-̺udh@~XәW~(t`HR&!y%ت欍#~33u;}sPLJ9Թ?ZÁ ,nm=&9re+I!x05N&,S 'HW1sϬR Zz2$5ުZ_=4j,mu9siN76%p#\qS F2˦<Ж}LŠU +weOʶ$>B̦j1ۘ⑇'v"kjPzq )9lqWڻ+ޑ{ZLMR?/Z!Bo_Zo]sԢck_?r΀?9q^f*@K^Вbfb>[KYzN*`SÐZ ;qȚv4r- G:R+ttcel:}XJ0va[ :lNi_(wbB*sO&*Δw8AwOo\Kɫ=,\o$ݻ>RscyI|#OluoC'7'P<MSkF0 1+!g`/Cv΢ xJ>k 56d&h.#ؐ,Ȱ]s9[ MMBeE{ҌyK6,܏ Єʳ2.wlS!lR%qoj" bEI%揓4W(V⮨Ղm053v~2 bRpDǬi_Y :CWǔVrgH_6%s[IW5N;I.LfPMuJ5*EBwTݷk ڪ#{9&x-SA)ͧV*bOo>Fw4:d(wik,̬]=Jh`ԊCtWFJ^veW{LUhh ІN+w`J|?6zNDE:"SHɝ,R§]X5~'xgmL v-0&|O€ kr"9@tQS˱71$2|#]u*e3zS16C6sߚMΈa,+7PS ;|Go<`^+=@!lV%ZLeG,~@(򼮕C{Fv'ErJ\t aպwNE ɇܰ~swY`%kiƋlԶ=p<6}E{ƒǜQ;;>ʶD727/¯vӞDEY݈*^E%@1+zy@Rev#BR}_'hgX26o<\@6EpeV{QՇ峩5_$bŕmKg]R/[Bp0KB/=l)~E!U^&|tA}4@ -rJ1%#0wZg¢R3HFl JPQA0Ia;ؠp7k *Xo" 8洪[&t{EH-9 {0jjmgSz~XEm/b6"y0JxĐ.H`k`c5< YxI9:[dv}FPgCsy5M\;?pYTDNl'xGT6Ϫ;Q-\!vݔflzDB@fUpkՔ%SbRGF0MJ'z;+ʬ +%p._JIJA!>X-`;/YZMpi]`њSϦ=ܽG4wZ:|cr>g/5Qܾwm pVAȋbXt>+D餡;_lA9 "=_Do? $O~%(:s$K:ߝlĕRe`bj'&2A'hG /1{)4^̊إDTڼ4g\%]9~ƭ,06L]ӽ=,4^D~ޓI6YbVKu//""aX%L j?F AրV\\i.(GJg8qJfVJuʍʛf|KJX<ɯ6xȑyd󪇶F( KAkG[#>TLR+LQiN)°3֨罈 k6ɺbq3ɧSC͘qpM.O8}F=`x"w3ՎL Y;<[ ;Ą44D#CrwhxQSnHި{ iB;LQ_6yF0=|j0̻ 2m{1m/B!40>ԛgl ~@4_x eגŢJʈkIj`.hG8p g?Ux<9 ﷷqejFy}xϛ?Z;Hw,%BῨZ XK&0Ov x| Cp8NfaG )RAl>ӯSf:K$kjWE"i-ʊѨ ^׳\©^c> stream xڍT.Sn[Z( !wNw8wۣ3sg_뽕|[>>z2 EG[ ddA1 A0g#D_r0h4!U{GPGH-@G(@ hpT! gLF9G lm?Q9(>6W{00 "o)+@,@30=Y;/`XZne;T y4acB"na^ ؁ zl=`z9]A8o#L%0Y!?AVˇɣ,!q\z*jvOV >͢ U?*+Gȟ>N?u,+࿹4E _/ÆR:$xnj^vMGDYɸTF-('8M@>\P]vW9j6w~~вr[빃B\-L׆8%F[˄^iwMbqpIL:-`wQv`_eHsPb%9W-Im6˹H3%*E4}rVF^3_6V},ʝ7u<," eؘFaUQ%U{ЂΓO'&%zxؚI9Zq1"u]DZP)b?xuEK/8!Jr3&#0>7 H>T,@y }bD}^*XrtO{;}u9rJb-yRD)?w$8NeG)j:۳Fʐuq7f<SMy†YJuz6w_E $7zo5NiXC~iOӋkt^,jWvZIbb;G |HL?u.eug^e2ZME^>EYQinD/;=20N>˂ ~7%o7UhSJ'$۸{5"APDpuFE(,4FGh)}orDv7hG/=У@]͘FcZI.8=(*{%­>#}"]{kbO0ԍw1S/ҨL_ϹȞBsɮu՜Z\֜oia1Zˈ2 "$P=ΈIMMDŽL}m-S8V2?&Udن Viitɔ~1DiC]l5<'Q~P"uHT3rܓCs"!5̜gbe8Npf}~W2띙0EnP`1\\;~o5Qp&Crhn5iƚ}7#a_ati6SsT59- UPMB:G,n`A`w]`ljzio*]-(=#C??K³6N _aqfh:{ CJS|T qlLGؚ{://NHP&)ڐ\t2a Cz^wF$,тHdYe#GNR,DO+?UwNUS?ԦA=sgl AQȄe@Mh.Y+ZrT¸g= ~/$GV\B^Jk@"]1JQS)YSߋaY5-:b S4a'|\KzHRY4v!qJ~ˎp(u_5dv!=;I@<SW,v%{21L57NZꍫOAI ixӋ(ՁX i9c S-!:DRJ Db'))&_g\Tǟ$>aN![0cn`hӅ'(; BUZ>gQTLB6}IsXCh'$vXgqNGS3eJ{P'$ #l`B__ko40Zb.`I8Ʀ!SmosMh!Jj|*ѻO}I#{ۜ-h`Bά{wfLw@K݄p ,VlJ¤ A4&f_jYO>j/+-$M- p+ŸϑA~-\;J=Vop ] dKHlid3e$e~Jhjpb*>j3@D%ƫ;ay LC|{:jrQ%W2V&Zm|x&5i bbRQϝCGCC!Kbϯ;\f|lRDRx HG3p61GPݎ".gR_%oţ~N>bRP@ jxJ;uI-7ڗ.$Ta7ˀRYẌGG] Bm5=r$UFp%r6&qґI⋭%Hp!Qkĩtbb3zz=eݸVq3CjqE[诣>D*%te~F|<oD'C yQ0tSP%A)qUBZ@⧑ {@]\u՗90S)ऄҌbڀ8%T:E `qw0> w~-ZBjk^!Bܕ\F8<3µ\-Sh-Og7wf?e3U֦܊i@=Mp=LJx̰@v85˂ޘS\h)-&%+jN/.CU$R3WZ.J#XŞו_+m$U9hu ˥]8$%SD1vt 0PZ|20Hׄi)Odw( ƺ:*k]d.;~-D7DFbUA]0WZRƳq"2 m9ۅ7 }vwgDLHXbj%b˨vn/\-749 Z5oޘDQ8?虒m\]rЬrZ5L}MB-e]X{ߕT&?Es{ȹ:v5դ5Ӣ.*7x:S|1PgcBo m__ `]=[ JW*f{T%r]LOuKjq7;xMR%e]aMZ+2 )5N"L"Fbt2#_zlhU!KOJJovf>ur '8G#55E~66$ꨗ A?4XJo|y{^mKѳ8qʚ2C('\~>Η?Y}Dx"~spM FpA:%ݮr+T]D*wA#e=o=5LǗѡc!\Y^{bUfsL]߫BAr W5VBMm2F+X-x_l:UZ>Tnj=Otƚԏ'FDLXQ鯾MWeDMݰ2 nߤtE90{f"gb0\4xU_Az?zͱf$ġbWm'hSXܧoy7ϙujΝρM@xQqD5| <FR8`ʹ#,PVR Ot0qxk^7rEQTbҸ=x p)yUm66`Mn-T0r=Q %fjs^ $P9fhzr;]Yf'UGZ8-#Uab/ 93vV_@;=aD߻{̅;sH#Ro[*.BH4nyy4|'%Lb]WŋF,|%lDɽKɆ \`G*_[FЦI#=:ߜEp!~hm(ۨD؋:A`QQ~O"x߶7%?M81hd|ɔ0ru<ʠƩrNa6tۃr.aȔ̓x[a.hYrM0G-NJ oUhLI-sn iEh:ƈLGtkV kQd؜XWaq+ %h`,\8 <afkun!tub>֏M ÃCcjILz/nl ڵ '/|UN?FN0r]"\J+_&b _N=deTOޗ7i}xiL~CnH3w}+N'2p5c뽖ӽMSaGo<6]Wj4+E|tf{ #oG4$ 8ޜ)5>[Q5'éo1Q5s٩w"_o[o6q)%1y7<ٰu3hλ3xǤ !E=>2Ԟjևʒ`O-iI_R_]Fb{%1t^ Ie!xct^g_":(˦)J?ʸ<鰆Z77T1ɚ!t)+RyWCZ{vn3f`dxVɂw&d|X/a/1iKܙv#E8ZFihPc[{.S+1EuNՏrh?Ԣ:|jxo0,5oO6U~Zij'SejX{{X*'6[pscM fqv^ 5al\7t),w7ȳaVYXHϷg]QR'^]ʟBҥzD;WӇ爀AUrj&ѓm 톨JҸ٣֬cNHrW뼩RĈ/r^ A;{/U[&;>l C8'}Qʣ y5{c ӦM8Zq_<6@2sZբc`=l/v(zmc1%ҥԻjw_[I5*#Ist+ϵpkM\3^'4Mj _]@"Ia{<˃ hl[~TwSvKO0 {Nii ABYV _82uz+(Fتk ג,JQis|_U\_NJu$ =^޺'T_ԳeȠ.4Nm3rW,|6wH\ -snV?v`cM;a]F}aXr}_P4|Tm]CMYgos>GZnq |i wjp4b 6|xE}kOP%?47v|iVA|UByd, Y 0^ H.{Kikɽ,G4Xh0Wѽ`EG<)pCt"d;P;;&otZ=E~H+Rd:6dJlgoxM]@5~ij3URKJ35 @Ƭr :Ǹ0<Ne>^Rm,-'OXk9)C IҾ[>C\YXcq>zr]\cC*6գs>|`x? ]V_qE2 c0u %p vo8FI`ؚތIZC\2ۥ|@p V z; ݊EFB?repfXh,@aJscX嗅}ad'?rm endstream endobj 267 0 obj << /Length1 1657 /Length2 9936 /Length3 0 /Length 11002 /Filter /FlateDecode >> stream xڍPk.Jxq/R(Pܭxq+RŝÖL\׺u:*M6)+PvcbȨis 89y99QtAn(t@W,/۫LN (;x\\œnNN1d=@V5v2 tE8yll^#ђ %$$;@4l-:KB0ں9 spxzz;C\lęX 7[60@Wg(t][_r *pY`+ 59@GIe+عo?:[ZB hȫyV;B^=AVn6w{. '7WvW-rur`+#G} ؽ9:Y{07h݉C rv*m*BGftqrr -u*vqX6Y_P|]=7wPV K7F'h~=|{\?>y2}a c`WIKCl<<6n>N/@WQ4AW)O?߻XWp '3t#Qoɻ;8fSQ;6x嬻+ [ߦvV hrwZ%7=8g WyJfiYd 0P Vqqr/fYڿS|]N)Xa|ssoW"q|^W '`۫ =5p! q8A C?HUgpXq/ p 8lȎ@r _# Fvp ~M/O_ӷtwqy܏ףez-Q "avua5Rlbt|\:1SC6\RGzVw$)|Z#ے%jOﶣ|F08U|$U?@LƦ+l ݥL.Y{ٯU?P2WͯX1&t"g,N r" qfW̵tqwH8"!q jS%ej0[4?:S,Qn; ($^V=qHF݊Fh^\3{mtlF6+y9& .2'<DWg&q8f5.7n<:5)=6=(Sc3kYN\MPK)u/G@٭zLwRP^r 7r63 Yњ"6ك^r: )6$[1?ZYz!5Pto{&ikptStjC\b[c|M)`. >$'( ߌay, =H ZbeN*llCEkB*@'J,26Е(g^v kmV^ʎ#&e M4΃?W>mV=҉'TwF(L]cONzRM(h0 PxH]}g.M[M'DK/ד;y<) v")qndRa:? C MUSy#d_$+m6?Pܾ^eq]КNb\ΐfdܟD4ey~K)Ó̐}s3ųѓqz&{1%GibYQfl-/o`7􉠴y~k8xQ!R϶BnqOv5zU* p 7ZƋ[*!V침:׽pRLr1gVEnČCܧB`)n_5 9}ʾ<;]$iPUĠy'Nd4b9)SLI3}ɤF̡yAg nȳ#( A`Λ Ȃ=peVU~x S=Y h˞H4*?yi>aF 2+H -@j#c`J-AdGm@Y]rqq!y'mW.Lf^iuxǵXyp';N;KzWEtY~gISIa1^: KygxcܻnvAZ: '' &ϔkeΕUuLV۪dn$2VŦ汗VR p9OEXF{V?z\kI<첦Ykjjk̠HLis dVoim^xLomq+ +Sj8_.Uފ`Lg:dgV3 W >Tq*] qFsg&ZD;geX7 V=ce7qw&1?3<3p 6w3u$.Lit܈EČ%aզ[J,u9`9-arGken؀712w4Vlʅ>?jߒM 09!nO`y؛w _SǟB-d* j1'NK 6Z%VWLiB@!8UGWBҳk25&+mǥД;fj\~I]phRlɼȄ݄aTȆY4ۆjѾJAҸ9nݖtک٩q2c,+bH nZegE" ?yZb؂`x`XS-tG =}@ZW)|WW/S cf d^] 1OsYp}tS+bNd?axH˕\ K82i?A__Rb IA>E043`X3!BF]j_(l^Y/oxZDACΪJpDl3϶pfSz@ 犁*:Z460Ź-_3pc$=S90 bES ~Ze%$3q).]u#"OkwC!άK eXtM0ʨ.8 5{iS&20U_vpgBq,B[OL븟_86뎁VZe[.{tУ9/ճ˦d$[7ЂcEE:PtG- b,wJ_cJOVb5[SFnd RZY8#Q5(>㹞Rq@mwj^[-ʶr%*h¯&y!7@ޫAC1~KQ7!)A_\Ǩjor|*DKd_k< _@uJ9iwRC:eInn05bnb5aN؇J"~{ԲG RMyܓz,9vq x}7^LJc@\|E maFzm k^\76o hNQ) ئ9s$HϘ}ۡnT r5lG &Bl0i#yXmp5B|ኘ(N+x1/y! :1ժm"cF{%.>MfiO2gzSYn ;6.bU_BgR JO{'hUX FN70:tC:t y 00JܝB  LMA kjIz嬖L?Xd7. 273EA#^f ZeNi+-w4 zN)3}~ 8~n -u\>jy6,U=+A9C1݌c$W:*Vҏ(`;/tdշ~`lI1ᐾ6j5s*#5wq=T*ZQ|h˗5<pOf&uz+O$~Ge)AEH*-P{BxT+­:VV7kp݈T` U؜"UQv==33OU@(0D XKʱ{N6(|푿dOP׺ADPfG )[Kkkc@ 6}T~zp\DPXQoNTA^z)u쓋n=#ٲ~n],eæ6Nr{^,틬CNrܜC4ۻ^)yOV' X3gBOA}R1bGs,9!`A-/ޚ=1i~ڶ %Ӝ2B #M?ж: :줠 :OR6;aF7Aj`5Oξ@o|Ulڞ5c&4$uI*I}20ے g"]`"AG $5'Xe\gsْ4}4*"vMB- g W CߺS6|9LGE(Q3tڦT'S]2]_xKV ܯ)/,SDv>ObwFo{5<c 8bw~o(964e^M:FHl2MM1tH U~'o#þ9YشUqn\o%LQ`R{>T0ޔr 5_ohT ouw%'i;<\X7(_d3FxŚG<'F⦷Ff$E_]%m{9+fPQ1xl";'oL zguuRSf£ZoΨ=^n{~*vpV?:[U #_d>[_yC`6O?Ϩ3HSx'* `]]ׅ;a:ź>SDZ1 w巽G"sSMM{EeC6{jD.tދ;X F:s&ׇ&/3G}0l=h7˄j K7~uSpq|=x`s9*Q|kV$MJ:8ŭ2)>09M-7RSJLs6lWej-ە3]8U[/{h?Ǎ~Ys=On*ub9U6}QXg{|W*ͿՌ^KJM(\˺F~S=>.-7 /q.sܳĘ|n ɦ ϞFX[L@hʤF@*J MBy>v|7UA‘\V%nZM{e7HuI͍L=N rpN kJG fX( {~I#:G!Z^ YyAjgPMAj`!Kz:Ⱥjel뼬1'V+7; p3Bpks\2 ڹ Py鱦) m‚\R*?L ,f߽sD j=XܝgZ:'-5ɝSBgAYkqQq*vrPVH,S{NF=Ew$p- M9@>ai/* ? f<:h$oDR!gkuC٬tBm-%AIBzRbmԝCmJKt6wyH`8;.]orbL]ASg  Yb:--rRrISG_6-c~~U1d8]F6sv&SKvHeaHu73!U.͸.,.n'ZMk/A=c1`bU,yiUnVpQS'+C?n* ѷo Ѻ G2|hWGJYT);itE;, ¶f'6ORΤ'5$}; }ZؒnĻv/AcqviRAzߵě|Z땘תa/νùX^_n2 .))7!;vz2e=^RFZKM~jwL+&QN? OOZ^|˔TVR ĸ͘~ϊln$6yW,Zat}qRyZ8b!ŦBn-/:ՎX', $(p:ScO4-Ģξ=PIwSq%XD+1G6 igܫKuL|l PtxG3 6i!)쒝9!|ܺY=*B %ѷU5v8oR U2r: 8 VE;0B>[yz5` Gg+Ïn/bЩXlj./ka+6[ӭmT(1ԛ3$4eW<5&KDvZ-6\пvf03ɍC|*0xex 틥د, N/ֆiū;BXGT0FuZ4q\FV^_(Q#}@5ض`Nػº6E Mr[CqҘà & Nڦ7R PhŽ)T7J~S/W$l;?$?f`NֳA00ݭ]ʫ czGgr\Qah^jRrMe>ņ[iOx{=ڹQS5fJH?kKC,!co}|Kbk. Ovػ8IYjWH̞l,#q3w 2W߆i DoPV%,"&_o_)n v]!XMH#3'*VƬqԫ䫵cl);E>=Kh$pGCknY]/pP6Ȉ<ԒJ#SET 27͟E( CoʜUAY8 BY*5}ǣz_4!@By{kcH_="r/x  @=|GY?e7?7[5>b ;mB\\LHETZƽa\ {5Lsxio΂'fz?Ȑf=8҈QU'|\1"S[%)$q8QTFF6b7'7l*G$Qb@F+g+Mde(;tp#20.Vt{UMiߞlNoLO8,a=s9gR= T'Iӓ5 ]m+KjibOQ~ [;˾LԆ endstream endobj 269 0 obj << /Length1 1494 /Length2 7343 /Length3 0 /Length 8349 /Filter /FlateDecode >> stream xڍTl7NtѱѭtIc6ABBi nIn>}glw Ȫg(`"( Hm DA a" rA&P; /jBc6(6pE`qi4c@Jm@ c|?+ A}`p= U/Flv mhO-w6U} s a(wAw ~A7YnpqQDS!t}~ 9vJp2<Y!0( $$P7(++ FpأK"?wO( AP[ NOt4 #'yAh_Ohn!>V)*"~ K$ЇGѳ_ = 'Yt_k1x@3  1o?v![Kڸ}ңBs_B6Z z7 P?TZ0gpzP`t譂8 w4*pv lH"ђ^C;o./`@'IOIh"I7  ߀@_2n!w'E#h6-B(G$|@h [W$߿.? Bg u F&k<~?H3xjBW2ސ/~Q>o?-M*`|hnwdG{&;FMB[14nzT^Լ_\9^+E|U9!`oR>,͟gC 0RzM~,aL# M)3[N]2vf`0g>0駸I7t!k't&9r'pT^Z#.~M=ե5O4pQs[X֝_7v,рS5YƺnK}gx7wP>Z+a†bEVP7ɔ;=T[CJ@Y1^ZG3..V{zw 򏂒!ub5ؐ@'yyKX Ij&;U9K-dxHu@ MdI!sg^L"kcpA+o.e֯R*pzuXhO{>=ڒ>\i ΝKtHۦ2IrpL7k鷉ȦS޻7ݐzh|p>#$'.f1(;+QukB^"Z>Z FF7-Ùi1nF5xe Ṟ̌pëԢV_۰PEA y$8pwv@m\4,(D^Jvl+]M2 48͉I}4զ&]RlCq~֢~{s" ogK;*o@^- Żk;=rv<8L1Y rb:YNd/Qߩ zc9X툓 H~ 1e^K-xҍz1cuS?G2#;0iT-՞˔d_K4rncUϗi7MrMD&}wh n{+e_ƭ.Ŵ infF\3oMg7f~j%t~'b?dBQ8Z@ <=[+*/r:-^?eUo)-_Ks~ا&Z4JP.|O=nsdJ-ڷ119ŝXyPq0k{W+m:KNd2~4v,l\?{' < ]mO=4p1p u?X8̩7i~G^%/D쇪eXO&;:|1!@ΟzuRn㥈. 1)Fu v'g/xޡV珧H8/ޘƧ x__O|]+} DzB/e%؛QpcGHKH'Tkbqr_9`}FtI%Gy{k5)uPe3XbmCۢo*豜?pgW| aN,ɀ}.HoM=r(] QKOOOIΒ5`IಇW Ay֟)Ȟ=tl{U4,1_֛11V+rtw YzO^?6Bg[7.BNƍ/2a$XkM?Lb`^0ͯ8S\m;z-Tnsyl}V}9E1҅<Ȳ4xRiaϦXITN5oTF13R|K2-܉=[) лٞAexMC,Qɝk^׾;Ă,{oZݪxQ_VWP7@1у2;O*Yǫ5 8OWhnWt)\ޅ!Pd5{?ޗ7ٕ_ְB_4S|0i(n,Շt:'1x/th4y²um.o%XP~X\tO歝YQc?3LW0SV9=ƀXTir^|Ƕ;bZ]{Vw*μvIf&2 z$#8XnuA ^2$$jfwNnmZp8R}%) (wcT \{]ka%U14rh&ܵU;zKV5Wb GO[mEN˼$Ӄ|(fG>gÌ^M0W'd=Y:ʖtoGrqgB˿Ft=f,.x^-QfbAyaZS"U6G 瓱۝l¤ J;mڰ{x'D 1Uc#Mpv=G4ʦ~Oy`0yd4e3Mz+0y?7R&эN7#}@kj-m=WOfrf %0XYc?۟7ԖW1lu_~^gpzUtjjhޔ%Un( btL0NR:SkGB+]cEd1MJ o77&ޓ\% _ΛoUz5? |@:ݶyƔ-U#e$z`6)Zs laabNJJ|C-kov+`'mԴpXj轫*mn'ͼ./8a!u<>hT/rems+yb{U%| P.(F kjMbR(!H9fo9t|~;hrYiCBe+x`~s+[!Jmܟʄ| [R|ܪ7ð>bΆqGQ-~}fGXcHAN]L'ۖ]2X,q;BF^T+2^*_FU[:k"#끡E󦾑6*VGy1 ̢@̒Pifyjz->ǟLw&\ن2zԑP'i2]+^`(+( x'd"mBN-hyzj $E)F9M]? t1#ݢHsSrYbKol8'ɕ*M~?3B"E+^9pW;4oѨn/pka>uYKJ*JÓI{z9j})5ok<"dɹKի{~sc…Z*=^Cfu Oר`) )kf5[Wz%ЇaG r*]FDW;%~܊wZi"^͔vGixkИ8yKhϊ]U>4d/۪֒}U|hYvW6JSvj2YdT_ĉͽH7amV2m9UԱnV-{<ㄌwql wOͻ]eP`3~)b{G@/<(7d#ty:DY8ļ{bE[fzz!l]=QJ.:z%< !ƟMq)8⯊{=Z"X+|񶬟$a!%~QH:xMHN@| Y3*<[.hù׎i}8h\g^F!٫L2쎎Z2JKMš$^JI)l,>/4هmĘGJ)V;~m1LOJaǔBpBw$/5a}$ eEj[9+? f!c/KN|)J.ϧ|vuR[ :8:Jnq(4G'IGøSc9V vɾ-sI,[yxC6+ۄ?b8OVAg@Q1pN Xs#9}">ܥ*4ݗ]U\+Q+ab$̎$A&YKH#DI"4 VM ;#ʻ|7ˎ4ˌ1 "TT`ۇ^u#!er/_L2k!8)˻xebtƆ즪ĜxԌg/7)EH}fwؿCN )FpmS]\rO|S^jq7Ы@Ok^vY!*QiTW7W }T2 #>`&͟"(ٜJ^u_gd͹K4 }fG[o`#g+is/ ez F<#l! ۯ>MyVQ&{̖21;KkY&?c>XQ#Ջ㟛/|_(ًD:܄=CZ()b>әE<#$li{#FȽgw"L⦙GwnT)G ~C<|{QX+ɿJ4{M7 ؕrdtyhုB2iWYۙ܀_"`;WKmDqCiD`})zD#s'ERZ쨨Q M*Ry5(bJ7Jbk6RKjqs,C!v5pdvyau2`b Ns^(ԹS[E-Sd|M]SJzUmqL ,܎v9%&:NlaW5^&Er^e _"]ZYASlfPVOE<֣jBZX;b4mEf{#=ye.uCG 1~ztl-s!q%{ z&+0NGE IX]3P-oёfzrDM^nm}-(?Fy7t#1$z= 5V!a,Ad1C+@a *Lw[q#uQ;/ ^z / v=Hi8Sy  om`xgʒ.)!D mSoUr}&Hi TǛlȼRH~:-2Jj×{䔷vycLc̏jT6-:?jwkw1&ߟSx8b79<y;ç8S~cgV>%33Cx8b&[x 7֬;1n0?rP(UvOq&W([`UZw+c`o[q"`h(J /qi9ւ%ug6b_\kF+D?yS/77vP=4ɱ+^jk)e1'NmŨt?%\Z|QڋmqD-)HP"%u\DU` c5aF-; Go6m~ l~ɵrsbA.:FGEp+niˎKƨ/*2`/d*v;|G:9+؆"*a)uSy/K8_u$RhUi.!|CZǪܸ5Nꢢ]*?c'] Ұ.3# ȚwOd%߃q/Nʌ6c7]khUn5Q) Am*JSД-綶}gʺ'(?5$e3L]Z1w%:V%p%NdB! t"ec=7K*ؠ놟^}c:6|}85=JE|CkI1Э>erq=b1-`s"=lxBe#塧S]XXV怀s2w1B }F;o@$K8QU;!dRbprZb5 &Oӧq ̱g,+ޖV \'){n(L]JFK r*د.W䢳q!5>g4G>)jj@$+ wu(B}s&660AE,E#lgo8&݌soiy[PJ,+1TԪ_rP# !@g1cU1 sbn; |_bOl .v#e,?L]Qr<~hslenì> stream xڍvT6%1:6Rn$0b1r4JR"H*"H7"%!! H{sy;z~LT]Pp-# d`8,@`PM8B/:0 (fBI DF"+%`8@ hh(C!~>uO0%P&Ɋz 4b޸aP/9 cBPš~((0q ]8h\1P408t@c8Q_WCNWD`( "H7+ 42aDP/G  "8߅CZ@(_`hO!W%k"]Qp$> z0w[=@$tqEdDu5A078( e@/sJnmqa}P>@Wx`p oÿw肀ap7Ov wu}^Bz c+[U߄6X1 Y4HHeqg1"O.S,Sp_k0e)Gv`i0 eme?PoW_v^18poW+q5 ۪f@%"ApG*p_@MP~_ P  7U0Oܣo74>R C. i p-Xn ]A #Q\G/ B~C A$ WN8 ?x!8Aw4O̿hV 8<|BnxyZ-PlmXqoꡐ#$CEn"H5cfnUSPe$)ʹ,1ll0=fɖjm52v1 ЛQz|שL{j{+fN'?x/v2.t$371F0~8]%^ lSɐg~,,ލc63>`˞.O$HukO*#R(r-n'oDjWd>Y!^8GbUտn?"~*LB@:>L KfW,ӔRUx+țMqW0 QޙЏW->'sIsB$?bP`bb}/FN$ӽ|8puwlߓ_4mQ92ρxwRdx*>M(/-s.'}5{ݙN;lN$Qg )gGBI>YRwS/&YVtD\wUR#No4I i4=ziBqswq:7ɩ#SREjCnlGCɀE4qS{#g. ӒMIXl](6ʂJnjN~'AޯZ/{f24*oGf,/ï _.=03Kټ9LԻ21Ko'|eF]uzq^k :v`f O e џ6]9i4熩JsvgEk UVN*ͨwUoIŤq;|N8ay:.Hx0&%*V 2\L 2KXRQ }|V4Hsg{8cJ$eä́+b$~zQ٫4/XOWj.N]]  !J*jxTmHN8jRds'UA,\+"+BLeٴvs$N ӟU{t+t" 4Dž뽟z^{Rm$v?tJsf|t&>k-`>M 8UxGEa]g9ubmefm]}ٖaB$﷨탊^>CBH ù߶g]zh" YavVk1=jBpƱҧJjN)~R^eSP2~!x`W s\C5Z lc6@Ng 'k3y0֟]4A2rP+xKlxBSA؞?lR\nZ$>ϓ.vNҟPǬ͏::+4+3w1c5[wnR5V!{~-aZ/e: 8`w6ΚUvWy Ʉ5VszeK|:R<Zƛhݩt(x{ǂ|*|j 6J;kz0V*G ҥ)v6ѻǙۓn]ߓ]%))RKۈ0:gЛ%Kb>}4=+X]q^تdtI[Aw6iF6[72K0/޶'m _1ԵN]9xdpOc ӁO# xG؂cE<ϫeʼA;iy9k1G"Y΢o]mCYjsAx:ͽ`ҭ<"}+}vSش Vj@MқgB/Bum]mPm.7xUBr2% )cm gϧV4Iܒ<& תrfYvtWJ-؁&,Jib\"8w4·:8;x'Zw |`Toda簘 F줩L|5V4+/Qc8U%;Nw[:ing?|5CHԻ, eDwR1a𼎟3 F [i.CaVshŷJLj6; ?V81tVvV}” &A%6㌂y/}e`W6qÌ 'f$ *__;\ c_+`N)8i'4>ō[fµN:T0؝}E|Լ`$|~'!3kjk %'ErOBZ'pq^$4b"-{ I*0MH4H}AToeV8r3/4|u @^;g<w ,i6k(ې2X Æ5d5ΛO8@i>Ȋww5z>VL_|Hl4$Ƭ'rR{ZP%pz;͊`lv+Y qA k53G,+RXQ|{go"A "[Uwպe3рw5{כlYsNH{ry6әn+I,ǐO*{Qs.Ri|1;<$r ){d 6$ivT_{QMaEmKߘ :rpM : >"'gV -t?!#?.u4>68#HH])/Wh~9R}Kl/jAEVAf,`H63Yqu1~x{]6YBXlgOvҸS oN < ;ye\ 9'x"S~1:} PWgJ W@bo/?)#TsRk>?3AFg&=EwKcDZqH%wqr>V3T3lPdMi&3lX<=+7cr2wnhrhO~h&6u2T~8 !;oC:dFy`|H-?÷aJ~?*Z:XX6ˢDzOƓ_F)/JGXHtB`JD"W3[ _$4APuoo<2G3_EQt, E2YIbo_f.+vN&v-e^tV/̻* Iq,wj'}޿$9qt#Ν5Ĭ"hpYsvY >k7ϸonEr番r=go_Bx~ X=EQSs"G[tSFpƇk(6[L2 sYьG*C:;r̶e Ð-T#gцMOZ1,Ҫcx!""Pͫi~¥=eu}纫dA;rW&ZNIH}),Otɭ8 C‚ͷ+  %*>,F!&َn _Wh(8&B2d,HK*-es7VQӷ'TU\'Rl::|R":fhRIR=3-p X$+R}<=] KQjHCGHf9AT<9] t^fp,j9SJ)d\J*/B`h5,5x֨ٲ-.EJՙyur`hQ >$3)djR|I J=F, NV>w)"&Bc9FK&HNVU|Rd e^S;Vu.휟gl]ݭ#EriΡw)26LU:bs_H?}ڊ0۞f8'Q#R?wQ >JҩI"R[VsGYo߭0vI?vߌ~Ћqb^kRzP-i\rKiȨ ?d_5\b,KRVKN"OyBsUlvVUs"rٿ M ] %]wi!jT_-A]r֙5 1UxlyF}#*v|oHbJևaj\O/mޠj%af)LImtmQjzzly#=}.8I$?[u|YuG{m;x4mZ{߽ MCs6 ξ@Q)qxo&qԸDL:6L">zaŁ>mz,e6w.E~]Du6Mj&uzL\ׁNmleo%LR  J9!,AoG}zTyGViDx U\Y! =d5aY]Z͝o W 31[&0sul^8%L51J?JF<j6s&^?Mݘw}w}mf{ϲ+yL# sސNmi<~Tj>?2n2HG]>5'p/v">h2S~y4ߺJyeӕ2(e(IRg|-bٶ[$!+6,Wi)H*;|{u2(pQs@6V0ޱeTE|UU^mk'M|q: cզ>UI[IfWs'>hBYCYf i)xXlBX?yXe@tM73AF't(ơi5'Q-2&x@AdJv)ewTzFf^EF~]p}q˅ƗF#(^Ge[6dg{a&Jk4t2WZNl>F7{,c3_9F[]hM5սzy MAM.v=@U wVJq|.u_)r& dZ:y#yCטnsO^9=`q€Wۼ*o,#xir]O¬%3ccA:ح+蔝"I_)m-5r*+^?& y$ڱ3/-=^¶ybDE'}L4o͑I(rY__S?~HΕ@@ϥ&O@>pGը~b{>xmMXg {CZ+?a LMQ΍ %%}`;Chsb(k)IcMA[4a0klIfU4A9>A0>lk:v}~踉 wD8d<9m>G,+*OQӺ#I1#tq@h)ƒ~UߎQӚgzU$'IQk1_e2ߊs+eU|#d[( zM'6.%_p2k7H-6%b>0h_k+ͦ"xlW㗴bcq? endstream endobj 273 0 obj << /Length1 2016 /Length2 13747 /Length3 0 /Length 14974 /Filter /FlateDecode >> stream xڍp$Z ǶfN2m۶ѱmL&mۙض3L웽wսUyWIiM쌀bvδt \aY%3)5)Ζ_zaGLL b `d0q1s108Α bjaHH=-̝?n#˜N;@halh 5t6|hlh P3:{<\nnnt6Ntvf|47 gs 4.@wbtps vn@hbkt| Pm1ǀ? `ja ȋ9; mM24v7t5640!@LP`s2vwvs+C|YDh? GG=n?gS [ӿR0qWppJCGft200s29_U<+񲷳~0~y9Ύ.@+7cdX;fp;Z>'ݏ2cws酔5$N*!!;w-3`8( aWُ*at?A ߱>&3: _oW|\RXX{G1./kTϺM,\lVcmͬ[D '1 w?\*9Yhc> y[X}_0tt4hbx1~ 9>9O6Kb } Ї/S> G?# 7/bY$,,IllUMzAF=d QMC'?e1>țYK_ßh,h/ٿ?o/տGQ?#ExP?qǦd?9ofN#GL U= t'5./oGV쌹Yxuݟ'WOZqtyBNx'4ڇ+Jq+JuҖ4ckhTaVE[- [4I!m@ܽa|m"xi_M|6J5R'd4(k313-> ڕ;-{m9N,buNn$2ZQx)=8##L!&oSBŒu..ȉF>]ޚIl!Ӊ O!?+ͽGU*y@$lB73oy3aaĎ5,{sL(Ir'Me_\(onp$,9cy̖*ɒ:Z~bJi=aTdwJI4=N[ܻ6 }T$2;C&Hڹ9ȍWaġУzM~"Y}jj1D%:~!xaӀDėu}/%ZD3WIk0vOq.+̱kՕjiʾ_&ZS6 , m>a ţGfL3֌fQ;bSH)j }~Gz5hbZHkM.iiu9dddH+ l5iV5Hwֆy9#vhWAN)4 }'] Q `$#i!k$X8ش3vy{w_N"8Okc{ "Aw@0 A]fX׽GFw A3*2P:]pPs IAv92m]as.3t q#@%jkHu J1 P澴U.jpd.I‚ָS Ow ~)fN1 88zݏd\@]:_5V|B(Edz<4lR{1kESy^3Btіu r]ТC3imB=Y7_ j:ʄ8\X /WqeoɟbXL&#TxzӲLҌʄ7 sWBlwj bs&L~7E8$\1NwnɾϜ):Fw!}Gok2}>nċrqF 5KQ*,:"ZQIj{2;4=;yƺ"{( Xh5/MԦ7{qy5;،l|WO![ )iAś'L~CmN."zO[@#ާH O0 +.}G60BF.Sf+ sV1M@;΃5~\*ZQxc)Џs&=ۭ2EUXᛳ*\S'I} ,@_9fI6x&03A5rB6NcQn[T.=A \` ă5~+M>`:f 'w s"u>@"@Y}q :H` >ܼW#!y m^LM /+|W#3>V=4p*r'l$Qg1P}y5+(bqv񹩴rڣm憣EDEhSOΑ?Z?ן#ϹE[#6Ԁ" @#H,AlbԙTVpZ/Mv^ˈG= uymęјL4 q?,-Wa.b겥-t̸yʾsUNz Wh}=+h zH z;>g.f3 4e ׬--x>4M>sW1w1xA w鮙]Iet1aUBD.VĬuk"pK%,8[S ɖJFz濷)}6f"XE`Bѣ0b=κ5)<J0r4Pc{WvCQ;-xIYYDr.(c-uߠZ}.q]BMBh'ܖr?dsFkiU lu\Yk%Q\?ŰT: hn-` o_ 8lWTO\OTԃ튖q%ͿK,~TyTF͏+ܩ$lZOesKun& /͋x#Ȝn /z!"A>b4OcI_0\u?_C W+1I.D&LYAJ,36lĐ%0Mے?4#6R,em eomJYzHX1CАK腸NY1~>jHO0{!VeF?猝xٵ~ g6g'-YGai8,[F0Gwyy|b[,aoާ撎[8 K~33+gj; \(Ml:[\ diU:)l y~P^Y=(֫hs="S'D#lub0Y/2ObᕴT3lbW><;i2&[p`&RVKͫ^YI?*J ^ƴgO)em#z0[;O?WH5-smZ o|,r9|+iP^; : tyr3uX|~n| UBNT2ߧE#0(2-Qi\<}7DAxe9 Qne'-L,o)h@--K=T6^iY򓀶Ӓ=T}|c^$nTbd>8dԋJ7EWm0R `e RSuF3HUꀣ$;1TV%Cٗ_^1֝(9唁dޮH۠zuaD d쮬^Cfq @Q#9o㷍ɿjGf#R336,:mnA@:/nJ=1(( .)P,SGK$Ѳba0C,S%-yL 4.P1 |lZa3g_ڜ)AEFe}kI*b&輆HEiTOVzCp-Ÿ&6bMq `. _q ɦ#>a:a)p\zcS{tQ b޻@>oDoU KJEx Z1羴!Rl!`%41aqa.'\6(՚GXf>@nET %m8i&e=F.aZLr_qfË^1JN.8kȧ|XEuY}yai݁(; ҺN̿Y6:wUѤc: ~f5գ]9wN0"bƽ;Vn{djd-rEq _"U+7,0 ]'V1[޺24tPR~}fҊA݋~ ^RmRu~MѝS[£ o~0xlʙL4\83k&TUj>|'m[K.d&QwvCU/+ߞXQ]P;^(Itf̻(бM޺|1h9Mi;5 +KnqLItʵSKt e9iQ~TmV@wlZހ={}{u(Te[rCL"v%-2:!Wi@/rO{w[Blf}d>b2>GY5[ T,atjN1̀[)TkiQ1OP {j"eamsD X%teߓN(9w^L`JcbA"HvLuzx=i)++MԭmadaňJeZ#}hu9˦Z,|'-;"yـ;W7Sbƿ;Т&@xAcʺ'@e=9xu Юj1 &L`QΙ;02|*f[\zh-# &\9sߣ'Mu?'sY#^4BFԻYK\^H+u z۩0du.o Y`Z1bG].bq{D11h.]w2$T?6VїRrh14ݪu~i~ZqC5h=8\yV33X찯z}*rw"76G n<$ãLߔ,NQoeQk]MHkdbX94+m3L rv}D9yr9d5g7/s]J./XL}5~wt6*{ɉOU4\۪ngi9Ӡ\š=?6!v2έɟh5jedb$Wj7@9M1 n/|^ e@s*t<c 0e(ח7 dAoc"׻i[6jghIxxbu^h9"Ņq;Թ^/{Ky#o!qѸj1 g6r*Xޥ3 "qXL0Q0k3֨Axm\HRG|R%yW޺PR0 7`󴔅yxU}H/lMj։raH @Y֞kSCFz>5 ]i+,5(V? 0I`w8B7hAg][> ;ŽW<s`:ikAW{/a n}S1dMș "9S DaS;-meA9Dw:8px%,PLlmgtH-}jwZ/P-rޞeޅt)5 nih.L]fhS̚0uC,*5Ҽ)Fp.Ҭ"k AE1ٝSQqe~ԭ&̋R`8b4sjE~O?R$fT{B$(㉳js~8P *V~Zm7Pª G *vK-$FM}32}O܆  j1*Q…XѶw k{E2 7hMfX>KUWdnV1P'x3\Qs@ i[pD Rv{A/ȻBto}A { b ]Zj帍u#BX"%y&*#4ZFfgjO9Az޻to(&$ 1ȉ c2D({exLhLt)s{qU|ΛmO9W(McNv'!?S[yyb9O}Sb* mE@Vx=oc kNR2 h&6k[L<줏,!O7PlG Z*gKv\Տ/1Hd:F{z #rFlƺkBC8V u`laG{T> J8Z3I\A$p|^K\,L1N9ϒC*}A~ ܆ou~#iYV"Ͳ}7@Z#9`y 56ncShCOZ$BaTrsXMb UygO{U|bBa|Ua+e"bmiy/qHcV.0wP9[ȀZM:~.KLDVDŸ/*jڦ[ev]K g[BƇY ΢_!~DͪP$"DS֐J zI_$UQ"=k{7X-T;}OU0c6>C <.h~ԥl|E %Z,VL#TA99 ]:Z4$w=&5 ":ʗ=V*!7'_ ~TzEqW+dnw,*+Ud8-Q3m?Zw`pRpbp9w8@D~1DG",c|G1 ^ֱmbnwshz^?{%75˵'e ln= Au5i2IEJw"><`%$ "rzp{x 7"vAD[X`+Lw˪bEcVA^n/{t1RHt˜(&?2 y ]N ӕSkH$iL7T9Oȇ\~3Y)CSny@D+(3UV`|3?Q~Kr5Pc0XlRR?Ňt vh: Ҟ_J7WO@l6(mB?h=**WJ 3S´K3{8[^IK 5.~ *kz3z;rAsgZU3.z+#$7c1OIwRtˆ]7ȰYmjET KuQsj ^[U _F;HiI^U,}Ik*37Nn })9F$8a^Xd;-y_`Lx9+ϽZ(4t@U:% Djo,x#}lS$=«YٹHo@\e(>#!2ڡVΧ=> :!0MA߻|)=L?ޙJð0T]$>5lM[]#mC;zt=/A^~?-h\iK=dڊf2rM<1T88 `!J{DoXL̯dDm "jݑdBiMR*8#Տe 860|u#b(b-8:Jb{eEz#N7{{?~>(覾r թ ֽH9=׻i%ޕ*1Oz-qE!/.[]3\lzIk#uL%sa){:b0Զh|7рnOt"FҊJ.1󰻞,xzl~Ub$ʖYO4y.Mv`#П!M5JzZ0qc п'f z ]x1=z9fn=~Jѷ,5w㨶A2mBp\ȁ$qڼJħfNe2k+"pk0V=׋~U !C?h+)k7ڪg S9sGRut!c~.Ad{2%b?$U#v9MQK;Ef4[,7.ä':^:J㏥;;z:Y'"hJov/Y`dɅ.9fEU5(ޣw˜,e/_ʴyzf](7W9̢:oZvaW.Z#dD5F F&@C#zdMkj ;IOuz?ة+LUt_æFak S-`1Ahr"R0hsGRs"00O"O>$&6(V'Qom8Ki9OT.kt'L1z;DAm"T+^e7(RwB=|/C@w-]ԓZGc(%/6gmCC rôܿac ?!wõkPmX7AMN^a}SXmqglGHm`%1uۦ-ϗF)* Yyvy$/O?gS9EeŨQZu=p WysJ й֬GV^۹Mdf]"7[NI_6It(zQ<T7yO~Ir߭*: vQBOP~LÝՙPWNq gt5c.(sX\Q$avDE3#8u4P}oՔ̴"ۇH>7*di K;a"gy yƓS}_쇮 "POq8snN=RS KŤOfw@DM^ 2Խ@23̇( `R]G"Һ_z ׇ}$iZ%$TmX.LG}WZ8\=tF"!jA5~f!<\L1bǣ>Õ+v^wh@ [DaSh: b=zmM z艱+, 8UuAғj2r7T.Qn2ѝ͗/Q Lt&G[/ް{z0u/92ݔB}fU~fxrH:2Xx?FzD7WN:KbT^"S)))ݼJs',T @9H@!Rqjw>ilgdAn3gY+?;,a$vvi3T(t]`QO8ٱĤ'v{+͠E}ޥ A[3!%quY^Z'&N!&%#e[8x I˪:, T !s 8^aǙ8Tғ{_% !7G3Lnœ̅ -Wi#OeE!2Ƨ-"N0Qr-Z3x}t+.hlq7nM _mS\[IZ#iǢuJbiwk T`;")@jO?<)ܾDF@}*ց4s-v:,.88BONBZ"H ,d.7V^-8f? m&eN<ո .Y%!>w? 7$E.J;]1LpP(8 .KB~> yZfLac'M#e>k{XC@ꑷqT(^{hUQ=+*uвl^~#}P&Q]Iײ4]b}_ VfCL_"D՗ y9aŢ8ܫΜmYK)kbY9"Kv/>!EUVY˰rJvOܭVvBXՎ`0ȘDWҮL/f@3_YAܐ8M_~`hrCaU]N(FRRS7qJ<@:*5<+fp>-{kthd 7J Q!905dlQ1!*C7 1F͒ӝ/eȣy L DA {sz94߫ 㘰07’u/`EEr~[r)8C$k׊rN͞nhPޙ"|Jo}n::rl]j1I@\LZjT<*K'vj1w,M]-+kMbn'ni,]{x'Rh0K۪Y?9eM|J˲R,<_JLjj.Qxu(p[ 47n &)]h,4 (?x<_UKl.(soԗ=iL uSE{r3VZg<1/ DxN>߯FU}p~>d4NSxI<mL,#UE4,:vB)<`:}E$]Gzm9=TsjăYLE^Q-B`9%” -XJOLFa4!U-@^-6!5g)C0_izDĨ07 \#:CvfCcϖO8]ރkeq >2{IeuqQߌX%4޿MW4XL Sr7o㥸Z{/y|0a7d40itJ9Rpg,`kbIa=xHk4e|.1T|k{D|iRZ>Ny%Ngzs˺X29{ ń"#FQ c0A$ȩ6H#CwAqDF3Et z8eLÛlVչ. !%[jďN2NI";\L|i P޳d,ɹb|i->|^ynS8U2֑6TU )T3DS3]H\@ hiaK z0#AI8ة<8  ZC%6~&#ɅF5YO,gJ} d6;>넓{v1abi9r8AM 'jiи|6a4)ˉʏI-JiUŒ3O&is  S*v8L8ĘG2DpVG͠kOn`\6lA (5ZVxB*Nj+iH ZH NƟ2 6Z*x=ƅݮTlXJV+qp@cHzU-_7eM>q13V L{tM^?t_-HP_&VPQCe]/M: D[4uv䶥uM>ySY-$fsrf z2DPOHٺJXzd9Q_?iʄ|yӮaJeӳ+tGˠA{"r_.VR}, ;u8I$J-ʩQ2}-ׄ-ePi}-i Wފz2zZ<]y_L&i ox1GSVt(SxZx^aIءdBVX[X;kO}RӸjf?x=c_$x-?ӽ5$ǂ>9-aBqMIHlt+# n/˻+6|cAsA d\}ÞkbAS:lH65h4 bY?qD~7EkU)U!uGNhi#|6Dž}m9 UW71р7I;R* endstream endobj 275 0 obj << /Length1 2135 /Length2 16168 /Length3 0 /Length 17457 /Filter /FlateDecode >> stream xڌt.cb۶m;t줓tбٱձmcvl}wzǸwU33"'VR6s0J8ػ330DULL LL,pjVI5.V<Ћ:]?dbƮf7[3+ ?<1cw+3<@G.leaq<LܜtVycWKljƶUS+tuuad`0sappxXZT.@gw/c;jV.:z;[+Sˇq6@UZܿ . ?W +MM-V@+/Cc[cwc+[c7H+? _  Qdq{3Q;; _Y9M?w[m<}lneof37GFu{+'7ؿ->Dpd@W;'7 zZ2\/G~> @?+s;33`C 4輳'@cL}1[f^n.߄qгrYؙLΏdl,Jۛ;G'awߋA X  Pp=&v&ӏ/1oZ׿1`M5ZWyJ쀰h"a 4Sr5רKׂZ\PLLKU6<~,)no`vs>Z>kh{ .z~sg `K/`d0JA,F?OSSǐAlF?qq `4bqf9? 61 g~tps)|,d-?n?2?~PnT?j18pq̇?8QsxTR*G֎B?*:1v_Ϻ0s]j G>sp_R~G?y~Pbn]?o3 h `l]P-A7?KJMD]|'4܃#Nu+BsR֚b2490Yp"\OO&`)Cƅ'Y_:_! R6CPC:҆>[F#nyvB r%$e_F"?-|^ l{:F`܁%1xq7i:2G>'7US^wf$#{:! Ǎcjv y"R79:F7^FXkf@ BguxQglZ|'%} <"{߻LU.K“CUM 1+ hW^0㷠ќח- ̡pNi־6z%tEc5G)AHKf84NH+B緸rHE sViqQ54ϙw-]?V;˖D)zPww"Sy|AȚx #(GfѬ Zv\^Cސtk .A ^.dfC ʌS?g~w24 nǻӹsFNA b qCt7mg Nƶ>UA˜`o!5ߤu(rsQ0T3G({CLEךtAS|@~2Fz{Uyy <})7!R|&6*Nnf`#Yog4;]~Љ31A_}+-5c%l n::Hr*gWx#ּ{'7@Jg0du6Z2e^{7 p~lZcР,|k9v[X*z2*w՗{TClxscԑMMյx6ƷrSLėiЙP*@Mʹ@3 eodΗ^ce@mcb{HV* IW=MfthT\R0  >A,o~N嬜%}Ƞ.ZCFW2 hl6h?~Q 7H&r@neԡƅNGNñD"M&n0Sd;(NY11̡5dWA`VW/$1\5?7dP- 5֜{֨4K@qٲ!] 7~f`<6ۙ^,9N:coe)Ybm=)*/_?"%QKOfqq^k$9 ",A 1źp[>nIB1i3KI:(i/Z@98L>]쬔gGK2 _"K |SXib(q##s8P>]i:u2Cw +-V ^ӂ''6ǎASX^,}+fFPΪgOF AsjCO8vCYD^LDL;_tCuFefC^X{Z_̫H *wee6t=pX k~JVS7*Sx:Џ Q VW{ #c]]LG'DW &a0^׾Bo`mp͜Ct84)GfD/;3ZM^G WoT O9MV?htS߂symiIQJ?%p5ԏAf 4ɻ@dEݳ{J9,hZ ".}M~~odl'{6&62jLZ$j? _SSU1ύ3SV \>9?, MW=ޥub J brԓp?bSz8oTEQQ}}OWHl%5d~T߮rlBۤ,F=[c2nmoBEuܴYa^RPG׭ kbP|.oiX݈Rl=aTYuKUطl`f|V/@KgDO,cUk77P+he+%DW4*f]tI94r>_ OQ=Jze7 a}}&Ic„5Ų#!E^̕QXzݛBPl-Y,iB`yQ`=IhoLu3cɅClƞr8OR=|U`)ޛj~=JoV$CPH)pJLİ8 e`:m֡[-zr0S[x( .ZP?J7RD@^[*h(f)eEvX P6/}G.yJ}9BRj6G X!a i;p{W&4H'(x)@$ lD(DW郘p,xbc_}wDh#/b!Mlu742d.TJRz3x%6J8L]F ꠔ,aZ;A3~G,wvM<`sv]U.;k}BW3{La&4P6+ 4(Rg H]R4Y8] 1~)W,UX[SJu*a;ߣ`*& ѽ>K&73Ԃ7%f?X+l O6y6:{H/\!vNKi-QH,[oihm.~)1rbU4B \Ԣt 3ils$ޮY2rP4U٢X #uj_PM#YV濪#NDt"J\JW%scAke7H$@ oizӃTϷp!ⴥN\* ǘ:x $!+ͪ@Knd, \v}DGqߴEVOb#ςo~b^:FCw8ss{MUf}EkZ[UiH^dhiJ*yk%I$LڡYR%h;Jqj TƁp 5m: ӹc1B?y2kwk by䋸]BiS g@MD/-BSEC_He92玟T3lX( jqTQ",)=LDv fbfzsekh*kUkzǸtWUXYhNH~Hp&f@ʝQP#9 ƈ%B=`b`~˜ ek*؀̌?&jnv %:)[xvKMMʮox;:e(ilDC|:ɥs,ݽ'k !RnD\hu~kB_1 ~X* L'"LlON^'b{O65 bm{$'{s>ˢӭOԁ9^JBX=ΜS ox՘], l${G twrc6T*e!򔚷W5N*wȧ%vZ15'Oŭ\36=rR2?'ܐ$T\4hPu._hA%:[QVL{پdg([.#D6aK-[>s$CdrD8JxCJj(8L@(ecbJj]}YuƎd56 I*-%ͼҹ3p;=aNCr' sBu*x}JP8zGy1EKo g$픏a%Uy"]uT(V!յ8$sPeMOyR_^ܨQ=8^!9ӛ9Tc gCX0r0 8%FvKk5}ȌrٳB^m6݇=Xr`UF[4afzx;l ]V4KxwqW;AxFA>JTj"e-ymJb0Pԁ 2(1S*s]c4Xonn96*R߈:Y{P再ިiURgnr&Йiw&M$y$+*Ey ; eʖzN|!*`O ʤ>װ5"tw,|ilu|dIM5&֐Nv8caڈ&* oD\ 9C+M"teo ͛a?<߷Z#0+L-%@MƝِ,ŸaBUa>bl?՛dNH;0l/-)dT] )7tZ5 ݜ1-xb`*o@ 8*Zxzd nl]~ Z/2[?׳rP28a3Ak>0쐈EL5֕1=ByWmj5Ru?~.R[ȞNLЉtcjb$&jOGδf!ͳ>ԎHx7[ܓPPTiNrSS2qU+\h Y pk'ыB*}vRhiqҎ?o$Up|xCjZ=!oSn`) EJ^2 _L\uPi R4KcV GӫNxtU6ҔI'V8Iq88SjW翾V֋ڧT%jK!,[TʵJ|3ywց4ᓅ@ZH[d _W` MXģMXF]‘/߹_\竣Ԧ}/ڰb^d*+v<+vu&m%:'<Ç721m!"Sd vs[#_FiM=΄^ĕ *( Ux)$۰Ujy+P{`  VEjj}C ?apT,a;NZIyҙɉ#/Qq0L 6 @#./$/@ u23](Tx Pt%vܠ2=>d _A/^ljQ B/zz0ٚŸaSyzX}mG39>Zpj/K?3u_0BKjKs+F#q!(5qgS;ʅfˋ 3*qLӰǭn66}(4[9&+*q ZFeC$+%xok5qX TMFj`" [9F.D2ہ$ =%=ۚӖU [G‚m3G$1RyWOɖ7]K&?DWIR!EnDS̆L9)&Im6_H 9GFurRI~75UbsWLRgچ ZA<%g;AIN}D,ָߧ\44d[)|c59Q`jqUt,|N@ayҹ2"b}g^Cdg%᩟+,*͓y^c ؁7=jZ 3 =@d3!ڤ5TU_8(@rHٗ C惹0mY,o> |Q']ɻ=#DIwA?MTq_L +rH!& q{h79*(iZfɗ8g(PU =S9zrlz$N!/՛&q`?-1P>99U,R &c3D}7>_Wi]8C͓znΤ.ڊ">CuagBs:$'NNQw{|[$vz)*1iyjc^cOf^7'E1&R"RT02,w !*XxycD7t\ynʆOqίe*9kDu-WS}XZ {'ߵeJNdsV: >f1P "]PskCe B, <"LM4sqԓ`|dܜcwI&]5dwn"M*>U-Z rii։׾V Í܊8 GM+4)֍aJ /u 4BkŮ,y*̺i#6{@fIW%a+)+ CG74\7O{B.j)<-}Ul; Ĭϻe5mB J/ST7暭r8Q+ƫjbMW5&e+8&$JIHrFw(fb(YevD8X THR X~bj#D7s\"Q_>D?ӑNrВCzTc՜W%MJgn3/'\*L@W.O.C\rd8L4G=Gqiu^fe&]T`נlx5;h^Yi!x Ω#W7>ϐXs-}\v̸ࣤ\LC_th6@1anK't[_ Q>Cc=iWCN?w"@qzWJ>j "#!1&v7[i

M4fʃkF$//KVݶTMmcc2NHrl"& ʖs-U>a7 .J5ngh*wvVEX#L@Ӂ~Dg$3} n ]JQDd&votΨ'\CIέkF!;a}DZ<+!5O  ,Lm(.oj>_u@T" }O{c}4뗐 .LRP$,݌W pcfLT,&GKgBccfI Wj@ Eu4գ7K(^Kn:5?O ]O5`# tYvB99Es`he]AŶK^LoDiYR]Iĩ궡={m+$(!ڋ!~BT5^ۑpaBid"L#\&c]4\=i(.۔ LY7t Qzt"ڕbrzb׼*r}v"(qm*DQAߺjAu1]g{J(0U~H)X[aPLi۠/# K G.]Q_G%A$󈣵tJKt6_m0Wwog Wd FKt7dm\6I}iqVR0źͅ'Bj_*&"׫GN p8Ge9H5?d6 X먰ܔЌ:Ps ,v7ʌ_%1> &:dXn3p6gH$\IДLh9$˯Gx{)66sL˓8fe zI8` @*+A|QZ\LnI@_)FӞV7rů gKZfb0oQ 1[UQLl&Bu)jDL=k`73ŝjEc! tw,JO8/V8&(R̓uO7QLQey:;h{ llSd$dp,MޅOQG^-myt NZ!E"tj˻8i p_=JgX"Lmu9MPͼa*;錔)1 +Z&QkIڂ^j5Xx]y+ e>!TAVL͛9#/ bКFDި|0wAOXʴ0C(V\r[Ӂ`BOJ7(AԋMj\b gMt͢?@ߪwe= 2C V54/EM Ll^/ڷ 3#y2_sq:_O n| '{<,#?ł~##"~M#e@gn(tkZ(z؆ɔμCiWE觟tGSi^SSBὖl+DIp,ױքx2KC +05%#e?ԗ*>֘wRW495 r3tAo+ĸ:C͐vE-KvqzUop.̙+f٤ϡ=hzza;Em7 S+؞o4WQ\L*dӅdKO:JmApN*12]d֯I>4x[N"Q~\} |=]{}~%mðmrG <_13Y2}kw 9`0euu4vTm[kNJN0UƣP~U/R;do/sK8}2hN Kq $jrEXnb(#RdžC\Iݲ4 J}ҫ+"t"q?'>U+Km:]"yXf3L@j` {a2匥bJEʠXI RHopHBWOXdv; )pM_r:Ia;7D5 ؁4m'C =k?aozTi̚޹x&}bUܹ݈֭BڄJ)S@ǥg9IRbšW]+g"*c-Gbi_k:ā*J` B\u΀` $ǒYcq2fn1+ݸLZ:o]!.3 \({.3MF>DCO)Cc% 9F8; L$DTne3WhePuJȅ%dXя؄\nGP_vҷKTv.M[o%d-C$x>R=lIMlEVq@KozE"#ࡈ5' >ЯRP \_nMjaJ-YWhU<2 Ɋ"5-hX3Iш>}|P,U Z:{+0w[{lNk&tEl`UZp( vR8_ğhaԷ?5J w KB:)vt JlBzd n;.M".+9qN\Y)r$<7-qZB 佗ߟcij]}|XPl2Ԥsv[oԸɑqUE< tqH3X2~n(Exsӎ;sj~ٙWbԏ&nO!x2vkvI`b.ak%~ lg_@:s)~ uC~75WJ&5.;FtQ2g}(]NJJE B ^h|",[͔uvVP6{EFvy Vs+"j_tnlת4TUh5S^'!!}[6s|B\+vXMTN/'ʁe/b Z+cmfB@2MA\C~bTgι\V$ D{BTx<klϮ$&YSLʇz?=>K[{yߊᵂK+J-tbtK>,jp=٩oUn>8 %iKYCJ*}n z qd lE( ns? qL!i?UiƁH(aiҡYD 5\GȬOɊWyb= w'4 pMsH8PP`!˰4->k. 6t!}Jݍ*Z*ɊtO^SpuiA¦$Uf[*GR;]U܅P9=P{새; hktܾrcz=86`z=i>ș{|o;s] daLQO٘!D ~]Go*$}Q+84Ao[:6nC`` gލ}k8PvL:ARcNz@~^8г- sW|睞 gm͵:C繓L(!7^Pmb5_M|+WGM!D4ugӀHT1؛C2:PS |S^ln*25U`h>R=s;mbbozz`39i2T{ @%+t&^s@q) .$G0YcqHvQ [l]eUۋyAp"  =D\ƕb4XP,P(c~U<ּ=1bJDW/B:I3l.'R8y[. [2g/WSOarlCFgQex*)ULo{Ză-DY }EXdmLęNtz7.+Q,>wTah,&C'`F+`ҳNDwI'Lư} j)[S҅8EU|LJPs2^#"ng4rɵa>\2¡޾[VSiCFZҢRA& m3±%p狝Ό ѓ73~y(*PʦGzzԼZřYgUgDu̇7r+OŒ{4'> stream xڍT6LKH H3twtJt 0 0Ct4HHwH!%tK#7s{[<;ww 4:6+ Ȫ8y1tA0'?4&> EpuZ% Tܝ<|AQ!Qnn/7!n9K @ o7= ~>Y<""Bn kK0@fthmЁX0J,nrqyzzrZ:C9!nv,O <6_qb2tAпy- N k  p:.@v`'Ͽ+b  ' @C9' rtB 'K+_[pȃZ\`PN(D_i]B`W}r 75\߬# ؂6DظpA@e\opss s=ׯ.p.-\d Zz07wF<<5 `1g@ۿ1@^cn>^6oKF^WNSodd ^_A7[ $4 DަT0,ΥO-{MxG 6[_f3к ] /Ve%|vNUym4A0ko^ז9@MYpps Z֎ ɿL@`kͯXYzc/<]z5.N0l!nn>\ҿ7K7p)FB.H/piF8߈/gY~#A{‹ F?/#_( z g=)@@0? \.BHV?]~.7fya޷iA8 2mKsu@.Gy5y2|~Wt[\G>}^@k/kPж:irOQDH_珲\e,lIF5'gYǼL)7dN8nκT62q WZ(LwaB=|5[E"g@%M9,N:c)ƈ~ 6ldOf᮫7~i%0^^؟@V: ) MH)ѫl(lybdE\ J՜ V=xċPv?Zҭ襀]j x7]RX>{\].hBSii-M=صMٺ:g25Q\U> OE-G^Orb7IZЇ㒥[K ;dx$5+px'!Ug36/ _-GjY7Do3ةVn8[نaIy1@v >'@!6XwWiwȖ'LT{m-Vs!lzfgMqoMNJ$PGϮ@$FS\QONa;vE*QdB6Ue@; _ƐSC?#c8-aИ! paJRpvZuxKKDQޟW{2zzO'|:]OR_V8C<}>ϳ[tRoh+XM6P]g){i zO2 xIXg͜Tr1'9#mD.I=To}RuOYT'JpɦN#C1sV?$&mԔ|˜Q(Ͳan}t.Q/'_i1(7s+)x; *"p w@-qtx+#)U-RhM4kNFEb|b<v P25KGWqyyۏwBNfy/#F L 6 / {-/očܕQe@&P3R~&47#J"俉"A2TO]GЀ36>ٌN9aF) A'䂣TVסvɃrBQo1hKU㕭K,ϖ9Kʋr jXvlj9XQ0-]e`ym*C33>i\m;IQߏ %]_11߄lq<ܱa㥾q"k:*bt Żxs=)a=&vר^m& m4d貣zz%\d,+L܂G#*pl_X6`v=p"9ô4/^ ,׳p*ZDcFɸ%nggɔ1tV_p),`tɃucJ}B%$`Rb=U ~%|clB=LmR݁f#,;k[#jhn܉  <nj=j ku@ :zc`84AےN~-ht9qdLf ȘؒA9sC{YXje7 NNH 'zY k:[ \D&=4,.S9cN )=q5GNq .ꋏ2' >R.=QoJ o*T̂f„jw}̀6_bs[)Bc*u僭 Cs9dW>ńT.N! %ciV,L@~N cĦ[eFP#9YE> bnDMjpQhUOOtZıײ߯W>W!&I)ڊ-ى'uduhdPo}ZI ZO}6flncا<=e|$`2MC[ӰN"̾Q9 }lL EgZ(F'&1Ԙ =vT9f1[^ZJMR{+ AbJb \;v+[]=ڄ"wC޻utl j/\׹@|v}Ȃi֥^qԒTZzEp1:wZHLOEG0fZo(&=B_*J"G!`{d7 Im TYZbȵvoz *򪶹gzş'x8;4KF2 0cӬNBlE ;OLL8U~Žm 3ktvs(׷.Ďи&·oD- }kԮ۔*wzt6b i`L߮_ e4ҋD!4'r4׽%7UoɚNBI}^pmr Ck`C"xd;(N-MtF OڴCJ.[A*kKxPb YOd[Dl S&*zn\i]&~>EV<$~Z<(|*M%>֫XoAr6OmOv1uh.i^_t> @ Z>$xQ զW>aK隠&3@[xPT[t\{hVPبN"K3`PJl:{׊d[KU0ڍi퉚S}Ń@@ᆱ@Cn?Dju`=?bE.4Q$`}6S WG) EGcSGb_nFdc^疡i{ xgx&ف| ^>t?m2fE0cOE [Ej`Vc*:χzWm<$.5y"y |Uյ8<[ZZӵiۃ6&M^g%;~ĭ}|Kv󳴦k~E>o"%ג-d/rSe6x.x"#ZsU\ss+)|b@T(/q1qBǦ0&zIY^%o4fdܹ*0ROd'iØODzDŽYiH b?s2Bx;5ot>Jhۖfaێi[fWNH)|[vㄝ6} !k`b6 lBSҴ&m%iQW/C\@ xNXn~p+'l/| 9k6khl:BIm v'e7Q1m {[Jo@;be/@^@@N;^QvzG}|dk+2hLxymەtmm(S 1־ANLZRwmyD1h q=m7ߨ!')0xgLcfSt&e]8_itǫ}T|5(Aк{ee*A;U䵐h9*EOѦf6vL8 SS1"tڢQ,L=ۏT !1?o*GbڑĈ^%?mg},:.@wMVB t_{[\+7CnKgoSo8OK- xF" N̘m/sr*dp(}5yURbђETm{.}g9 xӶӽdy<4ֽ >"䧶R,S%wf&W8Ԅ`xL)o+èLstp C{Ea)ReNx}cv "@TŊ4XaDM.ݰ=y-o Mps$!Xw{Ke*LZOltTJ581oG4D\{5sQSB%gVĦxU|2VX/^~?l^ݸx јf7|w>@ѣf%0-+k7J>kR]$i?,9KM-^_48WxG͓ rҍwoazkecL)FD0&3kCܕ=@"\ai+ϫٔt&ʧ ""u!%eɣ*9,2Tr8!g8UK3kNd gWvLĝҊ܉i[w!so oNd!~?wBxqpK|=/uClF,u@Y~j2y+3I|S钜6W9An^}ظmx".yB0㹗>Xv#;.9_\h9;5x$cY-$.D}=NFEkqf\wj蓇NQ[yԇحK.,`G" ySx\Y1i%C[uj" ,Dt쭴J܈ QTx3 "UZ~_[?-*US|7ؘ&ޛskH-6)MMq;LG 3EI󘞎I]RZ躑,`K h:)]6 O|ʝt I$Nn(@&s0w\?N۟Df'E ~'*>1Z^ Ts''N5$6tio~&YGmR@wv@~儉ua4NK\OkL%(Z.X1RoN4G$灱"(Ylw.^Z؊))9oWk*+VETJdӱˆ.#'! X%/T:t ll+@ rIm&p],IzXn'6ޏe_(Ș^X ~ɘ[]ŶgΠpM}"(zXS-b>QD+rtr ȴIG]#:wo)dX9oVщMX֍A Maj 4qGJ'<3~aop {xٳlJ.o5D]~@4ms:W3:+vHCe^5P}nd#H:1sS 4^ZgGxѥ΋VPdJevT{,Ta7$rW|K7nᜌ,rE1h]_H^ p_hL_M$~ cLժzlbx5ɷ5.lʥv̯_b,p$"/9Ȥ^l;=GgOξJԁ)KBkh-ʕ)Sa|2SŎulr)oƖW1~S`>U@_IJ[鱴uqldW O.ȢUc|;sײh>Xoygpi@Iuq‘bG,CAh622}T,^:)X *GR'6e; 9Ql[\Sf)ǵ+}/"L!hjR?ڍݼ`L 5.Y`|nq=y݂#?ʗQKܥxekټFW&jHIWʔi*F=(bVi_,Wt ܌ E\\+q̱*Z_1lQ+n ^SJPؐ-ZrapodXb[#^ ay,ЊNcRDl Y#-|=zdM'-Fߥ&(+e["4fF$mLYeK̷-Mۺ`r?U'lEa0-9R3yn)Fq@ƙ#GhK/m0v|͝ż+#|si穵:ט><ʚw]9#UC`#lBks΀0gy)spjxW2b&[R#<Fӭo%\e&d %R$G1jS*-Y?̅Q1^qp,q4=xzJdq DsѸ!5x1Ԫl1Uo8,t&7*QQX+#L֜]yQ>xʕIݰWO>RTgШ!]rttDU7Gގ|\?=85tO>R-+\Jp27y HER﵃kaPC?;zt J0xG[qn)RKsv"sA|Փ%%3c }7Ow 4j/h#G¨=|/,Qn޶K5Lnp ).nlx @Ǟ%pO˲滅xLVlA~k.c2ݒ?^j|'\ƕ$Mhd3JѧtIVj;jvd7%r7$>H\la9DO&' E0a= ><ʓ wխ$%|$o' x`?Eƛߊ|MJ8.W1JI?Zb[,etCxٲa J&|o;u!ޤG~P`)jV܉hO`TL\e WhҏuZqF G$骦? L820E[89"vIOZ;0CD7:AZ@s4f%n[srӚDc%fß G0 E6(ɾz!R~#~%GNv|kx<$ Fi7nA[# KBj m#jՉ?y>1] +D`.ĠϹ"xyr_`qKrq3u8L|C N]] [#(LJlΩ "Ld%KzI~R D\/,{@P)wey\񤙓$Y'?J*=!?VM֮{mX05e[˴2j𮑴xR8]5:>W1f#^@N$ْ&0&Q =F1R"^@th_|r[L"ʥu;o4(.\xv 䪒(4_Kx-}ZZzʖ*{uZɯf1P坍,1BSX H(p6?IPڍ)qt?QQ" rϏajՐO|s 0.P@V SS)] zK@L3{l.M~~TAakG[TZvAl5ϴȊ9& ݊k/" ??ۢQgOFĸljm8mPz3TWLn:d8ůuRM,Eԗ?jZk ӑ+,uAc;ToN.kR_ÖvNNsW endstream endobj 169 0 obj << /Type /ObjStm /N 100 /First 900 /Length 4994 /Filter /FlateDecode >> stream xM )*3Kv BP%`\UJFV FL L+`6S@c!"Kh"=ф$YipU$X|4_i/qx0RtPVt*(d4hT "җt G`x "` S JH(F5 (, ]eЉ3: &FN)ʡJ֕3:im*gl-t tt< hH3 ?o֩V{ ʵجCVKP~' GP6c -VQUdh *zd N)hB lz* {Eʆrh:E M.`F-v%SA# `MJsdJIJǰh];k=A?Gbj4Ch)ڣ=##|/677;X{lMvTb{2&'kAڧJM'jzkt|̚ɰW+hr1\L͸9^VMf>S#ËE#x|gl:g pLh[GsQOX`o1lF`9xz19.ƃL'b8@|[{.r\w A1{ L5HW'0%D Wx:;@z `hf}zx~, X;ɢz;d^6 <2(Fd;2 ծ@~@@xO͐ ;Cu˹7YśJ6R&lIo'=Y&h|M7 - 6HKFÍVn>n3h|}GԀ$ajϠΟ&GD!CΎm-?]'6D !"j~d5l `RM~Ϟb}7oB gI={z>Da }Yk3{^Gpޭ_CKI`2`ЊuRA%OX7kjz,B YՐ3 ɸځ򬉵:jP-#,`:V!G{-Z>H1F [$zݨkdIA'Sc0nd=gw %X F6p(koGjېbP0|'`kcGڻ+S MX[JV$(5cleԅO46b'l>3.f>foPqK #OQlp~:ΫŰᬩ佦8+T~pPg8#S kN3QkQ>ݘ[ԡ ?Sz@ EC1ij]j W_~XHŒTM9.>^3cMG*,;0YH6 Olt:cZekkA^6!B[KXANJEd;eN*R[هd4-匆79_nRI,s,]_#Kx,Dts耵EIF Nb.1à᤿B{{pF VWGz|M] db:[vՍթ;l}8eTfJ1T6P4*mmz}P.OJ7jFaJ-_X N70pN q }WwUƯt:^ ?*BpumX5U%Og'K"Vi4lo|@T#(mQצ^U>J"ӝ!",0oRgcsp‰-G/pLbA&!;8ѵ-;aj4-ܫV_Xchjm؂D?P6) !ۜi}y cyj^C ,I}oefnffaeӁ2' 2'A)q9LkyK d̄!!6;.y_j1t87¸g icOJY<:a,TeTc9 q?ïl ,4H bsT@r)>k(S|1^ń!ey^},rpo/Ԧxh'kDc}T:%xv5VQM9'D.+i4?+)T{Rʱ(T:`hS-a*}&F:oVnrlcI3:Α-ׄTs"^#ѡ.4rM[Ѐ=ܣ2֒Q[6KO ʙZ6$r JnĊ*=, ePlY:[`>ՐH O"OZEDH>:-$7j*| dh=ޭN, ujZpxlsWxP݉:,tNᖥ' ?%Il,Dud^6^ U ec6c#B>>~TR/ǟ PT_1zWemB{4ѩ^Ȫp]h0tp/.[](H&knÎtO_瘞5B1`]RJY[TFEwdh,=L]ڌ8XskHx"Z|L^O3 k"EG;/q X5mgG喯| `R%GzXOUP_h=a#ZyK;vEs,! "n~3)`Mth<`=mLG P=IYCyiIr?v0{ )) -AraJ<ԿڻgAaafGh1WgpI߳$Q\ xʶ0ɆJiE"t&2H'&$1JLI⯅R,R2%wOgYܷ{-mkV^Y3 ֈXeYn~QXڿ@t㻊6<m= q 2eUIJMJJʴxeMKa/:+esӉ <1GQK(3M9&b2yNMɘrIںBO|B^>! Ń05HmRF2ar\r&IcfC7m9'^/+1k\Z54 1Xʛ E@(8ȹe޴에!yrz!mIMnyY|a#$IEu o4Mt1fnY&.cg8jtŐ';Bި֊^;.ZAJWd^YؠP:_=_+6說-@nw)ӊfO}nfӮ}W?{|L)F a%QHMq)5uWjOѱXn7J퐰J1}5\ .o &g|t2!G> stream xڍTuX%t!ғFp#6HwwIJ(H#- %!}߱x\u^u<ᒶBZ4$ |#cA >n¢ G;Xa(8!'(}퓅jHPEB  De!np+7PX kH0ӐDFS@ΞYvP_e7] 4X,?‹BR-)ib9-0m9Ȅd[>|3MLHbC>˭1~. l=`/ u&6XS:,*s*~^򈠋c3LS}4Dt7V7?ɇ fǟ~ ,35sSށ6IxLԏ\sϴ8vH{^ZFl%{"O2R;n@DW@ !a.*Q=πC;Y5xhG EU_kɯG3L{ +hon`"E Aߨ[yS_+p=ynMv-B .FTBvyonIJ]%[;_VHO".f?uJ2: I~uUYjW˥ k5~)eyj -ԠL xؐo}Vt jkLɂߛiw+2vۇ/RV8LP))' "`Z9nIJU+~hG ƈՓrz}NN"Ir"wx\)E(CۏJ7=!>vY2v"1TUn)dB.}#CsY_>|?w9"cR%¬t*:6"w$$.^A:QNNkbxo?֨M~7YfT<)5kFlyt^2z]uLZf-5RULLJfZWzawZ8]X|- kPw'ZXa*\34H#%e84 f̅栆L\7X`j]'>$bƸ mZ9Bfd=}C!J^gk uZ7 =)G:l?5&wkS0N:dvߓ^EBDZ+< aJ'.!^濃{Z+@fڐU }[ICxh7dK `2oλOQE_VG+K{z|Đ`T70CXbX*A}ĔfD",闬YI)VtXճn噭~'<6ZP2+vl&"Gj#lb=n^2k^Vy/lY< UcJCvtʗt[&W 1I<@`pnp}mqgPoBy)at5 FmCSb^-9>E5q_P>MGcR'5\vi==yűR_kh-OJ~w-p9 2.XG{AComGlh:i/$01.+ڝze|:eӂ!1q>|L=-1ᥴ1ӊc8næjcNY6kp+㣇g|vcSƅsrr2  Zq˺ fչw%"ԢFJ$u)z'ꀏ9獧=VUzhE/=ӣnMr5q Z''8}(D8g *M>0I$^[fA¤DW5q{1^ oDY}=dTmVE vh%m1pj-7*ڧ!?aʗ,ً/*duQCX]Mɤe~ٜ "\62W3Ick%zFo+P呸bLRfw{`4PJO wwď?4!KR0XzJ Ŕ=JO-%SE9;05kUD.p<ꪤ'l]r| -(6LW1e&P.ēуOJ"[c>p'c<;iިiV<ލœZ\ra̹A {<>'A zo>8'UɊw^ [. ^{=JzQj^Pz6/0- Mq`)e]؝ oix xITMxT*6\Nl[ T1NKj<ὂJf+ j:ݕ\9Ki4o $TXߨ6'$f]JB-;͉A(ڛsZ.>?[=\d+07`h)if[Jq#h>pC!$!=Zh\Fw!FpQ\3y٬-(tm= Ð6}rMq86rZJ8 -7'xX`3}k-3-"mg!s$YfrqrڦʓB>;ڎP ]t:?͒Xs׽1>s:(.`LK=%5S`.6-}zx~iuϣ_Eꉚ{@TY?TC O+ǧF[gW9fRُ,! tV%Y0mÒi= [ߞ'2Spxu2MUG? IǔK7r&Ȧ.K_4 9t;88IlЯzh`0~aZHquIӈjgDY@n=OX ](ytЭ4L3zT`Im+4኶dUo=œ7oP\o⾛Y7 zTkWɺ1 [ɫ!gh9)$UN|f֨cLЇX>T3<%pX8:I 箊{C9̜#[8c{cF]ubL=p㘸p^F8[b_ /%/|V0kj@TMG_P!VOr:ZrR`Il)zK'>LJ P ל.)1s7ryxk,4v8.={sivh-a2iBF:S^u g{c#H(^5FdԱJ,1BCdёՕ§}!hsa"V'D]JѲMD9ld&4*3/Nk^cC7!D5^Ⴧ}P9&=}_>C~' 5R# ".CkQZsɞ# ?z-rn}Αꨓ*ޢHs޵ 98 (Z&:r}j[rV4p&j]^ߥRJSCA4Tʫcۄ/qf-*lFw~RnH^,ob!Ui?5M;7ZEv^*" 6ϹV7̍wY%*%%w4)+vX9Ȫz"nZ&ed9q pH(p}TgԝLl_ݸgRK"|xM&\8)#; TxSEQ7ecyGp4ߌ'yE$Rʵln) v iXobP1t ]L;lsf$lk4ݺG 'IgzWn4I$IO_6kǔ݋״UlGAs2Fݐ)1Ӥ>{Ɲ6>[1GvaNV/t |U7(-*Kwr-dla4/mQddyg(pt>y?LK; ҃> ^g*aL[fǟFa$GܧԮ ̪D~]@I\4}~t ΛÖx8~wt,t_u3>3xeԧ"?Ņj¡o7( m[ :7ΆrQfr@Sg+B??8b~Q<3JY߉^mjrhJQxFPtۃx#54즂N=UᳰE40]k."Oq(?H&#*bu sUnul^ LܹAY; x^SfѕIYM;)FWd֔׵˛ L25n_P:ߚ`I/Z={9,%T D^*w&U."{,o!5v;tk1G~JDh꒥\@ԟqbAO_ PWhOŨ [ݽB\tӐ/FaVE|.}U5^2Ւz&eY|[^sJ%ok-־^*M|[O,NiU%w Old:}};}Fu૷gi>n v sw58HS1 *%%sEOm'?輻V OɎ|s\j0dKkR-))h77;ލht^da/I|ւў>@$mm/{{=ߜQ)3S?`4)=2V?Mɋv*șXU?ZXث75RhAE)O<[~AYGy^EKk֕=sKф3UyO"^nWבteS$Z#KOFqW }9A%C}]t I6nhT=uXnc臭(鹞~RnlQd? @L{tpnLS}R kHaܣjQ)]^:^qNAr[/e 5#?1U3)/'_+f9Kҟ.E٧'1X̧kxz\ܸKd%N-lp؁7cHPi.H~dU]+ +M%6{[ ݄8O_ߧv EnT@Z9SxbUXW\-> & cR:\k*c@MdO*qAgN!!i\Ydm}]a!+ՠwWR4c #l+'-fXڢ-㜄+%w,(&'ֽtA^6^H*8"p:Cë(Th 6ñaU,U~41H[JQ wR@UMn+T;kGÖaܲAN9Z}Ch,z762r$kiWM{("Ƣ e8}Uvo7O6`=@K \'PP2&XQ\X;OSV.ǯ:<`UYQbs+Ǭ endstream endobj 282 0 obj << /Length1 1381 /Length2 6029 /Length3 0 /Length 6979 /Filter /FlateDecode >> stream xڍx4\ڶ Zhчu{NDc0D{ !: G'JM=5k}_w{3ks (!mHZ$(i PD&0пD&Pw ;ؔh P<@"H\ Re'-xD@QDJHWw#S[7Pp `@v`*Bp!}#*%$%vA "dy^0#{BF]F$9P9 h/;1a( @AC -+X/?@+ ; ]\`CZho4?QHL< m1߭ 0f? 0W4JQW6 씐..PE?e;w?@z!^vưp2F<0ѿmP4@()&& @PoЯF>f ~HW=f h"C=4?\@; :DΎ1CZc b}uga7 *_NEE7O@X )@ 1( y?}G ]>ݲptB&#(tc,+#U8/>z1*Fbo)/jC`.@1jP@8`-'RyC`h_nKopD~=a0Q@0"8c"( 5 %6aQ1C9kJ¨!A `f #݉~,H d# 1vxc Bg0Wa5 ^#x+щ}Qhg~Zjn6v/ g͕GL3~9~ԝR[W<(~eC;gDS$3XZp\l*Dm?JNYq yOYWCo[dzi]a?ş)}YZZzߗ!rT[-"kpuR&RyZ"]K2[5z!Egdz7r iNv`W䨶:<0:egZ CEe'-d+]M.(9(4ptXV= ~{4cIA|j~>ydD=h֑f'IC= Vse͓0h֩&P^rn-DbHk+S\*\!hK9˅*wdBe$o(%׮~*PHJ3MDXn'EC|şƁ֔e}poP5nSx{\i CjRlaV~hc~VI3{;Ɠ 3{Vt\T)*nqͅK%2 T&&q<"<ro$ڀi.Z?iPs=<>9nEF_V֚bL<=ұZ˩fZW( ͨ{κ2?+geKzH'˴[xI p,X.F>M=BXͲ7qnYRH6P-aaBkkV<٬Fo$ثU_Vi; >]zXaF)e)P,z" ^d%ԎOSZ EMO=ă޸*G]S*HtC9P+>ZfWuhB66L6FBٖ'lLGrk(R<$ V8zg݉ g-bgu?uox1K:P4'J+ήRARaUK9jJrl!Хb/O#bqzطZ; vīܝTlUջ HVK5> r!v-pxK7ZcO cHy쪘-Nx2.B}FڊsnQZk6b:?-G -՚QRr9s1m0:wb@]g^}1+9r ՞KXV(gK(r^=:Va>O)S+1jeUpM{S*e|}jl~\#̴%]wzj#|>5Nj> oVO*Peߵ1EjdATUfԖ4vT(5][ -O(/r^|'(Kō~Wnߎ=ޖ*v1ʎ m0qXs+}4Dtt05ͶM(@7ǭI}7KLcO NNx#|W_{5rZqDȓ+xP=9ilxp, ȯEt?K Y8QϑXS7b )y' l4,ܺ6OG4Їe%LsZ3 $q]B\r6 _+8d߯}3_ !}x~`IWVNRo\%:9j73eIF^;YF>(W9a as5espĹGstCrnYDHkO%zWsrbXW5@e=ࠫ ǐ\;yH}:44v:c/}-Tz2m!=xjszuu-q5wh|' O^߰UVFz<yHS乺uhꗫ=,"7W}d]ʔN“%1<ٚ0EdR3AU i|V(&Ωd E_ie}d#x׃mp:[tk \}%i72jcpdاjҚ'☥I_|0LK>NAhP*` PvTmhrBf7+~7l;K1%'yA@QB:*⦪/cQ!/g}7ޤ*^;!}Ǟ_C8mIbcN"/cOssۉ] :Vc9j٧N\} `HNw~rli#}-BvH;)d[jP+wN0fC$nyߧm2J\T4_F_3c#ܔhҀcΠ7W3m۶@.>9Aђ(NY8PCu[J:gh; 'uVr(X+.lz\\Xށ=lV S M.u}W:ZH;E,y,q]h.4;jI>C <,X >ˣXBtS{=1ǎ3O?t<ŚF喝'=2ɵl9)1OcGğH+Z'薧|U%RhaoZ'ݹηŃyOĒz&$@@Z_Mv4(On{`pg3eLa綝J#|{hƞ(?u84B?Kd æaLRsOxcMȎ--vn#Ѻ+%.eMf]|Hny/Kǻ D<ts<'ox^l5/_ 痨lܰKfDžE3 lenvFm^$Uoo]^~Ы.=K?h+^gNh=)_ @wccV[fǗ!jVE8V/SLg 5&\ԓyKGp0~exՖ6[ϱ=d|#$}#ƊY#!ZM>v Zpk{Zeز`nlڪY8Y4gޒɚdzU臯so2jm'Կz-(i,3+(2dف3X-HVAյ&g4>QcLɫwf@iT1&Ǒ |>/gtC8 D̺5Kk#5;bOwE5 &=wNUYT#,5ƥIM,(E|A$6QA,iѪeU-ϳ橧aiի ӊUo> @\ـ%hz|MB[Эi2::sPӅ endstream endobj 284 0 obj << /Length1 1698 /Length2 11564 /Length3 0 /Length 12651 /Filter /FlateDecode >> stream xڍP445@c A{%e9{{sikht9`7'@V]O Ńpǎ`vs@EŐu<^lr :p p pqxC@^k:'@ vGcAl<^ld3 vX ;KE+#@j{O f1; ۛ u`axC<:`w 8zvPobpX_BGA_6P_Z[/ si@_ 0#t.~.r3*ۑ~?~oƋr==^@ jkuOUl틢98C >`k-_ˮǾ9BZPw/K,+"?]ߺVP?_rs sl5O1Pˌ+ a E\?ŧ5x@"&´ VE/@A~/_%?E@]_ r|9y<_zy^ b<^^?7|`J4ܾ!Nܛcgs,p[kW;TV8vQgg:!OsZ@VRS{ݡ'Onc ^gk[V/R,쮧;9bk쎑qbM" bwJt~Rw?B58ÿ9E~pzs '@j1e'z}LWN4Wy{Ll;^YǶUix~0^v:t%Ӧ(k\瘥4R?^~jE|qTET_ j#fziYZ߸PGc 2[:a0yWC'=K:hcNB J(&!c&`d!Lp]t|hp4\Z_t^=K{QQNRdq־!OʘV9j2"Z-,`wb3ضE1e(9;109R~&):p5̊=BCQ5FK H#-ifZ{|%$C=U%x A 0T5[—;t6xg_Ȃ1f1Z+m)U.&~_)gh4vh,f'P#C!#2f؟7"c5s?BxZrjg/JV94BVUBN/K !xd6fW^ك(جlX:G  }&)ԅI\PXſ5_0<-[Z'q923Zj*Jɪ;jHwp|TȪ77sVE-?GtV>SQ:ӸBڻ]J%bx.һQKkN:TjKR0^wa|8mRLg.t ՗Lm~#-9ڜs)L,_ХH"ҰYKмZH8nW݀44n$GX{kH|zB|+}YoV g7D%?2.w0YPd܉p(c34׳-W;\l|D<6k}gPSbc&Hx[M9v7]y6F?%}Lb!m7Y?O%0|m}dѓL" $q!p6y7A@?jd9,lɈ?ڬ4+FG=   YUIMX4!SC ն{o:RD)!9r;VQB7Gkpq0*7+EyLP7gnMdCE6r8fC}#a[r`{XeEi7\Eiԟ+Cj$YiH.lR.@:nk[nuRQf-:f\exض2kr674kɕDNSQ_U(ƻ{Qg}MqB4#-`_37Շ*hM;N;f7Ƶ?Z6^tCϿٵR>?:R֌ƑI.j@[G5X=!S/Yi=!lfB_a Ci!!6M V]՗Bݯ_0$%c3R ڒ ,L 4|74g?h`dPF&̹23ԽK& LT|ۨ`~Cqbs-ZZWɏClpU; xO+zwD$] KF s#z RzaOוsGuU~_:f׉&(V~'[%y z+;lu<%E ׇ O ~VAxd&ƹ-t(+h wQOF<9!sD=40驋INt!n;?mvv`JQg-|[]ߧ %z,8`dq \6\+YaEn9bNʲCw9צՏ'h?&<ԝ?A:U~[7=8^mXL8H$+))k^KVN&QlSL0h#6`߱ O ~)t:q x؉z#ߐY7||3Y:oDIuUoϡ΃^KT!ϣ@þuz#i [@8>+^I#Ѝ:/诬~GقC^4m|2䖧ZCl@(YoIUe65yBGqoҪMK~e"#YIΚK^Dc/gp+<Ն&:~3lPch b }zw?4IƿH"zkK/↌*}x# 9XɈ CD5(Pu6|Q˩ n3r3opѸ{9WAOaF ;4xOoE@` ~CX޷M,7 ?}MmD5|t,THǜ!=4hTRJ _ًU Yˀة.t,4A|ޔԒgC]m'UIk:D#;d`^G&GLaf]ɪ2 P3_bV2vtf뷖F!):v,t鑾8}> *O-_)?Sm_SiK9%ƒk++Wsv u eh/Mؔ_*y4tgR_i ⵝeGN" \H1?Fp=]B{*ص7j%u@97+u(ƏMM뒫ƧxY9ha!*'(,#;Wm2*]nƖD_4uD\ݔU[Ye~^V.Jƍ}nINJ0@uN ]!qt_T leeY(Zb,w8w&EQz7T dRf#WN>bJk%,JDW̚^ IJ|*,'uga1ïXA\U&\0O }HLjݣ壞nsM(XM` 5{̞ dJU+[N!3k0B˪Zj<2dK [_;V5|L>ˎ[|!U)v-֯ A҅x 7O!c`AV= rp,뤔EWCyS3$=✜z7$f0yXJ ]V5eO~,ASO›HM I ۑ;_+\mm{R=;>lfJ7U@!8O.rO< 2#s^DhqiaB0dvkG^I2kĄߢ !jfG<䈼h0c 3Xeƛ lChzVѨpו5հCo#6ädeǕrz;@Jzv:y}ioԌNI6Qa.!3"qy~|, 3Uȑ20)Uб{5"ɀ\X)-=#lF7(y&14v1LG:VmO-8e%rmFq7' uoDs8ږ'g#؞*WLŷ(;1=Ƹ*"> 2Td_D]^>QZ£0±K6DKF EpivrD@R{dO0KW_;y=e'Qs%%xECKڨ-!}rS"+ ) eLp)L /F%݅#lI4?T&+w0L{Ȗ ?',侟fK(a6M?tvm[Euuӎ$ q!ĒS.s`#y'9hj6rc&J" ]ْo=hpV)V/`:%ȧR+pɮKKCasUU{l{0e#(KMqqxVa꟥0 7M]?y?yt '?OMix+/RKI|{vF"qM4lx&6qzbo5:NM(& WD r.+"8`HoAZE.t9&+4gDo&Fy͚Ḩ#ґؽXmўVI(g{^=jҀ9tVPw8WHXF~j9Z2E*e~ 4بѱZ Hă4h ͶCAAÖhâTQ\GwW#i(Y|o~gܥM@)a|ʠ¯ ?: }9:֌b*_'!(E ] XMpHB({X ܛ<{DNķcKv&љN S$OÑoY-T;+G|~dϩI秨oS@g4Ļul Q ZA;d@d6۷~6o(q FG %qFar4BLeSjX.3u]Bk>Rh_?eY{X`Gr^֦ዳW=GݲEږ=~HSk#IF:̴-"$gzsudz5">?n}ҲրyvKm.T슐/,2߄=(Y3uQCVH5^c, Elwb;M0gͮ<[߶\z>*kVO }^g k4 ;צf4iNmtX0JBy}+yj2/ߕ v|AGcd[l 7A:5r-0YW985 .gA`5g"h͉nէcsm/h=`U9>a.Qfl`pgXB٘uC3U(*t5Z_@qqbkq y߰B739=F~&| Eخk2  ,A湏/FaU]s_WL!8%d$5Ki_!+V()+W37!&F0 rG02_,'sZ:^s`F˶+QbLx /`R? {'41XSB+dۉa 66~OUd^nYI;xp!ᅦO'gFTM!شHx΂ zE&EJӘ !E>:ĐIG_ MesP׿*9N5x8ݢZ+Re"}8B˟.c'? ^7!,k!Ob<螑Dg|G]i-c^6CuoGUk$̇Al9=I.jތq ~/;yщWF * tTB: ft6 SξbWsw7@M!Ed鴰 :jة A[jeЛRyx*ګL,#u(\jp?lJ!V"}ᤸ[+k,sסA,sfl"#Mwnj)*D݀.^:{d'j3`? Fc>fyk ve , "P\Y9\N/~tI'ܷ/VJ;@8];[X70XGWB:?H `ӟ75F|<'>xuAn 0$E F${:̛Ȁ?hh~t7S^t$1OmG0GDwYwO͡)JqE%NTu$upڬ-N^F#0['4`_g oW,AՋuc FDWn_PmlnjJOfjXuًvKj|)!*VCzZp8(>|27T?7#0+X6Lv=&~= T2\t4#>fg[KQL@~:b;)v5AYQ, 1zp-6l4e-t2nmg:ʫG9$>\0bǥJ3مw(`ҍ@"SAǴyXE8Ps. ǩ$%$E<[G >ͧǠ&˻TB;jժ-}F7tL&a~GqloHgt~rV- <\r]$߶''b^{Q 3eg*_⑳UV:8wƌ0uIV'91P-]TuJeQ3d/ֶ)e? 2x #b,6)#f΅#Ws1bZ!J:2Ii s;KsDzc"m1(rYI{AIϻ" W[~VKKƓ򽥼a5-ԚaoV쪑k|Km\/8ODb։X@~"-'pkB/^z-kgm 1@5`Zpb7J=+.,H*\}Ml=jDY5S l !HP 7c\qwJȟgʙThr"Grk(a~NEUyyE&X*ΎBJM/ELq"f|1tEJqOسmejo⩨䖛=sz 43stq#A]-f_T <d=l JR_RLIOһM6fQETkkUZLX:vqzE;TIJxfRJd6n 2KiSgR5yW[TO+|9@b\;-430li, |Uily;RɛQ JaGh|L{-9'a,{](;w-Zn-wIKwovhm8E-0fΪYk~(D/iXT ^$jf Lhy\6 kPL&PYZ7jqS$׶Oe[*8iFdzl*F U5L»n30̢..9HՂ}_MMEדX> stream xڍtT[4" 3J3tJ CPRJHtJ#Jw4" (}ky~>{ͮg(h!aPc E@1"nn#8BÑQ0ڦ\t+$Iɂe@("% Px:"M$&VFNmA iE7  :zG(`aw`eEEE nh$q0a}Og"D#'8`k+ C#<0zs!XC!k!:H Bn/pj"E/bwM]9\7W{h( AᮿZUH77&U ^蟛uA ~8W' DHbRu e pnw]!^0 ߈á ;__> xߕյW߿WXYKS_OSRB%b@$H_/F ߱` cO^ ;}jaEn B?o)W 5OWn qE'ztco)nc ׃p0HB(G}`zp d؍+Cޖ( |u~?׺]ϿUE@LLR A D7}$~끴V2@T\{ 8 QDUZ Q"AQ?@:(*B]o]]w|`P3H\sMXi"ʨI~!*ת~فx9ŮoO>Sn^5]ӟҾks*?Fpia+wi2>7O'䱷2.oæ1hS۷ʼn_)U+NNuFX{$͗ aҙIܵ}bԲLr/_١ D[׽WuoӁfҙ7 J,cmm1}}z 5ϣLf=b:ްSommɝtafhE|g6t7P_퀞06!ug'q-CŒh/K(ag#/}I].3Z~gQȯ"NT1cjOErx'?LM^lj<#{/3Vv!E4o#Vo0$ sP ׹Xuđ8>5Jy6'*<}>4<cBTI !c@R!=|,BjB1H{IAZwmQ,_?Wspˆ9?T/SfK[i=qv0=*ʑtbӅpE9U)+G#CRZc;Q۲#{ieX44BI7K¢*>늹fu<`]SItwꞕ'tF2wlȑnh,@VTyV<;XR7Eu,>wR~٘VĪ! `:;N-49T#gk!N(xԗmx?8v\s,7 J׹uJ/ƭ[U#ya%|o֕KQ3ȏ61ΓSnު y5Τ<gdo(_罒y9Z1e}}]@\DZQioz~^AJe`4!gAe6_#G)lrD]G[17uJm*Q$>'ZR$+lڍʣAVɜ[< >{(7 ?ƅRID LkQ[m2U<.7tUʆxMEd&|`V~0*OUfa=pvW׃{Mܡ:ƒz]fsqfE9}?ޅ0)X<.yk_L]w`n B:L}F΃b^IVyi>hk֡hTJqL=4mlGg'9ĊdpުS*MXj)鬁+;5F'X01BZ1X_*5~K~&`?4[*HO7;'}CH̵U;6e"0]VǻZ{ҵUV޵DjzgcO,=Ωڠޯ]t{>G$K4/ѰwYS2"]]Dø?#6Do:-< >8㌢zWq0Veh2 .5E' )랚?|sea@kk%>OШr판>sM]6[W\QPɬ|L |a^c7UPVf<վ踿JMVOdo;`͢о۱VZ۶ rn-}=G@0v7SQF42[Sb?y\!:k+7ܰ>:8J=_wQT\B=#Pǟw.YŐD-_ZRwxFx~8((1nV*̈́x0a,U &r˙ A7Tc={xpA#T-شzːYxiz=? \n(s)Cv,̧#Boq=Wx2 5!iގ$Qk޿нP.8٢gSȮMk,JcU,;^:+5_]YYUR`eeIc92@GPLߣ9g4d~"}¤r2 f;d5򦟉}ݡ7%*MRRXj/Wipl=壷!Yy0PP5-[F 28=>uAv4#@[ad8n'3ng O; VLg$)m`#|G M9GIn όl?`*}Kj 8ZM\:BQͪP,{4`Ь|/ xE-4LQ|^?9K{: #L$_^=%DQO&6(R/Qza)P ꮮ&щ|]5/t+Mp8!(w,EҴg3Fۥ*Q pKr`I !)Sʻ.Iᳩg~D%9'(Yfq>{XH&;E9`G:@ y sQͳ/MjChH\QsuMI!L\uhQ!^hP_`oi*Urx iJlETx]o,+Xʐ@s{32?!1 yTkdϪfʒv»Bm,ɳ~CO<û1Qyi!hJh&nZUWƆ9tġ_ ,S< Y3;YIBK$/ݪ'9׫Uv!z%Gݡ޽8T7{uO~C-Iq8XFuYwx_jNBF%IԝؾМR?UnڕY7Qꋡ1o3.(Up-iؤ)5~ZrJHFX ʲQa9Q0fbڿ^.Uc@MR%Y)k[)?CR Y3.\ԫhR y#{!yeh&# +̵@j@D8dnYfH[u6HQ7 *;9Yxzy k>#ei Nr37ȧ2Yv},ܟֶ'me+ka7Zɀ,aoZ6%+ki9nvuoBX%=EΚ"nUuaa ]sڙDBr>X-T|bUuG\Y^ITs494 C!ѝ(h2Cq\ n<'}*[9G/ N)ef[->c]6`F"VʼτJ9] ZT%i%j:ܳ+f83$"fƷI:KxBCvv_ll-ݰYNk4Z#O{}|Ɣcd mKUy|8O9)"lͱ4pllm;67TɚRJc8(^"my=fsqзD'Zk@륨Lwz,˔ ksQCZfy1D+/~@ȌJ9(Wɨ}bvBҸ\TDc1'Ϟ"P asWF 5PJ9ǎ\˭suU2 ^'֫+R6+>oYgIfYFۤ [ɽ\ ؼA1f\ F.eҥb9\t\Rx8*`%g疐qUz!CF43վ}׸tğp8oj4iq驍ӹzC-33椾)ϼߺ@| \-Zo=m!v&ݥw#OQ冮^0 =utVɚpG Б1~;s OOP첩:pXK^:)EH$CG>jn CP11 8WRq4%KΊ![(+rVe2ɢ^q5?`-SKSenBN%>6pnT;y\m(QfNSR%͗ mQDn&;,eۆkJH3'7ᘪ2J!#jv9Eώ99K![zޱ:yK`Cڬޥ> եAD'ղW|!7W^:5)8EȣlIxgF℃};~ /c[,m >36/ҏL5br>/3 fzhpJcٗwMLT5^CT\vw~rjQB'鴭gO3-* :.άy_2m^22x.Q˧2 !{/QJ4bbٱG͟o-+ X? 3[+e<ؕ ZVBx/uG.?8Hf>9AtQߜ : #5ʥ%AX58s;9K1=  Ӄ0?+L4 L' k_ͽA/b>-n1i؜<$q*5adX[>y9Ye;R,8E.zXIt$O+.%a>@lM-B1^-Y]e4~S6{?Svo`ti k|;iX ͉_o{Y> / <ӠG#fcOK:zеVPtZč(E3;n1ЏEv ?jJ}"BYBČ{U}’b2Z䮜/*odXHJ.Lµ~}!ܪ[U{I)JCI^)%4hےS,f9ބd3wO2f** * YV={b movQȫt> Y,H+7<\__r:ʋ@=A׊W<ݰ(ojGe%uC  1x^Yץ޾z]]3v3A!<.c7Eqϐymx& 8k_p_<эW>v~r{C3}2`ӫ 臵ᑌiLvNvcAGj/(eWbi[+%­c螦Kg<!džQs MQ'Ǖ>ꃒGBۇ\_rfX57ncqPܐcڕ:^O@Ap<^}rΆtM3 Cߢ\|~7,AoGцo钔ȝs|8n v)׶{IuyµB]\crol[4}yY@(Wk^iet"N+ ziEWJam3r'&!Uy Qc!+ (8dS IያfK|bۦaOKAujgL!ԍP6I2`+Y_gPJ3\hF8ǬK 6v pwb{g;Kh 9"ԋu[Ŵ[p}rWøӳz pRKI$]'p`+౐ϹT\ -^f/91uOBE} G^^>zQ~"p.L,}{Mzunڪ&q/$A3aQyfJg.ÍEMeV}>N ZQhH32k3W'5Dpa܃~{̓;{+UtO<ë[B4cNɣZH%_rE}#;B{O~H>LMck2'rl8(%6Ў$ݷ r6j;g}§% *w$t?0<:È@[O*f"-ap|) endstream endobj 288 0 obj << /Length1 1485 /Length2 8123 /Length3 0 /Length 9125 /Filter /FlateDecode >> stream xڍT6,)!4 (3]C7H0PC !tHJK ) % tJI|s[3k׵i4%,f0Y%Rւ9@8Zpo3p)gyo"W; .."P7@pK!=Vm03`? {3P"a;Cs8 _%HGANNwww J GZ4`.0g7=8pZpK;7a.0gM@W_lٿ $CPOn*q =l@ >AtJ=3="2,`!9 ]p~' w'_7kpwX,,aȩwrA7c!\\\ 0y[s.6OpX[p]n0F8@ n8T7, ps^;/DA퓔Dxٹy ^./|w\B,?u73k Y 0CrC.^.Rې7 ypOZW2pP]_UY] AB `uOfv _v,fG[Eڿfw!\ߖ,.p^>9@|3/x/H &89K3ks8eB`?o$p (AN|N_:;k`0siPMuHE;^q) ۦz w^ԌDF23.[%(gfkl>c"W;.J Ŷ6}Sn g }$lNGߧ2?XEY#ñw q[L&Tl N<2z,˸(uTA,[2XšZ Fܣ# ®m6vJ'yUY)ZS-\!ǔ^gg/hb7_iu_GQ=8MZ:*)٦/l&^'_PXO4s"h)$xX QfLJ(OjDPsMAs=SvһjY7 1}54Dy>de }nNt0u4ˮ4*-KUC9M), 9B&yt\7LT)"F ҰA^^l^4%Հ kGz}. !TkMٵX1uم'uoO-rQa0ϫ_nk׶ZOڊ}ΝRGڼqd\} ^u'm1 qήE?!s`2|2.F9@lvfO=&k=[}$k(ɼvhU[,@X S+f|h.)^Eq*TuO.3.+a(,UVt4Oaxwl vp|oeLU㚶pre[z^d!Qީ+k'7/sGBD|dR0 ;>*|{hiK#Yʞtw'V[= / jcsˇy2SlsGJXÔ!1ׇORf:ʵ7Kc21{7βh~ Qs, 2 z srT?RWN$Zv79HZ_'0ԫ?[,|, ,EY甇,؎=|맪 VMG59_-e`pbW[O{E5AS.x[C]ˊ=u1_#Le)ңd,8 ?ɸ~0mtч6Y@J]2b)OU,}J>*_V6ﶇmb{xZO4Xh9/6epgk@!;.:"8lo? -ޣcX?.۾[!qx˝Te PSn+U\[&@Q4ûy!EnJ]m[oX)f-Z{_ǎ?ޖh5cӡCKuT;䣚vʔ߈:쎄Wtx5W|GJwMF4z?MNTf$3qf-XԘrZg JgFz@ӓp^ }/M4v4&KbykCۊޚy6"۝Fɘ+ ︳eBEh&w s#wrأ# N1;az`0SY,Qu% U G~e R<Ժ}9ƠN"ԉ> I VL W2y)Or;h A?N‘k$OmU S3 ݇MQ!t+: -z{mtrm B&T xpxb'XS :E-Fe,'{aö5]ĻiyU.Eo3qlzz2 2u#EeYru`X7MnCN`6>Do S}sai FTT{yK$5"VW$^MAy~seb{  L&CGM] gH#'c,ӧl N~6y4l+P"U1M eR@(QAҾU=SA^~@S$B䅇VqL\`,S}(p_O nu~z1(h,ZyZєwg.)=*鮟qS%״v .$ 7[(KȉV : BØ{uB_XE) o%4btJHWyֻ]?bQ6odV3 tW~8N9<ΩRbT)+wI)D?R 5M~g9( *"bX< 3](Cʲ{20>سg`gJ!2Pyfew5k!J:e˞1^:1mMDp {⧜C#{~3/C].Dtޛ]9ѽݩgmy49*T]}_aYL\xEyOY:c\38CNX k c}9ߴHu!%RXd)޷,*h`!6T(u0u͙a}i{۠vU6382)2 Yվljn\YPV/`RM -҈ `Rá~h+2.M\kv%$ 9^iR&5P;lF* l mnL!TU`CFQe9(u JBAQlx_9ZM,(j67MI5$r!TiӘ>_vɺKgh|77Z؟HH3/D *K^)geJ./A˕RLPWB}DߎK_GZn~!r;j@ ۗ d*J Q2¢3)!S.,\C/SdV|τ," Hw^s;h~qkԔ:4w*]Mhz+X?~%LV飇^X00:TKlٓ0U^w9h[qĺRHƱ5t [h8gΈ)^D7Mn75FkɒagmsP60k_:z<d zͪ52~߉P<k zP)ĥD #2]W8[r**9sZl-7Y 7H6Q+S qM|VKkZuJInJgawٱDoƓFڨL1h9c7b~H"%yjX),"<筜_^TO&;f1CU\BHg7,5wƝY%oŧN7bћ#sSے2D4pɽu)"ƓGA(Ք+No,+[/jI"ҎU*-s'yJ s0lVsLx^^ pmi'>1}o齼#pl *m U/'j%9*sÀܝ^ \쌾?+YGk='>*ҖBUWVd3oq2?r_Ta[fbZK<"E*~-rv/H=X'/AfD$ 2 /\̏?]l@d\3n2J#c;PC_.N`ovJElapx5DL9S}Z!c(*LwiMe_JRx`]9-k֋lJK%)IhNaC˜rJPT=f!t,Jځ2Dfy=yTE\Ä#!u(si(KX*ZQ6NWT|[(c,m$eZ/9ɞVMql$6mԞ6a]Cud/v]w4 bdImYo0 /$kv9Y?9; ҹxDlQb"-!l^s{'͞܁rE`NTb G0{} 0щ;Xpҋp}Zh MS/fluvzsK׻c.it&zBc4[ T5.GGzAW Z  Kߴ`&1 "t1{oBFi^ b+WzlAi4$jg dƮTFb b?=Bj;eqbX-ʶBƒ_XԝE&p2KhQt5E; ^UۿP hܦw#aʼڼk6ӏꈶS<;WDcO;X"wdCmO ΢R=r,ĵI7Uw4V/8~JGfc6$`YˎVJ`E[Κ?W!!wN`9{J73JV^mޫ +[ARCŒDUZ!4wsx:WfᐣۿxbV6a\u8n8O<$[zrI' dWUFM[݁ΓÖq0#-Q`%;$H\e6BY!dq{Q7c彩Ρ<ڭ=0z K/+śfٞӳYgvwVX+B.ZE4;Owگ $e7HX)e'qc.tuˬ\܊GcIN99n`N$(v%;`SRmehl(2(Q:t`w,%>TN:s\x)4g$߂ӭd U۴zρv/U6PNhӮ*۳T;JJAp}Z(Շҟ2!{v~Y endstream endobj 290 0 obj << /Length1 2615 /Length2 18537 /Length3 0 /Length 20044 /Filter /FlateDecode >> stream xڌP c]C`w%Xpw' ɹ꽢 f{ĊtBƶ@q[':&zFn #\ _9<ֆ_"@'L d(gkv0ع8̌\5uҶ6@Gxr[;wsS3'?(L\\F69'35 lkdtrfNNv ֎TWs'34) o` 4zxrʶ&N@H`enq8 v,@hcnp{ dn󗳁) Pwrs14r[ J . `ͭ' b6"@'G?;@}wgp-ml]mFNt.:] 3(ۿ -I?sk9;鯇 t=t/-YuM͒逸Ry.9t:?"$QUgl8 %Q.o{/5KVni\{gNdůne+! {e˾EFϑfͿ'v#FtCEϙz%>f)d0V؃K}|棧a4΢gIQb6#= X7:8w{=F?"\枲u {WWq -Ezápwa4r.10[KP7'72'guS&pXkC1sxXdz[Lͺ)w;#$b7qbUsRi._kr? 5miNQǏddXժ*8, L(-z8w Ҥ9L^2[ydZOT)M^7|"WyV1%l[{?aI'u\kWpuT]h7qG`yHۃ6g n,?]$j\ُ;rkvn׬J?]Ewè3db,?9=f2󚚦/VZδ[͓okkY )xi#,2>R!*BrO <_b$GiC4jy1-55nˉ:پۭ KumE(Ks9>uF2Kͳ^:k"jv'ge1wh;MM^Ώj0bBDAoQ]l+ST浯H3_pp鲒챃`2 L]{XrsZX˷uj8Nd8_/in>_5P&UR@[= Pl"Qem*rzF(H0+! l ]akDScۛ˹~ 0(+q_R(֯.U S+v xDJHÀnu{b]_<ÂR((>M3=>DrpnWΉ/wk ;~kB dJ>rM\Sw*O=( rӋOp}vp{cuF>@+xۊpp͞6YPs9'/os5 qy[\"Dׯ^ӧ9cHz3d.n X#fh杚. )y"R]>ܖQ )֟/FOř/O2i{t[U聩O[i@]5a| CE$ofFHkVW$Mneh8feKI+́gЏ0I_c%ΌqӧkM P [}6*Q(D:dBbE%Ky(5 AY8d J8Imr1NcXj`$#G';V:Ş6o.Z xMC=Tve2Oӡm+5ߕ7AXX #}Bwu*'@ҟR<~8#WJ/IχImR$Y ;9Sϋ^+Sk:14z nrg~4X$&cewiSU _~}ޝp;-.Ubvaaj-` KO ߻}QO5 B^`g]۷_ÍvS/~H!cݶ9&oxI2AeU" Ig~_ ӽY8VϓϤ ~?M}H@Xd;C\è=)MgG_K6pI0g\kA=̽7e.P7D4ced8gV3M s-?J#lwoDnIóJ+[8:kXaxDſȏZYzb9tlB䥉_"~WZV!ԿRNPO0?X*7IUvwLJSj,ᴇ71,\ð`a^dVj^S+苔86j)>+cx0l󒎛O7XC mjk"Q)X?+HmrfL1xAy;LwC2'zDޘYZkP*ݡzbX8kdh31hoGj8I%%Rl0!PtX*4go}ڷg98WջS֔E4 |zD}m8ͅncU:"!.nI 9Q?L!}3N*W!$4g⠦5<: ',pÔ!ͭ=F~lhVYT9&v`mPꅒ89 ^BA,~ӤBXPx̭' !^|.b;;׳ˆDZDy=~tńyA㮲j6zL6 րM9īcF'Kܴu_ XenWrB_Mmh3f~x}!m0|Wr̎ ,8&fT1EM^VS1H )0ʘ3)j{(LȔL%QrcXW^Hi4EyQiϘJ G!Hh)&kbY ?w[A2n~Ulk+InƙDƓ0܊S]1NfԜa v(G%Ncp ;^<"E`;7HA-"`` طN5K? Wo_ap"_O}薕vqa؝X$'bih+!o3dۂ:oJp;eO vYNX`+3kb:yCE~UK"[Au <+\9 K|(IdlEVA6ϔ&zkĻ!`E)㦹=?-Q (8eaۧ"٩CkNj min#2fxFnXQ^_z!.PÞrcMem|iolx\Bt[tL=mhs7ne{cW(#Ut׸FLT2鲅JCmo@:#ԌnCށK⯥gYmB'6]30B# DeStϬ8, iVK,K 2 ߡTAT#e?!r$PU֛5,k"x5eކG ` XFHQՍ9ǫ8-:=]KslKKlƜYq]V'=Ww\cCѝ=seIuff~0|(xָ#I958AFĨ} ju!]dZi v hgjGyYyjm@Jğxӽ4ĭKb)9q01`!_f i6zZ}qi/twLy Gx)vh~^4 .œepC&YLLF6#pqNn3^ל2f6L*>>T_E- #3W{(TҼĆ[Me=Q mf8|)F:{4tCYGUhY2<0;ayXlc"$vb^yf(fP> [߾mOmF&Xf6nI5 ܡ%I"ҧc%nʡ9F`h]ŜRt.Pvl-Mbc/nd !LtپHqd)e=B|7#S `8Z> ܣwr9qij2/jȈ7#ίNE?-̖/>xh:RSHj5=Fa^OVysyG?eW&gⶌ P+3^OփuL[ڭ ;BMNQ4LKq)C],AlOQn]]FKFI5r yœZ;C祦fP?Q}jLD|'K12?)_oQk& 4V3=mT~0g0,Qv}%FO4}8S7Y9Cud(;و?\+Tm1^jH&jޓ^q1K;+\xA­SV8Maޱ~b!N2c:q4,[V]q1>MMEs0a%8YN|OV'mnnʐfC˼3,U+׫ ~@\!umIޮoM)7cp!:ʂX/clKP夋1]*w_c=֕tgS8mBUVx8H*f'LRNʓ)}>_`SLOB;o1%3LJ Ww+4>,=*'QTn3ح6XyE°zhg]JG԰qG| "3d>^}v8љէzK%B$Ʀ"0K2ViJAQKK&, ڨ́WGfEKȩäl>rE羚|ߔ27smo 7CoC^?S"'qt.H G/,NtQQitn i$&; ӗ[)fUh|gctSlcg 3b˹r$հ%&罈$s =ޑ3W$x1KSWf3 I) P^#m+dpG%zN=f܍swrگ7vYԯ|JS`G3{Yl~.屝HSLƯZft`{s'ǸQ0FӜ;4xL7njmNeon_+_30Hv,]_K I.vc\'a(i]/di& ڮscd!J]Θ!ͅN/,!Iψ !-i l]u6}8Ņf\ƕOEaB1_*lA,ccǺ_ܝOoaF =bJYiPH;F v~tFnۀ" Д@gGr{͓!v)+=U]SˁHDau]Odeڣmޛ@(߯, Gݮb 4*Ố0D3 bw$9"| 94Ak5ˍQ@Q1*M}r +%Nc!2UpJ%F ZrTt wyv ;֌ }j\߀kNxJz~2nIɽj=Ėo & ,<_~gVL:[8 \8R_WTW蹄y4FP#>P&ۀvhC'6]hK}bXDPqh}hQe5]t~}wp[z$gbG@!,ck|؇tsKSF3^׸4q\3GcRgp웡MJ Gj==f:yDxqS4>'^GZFuy>dVQ`wOWe|Xx=^iDͅ@a*QG욛*ZRѐncd;RgﯡH'tE1,\z͎kln#glNccc$YQ:ac Ӥ؉:.Hxoq#~گVCC1X&=~XG ʸ3":hG=e&/֒̕ňHlϤ)> DSw; `&)B:ܩ>"IpзC 2w(զ48%ɘY?%ڳ753@MH'r{I"_Nڡ8=,n%ON&\3@fx*\)cq)Sn$Ji"adZ լ(z7rPIߘ|8S@Y0q2"聪ygFƦRs6^LJ-FS+հk4,sU)&BVoqHmMBQۨф̮ۿQpEfWIrAc.W)qUy2Q^<%CΕIO+P4>  '-hʲu?.t#{ōa/9]-e"lfBneg_g<3)0֡EOJOAI.j4$r04D X Dy`,!nw,<^j[~@S_Pdqk5j%Saw g#i|I &xG_?Z:pofOWGZxi6{ 'WkO!a ?Z;~O~J a̒ot>N.̼ ۷mm5]ZMh`RWH{2-j . iY4!_Vw"g.IvMz[`tx,|lҰ>;7$IDd72v\ͻ>98v.LKp9E?LJ[*^-=+C5#?2wC.Tq(EFk9.2NoAxԂq{WKy&$,!vbNAh8ZXR T YGpCo;ۗr-4X3e*/ͅ2AK4QgiI;61Io[dAo뇙b%-6#\H5vG>H)Έrr]JSw Xd Fqw) >~ a2W1yB|6KPɫwl(;TJⲎHN&=t7܁teX!\ NT? U,.)a9f1DzRCT @=@x `u\eu!%*SZB v?d).D'w2wk's[8⽺0{H(bKe2+t#+.1!'Qӹ5HB.dhڵB_N 6,s2azt蛨g+a Dv㜅%XZv8M^m+ERV.=jx3(5uz)f ɮ~7/9^SE@_d{k->O, xbo`yu bsuls%-hS*ra- L%n9ӡ.bKKh(-\dPvП2{owy ̸1c\*hxRУCh<}%Ne ru> " ebehL\#}n! O] J{9NIʢB W)6-M$;'LM8yrgra2!&>J{B>,X*ms'qO *_r&\d&O%%C\n焟a_$+:@Ç6WTLG:Xhegyʇ(#b̅к{n^gYx UqqW c%x{TN Q( o$ n*_/`&"&x[/OE/9s xi ^ &|*p~ϻ7Ey|k>GrV/6=1b;3Pjyǟ >GPٽ|!6Ɯbcz~4Q(Seգ@//_xRWb{\=m _~p7*aa1DW<xϪR0SzSck1uuCϕuV`K;xOIOSEZ[O_ Evs`%)ȍ]jzB\4*>u2؄H'ؓQ\M-g_x{wO/9kz:R([jl8MwH%ejS~o|ejSRo)C22kbTOsY=]3QxcAS&&hUc?ʖ OS}<nMŤ׌omԄ hDfpLk lY){Xl!mqx%y ƝMHM E7WXߥ~\Oo}^vILy$9xZIDOPMP)ͽOC[0^3rI/YPont3 F磣&za/r{ݙ "MP.~L xso3">&##AI K7`m`5xg"m{Dȍxs^6VM ^WOyjz2_a92WKh**~a$ V;-YT_,?`C"ݤ#2iXlh;ɉY{&Zo;ߟ!P Wfܺ*C0kǽ\*[¢Zlb01RuB"4` > \HM}jiBU-k$n<5ꑋVuڭN ہ}аz&,JrO-0ܐ8*JiD3 ҇Fx}KG?#b/1kJUϿiAE*B5j d`=! 0~}yCz_HêP$"诅Z/WQA/AŸa;Ͻ [×6oCܞW Ƙ8 ?j3:N GdBYFߊDSo؜wDIM\lvs#.=s+y1(Q./}mFKR*5b_x6~V; h56b8qE!a=b7#W5%QEdA*"چ<3A7;&p 3fqp=b~XWkߗ|2!`$F}@tرTraϋٴT=}AW^7YEEѭYv]ck_,ND6jhTvhƒ4>*P y}j| ^w.>^Jq`I9SIȢݗFE,n7GeZ!툋l䵙Jʼ }SߧJ+ғ"1 40PI|1MJqִAuαwyFe.n8ڝХ^-ӟE[k=vV)359,S*~ c0Ruc"cN-7֜6r':ͷAT=>9&w@4y!/;#:%3 bq/]4$r9zRE9OnOPS΋Fb-)'mr "KcMzG_hP^wX`V&<7'E($na0VT3ӟlR']uJ۷3}|7q}kJw$qb6+7^ڳ'u&\҃g݋ R, up(>/tq-5tQo~.115ˏi~""UTWbF 1H&)RҒ]Ψ=Z"aN!6| VRcXą6c*?j˴s St4AW*/ILr]L. cKG.V Fh{3r)BUQwUU'2rwJot$^p>ioy@WXHY-/DkY #⁃0GF:}ɖn{Nޏ>ђ;bZΖmqu5dKt;7Z{ZSc)<>8B'[Bn[OR)0܃9p0LJNYq$FX<-<'dfTCN=#>6Oi!$}\bhba_!L[FEF.( 0`p)ǒK75PeᶶQ[ +Or? O<`r? K{9B<i\'Lb<OMR$Dٓۦ0cCΕ %V{V81lÑᕼip$t\ݫ&UN^g-hN18 QxbRK:)%g{?G VP8L3Mhˀ>VVd ̕ 2Z|& VFg`vk&Ч#ַQ2<7TKRx_ x1m)`S#Lʯ 멖]ȕKFi»~@nP1%P fXy>X0tIvjdPX`-RwtRVu煼hIvD_Zr"02tJ5*}HAFɍlϥn |H"\υwpFa8 >ظyo6% J'Y}GSc tģp~ >]ì^] 74k"Mg-ޯw~Ǧ#r|.Sc,*r g-)M$9nsz>sM;h5 dN2e=%&֎@(5;P{dGe:Nmí]~ l;eȀV1rM/94)0P"Q 66v|2x맇qkGCkgpT=?OhW':|O_MՖ=8OMbr:"3vz }\\: (-0!b7YD'ԬKvhߍqCU1`Jk{soi,Oyq).+L¸|aT#uM-U!|h)0ZGdgj;:Q;.0ADj /]߳'9$.Bp&nE<]==-<딡OL lY;rQapj`-?=l[Y+g9hXaBH"ۨ[HA.j;G"̀7oA1SaBY+JL*1yyWeZs!!~Dgk&XpvNUήl0z\?۬"?9G?ihB2K2SП5~擡XW ^&lFm~Y<}R\3`'Ԩ8.SVi5d9Wqo,Y79wHşpȀI~E6zu.IOhʇھ!-zxnfUE6ƯW3AxЮi|KRa/YfB~$r A #ӓ"V1f\d (Z6M11]OyD=u)|!Y-ZC9롩MO1p7m8?$1**Hٚf|O/YQk4x2HH#a|((Ǹ-F1U[0ly; P>sOդ "T!NsS%SwG;.w(2 1RLw:0QJqVo>R=X)\A@Hڇ-_Y0ѧ[l'V3O?靹]̇W5zv4~!;JrjWZ(l@V 꿱Ke v:26+o AFx$$*.A">ƿ[m\ a&|c?e*#Spq=ArYS ey%%d@lI/Dd{)G뷾pt*]BfؕTF9~ VD}>usr@bA# `0v?W9Ic4۹+X'7kN'|k BI9$C,|9xN: Hd5A,1]k [}M' x.H\T6{B@+fb_U2Mn9_x@ܰ|8d6V W q:&8I ) .bc?p _?ynK3WZ >T{u "tlydN`5ǣ f;NPUiuIK{GĬ| g/mꉗ"nANl\m/qٿ&Gc# IFβn+<`\-YٕLh/PvF&vmhRbn5^p//g#UY'iKLm@e1(꺩eq7> stream xڍT 8mR1X ' dɾe/"c0)BBśQ Y 5뚙JX`X""!]s[[@(j RvD_$,´(L9PR@*!U@PA$=/a %CDNa/ > UUU/, D9bF"R~K!Nx~~~0F$i@;`%cIX 2p lA7 GCӀXB%`$y:`clXxc frHtߣ7`MF@X F(f“x/ ģ\Qbv?2zS029f}F%PȐ@͜{z~060Tobs&O(!cxcM3Z71@ "|D҂~u H$ ?3X7̼8"C?2!?[W 57<'N y$ xPR8 (*߳XU ~FpD@[)(da\'Lb2wB(!/,OY]|#? |g0uK0w¿^SP]!$H6Kv7A֒H7_@@\.' 3%2w h"fs JRPRm`D a"08" y*ipןHAH_p_T^?! @ff/LE*p/u 64Db>[ezX,򺇈V( R#'?֮ptWb4P]O1}:/cVN)]i/t~w>%FMuYvSd3¸i]EMpҺُ}xثIC ʂs;˧aܬcv?vL5NÂ:/"/j$L<}4OJq)#fDK m߿/ʨ ˸[-.VLݡZ *gJF^WbZOɺ9^7;QP=LR(爷p׏>Vж|GKҁ}8ȾnAZ׮C" IS~YE#Z\;魝nbXZ>Ҳs9%[nꐌ͉z ?#[4}׾̩{%\'Ai)LRzOl?iDZ#p5?luӠS.{|Q7^XxEc|Z*MR3I] ;^/\\eQOjpQ}Hċ>}xp*եZtfN79!"+ u.Y{6:5$u"-Y(wIp7|BzёY6oU'YN݉~uF)қ1*GY#J/)K13|ׇnWPG? d[VaReBW 䬁BuK&<'!syу++MOoѝğ.6 ՐKo77>Q0ۛkqohC%mK-,!-|*Vn$%$M״ǰ7:SNYOd{l<)>eLI(T1Oey!lmqc&Ng48+ MfT}\G䏣c'VTV!EJBvʪXR\룠M3.OUuIްKxAkgRϖ# )Sc&{='8آ&W\U4\q[?0Egm TQvO3iWH}I 4a0[yb!Uf!zöD&DjYe?]Lh۸W6)SVѳ-/ u1TEm%+>IGÈSP^ه7!б033.]jSZy˫%8Lz+`sRef./gv|ͳ> _̫7E4~B~܎wSOJ^׸=mC$74Y[珺=:C- ClUw%{+ĊH;+|Tuf'ŒИq7 b:~<8sҡkjM@Ҏlb쵯ZgԎx~+xIFhۥ v/'"&us]p&у"gOQZ g.s>{84$?*I~;ۍTrhEJ_Nu50cUHO%^qTȚrH 33g5GhZ;S8/:D3B.=Nҫb%Z3;wGC6'\>5|<1Zv]=>9p6k Y2Yׇ:LZl&Y |3)TYRKj!m94 *}y,ٌn% {\bTy|ʘud|:5/ "f볃D^*{h*|*p lL$bȟKeLUhܚ;ݾ N A'zKPτQ%{u%,ѧFH4fEss,,/CU|ei]1,@HNJ>`jU$+k@9~( ^ v9dtGTE(KtLଚxbqA!Ues!KwL_#]z(QOgZx2ol+]<÷V m5opPx dSMT% ǀ!6\rԙ+}Wq|Z˽ ,`eiL]HמƞqT*9!rV1[5TVˈ[ HEl+/Ӥco AvFe)e֯ ٱ䛉'OO郝lByg2gE_zѼʌNkAؘhWJۂ'Q<[#rH[{ܬ Z`7sZbقӘ|Gdz nvbYˇqΤO<^wkn+Yr2z"fAJ{e. 7*cCs 87ik|GA endstream endobj 294 0 obj << /Length1 1860 /Length2 5603 /Length3 0 /Length 6739 /Filter /FlateDecode >> stream xڍwT>! #DT@B> )nDs`%!)  H ҨHKJ 3~_s8ٴ6pe4 'H",898oi`h_8 `8Pa*&@!D?h@*ǂ8Ѯ#~<TRRg8c09‘ma.y+#*{zz XA4AD]8?4aHA#CAڃ~YeFxq`UU#u=_cqBB( &) ;6 ȟH=U+S=si<$n@B, ewf C"\;%_o ?׈*#vOa ~ Fc?v @ˆ+[gǏͿOE/!Q1ADE(~^? Qh>wأ1*& o~" !`?H$06>?Hx@u)JA|)A|- F)_u1c]a_Bxy4* C K x+ {8y2/?WKJ|Ͻ {mAh[× L }B"Tgo^(s#9<y4M*ZlLq)E7QQObo3;@+u.dj-sۨܽ>Kﶵ0|KAw՚ %Q߃H2 aJ7JU8X ζO"fVV\vV/1YC-7և`{㉎τEӰCԔ:J-KH}Ib!g ėN7u1bʮVftL-0G%QR!3&zS]ae*md Ji34_LC=S?Nߊ7/$05a^ǥFv>!TQڶDdLJEfGB[*bK=NCT/zn{Fz&͔&%dCoy/=by\An^GY ,>mv/gy1"݀*DmH $H>W2L`Mzyub(U+$6bsuo}!qzos#n;3#AI sepSU4Iׂ~ UkH Nֵϵ~aRwG=3_.:+˓g^ Mt{^sn狠ܜQnM jXE$Mjz?ʹ2[ʤ*dbn{}u{I9|ΣW6HG NB&jnfaca ܻaގizM˼BͿ67g.̉j6x!5R➡nhdR8m[12mefaDdVCp\E?a@ƽ {u"VJ?Jb1+m|~~IfnlR| !Wo!QN3swju߶5">+i*+KŨ߹5֪ %WԮ }Ŕ qNnz_z,ִ9|{0=ˀ7'/Hs{oZv* 7Ǘl?Zo@.k@h(%ĸYٔ`n2aH> SFk)dٶ%+39_J.? [UYI)Pe-JgՐXGoI-SK{<$r_*h9̡@ͪQ=ߍXSIvxI1!c1TfBxFW"U=WB "TR-[;:9 096QUbd4gVɡ$n ,w8#=֘Q+n!U?n\CHf¡؋x0r3Z}r :qu'l*\Z~FC __uY謻JLj#z4jdh ,1vxĨ MP>篌!pbʫORk+j'w"_,7PcDWm:-2A p-+v,kmÚ"(V_)K+ia:J7D/ Ω3~̎^o{* .Rp.SD1̀5c[Sİ)K}[$$QZ$N,{_@deќ& t**%I|? \i[ZiH_F4k_SA/&ʅ~JmW# iKIی*,ꀏ] $vC$DKU}b;^EJJL*vȨhXC'#{\~HX26E8M0}Lc>`> @RwLz42F/ݽL*]1,\m0"E#JdA`7N5{:TJ:Ҷ8ølI5yEEO z%DZ8rz!̚S~cy+( {ʕ ȕ_9IOrjX`~xRM8p5\_}_H5-w~_ Gc4k å畏}ϧ(4U*VRF :K:DXO! C|=*:q8EVxטqֶл?w !˝Wb5ڼxτ!&7_3K"Zhn:+mT|ɫ$LU ׳ tAL1{{J#F"Rm]5"k^Tk_boOTWlIޓ6="qb')".d)z 3d7Zyh$ʃ>y ~!ۧMj&ܒX`]Ŭ!,+n;y 3(:Rimsʡ!BwTCOC+M(ޢTaȺ9ݙh>7e˭-aJ+%]|H%3]~ǫϪ79aVdZ@[W 7ք0|'}P7*g"7ы4&DzzÒ>)~o`ݬ4S]Y[m qvP1rgj{L;~6; oK ,μ肠[ݧJ|RrΎnd򲸇0f<~Y^ӳ$?┢bK3ȃ>}F _m*dGmQZx[fDff`_?g$9cI]mNЫ)tڀY9׻V-`V?|mjb2XeK3cY~6-L;lZ-%:oa&̚QDm"d=Q16ݵ@#;]w 7/e%>{TDAA8KUb;G(K-*O?~҆ͦM]xs-h㫧ΔؙWe UoAm"Y5­尵ZԦDsVV'CrWD"S@D#@#טXYdHxvԿ9jMe"hoYݠ22@kb݀\]8⋉4]Tj6~Vש`'c8Vj}ͩA-~6"\;n_*Cϩ(x!i x!ʳڞXכB/)zD;yu]MKPj/xq c#tדfOf/5Mz%Bd\Q\PEcͥ[?-0:b2*GO)okN5U Uij)l>*7bͷW|g$PѹA^-4C'%}*1EGq6-;W7b4xjAmLN֋ !OˇBl錀/3v$O'gǾ݈ m9@jv_A5Ynrq9Ir# _Ju&%nD_m3[;UkOSBTfޚK\K@ Q6Z孄+Kkׯy<f,޿xT^|ҭCÝn0t@aܞ(હY4i:wĵ;49+.FsP5G "XQh?c8.7?ٺ$l4OawH~v]]u ǾISb}E+17r0 endstream endobj 296 0 obj << /Length1 1553 /Length2 7986 /Length3 0 /Length 9008 /Filter /FlateDecode >> stream xڍT6L J (=t4H5 03 !!twtHwI#%{yo=k\w\{=LZ2VHK"ԟ?@< ?9e `Ճ:9Ñ9A[<uD|@>aq>q+$ @`;Z@ME5 FX9#o.`6@Em s8PM@B 7M!E?ls;VhԭՑCP}_#׫ߎ f6nP+-8 bG_1;8tS| n b{{o8 z;6=_B@Гvn A Q)꼁H'B@^ 1 /߈V{@~ ? P y[f[*@a /_}n?½mڿK uBUA/лro KMn's{~vjF_űW,;u-)<>}:xPtum33#S@pMG/=[ghQl .%Wڛªܑ/"L &Y,Ӧh9(H'O&(2F~1>xF y1X߹ֈbpSk3et;,=P7[i(r Cqf 4q)Tw(Q54U p2tWlSSJA+ - nn+!y]kJ\U[` P#ͻ%ViIw:'`;0u$Hh^B S|_9ǣ9i,X/j 8ʮͱѧwxJ^L\=L4e"N'4+f=cUГ$~!Ja/}aXl>)9+ K1X1>Q[ᄞVg&R*V$ȣЭF;{ A8F& Mߋ,E#X, x+.N>/^tŊ˞co$HMFg| ~xEojo+J]sau~G3CY E+*K1bEF6?ꑭqHvK˓2@w6/'qbŧ ZB'|rM k"`6㴽qHU% r,U$YCK.`(Hp=)3r}5'5#{*)ѐwbXlҝQN[߽)R/чOlCm"`6ŮJ+렾}`{%n p3um$vJkt^sdzמt{v@{d,@y{!,)蓻z6Ǩ4GϛšGF=!NA<ns9t' q=,?.cm2uP˸t->pއpX3P`kV r@ahCJ0Lqmw3n1_ht7^ vP #U&ͻW<*n|tIJz_$@o[>Wu{aC~(tT;b&QYPG_۟i8.D nQO7 @GQ GFwQfr}G?T?UV2DMXnSd.Mv{'o.iwq SB*Nz_$Pn@`߳TtRg&WӫceL| }Lji;&(*b ٳ`]c} g8è}诌fG NYD8*/h/]g ZE^^&޴ߥ1'yOUDz"m\1(aK4J:7^ q`9X+9gjF'{I(BKRy<=jecZ 1% $X+a%ۍ׃BAeAg"+ɒ)%Tr}Zh^iNrL'{t_Y'+Ɲ_M$ ta}*6X5LX^u׿2Z}N)=| PL;{`oIX-`߉)^P@t`[?2Rr^u/Tg*ܐ,+8:WEdC<.;G ig*#Ez܉r6?)Xvrs)Glj쫧6S"ߛؖ]1>dV$ @*<]YwOlq_Z/SI€ẅ́^6$=Qժ[44V7٫&/<- rxim4 f*pR'㈷"5zrnxvn2;8olyuԻ6`>vkKdWQ× @+nֺ߇1LȌTOxsdIbݬY|yAjvշd)i^)i@6),R:GG>+>CbP_͡fpO/7~lƗ/zvwtG ~t|#ڦ%϶dQ>  f̮W6ϔј>]8{"dz3V?"KU-a15sBw=ŕ*OKJu'q>{u+\jHT亜A'Q_[}3Vg-1\B&u0d O fW_M}(.k*'ƣ꽰Zw{qmİ((`V/һõ śW/ ym*LIyFI` \NE1-, @Ǒ.[.hw ҘdM*c>H}Uө(Ez3eR'<[,ւoE)Oe7]OrRb4|Rf͓~Y=i$,ušIB/h?F"4Nt@(͡-a8+t,N)I?ҏuCߗgdu#zi  %;_NKKߧ0KX2ZW˙Ԫ}Q94\XB^)4D) ;6:u>o/;YQ.< `;6bf'/Tht=E椾c)ʦ-=oCy|z}OQ{4:Wᛟ댆cl{^VS< G7yJ!3qFހnI@_L!o-3_u=SIh e 8ayVvY?0DDJ$c6AêF9H:Tfc(̆$>|2xx8GծKsKD蒈yu~@mLYXsQb!UߝxTbt;'$Co>bTۑ Wl(7l<(zTV<ہ@-?ƲW`e30**)U"uSmh jJ1I!#ޞŠ sҰcQ%C jXw\yeI ]]f5+)[#^ܯ9Pe|I[=i.z[BWAT9g'*1d=GK޸HhhQze^+iM<Д(ljI^V,PdW@[_ʠ炉ID{ږt̆!̂D.5M?+w;Lt>kuLxVEnX{;<(h]>XJaP8اC|t*lyf'ܫeuN<jβz̫Hj %?+\!fלV3剿_%d٧Ӳ+}0m~+GƺljY%zU6FTGu9f1pBC0;R9kQF(v IR,;S6;jkXp +? x Q{9'}?E/nֺTO6q ҉^W|v._V}:̂SPX*,,%6OW3q46K/AA}w➐a|8F 6/{j˾+50~$ץ"2'8&W;9g-ƴEhp\2-cuF0<'2ѶpB0Yy.dEMLpyOKӬ%OrҒRbdvd]NizIN }>91يЍ:c!vrrό.5^V{.siAuIk/# #e&=/1eq-0(ӳ77^ jI,?$q+b"b,ĞjiS_x^exODCnw9{JF:Huؑ3l*l&w8Ieuc~Y$U۪06 |<IϪdnTcU@Ozm8~Y<"h& | BCT\\eηB{Z1\Ѹ\jyrٺKd /'q bm"63Sf\Wԕa۸gh<̠< ]eMPtN xU6>޵ݠk^hT|8k~/7hY uc0Ўc:=?t&wՖ@/&n\{aKkȽ}Us9K|='a0*(>ȋѯpg%ؑEay!5QUFap|oC2#ţ 8x7Jk,C1'M[Β‰0 B(Ѩftwݰ`)+%|ʬو:ʮ<{OF}r@B&im i0+; ~9k ?wfSro&K6!{dt?/ U_T.BB+11XgIƣ?tt cՋieOsn6wǜH/ZKCp@E=˃٨~&Ó9k&I#>c#tNPv.'|X՚1i5V';~OY UP9yi w߽]J8ӭ)5~o&٫b&/#ICIw[Վ ;Sdsw(^b^o_HF= 1袥K‰[XD = S\0~('u dHQnAPt su-~,铳t|':ۑZ<;HCPtLxt,:*Ub]MoSu#:nٽejIf8=bMeEquN$=\w&`%Jf_X1ɑqgmVt=~i$:wOeHYn>NuO. 4)prH)d, '80IFLT&*Z“VXS `m !}#5{ 8K+4?TQUr S#Uh,03[k5Wݔ2u趭ͻ< BF[==G/+e8߹,y.C-}d|T] 2}U]l.Ƃ.vi_" wsAn+5O?ra|ym<}V$D يYGwg~aR{Gn΅b%EZ]\< 1B*LYi65/Zސw\qvpC)qQjZr$RS 9DݕQT'Lx^}Op\Pg䔓/-n!ǃ!d*J=ɚ8_d šƛlx*k՛N1rMQ2_hVVP|V⁕SrWJPS޾)!sst~[#Hw*҂7.shT'g~<qRՕԻ;|5ϯ|l'Bh\$jzlN OXE21j?xCiHζ}92*23fe;ds"]YcAt?^y?沨ng!$ZkX-0.iN֤V~Q<"8~.2,5k~s$q{sKT?Zh!G oN+הȚy"H}&ABwL#Z9S'ے:*GU0מֹ & y3Ǎ-BbA|Ĝ/jFE%c<١<:]oT=/M;Nk7=e Qku]{IU˙UO_ozy^;ta]o2{v#O!o?d6Mt?DKgāTt)M#(IҸ)tInDSB]$7QmI=^VFZ,"Onb?i?=<i Jr,\ݧ{;ݫlU:åf;ଽ";y{REO6Vci:E9;I}8e/ .u26KptSPv.$BPXwȕK}ԉT,:d!5;Xc(]#-O4ZZ9:4ۅ.Sb?2 endstream endobj 298 0 obj << /Length1 807 /Length2 1509 /Length3 0 /Length 2099 /Filter /FlateDecode >> stream xeR{ yNl;034B%%;saF U#S=DEO.}/i=9眃tз$3]L3n(켡ʐ+?l׏AݍHx&$*˧CJR'22^Gf kTtbY%:z5/9;}.7Hf ƻˉ.~m}<#sk:/6S9%I3ז}ulQtj_'.dɖ8yْՊsW :۴p@vHǝ6*\tVm\1M#n=W׍_]m/zn|_|LۮZ4lZKK4Gj:GAن-S}#V!Iتgk#]]m uˋٓ/V;iO9:_q<ӲuAQ!a 0}RJksUfŸ:~͓JհV xߜDz /ĭ y pB'gt`05%C\׶c(iaw۫Nu*3;dDvfVFO9x_nUO=z&7OܠlO2ޯp,SC`y9woP(`~Jp78ϫ$+e4PyLjz=3\|}e= !1JTo>TbUߦy–ۜ>um駟*jm:`0n\(r~?l֝/mXn,1ҿ}qg]au@d (3lYe]c6`Khґ'ܼmlYйΘ9OU JNW@+ \GƅE/'~xjiSLEKHAUd^)@5$kl.{(X{R'8xO=Q/ԚɰBi} G壓"/T㌗d˒OB'UD5zvz([%p nkP뙳ӡ U'fh͸m*{yAFO0:/_df❣luɣŸ#^ endstream endobj 314 0 obj << /Author()/Title()/Subject()/Creator(LaTeX with hyperref package)/Producer(pdfTeX-1.40.18)/Keywords() /CreationDate (D:20200731120951+02'00') /ModDate (D:20200731120951+02'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) kpathsea version 6.2.3) >> endobj 281 0 obj << /Type /ObjStm /N 62 /First 523 /Length 2282 /Filter /FlateDecode >> stream xڭY]S}ϯ[,ڪo [`$qvfor-@20c-9j UU QÕ#DBMi0#4GDq7 1SG<ip,G a nB+'Č>G Rt0(`KFܣ~8 S > q)1qIGr` `҂|Ga+pp * \Xa(L=QXa@Kh``$A%\agn$\ +$%75ň H;Y =|s"œxϷ,a3Ԑ՗arߨҥ "h"DؾjܫiR-M_(FiXCQn|F] 3* {>HNE;xOpqx8 gx?u~^{>7%}>PW%us=:+_[|oa6 }b\5|Z Xv'VxX{][ <ֆ? ClEod)>-'חT7{'/9lAo$ ~bxdE\6ί.;_otg7ws"\6St`}m`lZSM8o;r-Fw(߻=ʿIWGg |HUߠ̻\t֐_+s]R;'q]6JMȋ 9/BWE'Z^³г {ryeM}mP杓;;ozw .vƲw94v~Kσ0V=^3N~'7ߨsGCM$"j+jVDl Ad˞kT{L|>g2Fs3-*|ⱚq>EҗR=x?jSjjhO- 4|)~xōڙ~9t, %बu'%׍BcoEhTk[I._\R+Mߊ70oZƛ7ܴ,?-O'DL,MdjiKIzºTƅ1mN켖l{-j)-+](Znw^OYd[HЧ8qA}GT5-u>XZ9XUQq42HmWCa5HKcmPQ`o۪Uo FmA)lC9A>nb(Dxb0#b0b01e`yW NɄHVѩ2PX4oюh!1@Ztu\_̄@0DZ t)UԌc1M*.f~%&W%KÓx-vֶ&e\ݤ]}lS-v`巍VC@N)ٮ;뺽*|tt__iӏ4jNAL㧞m^;UUZ+Ō?B9 _۳E8 /[ endstream endobj 315 0 obj << /Type /XRef /Index [0 316] /Size 316 /W [1 3 1] /Root 313 0 R /Info 314 0 R /ID [<670D999AEB455C62B9736807964B6993> <670D999AEB455C62B9736807964B6993>] /Length 779 /Filter /FlateDecode >> stream x%oQ_sUjKkhіEu0miK[:$"v,m  ]n-mш"&R'9tB=8E̎Ү(XNk# ZI)ȀZH9 죷 3AHm,E5Sz%(KjzR5BY*r-&*P ցhh`@#Ы }\rmP;m!um,u@[-u;I脴}.ַ챐?usTR|た'Jip~}Ԣ+E7J-z}[E?+]8҄ϕ&-ߦ,~X .[QiڒDiƒ?J will be in parallel if all the requirements are satisfied # nmf(esGolub, 3, nrun=5, .opt='v') # # # request a certain number of cores to use => no error if not possible # nmf(esGolub, 3, nrun=5, .opt='vp8') # # # force parallel computation: use option 'P' # nmf(esGolub, 3, nrun=5, .opt='vP') # # # require an improbable number of cores => error # nmf(esGolub, 3, nrun=5, .opt='vP200') # } ## ----mpi, eval=FALSE----------------------------------------------------- # # file: mpi.R # # if(requireNamespace("Biobase", quietly=TRUE)){ # ## 0. Create and register an MPI cluster # library(doMPI) # cl <- startMPIcluster() # registerDoMPI(cl) # library(NMF) # # # run on all workers using the current parallel backend # data(esGolub) # res <- nmf(esGolub, 3, 'brunet', nrun=n, .opt='p', .pbackend=NULL) # # # save result # save(res, file='result.RData') # # ## 4. Shutdown the cluster and quit MPI # closeCluster(cl) # mpi.quit() # } ## ----force_seq, cache=TRUE, backspace = TRUE----------------------------- if(requireNamespace("Biobase", quietly=TRUE)){ # parallel execution on 2 cores (if possible) res1 <- nmf(esGolub, 3, nrun=5, .opt='vp2', seed=123) # or use the doParallel with single core res2 <- nmf(esGolub, 3, nrun=5, .opt='vp1', seed=123) # force sequential computation by sapply: use option '-p' or .pbackend=NA res3 <- nmf(esGolub, 3, nrun=5, .opt='v-p', seed=123) res4 <- nmf(esGolub, 3, nrun=5, .opt='v', .pbackend=NA, seed=123) # or use the SEQ backend of foreach: .pbackend='seq' res5 <- nmf(esGolub, 3, nrun=5, .opt='v', .pbackend='seq', seed=123) # all results are all identical nmf.equal(list(res1, res2, res3, res4, res5)) } ## ----estimate_rank, cache=TRUE------------------------------------------- if(requireNamespace("Biobase", quietly=TRUE)){ # perform 10 runs for each value of r in range 2:6 estim.r <- nmf(esGolub, 2:6, nrun=10, seed=123456) } ## ----estimate_rank_plot, fig.width=10, fig.height=6---------------------- if(requireNamespace("Biobase", quietly=TRUE)){ plot(estim.r) } ## ----estimate_rank_hm_include, fig.width=14, fig.height=7, fig.keep='last'---- if(requireNamespace("Biobase", quietly=TRUE)){ consensusmap(estim.r, annCol=esGolub, labCol=NA, labRow=NA) } ## ----estimate_rank_random, cache=TRUE, fig.width=10, fig.height=6, fig.keep='last'---- if(requireNamespace("Biobase", quietly=TRUE)){ # shuffle original data V.random <- randomize(esGolub) # estimate quality measures from the shuffled data (use default NMF algorithm) estim.r.random <- nmf(V.random, 2:6, nrun=10, seed=123456) # plot measures on same graph plot(estim.r, estim.r.random) } ## ----multimethod, cache=TRUE--------------------------------------------- if(requireNamespace("Biobase", quietly=TRUE)){ # fit a model for several different methods res.multi.method <- nmf(esGolub, 3, list('brunet', 'lee', 'ns'), seed=123456, .options='t') } ## ----compare------------------------------------------------------------- if(requireNamespace("Biobase", quietly=TRUE)){ compare(res.multi.method) # If prior knowledge of classes is available compare(res.multi.method, class=esGolub$Cell) } ## ----errorplot_compute, cache=TRUE--------------------------------------- if(requireNamespace("Biobase", quietly=TRUE)){ # run nmf with .option='t' res <- nmf(esGolub, 3, .options='t') # or with .options=list(track=TRUE) } ## ----errorplot, out.width="0.5\\textwidth", fig.show='hold'-------------- if(requireNamespace("Biobase", quietly=TRUE)){ plot(res) plot(res.multi.method) } ## ----heatmap_coef_basis_inc, fig.width=14, fig.height=7, fig.keep='last'---- if(requireNamespace("Biobase", quietly=TRUE)){ layout(cbind(1,2)) # basis components basismap(res, subsetRow=TRUE) # mixture coefficients coefmap(res) } ## ----heatmap_consensus_inc, out.width="0.49\\textwidth", crop=TRUE, echo=1:2---- if(requireNamespace("Biobase", quietly=TRUE)){ # The cell type is used to label rows and columns consensusmap(res.multirun, annCol=esGolub, tracks=NA) plot(1:10) f2 <- fig_path("2.pdf") } ## ----hack_consensus, include=FALSE--------------------------------------- if(requireNamespace("Biobase", quietly=TRUE)){ file.copy('consensus.pdf', f2, overwrite=TRUE) } ## ----custom_algo_sig----------------------------------------------------- my.algorithm <- function(x, seed, param.1, param.2){ # do something with starting point # ... # return updated starting point return(seed) } ## ----custom_algo--------------------------------------------------------- my.algorithm <- function(x, seed, scale.factor=1){ # do something with starting point # ... # for example: # 1. compute principal components pca <- prcomp(t(x), retx=TRUE) # 2. use the absolute values of the first PCs for the metagenes # Note: the factorization rank is stored in object 'start' factorization.rank <- nbasis(seed) basis(seed) <- abs(pca$rotation[,1:factorization.rank]) # use the rotated matrix to get the mixture coefficient # use a scaling factor (just to illustrate the use of extra parameters) coef(seed) <- t(abs(pca$x[,1:factorization.rank])) / scale.factor # return updated data return(seed) } ## ----define_V------------------------------------------------------------ n <- 50; r <- 3; p <- 20 V <-syntheticNMF(n, r, p) ## ----custom_algo_run----------------------------------------------------- nmf(V, 3, my.algorithm, scale.factor=10) ## ----custom_algo_run_obj------------------------------------------------- # based on Kullback-Leibler divergence nmf(V, 3, my.algorithm, scale.factor=10, objective='KL') # based on custom distance metric nmf(V, 3, my.algorithm, scale.factor=10 , objective=function(model, target, ...){ ( sum( (target-fitted(model))^4 ) )^{1/4} } ) ## ----custom_algo_run_mixed, error = TRUE, try = TRUE--------------------- # put some negative input data V.neg <- V; V.neg[1,] <- -1; # this generates an error try( nmf(V.neg, 3, my.algorithm, scale.factor=10) ) # this runs my.algorithm without error nmf(V.neg, 3, my.algorithm, mixed=TRUE, scale.factor=10) ## ----nmf_models---------------------------------------------------------- nmfModel() ## ----custom_algo_NMFoffset----------------------------------------------- my.algorithm.offset <- function(x, seed, scale.factor=1){ # do something with starting point # ... # for example: # 1. compute principal components pca <- prcomp(t(x), retx=TRUE) # retrieve the model being estimated data.model <- fit(seed) # 2. use the absolute values of the first PCs for the metagenes # Note: the factorization rank is stored in object 'start' factorization.rank <- nbasis(data.model) basis(data.model) <- abs(pca$rotation[,1:factorization.rank]) # use the rotated matrix to get the mixture coefficient # use a scaling factor (just to illustrate the use of extra parameters) coef(data.model) <- t(abs(pca$x[,1:factorization.rank])) / scale.factor # 3. Compute the offset as the mean expression data.model@offset <- rowMeans(x) # return updated data fit(seed) <- data.model seed } ## ----custom_algo_NMFOffset_run------------------------------------------- # run custom algorithm with NMF model with offset nmf(V, 3, my.algorithm.offset, model='NMFOffset', scale.factor=10) ## ----custom_seed--------------------------------------------------------- # start: object of class NMF # target: the target matrix my.seeding.method <- function(model, target){ # use only the largest columns for W w.cols <- apply(target, 2, function(x) sqrt(sum(x^2))) basis(model) <- target[,order(w.cols)[1:nbasis(model)]] # initialize H randomly coef(model) <- matrix(runif(nbasis(model)*ncol(target)) , nbasis(model), ncol(target)) # return updated object return(model) } ## ----custom_seed_run----------------------------------------------------- nmf(V, 3, 'snmf/r', seed=my.seeding.method) ## ----options_algo, eval=1:6---------------------------------------------- #show default algorithm and seeding method nmf.options('default.algorithm', 'default.seed') # retrieve a single option nmf.getOption('default.seed') # All options nmf.options() ## ----nmf_options, echo=FALSE, results='asis'----------------------------- RdSection2latex('nmf.options', package='NMF') ## ----print_options------------------------------------------------------- nmf.printOptions() ## ----sessionInfo, echo=FALSE, results='asis'----------------------------- toLatex(sessionInfo()) NMF/inst/doc/NMF-vignette.Rnw0000644000176200001440000016126013620530420015350 0ustar liggesusers%\VignetteIndexEntry{An introduction to the package NMF} %\VignetteDepends{utils,NMF,Biobase,bigmemory,xtable,RColorBrewer,knitr,bibtex} %\VignetteKeyword{math} %\VignetteCompiler{knitr} %\VignetteEngine{knitr::knitr} \documentclass[a4paper]{article} %\usepackage[OT1]{fontenc} \usepackage[colorlinks]{hyperref} % for hyperlinks \usepackage{a4wide} \usepackage{xspace} \usepackage[all]{hypcap} % for linking to the top of the figures or tables % add preamble from pkgmaker <>= pkgmaker::latex_preamble() if(!requireNamespace("Biobase")) BiocManager::install("Biobase") @ \newcommand{\nmfpack}{\Rpkg{NMF}} \newcommand{\RcppOctave}{\textit{RcppOctave}\xspace} \newcommand{\matlab}{Matlab$^\circledR$\xspace} \newcommand{\MATLAB}{\matlab} \newcommand{\gauss}{GAUSS$^\circledR$\xspace} \newcommand{\graphwidth}{0.9\columnwidth} \newcommand{\refeqn}[1]{(\ref{#1})} % REFERENCES \usepackage[citestyle=authoryear-icomp , doi=true , url=true , maxnames=1 , maxbibnames=15 , backref=true , backend=bibtex]{biblatex} \AtEveryCitekey{\clearfield{url}} <>= pkgmaker::latex_bibliography('NMF') @ \newcommand{\citet}[1]{\textcite{#1}} \renewcommand{\cite}[1]{\parencite{#1}} \DefineBibliographyStrings{english}{% backrefpage = {see p.}, % for single page number backrefpages = {see pp.} % for multiple page numbers } %% % boxed figures \usepackage{float} \floatstyle{boxed} \restylefloat{figure} \usepackage{array} \usepackage{tabularx} \usepackage{xcolor} \usepackage{url} \urlstyle{rm} <>= set.seed(123456) library(knitr) knit_hooks$set(try = pkgmaker::hook_try, backspace = pkgmaker::hook_backspace()) @ % use cleveref for automatic reference label formatting \usepackage[capitalise, noabbrev]{cleveref} % multiple columns \usepackage{multicol} % define commands for notes \usepackage{todonotes} \newcommand{\nbnote}[1]{\todo[inline, backgroundcolor=blue!20!white]{\scriptsize\textsf{\textbf{NB:} #1}}\ \\} % default graphic width \setkeys{Gin}{width=0.95\textwidth} \begin{document} <>= # Load library(NMF) # limit number of cores used nmf.options(cores = 2) @ \title{An introduction to NMF package\\ \small Version \Sexpr{utils::packageVersion('NMF')}} \author{Renaud Gaujoux} % \\Address Computational Biology - University of Cape Town, South Africa, \maketitle This vignette presents the \citeCRANpkg{NMF}, which implements a framework for Nonnegative Matrix Factorization (NMF) algorithms in R \cite{R}. The objective is to provide an implementation of some standard algorithms, while allowing the user to easily implement new methods that integrate into a common framework, which facilitates analysis, result visualisation or performance benchmarking. If you use the package \nmfpack in your analysis and publications please cite: \bigskip \todo[inline, backgroundcolor=blue!10!white]{\fullcite{Rpackage:NMF}} Note that the \nmfpack includes several NMF algorithms, published by different authors. Please make sure to also cite the paper(s) associated with the algorithm(s) you used. Citations for those can be found in \cref{tab:algo} and in the dedicated help pages \code{?gedAlgorithm.}, e.g., \code{?gedAlgorithm.SNMF\_R}. \bigskip \paragraph{Installation:} The latest stable version of the package can be installed from any \href{http://cran.r-project.org}{CRAN} repository mirror: <>= # Install install.packages('NMF') # Load library(NMF) @ The \nmfpack is a project hosted on \emph{R-forge}\footnote{\url{https://r-forge.r-project.org/projects/nmf}}. The latest development version is available from \url{https://r-forge.r-project.org/R/?group_id=649} and may be installed from there\footnote{\code{install.packages("NMF", repos = "http://R-Forge.R-project.org")}}. \paragraph{Support:} UseRs interested in this package are encouraged to subscribe to the user mailing list (\href{https://lists.r-forge.r-project.org/mailman/listinfo/nmf-user}{nmf-user@lists.r-forge.r-project.org}), which is the preferred channel for enquiries, bug reports, feature requests, suggestions or NMF-related discussions. This will enable better tracking as well as fruitful community exchange. \paragraph{Important:} Note that some of the classes defined in the NMF package have gained new slots. If you need to load objects saved in versions prior 0.8.14 please use: <>= # eg., load from some RData file load('object.RData') # update class definition object <- nmfObject(object) @ \pagebreak \tableofcontents \pagebreak \section{Overview} \subsection{Package features} This section provides a quick overview of the \nmfpack package's features. \Cref{sec:usecase} provides more details, as well as sample code on how to actually perform common tasks in NMF analysis. <>= nalgo <- length(nmfAlgorithm()) nseed <- length(nmfSeed()) @ The \nmfpack package provides: \begin{itemize} \item \Sexpr{nalgo} built-in algorithms; \item \Sexpr{nseed} built-in seeding methods; \item Single interface to perform all algorithms, and combine them with the seeding methods; \item Provides a common framework to test, compare and develop NMF methods; \item Accept custom algorithms and seeding methods; \item Plotting utility functions to visualize and help in the interpretation of the results; \item Transparent parallel computations; \item Optimized and memory efficient C++ implementations of the standard algorithms; \item Optional layer for bioinformatics using BioConductor \cite{Gentleman2004}; \end{itemize} \subsection{Nonnegative Matrix Factorization} This section gives a formal definition for Nonnegative Matrix Factorization problems, and defines the notations used throughout the vignette. Let $X$ be a $n \times p$ non-negative matrix, (i.e with $x_{ij} \geq 0$, denoted $X \geq 0$), and $r > 0$ an integer. Non-negative Matrix Factorization (NMF) consists in finding an approximation \begin{equation} X \approx W H\ , \label{NMFstd} \end{equation} where $W, H$ are $n\times r$ and $r \times p$ non-negative matrices, respectively. In practice, the factorization rank $r$ is often chosen such that $r \ll \min(n, p)$. The objective behind this choice is to summarize and split the information contained in $X$ into $r$ factors: the columns of $W$. Depending on the application field, these factors are given different names: basis images, metagenes, source signals. In this vignette we equivalently and alternatively use the terms \emph{basis matrix} or \emph{metagenes} to refer to matrix $W$, and \emph{mixture coefficient matrix} and \emph{metagene expression profiles} to refer to matrix $H$. The main approach to NMF is to estimate matrices $W$ and $H$ as a local minimum: \begin{equation} \min_{W, H \geq 0}\ \underbrace{[D(X, WH) + R(W, H)]}_{=F(W,H)} \label{nmf_min} \end{equation} where \begin{itemize} \item $D$ is a loss function that measures the quality of the approximation. Common loss functions are based on either the Frobenius distance $$D: A,B\mapsto \frac{Tr(AB^t)}{2} = \frac{1}{2} \sum_{ij} (a_{ij} - b_{ij})^2,$$ or the Kullback-Leibler divergence. $$D: A,B\mapsto KL(A||B) = \sum_{i,j} a_{ij} \log \frac{a_{ij}}{b_{ij}} - a_{ij} + b_{ij}.$$ \item $R$ is an optional regularization function, defined to enforce desirable properties on matrices $W$ and $H$, such as smoothness or sparsity \cite{Cichocki2008}. \end{itemize} \subsection{Algorithms} NMF algorithms generally solve problem \refeqn{nmf_min} iteratively, by building a sequence of matrices $(W_k,H_k)$ that reduces at each step the value of the objective function $F$. Beside some variations in the specification of $F$, they also differ in the optimization techniques that are used to compute the updates for $(W_k,H_k)$. For reviews on NMF algorithms see \cite{Berry2007, Chu2004} and references therein. The \nmfpack package implements a number of published algorithms, and provides a general framework to implement other ones. \Cref{tab:algo} gives a short description of each one of the built-in algorithms: The built-in algorithms are listed or retrieved with function \code{nmfAlgorithm}. A given algorithm is retrieved by its name (a \code{character} key), that is partially matched against the list of available algorithms: <>= # list all available algorithms nmfAlgorithm() # retrieve a specific algorithm: 'brunet' nmfAlgorithm('brunet') # partial match is also fine identical(nmfAlgorithm('br'), nmfAlgorithm('brunet')) @ \begin{table}[h!t] \begin{tabularx}{\textwidth}{lX} \hline Key & Description\\ \hline \code{brunet} & Standard NMF. Based on Kullback-Leibler divergence, it uses simple multiplicative updates from \cite{Lee2001}, enhanced to avoid numerical underflow. \begin{eqnarray} H_{kj} & \leftarrow & H_{kj} \frac{\left( \sum_l \frac{W_{lk} V_{lj}}{(WH)_{lj}} \right)}{ \sum_l W_{lk} }\\ W_{ik} & \leftarrow & W_{ik} \frac{ \sum_l [H_{kl} A_{il} / (WH)_{il} ] }{\sum_l H_{kl} } \end{eqnarray} \textbf{Reference:} \cite{Brunet2004}\\ \hline % \code{lee} & Standard NMF. Based on euclidean distance, it uses simple multiplicative updates \begin{eqnarray} H_{kj} & \leftarrow & H_{kj} \frac{(W^T V)_{kj}}{(W^T W H)_{kj}}\\ W_{ik} & \leftarrow & W_{ik} \frac{(V H^T)_{ik}}{(W H H^T)_{ik}} \end{eqnarray} \textbf{Reference:} \cite{Lee2001}\\ \hline % %\code{lnmf} & Local Nonnegative Matrix Factorization. Based on a %regularized Kullback-Leibler divergence, it uses a modified version of %Lee and Seung's multiplicative updates. % %\textbf{Reference:} \cite{Li2001}\\ % \code{nsNMF} & Non-smooth NMF. Uses a modified version of Lee and Seung's multiplicative updates for Kullback-Leibler divergence to fit a extension of the standard NMF model. It is meant to give sparser results. \textbf{Reference:} \cite{Pascual-Montano2006}\\ \hline % \code{offset} & Uses a modified version of Lee and Seung's multiplicative updates for euclidean distance, to fit a NMF model that includes an intercept. \textbf{Reference:} \cite{Badea2008}\\ \hline % \code{pe-nmf} & Pattern-Expression NMF. Uses multiplicative updates to minimize an objective function based on the Euclidean distance and regularized for effective expression of patterns with basis vectors. \textbf{Reference:} \cite{Zhang2008}\\ \hline % \code{snmf/r}, \code{snmf/l} & Alternating Least Square (ALS) approach. It is meant to be very fast compared to other approaches. \textbf{Reference:} \cite{KimH2007}\\ \hline \end{tabularx} \caption{Description of the implemented NMF algorithms. The first column gives the key to use in the call to the \texttt{nmf} function.\label{tab:algo}} \end{table} \subsection{Initialization: seeding methods} NMF algorithms need to be initialized with a seed (i.e. a value for $W_0$ and/or $H_0$\footnote{Some algorithms only need one matrix factor (either $W$ or $H$) to be initialized. See for example the SNMF/R(L) algorithm of Kim and Park \cite{KimH2007}.}), from which to start the iteration process. Because there is no global minimization algorithm, and due to the problem's high dimensionality, the choice of the initialization is in fact very important to ensure meaningful results. The more common seeding method is to use a random starting point, where the entries of $W$ and/or $H$ are drawn from a uniform distribution, usually within the same range as the target matrix's entries. This method is very simple to implement. However, a drawback is that to achieve stability one has to perform multiple runs, each with a different starting point. This significantly increases the computation time needed to obtain the desired factorization. To tackle this problem, some methods have been proposed so as to compute a reasonable starting point from the target matrix itself. Their objective is to produce deterministic algorithms that need to run only once, still giving meaningful results. For a review on some existing NMF initializations see \cite{Albright2006} and references therein. The \nmfpack\ package implements a number of already published seeding methods, and provides a general framework to implement other ones. \Cref{tab:seed} gives a short description of each one of the built-in seeding methods: The built-in seeding methods are listed or retrieved with function \code{nmfSeed}. A given seeding method is retrieved by its name (a \code{character} key) that is partially matched against the list of available seeding methods: <>= # list all available seeding methods nmfSeed() # retrieve a specific method: 'nndsvd' nmfSeed('nndsvd') # partial match is also fine identical(nmfSeed('nn'), nmfSeed('nndsvd')) @ \begin{table}[h!t] \begin{tabularx}{\textwidth}{lX} \hline Key & Description\\ \hline \code{ica} & Uses the result of an Independent Component Analysis (ICA) (from the \citeCRANpkg{fastICA}). Only the positive part of the result are used to initialize the factors.\\ \hline % \code{nnsvd} & Nonnegative Double Singular Value Decomposition. The basic algorithm contains no randomization and is based on two SVD processes, one approximating the data matrix, the other approximating positive sections of the resulting partial SVD factors utilizing an algebraic property of unit rank matrices. It is well suited to initialize NMF algorithms with sparse factors. Simple practical variants of the algorithm allows to generate dense factors. \textbf{Reference:} \cite{Boutsidis2008}\\ \hline % \code{none} & Fix seed. This method allows the user to manually provide initial values for both matrix factors.\\ \hline % \code{random} & The entries of each factors are drawn from a uniform distribution over $[0, max(V)]$, where $V$ is the target matrix.\\ \hline \end{tabularx} \caption{Description of the implemented seeding methods to initialize NMF algorithms. The first column gives the key to use in the call to the \texttt{nmf} function.\label{tab:seed}} \end{table} \subsection{How to run NMF algorithms} Method \code{nmf} provides a single interface to run NMF algorithms. It can directly perform NMF on object of class \code{matrix} or \code{data.frame} and \code{ExpressionSet} -- if the \citeBioCpkg{Biobase} is installed. The interface has four main parameters: \medskip \fbox{\code{nmf(x, rank, method, seed, ...)}} \begin{description} \item[\code{x}] is the target \code{matrix}, \code{data.frame} or \code{ExpressionSet} \footnote{\code{ExpressionSet} is the base class for handling microarray data in BioConductor, and is defined in the \pkgname{Biobase} package.} \item[\code{rank}] is the factorization rank, i.e. the number of columns in matrix $W$. \item[\code{method}] is the algorithm used to estimate the factorization. The default algorithm is given by the package specific option \code{'default.algorithm'}, which defaults to \code{'brunet'} on installation \cite{Brunet2004}. \item[\code{seed}] is the seeding method used to compute the starting point. The default method is given by the package specific option \code{'default.seed'}, which defaults to \code{'random'} on initialization (see method \code{?rnmf} for details on its implementation). \end{description} See also \code{?nmf} for details on the interface and extra parameters. \subsection{Performances} Since version 0.4, some built-in algorithms are optimized in C++, which results in a significant speed-up and a more efficient memory management, especially on large scale data. The older R versions of the concerned algorithms are still available, and accessible by adding the prefix \code{'.R\#'} to the algorithms' access keys (e.g. the key \code{'.R\#offset'} corresponds to the R implementation of NMF with offset \cite{Badea2008}). Moreover they do not show up in the listing returned by the \code{nmfAlgorithm} function, unless argument \code{all=TRUE}: <>= nmfAlgorithm(all=TRUE) # to get all the algorithms that have a secondary R version nmfAlgorithm(version='R') @ \Cref{tab:perf} shows the speed-up achieved by the algorithms that benefit from the optimized code. All algorithms were run once with a factorization rank equal to 3, on the Golub data set which contains a $5000\times 38$ gene expression matrix. The same numeric random seed (\code{seed=123456}) was used for all factorizations. The columns \emph{C} and \emph{R} show the elapsed time (in seconds) achieved by the C++ version and R version respectively. The column \emph{Speed.up} contains the ratio $R/C$. <>= # retrieve all the methods that have a secondary R version meth <- nmfAlgorithm(version='R') meth <- c(names(meth), meth) meth if(requireNamespace("Biobase", quietly=TRUE)){ # load the Golub data data(esGolub) # compute NMF for each method res <- nmf(esGolub, 3, meth, seed=123456) # extract only the elapsed time t <- sapply(res, runtime)[3,] } @ <>= # speed-up m <- length(res)/2 su <- cbind( C=t[1:m], R=t[-(1:m)], Speed.up=t[-(1:m)]/t[1:m]) library(xtable) xtable(su, caption='Performance speed up achieved by the optimized C++ implementation for some of the NMF algorithms.', label='tab:perf') @ \subsection{How to cite the package NMF} To view all the package's bibtex citations, including all vignette(s) and manual(s): <>= # plain text citation('NMF') # or to get the bibtex entries toBibtex(citation('NMF')) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Use case: Golub dataset}\label{sec:usecase} We illustrate the functionalities and the usage of the \nmfpack package on the -- now standard -- Golub dataset on leukemia. It was used in several papers on NMF \cite{Brunet2004, Gao2005} and is included in the \nmfpack package's data, wrapped into an \code{ExpressionSet} object. For performance reason we use here only the first 200 genes. Therefore the results shown in the following are not meant to be biologically meaningful, but only illustrative: <>= if(requireNamespace("Biobase", quietly=TRUE)){ data(esGolub) esGolub esGolub <- esGolub[1:200,] # remove the uneeded variable 'Sample' from the phenotypic data esGolub$Sample <- NULL } @ % TODO: pass to 50 genes for dev \paragraph{Note:} To run this example, the \code{Biobase} package from BioConductor is required. \subsection{Single run}\label{sec:single_run} \subsubsection{Performing a single run} To run the default NMF algorithm on data \code{esGolub} with a factorization rank of 3, we call: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # default NMF algorithm res <- nmf(esGolub, 3) } @ Here we did not specify either the algorithm or the seeding method, so that the computation is done using the default algorithm and is seeded by the default seeding methods. These defaults are set in the package specific options \code{'default.algorithm'} and \code{'default.seed'} respectively. See also \cref{sec:algo,sec:seed} for how to explicitly specify the algorithm and/or the seeding method. \subsubsection{Handling the result} The result of a single NMF run is an object of class \code{NMFfit}, that holds both the fitted NMF model and data about the run: <>= if(requireNamespace("Biobase", quietly=TRUE)){ res } @ The fitted model can be retrieved via method \code{fit}, which returns an object of class \code{NMF}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ fit(res) } @ The estimated target matrix can be retrieved via the generic method \code{fitted}, which returns a -- generally big -- \code{matrix}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ V.hat <- fitted(res) dim(V.hat) } @ Quality and performance measures about the factorization are computed by method \code{summary}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ summary(res) # More quality measures are computed, if the target matrix is provided: summary(res, target=esGolub) } @ If there is some prior knowledge of classes present in the data, some other measures about the unsupervised clustering's performance are computed (purity, entropy, \ldots). Here we use the phenotypic variable \code{Cell} found in the Golub dataset, that gives the samples' cell-types (it is a factor with levels: T-cell, B-cell or \code{NA}): <>= if(requireNamespace("Biobase", quietly=TRUE)){ summary(res, class=esGolub$Cell) } @ The basis matrix (i.e. matrix $W$ or the metagenes) and the mixture coefficient matrix (i.e matrix $H$ or the metagene expression profiles) are retrieved using methods \code{basis} and \code{coef} respectively: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # get matrix W w <- basis(res) dim(w) # get matrix H h <- coef(res) dim(h) } @ If one wants to keep only part of the factorization, one can directly subset on the \code{NMF} object on features and samples (separately or simultaneously). The result is a \code{NMF} object composed of the selected rows and/or columns: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # keep only the first 10 features res.subset <- res[1:10,] class(res.subset) dim(res.subset) # keep only the first 10 samples dim(res[,1:10]) # subset both features and samples: dim(res[1:20,1:10]) } @ \subsubsection{Extracting metagene-specific features} In general NMF matrix factors are sparse, so that the metagenes can usually be characterized by a relatively small set of genes. Those are determined based on their relative contribution to each metagene. Kim and Park \cite{KimH2007} defined a procedure to extract the relevant genes for each metagene, based on a gene scoring schema. The NMF package implements this procedure in methods \code{featureScore} and \code{extractFeature}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # only compute the scores s <- featureScore(res) summary(s) # compute the scores and characterize each metagene s <- extractFeatures(res) str(s) } @ \subsection{Specifying the algorithm}\label{sec:algo} \subsubsection{Built-in algorithms} The \nmfpack package provides a number of built-in algorithms, that are listed or retrieved by function \code{nmfAlgorithm}. Each algorithm is identified by a unique name. The following algorithms are currently implemented (cf. \cref{tab:algo} for more details): <>= nmfAlgorithm() @ %\begin{tech} %Internally, all algorithms are stored in objects that inherit from class %\code{NMFStrategy}. This class defines the minimum interface %\end{tech} The algorithm used to compute the NMF is specified in the third argument (\code{method}). For example, to use the NMF algorithm from Lee and Seung \cite{Lee2001} based on the Frobenius euclidean norm, one make the following call: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # using Lee and Seung's algorithm res <- nmf(esGolub, 3, 'lee') algorithm(res) } @ To use the Nonsmooth NMF algorithm from \cite{Pascual-Montano2006}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # using the Nonsmooth NMF algorithm with parameter theta=0.7 res <- nmf(esGolub, 3, 'ns', theta=0.7) algorithm(res) fit(res) } @ Or to use the PE-NMF algorithm from \cite{Zhang2008}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # using the PE-NMF algorithm with parameters alpha=0.01, beta=1 res <- nmf(esGolub, 3, 'pe', alpha=0.01, beta=1) res } @ %\begin{tech} %Although the last two calls looks similar these are handled % %In the case of the nsNMF algorithm, the fitted model is an object of class %\code{NMFns} that extends the standard NMF model \code{NMFstd}, as it introduces %a smoothing matrix $S$, parametrised by a real number $\theta \in [0,1]$, such %that the fitted model is: %$$ %V \approx W S(\theta) H. %$$ % %Hence the call to function \code{nmf}, parameter $\theta$ is used to % %\end{tech} \subsubsection{Custom algorithms} The \nmfpack package provides the user the possibility to define his own algorithms, and benefit from all the functionalities available in the NMF framework. There are only few contraints on the way the custom algorithm must be defined. See the details in \cref{sec:algo_custom}. \subsection{Specifying the seeding method}\label{sec:seed} The seeding method used to compute the starting point for the chosen algorithm can be set via argument \code{seed}. Note that if the seeding method is deterministic there is no need to perform multiple run anymore. \subsubsection{Built-in seeding methods} Similarly to the algorithms, the \code{nmfSeed} function can be used to list or retrieve the built-in seeding methods. The following seeding methods are currently implemented: <>= nmfSeed() @ To use a specific method to seed the computation of a factorization, one simply passes its name to \code{nmf}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ res <- nmf(esGolub, 3, seed='nndsvd') res } @ \subsubsection{Numerical seed}\label{sec:numseed} Another possibility, useful when comparing methods or reproducing results, is to set the random number generator (RNG) by passing a numerical value in argument \code{seed}. This value is used to set the state of the RNG, and the initialization is performed by the built-in seeding method \code{'random'}. When the function \code{nmf} exits, the value of the random seed (\code{.Random.seed}) is restored to its original state -- as before the call. In the case of a single run (i.e. with \code{nrun=1}), the default is to use the current RNG, set with the R core function \code{set.seed}. In the case of multiple runs, the computations use RNGstream, as provided by the core RNG ``L'Ecuyer-CMRG" \cite{Lecuyer2002}, which generates multiple independent random streams (one per run). This ensures the complete reproducibility of any given set of runs, even when their computation is performed in parallel. Since RNGstream requires a 6-length numeric seed, a random one is generated if only a single numeric value is passed to \code{seed}. Moreover, single runs can also use RNGstream by passing a 6-length seed. <>= if(requireNamespace("Biobase", quietly=TRUE)){ # single run and single numeric seed res <- nmf(esGolub, 3, seed=123456) showRNG(res) # multiple runs and single numeric seed res <- nmf(esGolub, 3, seed=123456, nrun=2) showRNG(res) # single run with a 6-length seed res <- nmf(esGolub, 3, seed=rep(123456, 6)) showRNG(res) } @ \nbnote{To show the RNG changes happening during the computation use \texttt{.options='v4'} to turn on verbosity at level 4.\\ In versions prior 0.6, one could specify option \texttt{restore.seed=FALSE} or \texttt{'-r'}, this option is now deprecated.} \subsubsection{Fixed factorization} Yet another option is to completely specify the initial factorization, by passing values for matrices $W$ and $H$: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # initialize a "constant" factorization based on the target dimension init <- nmfModel(3, esGolub, W=0.5, H=0.3) head(basis(init)) # fit using this NMF model as a seed res <- nmf(esGolub, 3, seed=init) } @ \subsubsection{Custom function} The \nmfpack package provides the user the possibility to define his own seeding method, and benefit from all the functionalities available in the NMF framework. There are only few contraints on the way the custom seeding method must be defined. See the details in \cref{sec:seed_custom}. \subsection{Multiple runs} When the seeding method is stochastic, multiple runs are usually required to achieve stability or a resonable result. This can be done by setting argument \code{nrun} to the desired value. For performance reason we use \code{nrun=5} here, but a typical choice would lies between 100 and 200: <>= if(requireNamespace("Biobase", quietly=TRUE)){ res.multirun <- nmf(esGolub, 3, nrun=5) res.multirun } @ By default, the returned object only contains the best fit over all the runs. That is the factorization that achieved the lowest approximation error (i.e. the lowest objective value). Even during the computation, only the current best factorization is kept in memory. This limits the memory requirement for performing multiple runs, which in turn allows to perform more runs. The object \code{res.multirun} is of class \code{NMFfitX1} that extends class \code{NMFfit}, the class returned by single NMF runs. It can therefore be handled as the result of a single run and benefit from all the methods defined for single run results. \medskip If one is interested in keeping the results from all the runs, one can set the option \code{keep.all=TRUE}: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # explicitly setting the option keep.all to TRUE res <- nmf(esGolub, 3, nrun=5, .options=list(keep.all=TRUE)) res } @ <>= if(requireNamespace("Biobase", quietly=TRUE)){ # or using letter code 'k' in argument .options nmf(esGolub, 3, nrun=5, .options='k') } @ In this case, the result is an object of class \code{NMFfitXn} that also inherits from class \code{list}. Note that keeping all the results may be memory consuming. For example, a 3-rank \code{NMF} fit\footnote{i.e. the result of a single NMF run with rank equal 3.} for the Golub gene expression matrix ($5000 \times 38$) takes about \Sexpr{round(object.size(fit(res.multirun))/1000)}Kb\footnote{This size might change depending on the architecture (32 or 64 bits)}. \subsection{Parallel computations}\label{multicore} To speed-up the analysis whenever possible, the \nmfpack package implements transparent parallel computations when run on multi-core machines. It uses the \code{foreach} framework developed by REvolution Computing \citeCRANpkg{foreach}, together with the related \code{doParallel} parallel backend from the \citeCRANpkg{doParallel} -- based on the \pkgname{parallel} package -- to make use of all the CPUs available on the system, with each core simultaneously performing part of the runs. \subsubsection{Memory considerations} Running multicore computations increases the required memory linearly with the number of cores used. When only the best run is of interest, memory usage is optimized to only keep the current best factorization. On non-Windows machine, further speed improvement are achieved by using shared memory and mutex objects from the \citeCRANpkg{bigmemory} and the \citeCRANpkg{synchronicity}. \subsubsection{Parallel foreach backends} The default parallel backend used by the \code{nmf} function is defined by the package specific option \code{'pbackend'}, which defaults to \code{'par'} -- for \code{doParallel}. The backend can also be set on runtime via argument \code{.pbackend}. \medskip \paragraph{IMPORTANT NOTE:} The parallel computation is based on the \pkgname{doParallel} and \pkgname{parallel} packages, and the same care should be taken as stated in the vignette of the \citeCRANpkg{doMC}: \begin{quote} \emph{... it usually isn't safe to run doMC and multicore from a GUI environment. In particular, it is not safe to use doMC from R.app on Mac OS X. Instead, you should use doMC from a terminal session, starting R from the command line.} \end{quote} Therefore, the \code{nmf} function does not allow to run multicore computation from the MacOS X GUI. From version 0.8, other parallel backends are supported, and may be specified via argument \code{.pbackend}: \begin{description} \item[\code{.pbackend='mpi'}] uses the parallel backend \citeCRANpkg{doParallel} and \citeCRANpkg{doMPI} \item[\code{.pbackend=NULL}]{} \end{description} It is possible to specify that the currently registered backend should be used, by setting argument \code{.pbackend=NULL}. This allow to perform parallel computations with ``permanent'' backends that are configured externally of the \code{nmf} call. \subsubsection{Runtime options} There are two other runtime options, \code{parallel} and \code{parallel.required}, that can be passed via argument \code{.options}, to control the behaviour of the parallel computation (see below). \medskip A call for multiple runs will be computed in parallel if one of the following condition is satisfied: \begin{itemize} \item call with option \code{'P'} or \code{parallel.required} set to TRUE (note the upper case in \code{'P'}). In this case, if for any reason the computation cannot be run in parallel (packages requirements, OS, ...), then an error is thrown. Use this mode to force the parallel execution. \item call with option \code{'p'} or \code{parallel} set to TRUE. In this case if something prevents a parallel computation, the factorizations will be done sequentially. \item a valid parallel backend is specified in argument \code{.pbackend}. For the moment it can either be the string \code{'mc'} or a single \code{numeric} value specifying the number of core to use. Unless option \code{'P'} is specified, it will run using option \code{'p'} (i.e. try-parallel mode). \end{itemize} \nbnote{The number of processors to use can also be specified in the runtime options as e.g. \texttt{.options='p4'} or \texttt{.options='P4'} -- to ask or request 4 CPUs.} \paragraph{Examples}\ \\ The following exmaples are run with \code{.options='v'} which turn on verbosity at level 1, that will show which parallell setting is used by each computation. Although we do not show the output here, the user is recommended to run these commands on his machine to see the internal differences of each call. <>= if(requireNamespace("Biobase", quietly=TRUE)){ # the default call will try to run in parallel using all the cores # => will be in parallel if all the requirements are satisfied nmf(esGolub, 3, nrun=5, .opt='v') # request a certain number of cores to use => no error if not possible nmf(esGolub, 3, nrun=5, .opt='vp8') # force parallel computation: use option 'P' nmf(esGolub, 3, nrun=5, .opt='vP') # require an improbable number of cores => error nmf(esGolub, 3, nrun=5, .opt='vP200') } @ \subsubsection{High Performance Computing on a cluster} To achieve further speed-up, the computation can be run on an HPC cluster. In our tests we used the \citeCRANpkg{doMPI} to perform 100 factorizations using hybrid parallel computation on 4 quadri-core machines -- making use of all the cores computation on each machine. <>= # file: mpi.R if(requireNamespace("Biobase", quietly=TRUE)){ ## 0. Create and register an MPI cluster library(doMPI) cl <- startMPIcluster() registerDoMPI(cl) library(NMF) # run on all workers using the current parallel backend data(esGolub) res <- nmf(esGolub, 3, 'brunet', nrun=n, .opt='p', .pbackend=NULL) # save result save(res, file='result.RData') ## 4. Shutdown the cluster and quit MPI closeCluster(cl) mpi.quit() } @ Passing the following shell script to \emph{qsub} should launch the execution on a Sun Grid Engine HPC cluster, with OpenMPI. Some adaptation might be necessary for other queueing systems/installations. \begin{shaded} \small \begin{verbatim} #!/bin/bash #$ -cwd #$ -q opteron.q #$ -pe mpich_4cpu 16 echo "Got $NSLOTS slots. $TMP/machines" orterun -v -n $NSLOTS -hostfile $TMP/machines R --slave -f mpi.R \end{verbatim} \end{shaded} \subsubsection{Forcing sequential execution} When running on a single core machine, \nmfpack package has no other option than performing the multiple runs sequentially, one after another. This is done via the \code{sapply} function. On multi-core machine, one usually wants to perform the runs in parallel, as it speeds up the computation (cf. \cref{multicore}). However in some situation (e.g. while debugging), it might be useful to force the sequential execution of the runs. This can be done via the option \code{'p1'} to run on a single core , or with \code{.pbackend='seq'} to use the foreach backend \code{doSEQ} or to \code{NA} to use a standard \code{sapply} call: <>= if(requireNamespace("Biobase", quietly=TRUE)){ # parallel execution on 2 cores (if possible) res1 <- nmf(esGolub, 3, nrun=5, .opt='vp2', seed=123) # or use the doParallel with single core res2 <- nmf(esGolub, 3, nrun=5, .opt='vp1', seed=123) # force sequential computation by sapply: use option '-p' or .pbackend=NA res3 <- nmf(esGolub, 3, nrun=5, .opt='v-p', seed=123) res4 <- nmf(esGolub, 3, nrun=5, .opt='v', .pbackend=NA, seed=123) # or use the SEQ backend of foreach: .pbackend='seq' res5 <- nmf(esGolub, 3, nrun=5, .opt='v', .pbackend='seq', seed=123) # all results are all identical nmf.equal(list(res1, res2, res3, res4, res5)) } @ \subsection{Estimating the factorization rank} A critical parameter in NMF is the factorization rank $r$. It defines the number of metagenes used to approximate the target matrix. Given a NMF method and the target matrix, a common way of deciding on $r$ is to try different values, compute some quality measure of the results, and choose the best value according to this quality criteria. Several approaches have then been proposed to choose the optimal value of $r$. For example, \cite{Brunet2004} proposed to take the first value of $r$ for which the cophenetic coefficient starts decreasing, \cite{Hutchins2008} suggested to choose the first value where the RSS curve presents an inflection point, and \cite{Frigyesi2008} considered the smallest value at which the decrease in the RSS is lower than the decrease of the RSS obtained from random data. The \nmfpack package provides functions to help implement such procedures and plot the relevant quality measures. Note that this can be a lengthy computation, depending on the data size. Whereas the standard NMF procedure usually involves several hundreds of random initialization, performing 30-50 runs is considered sufficient to get a robust estimate of the factorization rank \cite{Brunet2004, Hutchins2008}. For performance reason, we perform here only 10 runs for each value of the rank. <>= if(requireNamespace("Biobase", quietly=TRUE)){ # perform 10 runs for each value of r in range 2:6 estim.r <- nmf(esGolub, 2:6, nrun=10, seed=123456) } @ The result is a S3 object of class \code{NMF.rank}, that contains a \code{data.frame} with the quality measures in column, and the values of $r$ in row. It also contains a list of the consensus matrix for each value of $r$. All the measures can be plotted at once with the method \code{plot} (\cref{fig:estim_all}), and the function \code{consensusmap} generates heatmaps of the consensus matrix for each value of the rank. In the context of class discovery, it is useful to see if the clusters obtained correspond to known classes. This is why in the particular case of the Golub dataset, we added annotation tracks for the two covariates available ('Cell' and 'ALL.AML'). Since we removed the variable 'Sample' in the preliminaries, these are the only variables in the phenotypic \code{data.frame} embedded within the \code{ExpressionSet} object, and we can simply pass the whole object to argument \code{annCol} (\cref{fig:estim_all_hm}). One can see that at rank 2, the clusters correspond to the ALL and AML samples respectively, while rank 3 separates AML from ALL/T-cell and ALL/B-cell\footnote{Remember that the plots shown in \cref{fig:estim_all_hm} come from only 10 runs, using the 200 first genes in the dataset, which explains the somewhat not so clean clusters. The results are in fact much cleaner when using the full dataset (\cref{fig:heatmap_consensus}).}. \begin{figure} <>= if(requireNamespace("Biobase", quietly=TRUE)){ plot(estim.r) } @ \caption{Estimation of the rank: Quality measures computed from 10 runs for each value of $r$. \label{fig:estim_all}} \end{figure} \begin{figure} <>= if(requireNamespace("Biobase", quietly=TRUE)){ consensusmap(estim.r, annCol=esGolub, labCol=NA, labRow=NA) } @ \caption{Estimation of the rank: Consensus matrices computed from 10 runs for each value of $r$. \label{fig:estim_all_hm}} \end{figure} \subsubsection{Overfitting} Even on random data, increasing the factorization rank would lead to decreasing residuals, as more variables are available to better fit the data. In other words, there is potentially an overfitting problem. In this context, the approach from \cite{Frigyesi2008} may be useful to prevent or detect overfitting as it takes into account the results for unstructured data. However it requires to compute the quality measure(s) for the random data. The \nmfpack package provides a function that shuffles the original data, by permuting the rows of each column, using each time a different permutation. The rank estimation procedure can then be applied to the randomized data, and the ``random'' measures added to the plot for comparison (\cref{fig:estim_all_rd}). \begin{figure} <>= if(requireNamespace("Biobase", quietly=TRUE)){ # shuffle original data V.random <- randomize(esGolub) # estimate quality measures from the shuffled data (use default NMF algorithm) estim.r.random <- nmf(V.random, 2:6, nrun=10, seed=123456) # plot measures on same graph plot(estim.r, estim.r.random) } @ \caption{Estimation of the rank: Comparison of the quality measures with those obtained from randomized data. The curves for the actual data are in blue and green, those for the randomized data are in red and pink. The estimation is based on Brunet's algorithm.} \label{fig:estim_all_rd} \end{figure} \subsection{Comparing algorithms} To compare the results from different algorithms, one can pass a list of methods in argument \code{method}. To enable a fair comparison, a deterministic seeding method should also be used. Here we fix the random seed to 123456. <>= if(requireNamespace("Biobase", quietly=TRUE)){ # fit a model for several different methods res.multi.method <- nmf(esGolub, 3, list('brunet', 'lee', 'ns'), seed=123456, .options='t') } @ Passing the result to method \code{compare} produces a \code{data.frame} that contains summary measures for each method. Again, prior knowledge of classes may be used to compute clustering quality measures: <>= if(requireNamespace("Biobase", quietly=TRUE)){ compare(res.multi.method) # If prior knowledge of classes is available compare(res.multi.method, class=esGolub$Cell) } @ Because the computation was performed with error tracking enabled, an error plot can be produced by method \code{plot} (\cref{fig:errorplot}). Each track is normalized so that its first value equals one, and stops at the iteration where the method's convergence criterion was fulfilled. \subsection{Visualization methods} \subsubsection*{Error track} If the NMF computation is performed with error tracking enabled -- using argument \code{.options} -- the trajectory of the objective value is computed during the fit. This computation is not enabled by default as it induces some overhead. <>= if(requireNamespace("Biobase", quietly=TRUE)){ # run nmf with .option='t' res <- nmf(esGolub, 3, .options='t') # or with .options=list(track=TRUE) } @ The trajectory can be plot with the method \code{plot} (\cref{fig:errorplot}): \begin{figure}[!htbp] <>= if(requireNamespace("Biobase", quietly=TRUE)){ plot(res) plot(res.multi.method) } @ \caption{Error track for a single NMF run (left) and multiple method runs (right)} \label{fig:errorplot} \end{figure} \subsubsection*{Heatmaps} The methods \code{basismap}, \code{coefmap} and \code{consensusmap} provide an easy way to visualize respectively the resulting basis matrix (i.e. metagenes), mixture coefficient matrix (i.e. metaprofiles) and the consensus matrix, in the case of multiple runs. It produces pre-configured heatmaps based on the function \code{aheatmap}, the underlying heatmap engine provided with the package NMF. The default heatmaps produced by these functions are shown in \cref{fig:heatmap_coef_basis,fig:heatmap_consensus}. They can be customized in many different ways (colours, annotations, labels). See the dedicated vignette \emph{``NMF: generating heatmaps"} or the help pages \code{?coefmap} and \code{?aheatmap} for more information. An important and unique feature of the function \code{aheatmap}, is that it makes it possible to combine several heatmaps on the same plot, using the both standard layout calls \texttt{par(mfrow=...)} and \texttt{layout(...)}, or grid viewports from \texttt{grid} graphics. The plotting context is automatically internally detected, and a correct behaviour is achieved thanks to the \citeCRANpkg{gridBase}. Examples are provided in the dedicated vignette mentioned above. The rows of the basis matrix often carry the high dimensionality of the data: genes, loci, pixels, features, etc\ldots The function \code{basismap} extends the use of argument \code{subsetRow} (from \code{aheatmap}) to the specification of a feature selection method. In \cref{fig:heatmap_coef_basis} we simply used \code{subsetRow=TRUE}, which subsets the rows using the method described in \cite{KimH2007}, to only keep the basis-specific features (e.g. the metagene-specific genes). We refer to the relevant help pages \code{?basismap} and \code{?aheatmap} for more details about other possible values for this argument. \begin{figure}[!htbp] \centering <>= if(requireNamespace("Biobase", quietly=TRUE)){ layout(cbind(1,2)) # basis components basismap(res, subsetRow=TRUE) # mixture coefficients coefmap(res) } @ \caption{Heatmap of the basis and the mixture coefficient matrices. The rows of the basis matrix were selected using the default feature selection method -- described in \cite{KimH2007}.} \label{fig:heatmap_coef_basis} \end{figure} In the case of multiple runs the function \code{consensusmap} plots the consensus matrix, i.e. the average connectivity matrix across the runs (see results in \cref{fig:heatmap_consensus} for a consensus matrix obtained with 100 runs of Brunet's algorithm on the complete Golub dataset): \begin{figure}[ht] <>= if(requireNamespace("Biobase", quietly=TRUE)){ # The cell type is used to label rows and columns consensusmap(res.multirun, annCol=esGolub, tracks=NA) plot(1:10) f2 <- fig_path("2.pdf") } @ <>= if(requireNamespace("Biobase", quietly=TRUE)){ file.copy('consensus.pdf', f2, overwrite=TRUE) } @ \caption{Heatmap of consensus matrices from 10 runs on the reduced dataset (left) and from 100 runs on the complete Golub dataset (right).} \label{fig:heatmap_consensus} \end{figure} \section{Extending the package} We developed the \nmfpack\ package with the objective to facilitate the integration of new NMF methods, trying to impose only few requirements on their implementations. All the built-in algorithms and seeding methods are implemented as strategies that are called from within the main interface method \code{nmf}. The user can define new strategies and those are handled in exactly the same way as the built-in ones, benefiting from the same utility functions to interpret the results and assess their performance. \subsection{Custom algorithm} %New NMF algrithms can be defined in two ways: % %\begin{itemize} %\item as a single \code{function} %\item as a set of functions that implement a pre-defined \emph{iterative schema} %\end{itemize} % %\subsubsection{Defined as a \code{function}} \subsubsection{Using a custom algorithm}\label{sec:algo_custom} To define a strategy, the user needs to provide a \code{function} that implements the complete algotihm. It must be of the form: <>= my.algorithm <- function(x, seed, param.1, param.2){ # do something with starting point # ... # return updated starting point return(seed) } @ Where: \begin{description} \item[target] is a \code{matrix}; \item[start] is an object that inherits from class \code{NMF}. This \code{S4} class is used to handle NMF models (matrices \code{W} and \code{H}, objective function, etc\dots); \item[param.1, param.2] are extra parameters specific to the algorithms; \end{description} The function must return an object that inherits from class \code{NMF}. For example: <>= my.algorithm <- function(x, seed, scale.factor=1){ # do something with starting point # ... # for example: # 1. compute principal components pca <- prcomp(t(x), retx=TRUE) # 2. use the absolute values of the first PCs for the metagenes # Note: the factorization rank is stored in object 'start' factorization.rank <- nbasis(seed) basis(seed) <- abs(pca$rotation[,1:factorization.rank]) # use the rotated matrix to get the mixture coefficient # use a scaling factor (just to illustrate the use of extra parameters) coef(seed) <- t(abs(pca$x[,1:factorization.rank])) / scale.factor # return updated data return(seed) } @ To use the new method within the package framework, one pass \code{my.algorithm} to main interface \code{nmf} via argument \code{method}. Here we apply the algorithm to some matrix \code{V} randomly generated: <>= n <- 50; r <- 3; p <- 20 V <-syntheticNMF(n, r, p) @ <>= nmf(V, 3, my.algorithm, scale.factor=10) @ \subsubsection{Using a custom distance measure} The default distance measure is based on the euclidean distance. If the algorithm is based on another distance measure, this one can be specified in argument \code{objective}, either as a \code{character} string corresponding to a built-in objective function, or a custom \code{function} definition\footnote{Note that from version 0.8, the arguments for custom objective functions have been swapped: (1) the current NMF model, (2) the target matrix}: <>= # based on Kullback-Leibler divergence nmf(V, 3, my.algorithm, scale.factor=10, objective='KL') # based on custom distance metric nmf(V, 3, my.algorithm, scale.factor=10 , objective=function(model, target, ...){ ( sum( (target-fitted(model))^4 ) )^{1/4} } ) @ %\subsubsection{Using the iterative schema} % %NMF algorithms generally implement the following common iterative schema: % %\begin{enumerate} %\item %\item %\end{enumerate} \subsubsection{Defining algorithms for mixed sign data} All the algorithms implemented in the \nmfpack package assume that the input data is nonnegative. However, some methods exist in the litterature that work with relaxed constraints, where the input data and one of the matrix factors ($W$ or $H$) are allowed to have negative entries (eg. semi-NMF \cite{Ding2010, Roux2008}). Strictly speaking these methods do not fall into the NMF category, but still solve constrained matrix factorization problems, and could be considered as NMF methods when applied to non-negative data. Moreover, we received user requests to enable the development of semi-NMF type methods within the package's framework. Therefore, we designed the \nmfpack package so that such algorithms -- that handle negative data -- can be integrated. This section documents how to do it. By default, as a safe-guard, the sign of the input data is checked before running any method, so that the \code{nmf} function throws an error if applied to data that contain negative entries \footnote{Note that on the other side, the sign of the factors returned by the algorithms is never checked, so that one can always return factors with negative entries.}. To extend the capabilities of the \nmfpack package in handling negative data, and plug mixed sign NMF methods into the framework, the user needs to specify the argument \code{mixed=TRUE} in the call to the \code{nmf} function. This will skip the sign check of the input data and let the custom algorithm perform the factorization. As an example, we reuse the previously defined custom algorithm\footnote{As it is defined here, the custom algorithm still returns nonnegative factors, which would not be desirable in a real example, as one would not be able to closely fit the negative entries.}: <>= # put some negative input data V.neg <- V; V.neg[1,] <- -1; # this generates an error try( nmf(V.neg, 3, my.algorithm, scale.factor=10) ) # this runs my.algorithm without error nmf(V.neg, 3, my.algorithm, mixed=TRUE, scale.factor=10) @ \subsubsection{Specifying the NMF model} If not specified in the call, the NMF model that is used is the standard one, as defined in \cref{NMFstd}. However, some NMF algorithms have different underlying models, such as non-smooth NMF \cite{Pascual-Montano2006} which uses an extra matrix factor that introduces an extra parameter, and change the way the target matrix is approximated. The NMF models are defined as S4 classes that extends class \code{NMF}. All the available models can be retreived calling the \code{nmfModel()} function with no argument: <>= nmfModel() @ One can specify the NMF model to use with a custom algorithm, using argument \code{model}. Here we first adapt a bit the custom algorithm, to justify and illustrate the use of a different model. We use model \code{NMFOffset} \cite{Badea2008}, that includes an offset to take into account genes that have constant expression levels accross the samples: <>= my.algorithm.offset <- function(x, seed, scale.factor=1){ # do something with starting point # ... # for example: # 1. compute principal components pca <- prcomp(t(x), retx=TRUE) # retrieve the model being estimated data.model <- fit(seed) # 2. use the absolute values of the first PCs for the metagenes # Note: the factorization rank is stored in object 'start' factorization.rank <- nbasis(data.model) basis(data.model) <- abs(pca$rotation[,1:factorization.rank]) # use the rotated matrix to get the mixture coefficient # use a scaling factor (just to illustrate the use of extra parameters) coef(data.model) <- t(abs(pca$x[,1:factorization.rank])) / scale.factor # 3. Compute the offset as the mean expression data.model@offset <- rowMeans(x) # return updated data fit(seed) <- data.model seed } @ Then run the algorithm specifying it needs model \code{NMFOffset}: <>= # run custom algorithm with NMF model with offset nmf(V, 3, my.algorithm.offset, model='NMFOffset', scale.factor=10) @ \subsection{Custom seeding method}\label{sec:seed_custom} The user can also define custom seeding method as a function of the form: <>= # start: object of class NMF # target: the target matrix my.seeding.method <- function(model, target){ # use only the largest columns for W w.cols <- apply(target, 2, function(x) sqrt(sum(x^2))) basis(model) <- target[,order(w.cols)[1:nbasis(model)]] # initialize H randomly coef(model) <- matrix(runif(nbasis(model)*ncol(target)) , nbasis(model), ncol(target)) # return updated object return(model) } @ To use the new seeding method: <>= nmf(V, 3, 'snmf/r', seed=my.seeding.method) @ \section{Advanced usage} \subsection{Package specific options} The package specific options can be retieved or changed using the \code{nmf.getOption} and \code{nmf.options} functions. These behave similarly as the \code{getOption} and \code{nmf.options} base functions: <>= #show default algorithm and seeding method nmf.options('default.algorithm', 'default.seed') # retrieve a single option nmf.getOption('default.seed') # All options nmf.options() @ Currently the following options are available: <>= RdSection2latex('nmf.options', package='NMF') @ The default/current values of each options can be displayed using the function \code{nmf.printOptions}: <>= nmf.printOptions() @ %% latex table generated in R 2.10.1 by xtable 1.5-6 package %% Wed Apr 7 15:27:05 2010 %\begin{table}[ht] %\begin{center} %\begin{tabularx}{\textwidth}{>{\ttfamily}rlX} % \hline %Option & Default value & Description\\ %\hline %default.algorithm & brunet & Default NMF algorithm used by the \code{nmf} function when argument \code{method} is missing. %The value should the key of one of the available NMF algorithms. %See \code{?nmfAlgorithm}.\\ %track.interval & 30 & Number of iterations between two points in the residual track. %This option is relevant only when residual tracking is enabled. %See \code{?nmf}.\\ %error.track & FALSE & Toggle default residual tracking. %When \code{TRUE}, the \code{nmf} function compute and store the residual track in the result -- if not otherwise specified in argument \code{.options}. %Note that tracking may significantly slow down the computations.\\ %default.seed & random & Default seeding method used by the \code{nmf} function when argument \code{seed} is missing. %The value should the key of one of the available seeding methods. %See \code{?nmfSeed}.\\ %backend & mc & Default parallel backend used used by the \code{nmf} function when argument \code{.pbackend} is missing. %Currently the following values are supported: \code{'mc'} for multicore, \code{'seq'} for sequential, \code{''} for \code{sapply}.\\ %verbose & FALSE & Toggle verbosity.\\ %debug & FALSE & Toggle debug mode, which is an extended verbose mode.\\ %\hline %\end{tabularx} %\end{center} %\caption{} %\end{table} \pagebreak \section{Session Info} <>= toLatex(sessionInfo()) @ \printbibliography[heading=bibintoc] \end{document} NMF/inst/CITATION0000644000176200001440000000324013620502674013010 0ustar liggesusers## R >= 2.8.0 passes package metadata to citation(). if( !exists('meta') || is.null(meta) ) meta <- packageDescription("NMF") year <- sub(".*(2[[:digit:]]{3})-.*", "\\1", meta$Date, perl = TRUE) vers <- paste("R package version", meta$Version) author <- as.personList(meta$Author) url <- sprintf("https://cran.r-project.org/package=%s", meta$Package) citHeader(sprintf("To cite the package '%s' in publications use:", meta$Package)) citEntry(entry="Article" , title = "A flexible R package for nonnegative matrix factorization" , author = personList(as.person("Renaud Gaujoux"), as.person("Cathal Seoighe")) , journal = "BMC Bioinformatics" , year = 2010 , volume = 11 , number = 1 , pages = 367 , url = "http://www.biomedcentral.com/1471-2105/11/367" , doi = "10.1186/1471-2105-11-367" , issn = "1471-2105" , textVersion = paste("Renaud Gaujoux, Cathal Seoighe (2010)" , "A flexible R package for nonnegative matrix factorization" , "BMC Bioinformatics 2010, 11:367" , "[http://www.biomedcentral.com/1471-2105/11/367]" , sep=". ") ) citEntry(entry="Manual" , title = vign <- "Using the package NMF" , author = author , publisher = "CRAN" , year = year , note = vers , url = url , textVersion = sprintf("%s (%s). %s. CRAN. %s. [%s]", author, year, vign, vers, url) , header = "Vignette(s):" ) citEntry(entry="Manual" , title = "The package NMF: manual pages" , author = author , publisher = "CRAN" , year = year , note = vers , url = url , textVersion = sprintf("%s (%s). %s CRAN. %s. [%s]", author, year, meta$Title, vers, url) , header = "Technical documentation:" ) NMF/inst/scripts/0000755000176200001440000000000013620502674013343 5ustar liggesusersNMF/inst/scripts/report.Rmd0000644000176200001440000000334613620502674015330 0ustar liggesusers ```{r setup, echo = FALSE} library(knitr) library(NMF) options(width = 300) opts_knit$set(root.dir = opts_knit$get('output.dir')) ``` # Input ## Method definition ```{r load_custom} if( file.exists(f <- file.path(opts_knit$get('output.dir'), 'functions.R')) ){ cat("Sourcing custom definition in '", f,"' ... ", sep ='') source(f) cat('OK\n') } ``` ## Data ```{r data} # Target matrix class(x) dim(x) head(x[, 1:5]) ``` ## Parameters ```{r args} # Factorisation ranks rank # Methods unlist(method) # Reference class summary(colClass) ``` # Run ```{r run} # run NMF for all ranks res <- nmfRun(x, rank, method) ``` # Results ## Plots ```{r resplots, echo = FALSE, fig.width = 10, fig.height = 7} dummy <- lapply(names(res), function(x){ cat("##", x, "\n") fit <- res[[x]] # consensus map consensusmap(fit, main = x, annCol = colClass) # measures if( length(rank) > 1){ p <- plot(fit) print(p) } }) ``` ## Accuracy ```{r summary, echo = FALSE, fig.width = 15, fig.height = 8} # compute summary measures for all survey fits s <- lapply(names(res), function(x){ NMF::summary(res[[x]], class = colClass) }) # complete missing measures snames <- unique(unlist(lapply(s, names))) s <- lapply(s, function(x){ if( any(i <- !snames %in% names(x)) ){ nas <- rep(NA, nrow(x)) x <- cbind(x, sapply(snames[i], function(x) nas)) } x[, snames] }) print(s_all <- do.call(rbind, s)) library(reshape2) accuracy <- melt(s_all, id.vars = c('method', 'seed', 'rank', 'metric')) accuracy <- accuracy[!accuracy$variable %in% c('rng', 'nrun'),] ggplot(accuracy) + geom_bar(aes(x = rank, y = value, fill = method), position='dodge', stat='identity') + facet_wrap(~variable, scales = 'free') + scale_x_discrete(breaks = unique(accuracy$rank)) ``` NMF/inst/scripts/grid.R0000644000176200001440000000375613620502674014426 0ustar liggesusers# NMF package # # Helper code to allow mixing grid/base graphics. # This code is only loaded with the explicit request of the user, # either via option 'grid.patch' or environment variable R_PACKAGE_NMF_GRID_PATCH. # # The functions in this file were adapted from the grid package, which is # under the following GPL license: # # R : A Computer Language for Statistical Data Analysis # Copyright (C) 2001-3 Paul Murrell # 2003 The R Core Team # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, a copy is available at # http://www.r-project.org/Licenses/ # # Author: Renaud Gaujoux # Created: Sep 16, 2013 ############################################################################### # This is essentially where the patch lies: not calling L_gridDirty grid.Call <- function (fnname, ...) { #.Call(L_gridDirty) .Call(fnname, ..., PACKAGE = "grid") } # One has to test for nullity since not using L_gridDirty means potentially # returning a NULL viewport current.viewport <- function() { cv <- grid.Call(grid:::L_currentViewport) if( !is.null(cv) ) grid:::vpFromPushedvp(cv) } # same thing here: call patched current.viewport and # check for nullity current.vpPath <- function(){ names <- NULL pvp <- current.viewport() if( is.null(pvp) ) return(NULL) while ( !is.null(pvp) && !grid:::rootVP(pvp)) { names <- c(names, pvp$name) pvp <- pvp$parent } if (!is.null(names)) grid:::vpPathFromVector(rev(names)) else names }