R/amexowrapper.R

DODEBUG = new.env(parent = emptyenv())
DODEBUG$TEST = FALSE

#################################
# Creator                       #
#################################

#' @title Create an mlr learner object that smoothes out the parameter space.
#'
#' @description
#' So what are we doing here?
#' The AMExoWrapper mostly handles parameter space magic. Specifically, this is:
#' \itemize{
#'   \item introduce parameters that chooses which wrappers are used, and in
#'     which sequence
#'   \item introduce parameters that control whether the wrapper(s) are used to
#'     facilitate compatibility of learners with the data, i.e. remove missing
#'     values, convert data types that are not supportet
#'   \item set \dQuote{pseudoparameters}, which are some special variable
#'     values that the `requires`-expressions of other parameters can use to
#'     specify that some parameters are only relevant in the presence of
#'     certain data types.
#'   \item remove parameters that always take on a single value (maybe dependent
#'     on a `requires` from the search space) and set them internally before
#'     calling the wrapped model.
#'   \itemize{
#'     \item bonus: if a parameter name contains a suffix of .AMLRFIX#,
#'       where # is a number, it will be stripped from the parameter name. This
#'       way it is possible to define alternative ranges for a parameter,
#'       depending on requirements.
#'   }
#' }
#'
#' In the following, \dQuote{XXX} and \dQuote{YYY} will always be one of
#' \dQuote{numerics}, \dQuote{ordereds}, \dQuote{factors}.
#' The parameters that are introduced and exposed to the outside are:
#' \itemize{
#'   \item automlr.convert: Whether to do any conversion of input data.
#'   \item automlr.convert.XXX: Whether to convert input data of type
#'     \dQuote{XXX}.
#'   \item automlr.convert.XXX.to: What type to convert \dQuote{XXX}-typed
#'     data into, if there is any choice.
#'   \item automlr.wrapafterconvert.XXX: Whether to apply preprocessing wrapper
#'     after conversion of data originally of type \dQuote{XXX}.
#'   \item automlr.wconverting.XXX.to.YYY: Which conversion wrapper to use for
#'     conversion of data from type \dQuote{XXX} to type \dQuote{YYY}.
#'   \item automlr.impute: Whether to do imputation of missing values.
#'   \item automlr.missing.indicators: Whether imputation is supposed to
#'     introduce \dQuote{factor} typed missing indicator variables.
#'   \item automlr.wimputing.XXX: Which imputation wrapper to use for
#'     imputation of \dQuote{XXX}-typed data, if there is any choice.
#'   \item automlr.preproc.XXX: Which  non-converting, non-imputing wrappers are
#'     used for preprocessing, with values of the format
#'     \code{outermostWrapper$wrapper..$wrapper$innermostwrapper}.
#' }
#'
#' The following parameters can be used by wrappers and learners in their
#' \code{$requires}-parameter; they will be replaced here:
#' \itemize{
#'   \item automlr.missing.indicators: Should be used by the imputing wrapper.
#'     Indicates whether imputation is supposed to introduce \dQuote{factor}
#'     typed missing indicator variables.
#'   \item automlr.has.XXX: May be used by learners:
#'     Indicates that the \dQuote{XXX} type is present in the data.
#'     Besides the types, mentioned above, \dQuote{XXX} may also be
#'     \dQuote{missings}.
#'   \item automlr.targettype: One of \dQuote{oneclass}, \dQuote{twoclass},
#'     \dQuote{multiclass}.
#' }
#' @param modelmultiplexer [\code{ModelMultiplexer}]\cr
#'   A modelmultiplexer object that should have a \code{$searchspace} slot.
#' @param wrappers [\code{list}]\cr
#'   A named list of wrappers. Names must not contain \code{$}-character.
#' @param taskdesc [\code{TaskDesc}]\cr
#'   The \dQuote{TaskDesc} object of the task to be optimized over.
#' @param missings [\code{logical}]\cr
#'   A logical, with names according to the \emph{present} feature data types
#'   (a subset of numerics, factors, ordered) indicating whether the columns
#'   in question have any missing values.
#' @param canHandleX [\code{list}]\cr
#'   A named list that maps "missings", "numerics", "factors", and "ordered"
#'   to a vector of learner names that can handle the respective data.
#' @param allLearners [\code{character}]\cr
#'   The list of all learner names.
#' @param modelTuneParsets [\code{list} of \code{ParamSet}]\cr
#'   List of each learner's ParamSet, indexed by learner ID.
#'
#' @return [\code{AMExoWrapper}]
#' A \code{Learner} that incorporates the wrappers and learners suitable for mlr
#' learners.
#'
#' The slot \code{$searchspace} should be used as \code{ParamSet} to tune
#' parameters over.
makeAMExoWrapper = function(modelmultiplexer, wrappers, taskdesc, missings,
    canHandleX, allLearners, modelTuneParsets) {

  aux = buildWrapperSearchSpace(wrappers, missings, canHandleX, allLearners)

  wrapperparnames = getParamIds(aux$wrapperps)

  completeSearchSpace = c(modelmultiplexer$searchspace, aux$wrapperps)

  # automlr.has.XXX replaces the parameters that are external-only.
  propertiesReplace = aux$replaces
  classlvlcount = min(3, length(taskdesc$class.levels))
  targettype = c("oneclass", "twoclass", "multiclass")[classlvlcount]
  propertiesReplace$automlr.targettype = targettype

  # all the transformations that need to be done before training, but after
  # the exact data is available.
  expressiontrafos = filterNull(extractSubList(completeSearchSpace$pars,
          "amlr.expressionTrafo", simplify = FALSE))
  fulltps = modelTuneParsets
  # a list 'param name' => 'which learner it belongs to'
  learners.by.params = list()
  for (l in names(fulltps)) {
    fullpnames = paste(l, getParamIds(fulltps[[l]]), sep = ".")
    learners.by.params[fullpnames] = l
  }
  # a list 'learner param' => 'collection of param names in the same learner'
  epsources = sapply(names(expressiontrafos), function(n) {
        l = learners.by.params[[n]]
        assert(!is.null(l))
        getParamIds(fulltps[[l]])
      }, simplify = FALSE)

  shadowparams = c(wrapperparnames,
      extractSubList(Filter(function(x) isTRUE(x$amlr.isDummy),
              completeSearchSpace$pars), "id"))
  # what's missing is removing the singleton parameters from the search space
  # and replacing them with direct setting of parameter values internally.
  # Easy to forget: parameters set for the modelmultiplexer via setHyperPars,
  # but not visible externally, also need to be treated like this.

  aux = extractStaticParams(completeSearchSpace)
  staticParams = aux$staticParams
  substitutions = c(aux$substitutions, propertiesReplace,
      getHyperPars(modelmultiplexer))
  finalSubstitutions = aux$finalSubstitutions
  completeSearchSpace = aux$completeSearchSpace

  # replace the singleton values inside the requirements of other parameters.

  completeSearchSpace$pars = substituteParamList(completeSearchSpace$pars,
      substitutions)
  completeSearchSpace$pars = substituteParamList(completeSearchSpace$pars,
      finalSubstitutions)
  staticParams = substituteParamList(staticParams, substitutions)
  staticParams = substituteParamList(staticParams, finalSubstitutions)

  # dependency on parameters with expression bounds is forbidden.
  badreqs = names(expressiontrafos)
  checkBadreqs(completeSearchSpace$pars, badreqs)
  checkBadreqs(staticParams, badreqs)

#  # shadowparams are supposed to be only visible on the outside.
#  # automlr.wrappersetup is handled separately.
#  staticParams[extractSubList(staticParams, "id") %in%
#          c("automlr.wrappersetup", shadowparams)] = NULL

  completeSearchSpace = simplifyRequirements(completeSearchSpace, staticParams)

  # transform into "LearnerParam" types. This is mostly dumb relabeling, except
  # for one thing: The limits / values of the parameters with "trafo" have to be
  # reverse-transformed.
  learnerPars = makeLearnerPars(completeSearchSpace)

  visibleHyperIndex = names(getHyperPars(modelmultiplexer)) %in%
      getParamIds(learnerPars)

  properties = c(names(taskdesc$n.feat)[taskdesc$n.feat > 0],
      if (taskdesc$has.missings) "missings")
  classlvlcount = min(3, length(taskdesc$class.levels))
  properties %c=% c("oneclass", "twoclass", "multiclass")[classlvlcount]

  learner = wrapLearner("AMExoWrapper", "amlr", "automlrlearner",
      learner = modelmultiplexer,
      type = taskdesc$type,
      properties = properties,
      par.set = learnerPars,
      par.vals = getHyperPars(modelmultiplexer)[visibleHyperIndex],
      config = list(show.info = FALSE, on.learner.error = "quiet",
          on.learner.warning = "quiet", on.par.without.desc = "stop",
          on.par.out.of.bounds = "stop", show.learner.output = FALSE))

  learner$staticParams = staticParams
  learner$searchspace = completeSearchSpace
  learner$fix.factors.prediction = TRUE
  learner$wrappers = extractSubList(wrappers, "cpo", simplify = FALSE)
  learner$shadowparams = shadowparams
  learner$wrapperparnames = wrapperparnames

  learner$expressiontrafos = expressiontrafos
  learner$epsources = epsources
  learner$learners.by.params = learners.by.params
  learner
}

