
# check the label of a learning algorithm.
check.learning.algorithm = function(algorithm, class = "all", bn) {

  # select the right class of algorithms.
  ok = character(0)

  if ("constraint" %in% class)
    ok = c(ok, constraint.based.algorithms)
  if ("markov.blanket" %in% class)
    ok = c(ok, markov.blanket.algorithms)
  if ("neighbours" %in% class)
    ok = c(ok, local.search.algorithms)
  if ("score" %in% class)
    ok = c(ok, score.based.algorithms)
  if ("mim" %in% class)
    ok = c(ok, mim.based.algorithms)
  if ("classifier" %in% class)
    ok = c(ok, classification.algorithms)
  if ("lingam" %in% class)
    ok = c(ok, lingam.algorithms)
  if ("all" %in% class)
    ok = available.learning.algorithms

  if (missing(algorithm) || is.null(algorithm)) {

    # use the one specified by the bn object as the default.
    if (missing(bn))
      stop("the learning algorithm must be a character string.")
    else if (is(bn, "bn"))
      algorithm = bn$learning$algo

  }#THEN
  else {

    check.label(algorithm, choices = ok, labels = learning.labels,
      argname = "learning algorithm", see = "bnlearn-package")

  }#ELSE

  return(algorithm)

}#CHECK.LEARNING.ALGORITHM

# check size of the largest conditioning set in the independence tests.
check.largest.sx.set = function(max.sx, data) {

  if (missing(max.sx) || is.null(max.sx)) {

    max.sx = Inf

  }#THEN
  else if (!isTRUE(all.equal(max.sx, Inf))) {

    if (!is.positive.integer(max.sx))
      stop("max.sx must be a positive integer number.")
    if (max.sx >= ncol(data))
      warning("max.sx should be lower than the number of nodes, the limit will be ignored.")

  }#ELSE

  return(max.sx)

}#CHECK.LARGEST.SX.SET

# check the threshold on the maximum number of parents (one per node).
check.maxp = function(maxp, nodes) {

  nnodes = length(nodes)

  # the default is no limit on the number of parents.
  if (missing(maxp) || is.null(maxp))
    maxp = Inf

  if (is.null(names(maxp))) {

    # a single (unnamed) value applies to all the nodes.
    if (length(maxp) == 1) {

      maxp = structure(rep(maxp, nnodes), names = nodes)

    }#THEN
    else {

      # an unnamed vector is assumed to be in the same order as the nodes.
      if (length(maxp) != nnodes)
        stop("maxp must have one element for each node.")
      names(maxp) = nodes

    }#ELSE

  }#THEN
  else {

    # a named vector must have one element for each node.
    if (!setequal(names(maxp), nodes))
      stop("maxp must have one element for each node.")
    maxp = maxp[nodes]

  }#ELSE

  # each element must be a positive integer or Inf (that is, no limit).
  if (!is.numeric(maxp) || anyNA(maxp))
    stop("maxp must be a vector of positive integer numbers.")
  finite = is.finite(maxp)
  if (any(maxp[finite] < 1) || any(maxp[finite] %/% 1 != maxp[finite]) ||
      any(maxp[!finite] != Inf))
    stop("maxp must be a vector of positive integer numbers.")

  # a limit as large as the number of nodes can never be reached.
  if (any(maxp[finite] >= nnodes))
    warning("at least one maximum number of parents is not lower than the number of nodes, the limit will be ignored.")

  # make sure the values are stored as doubles, preserving the names.
  storage.mode(maxp) = "double"

  return(maxp)

}#CHECK.MAXP

# check arguments controlling random restart.
check.restart = function(restart) {

  # set the default value if not specified.
  if (missing(restart) || is.null(restart))
    return(0)

  # the number of random restarts must be a non-negative integer (zero means no
  # random restarts); check the type before comparing, so that NA, vectors and
  # non-numeric values get a meaningful error message.
  if (!is.non.negative.integer(restart))
    stop("the number of random restarts must be a non-negative numeric value.")

  return(restart)

}#CHECK.RESTART

