#  CONTRASTS

contrasts.fit <- function(fit,contrasts=NULL,coefficients=NULL)
#	Convert coefficients and std deviations in fit object to reflect contrasts of interest
#	Note: does not completely take probe-wise weights into account
#	because this would require refitting the linear model for each probe
#	Gordon Smyth
#	Created 13 Oct 2002.  Last modified 15 Jul 2024.
{
#	Check number of arguments
	if(identical(is.null(contrasts),is.null(coefficients))) stop("Must specify exactly one of contrasts or coefficients")

#	If coefficients are input, just subset
	if(!is.null(coefficients)) return(fit[,coefficients])

#	Check for valid fit object
	if(is.null(fit$coefficients)) stop("fit must contain coefficients component")
	if(is.null(fit$stdev.unscaled)) stop("fit must contain stdev.unscaled component")

#	Remove test statistics in case eBayes() has previously been run on the fit object
	fit$t <- NULL
	fit$p.value <- NULL
	fit$lods <- NULL
	fit$F <- NULL
	fit$F.p.value <- NULL

#	Number of coefficients in fit
	ncoef <- ncol(fit$coefficients)

#	Check contrasts.
	if(!is.numeric(contrasts)) stop("contrasts must be a numeric matrix")
	if(anyNA(contrasts)) stop("NAs not allowed in contrasts")
	contrasts <- as.matrix(contrasts)
	if(!identical(nrow(contrasts),ncoef)) stop("Number of rows of contrast matrix must match number of coefficients in fit")
	rn <- rownames(contrasts)
	cn <- colnames(fit$coefficients)
#	If necessary, convert intercept label from model.matrix() to valid variable name
	if(identical(rn[1],"(Intercept)")) rn[1] <- "Intercept"
	if(identical(cn[1],"(Intercept)")) cn[1] <- "Intercept"
	if(!is.null(rn) && !is.null(cn) && !identical(rn,cn)) warning("row names of contrasts don't match col names of coefficients")
	fit$contrasts <- contrasts

#	Special case of contrast matrix with 0 columns
	if(!ncol(contrasts)) return(fit[,0])

#	Correlation matrix of estimable coefficients
#	Test whether design was orthogonal
	if(is.null(fit$cov.coefficients)) {
		warning("cov.coefficients not found in fit - assuming coefficients are orthogonal",call.=FALSE)
		var.coef <- colMeans(fit$stdev.unscaled^2)
		fit$cov.coefficients <- diag(var.coef,nrow=ncoef)
		cormatrix <- diag(nrow=ncoef)
		orthog <- TRUE
	} else {
		cormatrix <- cov2cor(fit$cov.coefficients)
		if(length(cormatrix) < 2) {
			orthog <- TRUE
		} else {
			orthog <- sum(abs(cormatrix[lower.tri(cormatrix)])) < 1e-12
		}
	}

#	If design matrix was singular, reduce to estimable coefficients
	r <- nrow(cormatrix)
	if(r < ncoef) {
		if(is.null(fit$pivot)) stop("cor.coef not full rank but pivot column not found in fit")
		est <- fit$pivot[1:r]
		if(any(contrasts[-est,]!=0)) stop("trying to take contrast of non-estimable coefficient")
		contrasts <- contrasts[est,,drop=FALSE]
		fit$coefficients <- fit$coefficients[,est,drop=FALSE]
		fit$stdev.unscaled <- fit$stdev.unscaled[,est,drop=FALSE]
		ncoef <- r
	}

#	Remove coefficients that don't appear in any contrast
#	(Not necessary but can make function faster)
	ContrastsAllZero <- which(rowSums(abs(contrasts))==0)
	if(length(ContrastsAllZero)) {
		contrasts <- contrasts[-ContrastsAllZero,,drop=FALSE]
		fit$coefficients <- fit$coefficients[,-ContrastsAllZero,drop=FALSE]
		fit$stdev.unscaled <- fit$stdev.unscaled[,-ContrastsAllZero,drop=FALSE]
		fit$cov.coefficients <- fit$cov.coefficients[-ContrastsAllZero,-ContrastsAllZero,drop=FALSE]
		cormatrix <- cormatrix[-ContrastsAllZero,-ContrastsAllZero,drop=FALSE]
		ncoef <- ncol(fit$coefficients)
	}

#	Replace NA coefficients with large (but finite) standard deviations
#	to allow zero contrast entries to clobber NA coefficients.
	NACoef <- anyNA(fit$coefficients)
	if(NACoef) {
		i <- is.na(fit$coefficients)
		fit$coefficients[i] <- 0
		fit$stdev.unscaled[i] <- 1e30
	}

#	New coefficients
	fit$coefficients <- fit$coefficients %*% contrasts

#	Test whether design was orthogonal
	if(length(cormatrix) < 2) {
		orthog <- TRUE
	} else {
		orthog <- all(abs(cormatrix[lower.tri(cormatrix)]) < 1e-14)
	}

#	New correlation matrix
	R <- chol(fit$cov.coefficients)
	fit$cov.coefficients <- crossprod(R %*% contrasts)
#	fit$pivot <- NULL

#	New standard deviations
	if(orthog)
		fit$stdev.unscaled <- sqrt(fit$stdev.unscaled^2 %*% contrasts^2)
	else {
		R <- chol(cormatrix)
		ngenes <- NROW(fit$stdev.unscaled)
		ncont <- NCOL(contrasts)
		U <- matrix(1,ngenes,ncont,dimnames=list(rownames(fit$stdev.unscaled),colnames(contrasts)))
		o <- array(1,c(1,ncoef))
		for (i in 1:ngenes) {
			RUC <- R %*% .vecmat(fit$stdev.unscaled[i,],contrasts)
			U[i,] <- sqrt(o %*% RUC^2)
		}
		fit$stdev.unscaled <- U
	}

#	Replace NAs if necessary
	if(NACoef) {
		i <- (fit$stdev.unscaled > 1e20)
		fit$coefficients[i] <- NA
		fit$stdev.unscaled[i] <- NA
	}

	fit
}