#################################
# Learner Interface             #
#################################

#' @export
trainLearner.AMExoWrapper = function(.learner, .task, .subset, .weights = NULL,
    automlr.wrappersetup, ...) {
  .task = subsetTask(.task, .subset)
  .subset = NULL
  # train selected learner model and remove prefix from its param settings
  learner = .learner$learner

  args = getEffectiveHyperPars(learner, .learner$staticParams, list(...))

  wrapperargs = args[names(args) %in% c(.learner$wrapperparnames,
          .learner$shadowparams)]

  args = args[names(args) %nin% .learner$wrapperparnames]

  cpo = buildCPO(handleAmlrfix(wrapperargs), .learner$wrappers)
  if (!DODEBUG$TEST) {
    cpo = cpo %>>% cpoDropConstants(ignore.na = TRUE, id = "automlr.drop.constants")
  }

  # TODO: the "right" solution here is to have another
  # 'applyExpressionBoundTrafos-Wrapper', but this will work for now

  retrafo(.task) = NULL
  inverter(.task) = NULL
  .task = tagInvert(.task, FALSE)
  .task = .task %>>% cpo

  learner$retrafo = retrafo(.task)

  args = applyExpressionBoundTrafos(args, .learner$expressiontrafos,
      .learner$epsources, .learner$learners.by.params, .task)

  args = handleAmlrfix(args)

  sl = args$selected.learner
  if (!DODEBUG$TEST) {
    catf("Training %s", sl)
  }

  learner$properties = learner$base.learners[[sl]]$properties

  learner = setHyperPars(learner,
      par.vals = dropNamed(args, .learner$shadowparams))

  .learner$learner = learner  # respect automlrWrappedLearner interface

  ret = NextMethod("trainLearner")
  if (!DODEBUG$TEST) {
    catf("Done %s", sl)
  }
  ret
}

