#================================================================================
# FONCTIONS DE CALCUL D'UNE FRACTION REGULIERE DE RESOLUTION DONNEE
# Auteur: H. Monod, INRA Jouy en Josas
# Copyright INRA 2009
# Ce programme est une version preliminaire et simplifiee d'une librairie R
# en preparation par A. Kobilinsky, H. Monod, A. Bouvier 
#================================================================================
regular.fraction <- function(s,p,r,resolution){
  # DESCRIPTION
  #  generates a regular fractional factorial design of given resolution,
  #  for s factors at p levels in p^r units
  # ARGUMENTS
  #  s : number of input factors
  #  p : unique prime number of levels of all input and unit factors
  #  r : number of unit factors (so that there are N=p^r units)
  #  resolution : resolution of the fraction
  #  max.sol : maximum number of solutions
  # DETAILS
  #  This is a simplified version of a more general library in preparation.
  #  In this version, all factors must have the same prime number of levels
  #  and only fractions with a given resolution can be constructed. The first
  #  q factors are used as basic factors. The first solution is kept although
  #  it may not be the most interesting one (no control of aberration). This
  #  function is programmed entirely in R and so it is not efficient with respect
  #  to computer time. There is no explicit check on the arguments and so it
  #  is up to the user to restrict p to a prime number such as 2, 3, 5 or 7.
  # OUTPUT:
  #  a list with two components: plan (the design in base p) and matrice.cle
  #  (the design key). The design has N=p^r rows (units) and s columns (factors).
  #  All its elements are integers modulo p that represent the factor levels.

  # ensemble ineligible
  cat("Determination des termes ineligibles: ")
  ineligible <- diag(s)
  for(reso in 2:(resolution-1)){
    combis <- combn(s,reso)
    ncombi <- ncol(combis)
    select <- cbind( c(combis), rep(seq(ncombi),rep(reso,ncombi)) )
    ineli <- matrix(0,s,ncombi)
    ineli[select] <- 1
    ineligible <- cbind(ineligible,ineli)
  }
  cat(ncol(ineligible)," termes ineligibles.\n")
  if( (p!=2) ){
    ineligible <- representative.basep(ineligible,p)
  }
  # Identification of the last non-zero coefficients in each ineligible trt character
  ineligible.lnz <- apply(ineligible, 2, function(x){max(seq(along=x)[x!=0])})
  # initialisation of PhiStar by using the first q factors as basic factors
  PhiStar <- diag(r)
  #
  f <- ncol(PhiStar)
  if(s == f){
    check <- !any(apply(((PhiStar %*% ineligible)%%p)==0, 2, all))
    if(check) return(list(PhiStar))
  }
  # Calculation of the set of initially admissible elements of U*
  admissible <- t(convertinto.basep(seq((p^r)-1),p)) 
  nb.admissible <- ncol(admissible)
  # Backtrack search - preliminaries
  eeU <- list(length=s-f)
  leeU <- rep(NA,s-f)
  neeU <- rep(0,s-f)
  # Backtrack search
  cat("Recherche d'une solution (algorithme backtrack).\n")
  jprev <- 0  ;  j <- 1
  solved <- FALSE
  while((j > 0)&(!solved)){
    PhiStar <- PhiStar[,seq(f+j-1), drop=FALSE]
    if(jprev < j){
      ineligible.j <- ineligible[ seq(f+j-1), ineligible.lnz==(f+j), drop=FALSE ]
      admissible.keep <- planor.kernelcheck.basep(PhiStar, admissible, ineligible.j, p)
      eeU[[j]] <- seq(nb.admissible)[admissible.keep]
      leeU[j] <- length(eeU[[j]])
      neeU[j] <- 0
    }
    if(neeU[j] < leeU[j]){
      neeU[j] <- neeU[j]+1
      newcolj <- (eeU[[j]])[neeU[j]]
      PhiStar <- cbind(PhiStar,admissible[,newcolj])
      if(j == (s-f)){
        cat("Solution obtenue. ")
        solved <- TRUE
        jprev <- j ; j <- j
      }
      else{
        jprev <- j ; j <- j+1
      }
    }
    else{
      jprev <- j ; j <- j-1
    }
  }
  if(solved){
    # Construction du plan
    plan <- crossing(rep(p,r),start=0) %*% PhiStar %%p
    # Sortie
    out <- list(plan=plan, matrice.cle=PhiStar, p=p)
  }
  else{
    cat("Pas de solution. ")
    out <- NULL
  }
  cat("Recherche terminee.\n")
  return(out)
}
#---------------------------------------------------------------------------
planor.kernelcheck.basep <- function(PhiStar, admissible, IneligibleSet, p){
  ImagesIS <- (- PhiStar %*% IneligibleSet)%%p
  avoid <- convertfrom.basep( t(ImagesIS), p)
  candidate <- convertfrom.basep( t(admissible), p)
  test <- !(candidate %in% avoid)
  return(test)
}
#---------------------------------------------------------------------------
convertinto.basep <- function (x, p) {
  # Conversion of an integer or integer vector x into base p
  # The coefficients are ordered by increasing powers of p
  if (!is.numeric(x)) 
    stop("cannot decompose non-numeric arguments")
  if (length(x) > 1) {
    l <- matrix(0, length(x), length(Recall(max(x),p)))
    for(i in seq(along = x)){
      dec.i <- Recall(x[i],p)
      l[i, seq(along=dec.i) ] <- dec.i
    }
    return(l)
  }
  if (x != round(x) || x < 0) 
    return(x)
  val <- x%%p
  while ( (x <- x%/%p) > 0 ) {
    newval <- x%%p
    val <- c(val,newval)
  }
    return(val)
}
#---------------------------------------------------------------------------
convertfrom.basep <- function (x, p) {
  # Conversion of integers x coded as vectors of coefficients in base p
  # to classical integers in base 10
  
  if (!is.numeric(x)) 
    stop("cannot recompose non-numeric arguments")
  if( (max(x)>p) || (min(x)<0) )
    stop("x must be reduced modulo p")
  if (is.matrix(x)) {
    l <- rep(NA, nrow(x))
    for(i in seq(along = l)){
      l[i] <- Recall(x[i,],p)
    }
    return(l)
  }
  val <- sum( x * p^(seq(along=x)-1) )
  return(val)
}
#---------------------------------------------------------------------------
inverses.basep <- function(p){
  # Raw calculation of the inverses modulo p

  if(p==2) return(1)
  else if(p==3) return(c(1,2))
  products <- outer(seq(2,p-2), seq(2,p-2), "*")%%p
  inverses <- 1 + apply(products, 1, function(x){ seq(along=x)[x==1] })
  return( c(1,inverses,p-1) )
}
#---------------------------------------------------------------------------
representative.basep <- function(mat,p){
  # generates the minimal set of representatives in base p
  # of the columns x of matrix mat 

  mat <- as.matrix(mat)
  #
  if(p==2) return(mat %%2)
  #
  representative <- NULL
  for(j in seq(ncol(mat))){
    x <- mat[,j]
    select <- seq(x)[x != 0]
    nbtocross <- length(select)-1
    if( nbtocross <= 0 ) mat.j <- x
    else{
      select <- select[seq(nbtocross)]
      N <- (p-1)^nbtocross
      mat.j <- matrix(x, nrow(mat), N)
      mat.j[select,] <- t( crossing(rep(p-1,nbtocross),start=1) )
    }
    representative <- cbind(representative, mat.j)
  }
  return(representative %%p)
}
#---------------------------------------------------------------------------
crossing <- function(n,start=1){
  # Generates all n1 x n2 x ... x ns combinations of size s with n1,...,ns integers

  N <- prod(n)
  s <- length(n)
  n <- c(n,1)
  crosses <- matrix(NA, N, s)
  for(i in seq(s))
    {
      motif <- start + seq(n[s+1-i])-1
      repet1 <- rep( prod(n[s+1-i+seq(i)]), n[s+1-i] )
      if(i==s){ repet2 <- 1 }
      else{ repet2 <- prod(n[seq(s-i)]) }
      crosses[,s-i+1] <- rep( rep( motif, repet1 ), repet2 )
    }
  return(crosses)
}  
#---------------------------------------------------------------------------