check.perturb = function(perturb) {

  # set the default value if not specified.
  if (missing(perturb) || is.null(perturb))
      return(1)

  if (!is.positive.integer(perturb))
    stop("the number of changes at each random restart must be a non-negative numeric value.")
  else
    return(perturb)

}#CHECK.PERTURB

# check the maximum number of iterations.
check.max.iter = function(max.iter, default = Inf, max = Inf) {

  # set the default value if not specified.
  if (missing(max.iter) || is.null(max.iter))
    return(default)

  # the number of iterations must be positive and not fractional.
  if (!is.positive.integer(max.iter) && !any(is.infinite(max.iter)))
    stop("the maximum number of iterations must be a positive integer number.")
  # check the upper bound, if any.
  if (max.iter > max)
    stop("the maximum number of iterations must be at most ", max, ".")

  return(max.iter)

}#CHECK.MAX.ITER

# check arguments related to the tabu list.
check.tabu = function(tabu) {

  # set the default value if not specified.
  if (missing(tabu) || is.null(tabu))
    return(10)

  if (!is.positive.integer(tabu))
    stop("the length of the tabu list must be a positive integer number.")
  else
    return(tabu)

}#CHECK.TABU

check.max.tabu = function(max, tabu) {

  if (missing(max) || is.null(max))
    return(tabu)

  # check the number of iterations the algorithm can perform without
  # improving the best network score.
  if (!is.positive.integer(max))
    stop("the maximum number of iterations without any score improvement must be a positive integer number.")
  # the tabu list should be longer than that, otherwise the search can do a
  # U-turn and return to the local maximum it left before (thus creating an
  # endless loop).
  if (max > tabu)
    stop("the maximum number of iterations without any score improvement must not be greater than the length of the tabu list.")

  return(max)

}#CHECK.MAX.TABU

# check the arguments of a learning algorithm (for use in bootstrap).
check.learning.algorithm.args = function(args, algorithm, bn) {

  # convert args into a list, if it's not one already.
  if (!is.list(args))
      args = as.list(args)

  # if a reference bn is available, inherit the values of as many arguments as
  # possible.
  if (!(missing(algorithm) || missing(bn))) {

    # use the same score/conditional independence test.
    if (algorithm %in% constraint.based.algorithms) {

      # it's essential to check it's actually an independence test,
      # it could be a score function or NA.
      if ("test" %!in% names(args))
        if (bn$learning$test %in% available.tests)
          args[["test"]] = bn$learning$test

      # pass along all the learning arguments in bn$learning$args.
      if (length(bn$learning$args) > 0) {

        if ("alpha" %!in% names(args))
          args[["alpha"]] = bn$learning$args$alpha

        if ("test" %in% names(args) && ("B" %!in% names(args)))
          if (args[["test"]] %in% resampling.tests)
            args[["B"]] = bn$learning$args$B

      }#THEN

    }#THEN
    else if (algorithm %in% score.based.algorithms) {

      if ("score" %!in% names(args))
        if (bn$learning$test %in% available.scores)
          args[["score"]] = bn$learning$test

      # set the appropriate value for the optimization flag.
      if ("optimized" %!in% names(args))
        args[["optimized"]] = bn$learning$optimized

      # pass along the relevant arguments in bn$learning$args if the score
      # function is the same (different scores have parameters with the same
      # name but different meanings).
      if (("score" %in% names(args)) && (args[["score"]] == bn$learning$test))
        for (arg in names(bn$learning$args))
          if ((arg %!in% names(args)) && (arg %in% (score.extra.args[[args$score]])))
            args[[arg]] = bn$learning$args[[arg]]

    }#THEN

    # pass along whitelist and blacklist.
    if (!is.null(bn$learning$whitelist))
      args[["whitelist"]] = bn$learning$whitelist
    if (!is.null(bn$learning$blacklist))
      args[["blacklist"]] = bn$learning$blacklist

  }#THEN

  # remove any spurious x arguments, the data are provided by the bootstrap.
  if ("x" %in% names(args)) {

    args$x = NULL

    warning("removing 'x' from 'algorithm.args', the data set is provided by the bootstrap sampling.")

  }#THEN

  return(args)

}#CHECK.LEARNING.ALGORITHM.ARGS