#' @export
predictLearner.AMExoWrapper = function(.learner, .model, .newdata, ...) {
  sl = .model$learner.model$learner$par.vals$selected.learner
  if (!DODEBUG$TEST) {
    catf("Predicting %s", sl)
  }
  .newdata = .newdata %>>% .model$learner.model$learner$retrafo

  ret = NextMethod("predictLearner")
  if (!DODEBUG$TEST) {
    catf("Done %s", sl)
  }
  ret
}

# collect hyperparameters from 'staticParams', the given parameters, and the
#
getEffectiveHyperPars = function(learner, staticParams, params) {
  envir = insert(getHyperPars(learner), params)
  for (fp in staticParams) {
    if (is.null(fp$requires) || isTRUE(eval(fp$requires, envir = envir))) {
      if (fp$id %in% names(params)) {
        stopf(paste0("Parameter '%s' is a static (internal) parameter but was",
                " also given externally."),
            fp$id)
      }
      params[[fp$id]] = fp$value
    }
  }
  params
}

handleAmlrfix = function(params) {
  pnames = names(params)
  for (p in pnames) {
    tp = removeAmlrfix(p)
    if (tp != p) {
      if (tp %in% names(params)) {
        stopf(paste0("Parameter '%s' and '%s' both given although they should",
                " be exclusive."),
            tp, p)
      }
      params[[tp]] = params[[p]]
      params[[p]] = NULL
    }
  }
  params
}

applyExpressionBoundTrafos = function(args, expressiontrafos, epsources,
    learners.by.params, task) {

  tohandle = intersect(names(expressiontrafos), names(args))
  env0 = list(n = getTaskSize(task), p = length(getTaskFeatureNames(task)))
  for (th in tohandle) {
    sources = epsources[[th]]
    lname = learners.by.params[[th]]
    sl = stats::setNames(args[paste(lname, sources, sep = ".")],
        paste0("PARAM.", sources))
    args[[th]] = expressiontrafos[[th]](args[[th]], c(env0, sl))
  }
  args
}

#################################
# Searchspace                   #
#################################

extractStaticParams = function(completeSearchSpace) {
  # How the substitution mechanism works:
  # There are two distinct problems that this is supposed to solve:
  # 1) Some parameters have different feasible regions depending on other
  #    variables.
  # 2) Singleton parameters that only take on one value should not be visible
  #    outside
  #
  # The first problem is solved by letting different external parameters with
  # name `varname.AMLRFIX#` refer to the same parameter `varname` of the actual
  # learner. These different external parameters should all have mutually
  # exclusive requirements. The parameters have to be substituted at two places:
  # when setting the hyperparameters of the actual learners, and inside the
  # requirement definitions of the individual parameters. This way, one
  # parameter can refer to `varname` without needing to worry about different
  # .AMLRFIXes.
  #
  # The second problem is solved using a similar mechanism, since it follows the
  # same principle: We want to have different parameters visible to the outside
  # than the parameters we present to the learners.
  #
  # We have the following datastructures:
  # staticParams :: lists information about all the parameters that are set on
  #                 the inside
  #                 but not visible to the public.
  # substitutions :: substitutions that will be performed inside the
  #                  requirements of other parameters, as well as recursively on
  #                  the substitutions themselves
  # finalSubstitutions :: Substitutions that will be performed once after the
  #                       other substitutions were done. This is to prevent
  #                       endless recursion.
  # completeSearchSpace :: The search space that will be given externally.
  #

  # all parameters that have only a single value
  staticParams = list()
  # substitution that will be used instead of the param inside of other
  # parameter's $requires.
  substitutions = list()
  finalSubstitutions = list()
  for (param in getParamIds(completeSearchSpace)) {
    curpar = completeSearchSpace$pars[[param]]
    parid = removeAmlrfix(curpar$id)
    leaf = paste0(parid, ".AMLRFINAL")
    if ((curpar$type %in% c("discrete", "discretevector") &&
          length(curpar$values) == 1) ||  # this is a 'fixed' value
        (curpar$type %in%
          c("numeric", "integer", "numericvector", "integervector") &&
          all(curpar$lower == curpar$upper))) {  # valid interval is a point
      fixvalue = if (curpar$type == "discrete") {
        curpar$values[[1]]
      } else if (curpar$type == "discretevector") {
        if (!is.null(curpar$amlr.isNotCat) && curpar$amlr.isNotCat) {
          rep(unname(curpar$values[[1]]), curpar$len)
        } else {
          rep(unname(curpar$values[1]), curpar$len)
        }
      } else {
        assert(all(curpar$lower[1] == curpar$lower))
        curpar$lower
      }
      completeSearchSpace$pars[[param]] = NULL
      if (!is.null(curpar$requires)) {
        # see if we can skip this when the requirement is trivially false
        if (tryCatch(isFALSE(eval(curpar$requires, envir = globalenv())),
            error = function(e) FALSE)) {
          subst = asQuoted(leaf)
        } else {
          # the following is a bit unfortunate, because it introduces a kind of
          # recursive dependence. I don't see a better way, however. The problem
          # is that if we have a variable xyz, and a variable xyz.AMLRFIX1, then
          # we want to remove the xyz.AMLRFIX1 and replace it with its fixed value
          # given the requirement. However, if the requirement is not given, the
          # parameter space given value must be used.
          # SOLUTION: append a suffix that prevents cycling in on itself.

          subst = substitute(if (isTRUE(req)) value else original,
              list(req = curpar$requires, value = fixvalue,
                  original = asQuoted(leaf)))
        }
        if (parid == curpar$id) {
          # the value itself is fixed -> if there are references remaining in
          # a requirement after all substitutions were done, it is an error.
          # This can happen, if the requirements of the fixed values do not
          # cover the whole domain.
          finalSubstitutions[[leaf]] = substitute(
              stop(errtext),
              list(errtext = sprintf(paste("Parameter %s",
                          "is fixed, but its reqs do not cover",
                          "the whole domain."), parid)))
        }
        if (is.null(finalSubstitutions[[leaf]])) {
          # check whether it is null; we don't want to overwrite it if the
          # original is a fixed value.
          if (parid %in% getParamIds(completeSearchSpace)) {
            finalSubstitutions[[leaf]] = asQuoted(parid)
          } else {
            finalSubstitutions[[leaf]] = substitute(
                    stop(errtext),
                    list(errtext = sprintf(paste("Parameter %s",
                                "is fixed, but its reqs do not cover",
                                "the whole domain."), parid)))
          }
        }
      } else {
        subst = fixvalue
      }
      # staticParams is not a named list, because the same parid may occur
      # multiple times (after stripping .AMLRFIX#)
      staticParams %c=% list(list(id = parid, value = fixvalue,
              requires = curpar$requires))
      if (parid %in% names(substitutions)) {
        # yay, we already have this substitution. This is only allowed to happen
        # if there are exclusive reuqirements, so we are able to substitute the
        # substitutions inside each other.
        # Exception: there was a substitution earlier with trivially false
        # requirement. In that case, substitions[[parid]] is *.AMLRFINAL.
        if (is.null(curpar$requires)) {
          assert(identical(substitutions[[parid]], asQuoted(leaf)))
        }
        sl = list()
        sl[leaf] = substitutions[parid]
        # FIXME: the following has the c() vs. c vulnerability
        substitutions[[parid]] = do.call(substitute, list(subst, sl))
      } else {
        substitutions[parid] = list(subst)  # subst may be NULL here
      }
    } else {
      # FIXME: the following is half a copy of the code above. maybe it is
      # possible to clean it up at some point.
      if (parid != curpar$id) {
        # substituting .AMLRFIX
        if (is.null(curpar$requires)) {
          opr = completeSearchSpace$pars[[parid]]$requires
          assert(!is.null(opr))
          assert(isFALSE(eval(opr, envir = globalenv())))
          curpar$requires = quote(identity(TRUE))
        }
        subst = substitute(if (isTRUE(req)) thisfix else original,
            list(req = curpar$requires,
                thisfix = asQuoted(curpar$id), original = asQuoted(leaf)))
        if (parid %in% names(substitutions)) {
          sl = list()
          sl[[leaf]] = substitutions[[parid]]
          substitutions[[parid]] = do.call(substitute, list(subst, sl))
        } else {
          if (is.null(finalSubstitutions[[leaf]])) {
              # check whether it is null; we don't want to overwrite it if the
              # original is a fixed value.
              finalSubstitutions[[leaf]] = asQuoted(parid)
          }
          substitutions[[parid]] = subst
        }
      }
    }
  }
  list(completeSearchSpace = completeSearchSpace,
      staticParams = staticParams,
      substitutions = substitutions,
      finalSubstitutions = finalSubstitutions)
}

substituteParamList = function(paramList, substitutions, maxCycles = 32) {

  for (dummy in seq_len(maxCycles)) {
    # go `cycles` steps deep, in case one of the substituted variables itself
    # requires another variable.
    dirty = FALSE
    for (pid in seq_along(paramList)) {
      req = paramList[[pid]]$requires
      if (!is.null(req)) {
        paramList[[pid]]$requires = replaceRequires(req, substitutions)
        if (!langIdentical(paramList[[pid]]$requires, req)) {
          dirty = TRUE
        }
      }
    }
    if (!dirty) {
      return(paramList)
    }
  }
  stop("Too much recursion when replacing requirements")
}

# remove variable references in requirements that do not point to existing
# parameters.
removeDanglingReqs = function(ps, danglingparams) {
  subst = sapply(danglingparams, function(x)
        substitute(stop(msg), list(msg = sprintf("%s: %s referenced, but %s",
                    "Error during requirement evaluation", x,
                    "its requirements are never fulfilled."))),
      simplify = FALSE)
  ps$pars = substituteParamList(ps$pars, subst)
  ps
}



# check that there is no dependency on 'badreqs'. This is currently only used
# for parameters that have expression bounds.
checkBadreqs = function(paramList, badreqs) {
  substs = sapply(badreqs, function(dummy) quote(stop()), simplify = FALSE)
  for (pid in seq_along(paramList)) {
    req = paramList[[pid]]$requires
    if (!is.null(req)) {
      if (!langIdentical(req, replaceRequires(req, substs))) {
        stopf("Parameter %s has requirement %s.", paramList[[pid]]$id,
            "depending on expression bound")
      }
    }
  }
}

# check if any requirements do not actually depend on parameters any more, e.g.
# because of fixed values. Remove parameters with unfulfilled requirements,
# simplify parameters with requirements only depending on 'selected.learner'
# and remove requirements that are always TRUE.
simplifyRequirements = function(completeSearchSpace, staticParams) {
  allNames = getParamIds(completeSearchSpace)
  paramReferenceStop = rep(list(quote(stop("AMLR VARREF STOP"))),
      length(allNames))
  names(paramReferenceStop) = allNames
  danglingparams = character(0)
# paramReferenceStop = list2env(paramReferenceStop, parent = baseenv())
  # Now after all the replacing going on, there might be parameters that have a
  # `requires` always TRUE or always FALSE.
  for (param in getParamIds(completeSearchSpace)) {
    curpar = completeSearchSpace$pars[[param]]
    # we test whether the requires is trivially TRUE or FALSE by evaluating it
    # in an empty environment.
    if (is.null(curpar$requires)) {
      next
    }

    # take the environment that would usually be present, replace all values
    # with stop("EXPECTED STOP"), and check if the expected stop happened. Is it
    # possible to stop on variable reference? apparently not, so we have to go
    # the long replaceRequires route. This would have the advantage to not
    # filter out trivial invocations of e.g. c(), and also that we could check
    # for the simplest of syntax errors by watching whether the error happening
    # is actually the error we expect.
    if (is.null(curpar$amlr.learnerName)) {
      paramReferenceStop$selected.learner = quote(stop("AMLR VARREF STOP"))
    } else {
      # maybe the requires is a mlr learner's requires that now only depends on
      # selected.learner being something. if it is FALSE even if
      # selected.learner equals the given learner, then we remove the parameter.
      # This would be the case e.g. if the parameter is only sensible if there
      # are NAs in the data and the current data set does not have NAs.
      paramReferenceStop$selected.learner = curpar$amlr.learnerName
    }
    req = replaceRequires(curpar$requires, paramReferenceStop)
    tryResult = try(reqValue <- eval(req, parent.env(environment())),
        silent = TRUE)
    evalResult = simplifyEval(req)
    if (!is.error(tryResult) || !is.null(evalResult)) {
      if (!is.error(tryResult) && !is.null(evalResult) &&
          !identical(tryResult, evalResult[[1]])) {
        stopf("Different requirement simplification heuristics %s%s",
            " gave different results for parameter ", curpar$id)
      }
      if (is.error(tryResult)) {
        reqValue = evalResult[[1]]
      }
      if (isTRUE(reqValue)) {
        # always true -> remove requirement
        if (is.null(curpar$amlr.learnerName) ||
            "selected.learner" %in% extractSubList(staticParams, "id")) {
          completeSearchSpace$pars[[param]]$requires = NULL
        } else {
          completeSearchSpace$pars[[param]]$requires = substitute(
              selected.learner == SL, list(SL = curpar$amlr.learnerName))
        }
      } else {
        # always false -> remove the parameter.
        completeSearchSpace$pars[[param]] = NULL
        danglingparams %c=% param
      }
    }
    if (is.error(tryResult)) {
      errormsg = attr(tryResult, "condition")$message
      if (!identical(errormsg, "AMLR VARREF STOP")) {
        stopf("Error while evaluating requirement for parameter '%s'%s: '%s'.",
            param, ifelse(is.null(curpar$amlr.learnerName), "",
                paste0(" of learner ", curpar$amlr.learnerName)),
            errormsg)
      }
#      if ((curpar$requires) == "expression") {  # this apparently happens.
#        newreq = deExpression(curpar$requires)
#        completeSearchSpace$pars[[param]]$requires = newreq
#      } else
      if (!is.call(curpar$requires)) {
        stopf("Parameter '%s' has broken requirement:\n%s", param,
            collapse(deparse(curpar$requires), sep = "\n"))
      }
    }
  }
  removeDanglingReqs(completeSearchSpace, danglingparams)
}

simplifyEval = function(lang) {
  if (!is.recursive(lang)) {
    if (is.symbol(lang)) {
      return(NULL)
    } else {
      return(list(eval(lang, baseenv())))
    }
  }
  asCond = function(x) {
    x && TRUE
  }
  logOrNull = function(x) {
    if (is.null(x)) {
      NULL
    } else {
      list(asCond(x[[1]]))
    }
  }
  if (langIdentical(lang[[1]], quote(`if`))) {
    # emulate 'if': if conditional is unknown,
    # we may still rescue the situation if both
    # cases give same result.
    cond = simplifyEval(lang[[2]])
    if (is.null(cond)) {
      ca = simplifyEval(lang[[3]])
      cb = simplifyEval(lang[[4]])
      if (identical(ca, cb)) {
        return(ca)
      } else {
        return(NULL)
      }
    } else if (cond[[1]]) {
      return(simplifyEval(lang[[3]]))
    } else {
      return(simplifyEval(lang[[4]]))
    }
  } else if (langIdentical(lang[[1]], quote(`&&`))) {
    ca = simplifyEval(lang[[2]])
    if (is.null(ca) || asCond(ca[[1]])) {
      cb = simplifyEval(lang[[3]])
      if (is.null(cb) || !asCond(cb[[1]])) {
        return(logOrNull(cb))
      }
      return(logOrNull(ca))
    } else {
      return(list(FALSE))
    }
  } else if (langIdentical(lang[[1]], quote(`||`))) {
    ca = simplifyEval(lang[[2]])
    if (is.null(ca) || !asCond(ca[[1]])) {
      cb = simplifyEval(lang[[3]])
      if (is.null(cb) || asCond(cb[[1]])) {
        return(logOrNull(cb))
      }
      return(logOrNull(ca))
    } else {
      return(list(TRUE))
    }
  }
  if (length(lang) == 2) {
    allowedunary = list(quote(`(`), quote(isTRUE), quote(isFALSE),
        quote(identity), quote(as.null), quote(`!`), quote(`+`), quote(`-`))
    for (f in allowedunary) {
      if (!langIdentical(lang[[1]], f)) {
        next
      }
      ca = simplifyEval(lang[[2]])
      if (is.null(ca)) {
        return(NULL)
      }
      return(list(get(as.character(f))(ca[[1]])))
    }
  } else if (length(lang) == 3) {
    allowedbinary = list(quote(`==`), quote(`+`), quote(`-`), quote(`*`),
        quote(`/`), quote(`!=`), quote(`>`), quote(`<`), quote(`>=`),
        quote(`<=`))
    for (f in allowedbinary) {
      if (!langIdentical(lang[[1]], f)) {
        next
      }
      ca = simplifyEval(lang[[2]])
      if (is.null(ca)) {
        return(NULL)
      }
      cb = simplifyEval(lang[[3]])
      if (is.null(cb)) {
        return(NULL)
      }
      return(list(get(as.character(f))(ca[[1]], cb[[1]])))
    }
  }
  NULL
}

getSearchspace.AMExoWrapper = function(learner) {
  learner$searchspace
}
mlr-org/automlr documentation built on May 23, 2019, 3:02 a.m.