R/np.smoothcoef.bw.R

Defines functions error npscoefbw.formula npscoefbw

Documented in npscoefbw npscoefbw.formula

npscoefbw <-
  function(...){
    mc <- match.call(expand.dots = FALSE)
    npRejectRenamedScaleFactorSearchArgs(names(mc$...), where = "npscoefbw")
    target <- .np_bw_dispatch_target(dots = mc$...,
                                     data_arg_names = c("xdat", "ydat", "zdat"),
                                     eval_env = parent.frame())
    UseMethod("npscoefbw", target)
  }

npscoefbw.formula <-
  function(formula, data, subset, na.action, call, ...){
    mf <- match.call(expand.dots = FALSE)
    m <- match(c("formula", "data", "subset", "na.action"),
               names(mf), nomatch = 0)
    mf <- mf[c(1,m)]

    formula.call <- .np_bw_formula_from_call(call_obj = call, eval_env = parent.frame())
    if (!is.null(formula.call))
      mf[[2]] <- formula.call

    mf[[1]] <- as.name("model.frame")

    formula.obj <- .np_bw_resolve_formula(formula_obj = formula,
                                        formula_call = formula.call,
                                        eval_env = parent.frame())
    chromoly <- explodePipe(formula.obj, env = environment(formula))

    bronze <- sapply(chromoly, paste, collapse = " + ")
    formula.all <- if (missing(data)) {
      terms(as.formula(paste(" ~ ", paste(bronze, collapse = " + ")),
                       env = environment(formula)))
    } else {
      terms(as.formula(paste(" ~ ", paste(bronze, collapse = " + ")),
                       env = environment(formula)), data = data)
    }

    orig.ts <- if (missing(data))
      .np_terms_ts_mask(terms_obj = formula.all,
                        data = environment(formula.all),
                        eval_env = environment(formula.all))
    else .np_terms_ts_mask(terms_obj = formula.all,
                           data = data,
                           eval_env = environment(formula.all))

    mf[["formula"]] <-
      as.formula(paste(bronze[1]," ~ ",
                       paste(bronze[2:length(bronze)],
                             collapse =" + ")),
                 env = environment(formula))

    mf[["formula"]] <- terms(mf[["formula"]])
    if(all(orig.ts)){
      args <- (as.list(attr(mf[["formula"]], "variables"))[-1])
      attr(mf[["formula"]], "predvars") <- as.call(c(quote(as.data.frame),as.call(c(quote(ts.intersect), args))))
    }else if(any(orig.ts)){
      arguments <- (as.list(attr(mf[["formula"]], "variables"))[-1])
      arguments.normal <- arguments[which(!orig.ts)]
      arguments.timeseries <- arguments[which(orig.ts)]

      ix <- sort(c(which(orig.ts),which(!orig.ts)),index.return = TRUE)$ix
      attr(mf[["formula"]], "predvars") <- bquote(.(as.call(c(quote(cbind),as.call(c(quote(as.data.frame),as.call(c(quote(ts.intersect), arguments.timeseries)))),arguments.normal,check.rows = TRUE)))[,.(ix)])
    }
    
    mf.args <- as.list(mf[-1L])
    mf <- do.call(stats::model.frame, mf.args, envir = parent.frame())
    
    ydat <- model.response(mf)
    xdat <- mf[, chromoly[[2]], drop = FALSE]
    miss.z <- !(length(chromoly) == 3)
    if (!miss.z)
      zdat <- mf[, chromoly[[3]], drop = FALSE]
    
    bw.args <- list(xdat = xdat, ydat = ydat)
    if (!miss.z)
      bw.args$zdat <- zdat
    tbw <- do.call(npscoefbw, c(bw.args, list(...)))

    ## clean up (possible) inconsistencies due to recursion ...
    tbw$call <- match.call(expand.dots = FALSE)
    environment(tbw$call) <- parent.frame()
    tbw$formula <- formula
    tbw$rows.omit <- as.vector(attr(mf,"na.action"))
    tbw$nobs.omit <- length(tbw$rows.omit)
    tbw$terms <- attr(mf,"terms")
    tbw$chromoly <- chromoly

    tbw <-
      updateBwNameMetadata(nameList =
                           list(ynames =
                                attr(mf, "names")[attr(tbw$terms, "response")]),
                           bws = tbw)
    
    tbw
  }

npscoefbw.NULL <-
  function(xdat = stop("invoked without data 'xdat'"),
           ydat = stop("invoked without data 'ydat'"),
           zdat = NULL,
           bws, ...){

    miss.z <- missing(zdat)

    xdat <- toFrame(xdat)

    if(!miss.z)
      zdat <- toFrame(zdat)

    n.bw <- if (miss.z) ncol(xdat) else ncol(zdat)
    bws <- double(n.bw)

    bw.args <- list(xdat = xdat, ydat = ydat, bws = bws)
    if (!miss.z)
      bw.args$zdat <- zdat
    tbw <- do.call(npscoefbw.default, c(bw.args, list(...)))

    ## clean up (possible) inconsistencies due to recursion ...
    mc <- match.call(expand.dots = FALSE)
    environment(mc) <- parent.frame()
    tbw$call <- mc

    tbw <-
      updateBwNameMetadata(nameList = list(ynames = deparse(substitute(ydat))),
                           bws = tbw)

    tbw
  }

.npscoef_nn_candidate_bandwidth <- function(param, bwtype, nobs) {
  if (identical(bwtype, "fixed"))
    return(as.double(param))

  lower <- 2L
  upper <- max(1L, as.integer(nobs) - 1L)
  hard.upper <- .Machine$integer.max / 2
  vapply(param, function(h) {
    if (!is.finite(h))
      return(NA_real_)
    k <- .np_round_half_to_even(h)
    if (k < lower)
      return(NA_real_)
    if (k > upper &&
        npExtendedNnEnabled() &&
        bwtype %in% c("generalized_nn", "adaptive_nn") &&
        k <= hard.upper) {
      return(as.double(k))
    }
    if (k > upper)
      return(NA_real_)
    as.double(k)
  }, numeric(1))
}

.npscoef_bw_scale_multiplier <- function(scbw) {
  out <- rep.int(1.0, length(scbw$bw))
  if (!isTRUE(scbw$scaling))
    return(out)

  if (is.null(scbw$ncatfac) || !is.finite(scbw$ncatfac) ||
      is.null(scbw$nconfac) || !is.finite(scbw$nconfac))
    stop("scaled smooth-coefficient bandwidth state is missing scale factors",
         call. = FALSE)

  out[] <- as.double(scbw$ncatfac)
  if (any(scbw$icon)) {
    if (is.null(scbw$sdev) || any(!is.finite(scbw$sdev)))
      stop("scaled smooth-coefficient bandwidth state is missing continuous scales",
           call. = FALSE)
    icon.cumsum <- cumsum(scbw$icon)
    out[scbw$icon] <- as.double(scbw$nconfac) *
      as.double(scbw$sdev)[icon.cumsum[scbw$icon]]
  }
  out
}

.npscoef_apply_bw_to_scbw <- function(scbw, param, nobs = scbw$nobs) {
  param <- .npscoef_nn_candidate_bandwidth(param = param,
                                           bwtype = scbw$type,
                                           nobs = nobs)
  if (length(param) != length(scbw$bw))
    stop("smooth-coefficient bandwidth candidate has wrong length",
         call. = FALSE)
  scbw$bw <- as.double(param)
  if (isTRUE(scbw$scaling))
    scbw$bandwidth[[1L]] <- scbw$bw * .npscoef_bw_scale_multiplier(scbw)
  else
    scbw$bandwidth[[1L]] <- scbw$bw
  scbw
}

.npscoefbw_start_controls <- function(scale.factor.init.lower = 0.1,
                                     scale.factor.init.upper = 2.0,
                                     scale.factor.init = 0.5,
                                     lbd.init = 0.5,
                                     hbd.init = 1.5,
                                     dfac.init = 1.0,
                                     scale.factor.search.lower = 0,
                                     where = "npscoefbw") {
  cont.start <- npContinuousSearchStartControls(
    scale.factor.init.lower,
    scale.factor.init.upper,
    scale.factor.init,
    scale.factor.search.lower,
    where = where
  )
  lbd.init <- npValidatePositiveFiniteNumeric(lbd.init, "lbd.init")
  hbd.init <- npValidatePositiveFiniteNumeric(hbd.init, "hbd.init")
  dfac.init <- npValidatePositiveFiniteNumeric(dfac.init, "dfac.init")

  if (hbd.init < lbd.init) {
    stop(sprintf("%s: 'hbd.init' must be greater than or equal to 'lbd.init'", where),
         call. = FALSE)
  }
  if (lbd.init > 2 || hbd.init > 2 || dfac.init > 2) {
    stop(sprintf("%s: categorical start factors must be less than or equal to 2", where),
         call. = FALSE)
  }

  list(
    scale.factor.init.lower = cont.start$scale.factor.init.lower,
    scale.factor.init.upper = cont.start$scale.factor.init.upper,
    scale.factor.init = cont.start$scale.factor.init,
    scale.factor.search.lower = as.double(scale.factor.search.lower),
    lbd.init = as.double(lbd.init),
    hbd.init = as.double(hbd.init),
    dfac.init = as.double(dfac.init)
  )
}

.npscoef_start_factor_vector <- function(param,
                                         icon = NULL,
                                         iord = NULL,
                                         iuno = NULL,
                                         continuous.factor,
                                         categorical.factor,
                                         where = "npscoefbw") {
  ndim <- length(param)
  if (is.null(icon) || is.null(iord) || is.null(iuno))
    return(rep.int(as.double(continuous.factor), ndim))

  icon <- as.logical(icon)
  iord <- as.logical(iord)
  iuno <- as.logical(iuno)

  if (length(icon) != ndim || length(iord) != ndim || length(iuno) != ndim) {
    stop(sprintf("%s: invalid fixed-start coordinate map", where), call. = FALSE)
  }

  cat.mask <- iord | iuno
  factor <- rep_len(NA_real_, ndim)
  factor[icon] <- as.double(continuous.factor)
  factor[cat.mask] <- as.double(categorical.factor)

  if (anyNA(factor)) {
    stop(sprintf("%s: unsupported fixed-start coordinate type", where), call. = FALSE)
  }

  factor
}

.npscoef_default_start_bandwidth <- function(param,
                                             bwtype,
                                             nobs,
                                             start.controls = .npscoefbw_start_controls(),
                                             icon = NULL,
                                             iord = NULL,
                                             iuno = NULL) {
  if (identical(bwtype, "fixed"))
    return(as.double(
      param * .npscoef_start_factor_vector(
        param = param,
        icon = icon,
        iord = iord,
        iuno = iuno,
        continuous.factor = start.controls$scale.factor.init,
        categorical.factor = start.controls$dfac.init
      )
    ))

  start <- if (npExtendedNnEnabled() &&
               bwtype %in% c("generalized_nn", "adaptive_nn") &&
               is.finite(start.controls$scale.factor.init) &&
               start.controls$scale.factor.init > max(1L, as.integer(nobs) - 1L)) {
    start.controls$scale.factor.init
  } else {
    sqrt(nobs)
  }
  .npscoef_nn_candidate_bandwidth(
    param = rep.int(as.double(start), length(param)),
    bwtype = bwtype,
    nobs = nobs
  )
}

.npscoef_random_start_bandwidth <- function(param,
                                            bwtype,
                                            nobs,
                                            start.controls = .npscoefbw_start_controls(),
                                            icon = NULL,
                                            iord = NULL,
                                            iuno = NULL) {
  if (identical(bwtype, "fixed")) {
    draws <- .npscoef_start_factor_vector(
      param = param,
      icon = icon,
      iord = iord,
      iuno = iuno,
      continuous.factor = start.controls$scale.factor.init.lower,
      categorical.factor = start.controls$lbd.init
    )

    for (ii in seq_along(draws)) {
      if (!is.null(icon) && !is.null(iord) && !is.null(iuno) &&
          !isTRUE(as.logical(icon)[ii])) {
        draws[ii] <- runif(1L, min = start.controls$lbd.init, max = start.controls$hbd.init)
      } else {
        draws[ii] <- runif(1L, min = start.controls$scale.factor.init.lower, max = start.controls$scale.factor.init.upper)
      }
    }

    return(as.double(draws * param))
  }

  upper <- max(1L, as.integer(nobs) - 1L)
  .npscoef_nn_candidate_bandwidth(
    param = runif(length(param), min = 2, max = max(2L, upper)),
    bwtype = bwtype,
    nobs = nobs
  )
}

.npscoef_candidate_is_admissible <- function(param,
                                            bwtype,
                                            nobs,
                                            lower = NULL) {
  candidate <- .npscoef_nn_candidate_bandwidth(
    param = param,
    bwtype = bwtype,
    nobs = nobs
  )
  if (any(!is.finite(candidate)))
    return(FALSE)
  if (identical(bwtype, "fixed")) {
    if (!is.null(lower))
      return(all(candidate >= lower))
    return(all(candidate > 0))
  }
  TRUE
}

.npscoef_finalize_bandwidth <- function(param,
                                        bwtype,
                                        nobs,
                                        lower = NULL,
                                        where = "npscoefbw") {
  candidate <- .npscoef_nn_candidate_bandwidth(param = param, bwtype = bwtype, nobs = nobs)
  if (any(!is.finite(candidate))) {
    if (identical(bwtype, "fixed")) {
      stop(sprintf("%s: bandwidth must be finite", where), call. = FALSE)
    }
    stop(
      sprintf(
        "%s: nearest-neighbor bandwidth must be an integer vector in [2, %d]",
        where,
        max(2L, as.integer(nobs) - 1L)
      ),
      call. = FALSE
    )
  }
  if (identical(bwtype, "fixed") && any(candidate <= 0)) {
    stop(sprintf("%s: bandwidth must be strictly positive", where), call. = FALSE)
  }
  if (identical(bwtype, "fixed") && !is.null(lower) && any(candidate < lower)) {
    stop(sprintf("%s: bandwidth is below the continuous scale-factor lower bound", where),
         call. = FALSE)
  }
  as.double(candidate)
}

npscoefbw.scbandwidth <- 
  function(xdat = stop("invoked without data 'xdat'"),
           ydat = stop("invoked without data 'ydat'"),
           zdat = NULL,
           bws,
           backfit.iterate = FALSE,
           backfit.maxiter = 100,
           backfit.tol = .Machine$double.eps,
           bandwidth.compute = TRUE,
           cv.iterate = FALSE,
           cv.num.iterations = 1,
           nmulti,
           optim.abstol = .Machine$double.eps,
           optim.maxattempts = 10,
           optim.maxit = 500,
           optim.method = c("Nelder-Mead", "BFGS", "CG"),
           optim.reltol = sqrt(.Machine$double.eps),
           random.seed = 42,
           scale.factor.init.lower = 0.1,
           scale.factor.init.upper = 2.0,
           scale.factor.init = 0.5,
           lbd.init = 0.5,
           hbd.init = 1.5,
           dfac.init = 1.0,
           scale.factor.search.lower = NULL,
           ...,
           nomad.opts = list()){

    nomad.opts <- .np_nomad_normalize_user_opts(nomad.opts, "npscoefbw")
    dots <- list(...)
    if (length(nomad.opts))
      dots$nomad.opts <- nomad.opts
    npRejectUnsupportedBwsolver(dots, "npscoefbw")
    
    ## Save seed prior to setting

    seed.state <- .np_seed_enter(random.seed)
    on.exit(.np_seed_exit(seed.state, remove_if_absent = TRUE), add = TRUE)


    miss.z <- missing(zdat)
    
    xdat <- toFrame(xdat)

    if (!miss.z)
      zdat <- toFrame(zdat)
    
    if (missing(nmulti)){
      nmulti <- npDefaultNmulti(if (miss.z) NCOL(xdat) else NCOL(zdat))
    }
    regtype <- if (is.null(bws$regtype)) "lc" else bws$regtype
    cv.iterate <- npValidateScalarLogical(cv.iterate, "cv.iterate")
    backfit.iterate <- npValidateScalarLogical(backfit.iterate, "backfit.iterate")
    bandwidth.compute <- npValidateScalarLogical(bandwidth.compute, "bandwidth.compute")
    nmulti <- npValidateNmulti(nmulti)
    .np_progress_bandwidth_set_total(nmulti)
    backfit.maxiter <- npValidatePositiveInteger(backfit.maxiter, "backfit.maxiter")
    backfit.tol <- npValidatePositiveFiniteNumeric(backfit.tol, "backfit.tol")
    optim.maxattempts <- npValidatePositiveInteger(optim.maxattempts, "optim.maxattempts")
    optim.maxit <- npValidatePositiveInteger(optim.maxit, "optim.maxit")
    optim.reltol <- npValidatePositiveFiniteNumeric(optim.reltol, "optim.reltol")
    optim.abstol <- npValidatePositiveFiniteNumeric(optim.abstol, "optim.abstol")
    scale.factor.search.lower <- npResolveScaleFactorLowerBound(
      if (is.null(scale.factor.search.lower)) npGetScaleFactorSearchLower(bws) else scale.factor.search.lower
    )
    start.controls <- .npscoefbw_start_controls(
      scale.factor.init.lower = scale.factor.init.lower,
      scale.factor.init.upper = scale.factor.init.upper,
      scale.factor.init = scale.factor.init,
      lbd.init = lbd.init,
      hbd.init = hbd.init,
      dfac.init = dfac.init,
      scale.factor.search.lower = scale.factor.search.lower,
      where = "npscoefbw"
    )
    if (cv.iterate)
      cv.num.iterations <- npValidatePositiveInteger(cv.num.iterations, "cv.num.iterations")
    spec <- .npscoef_canonical_spec(
      source = bws,
      zdat = if (miss.z) xdat else zdat,
      where = "npscoefbw"
    )
    reg.engine <- spec$regtype.engine

    if (!identical(reg.engine, "lc") && cv.iterate)
      stop("cv.iterate currently supports regtype='lc' for npscoefbw")

    if (!(is.vector(ydat) || is.factor(ydat)))
      stop("'ydat' must be a vector or a factor")

    if (miss.z) {
      bwMatch(xdat, bws$xdati)
    } else {
      bwMatch(zdat, bws$zdati)
    }
    
    if (dim(xdat)[1] != length(ydat))
      stop("number of regression data and response data do not match")

    if (ncol(xdat) == 1 && missing(cv.iterate))
      cv.iterate = FALSE

    if (!all(bws$xdati$icon))
      stop("Only continuous 'x' regressors are supported in this version.")

    optim.method <- match.arg(optim.method)
      
    ## catch and destroy NA's
    keep.rows <- rep_len(TRUE, nrow(xdat))
    train.df <- data.frame(xdat, ydat)
    if (!miss.z)
      train.df <- data.frame(train.df, zdat)
    rows.omit <- attr(na.omit(train.df), "na.action")
    if (length(rows.omit) > 0L)
      keep.rows[as.integer(rows.omit)] <- FALSE

    if (!any(keep.rows))
      stop("Data has no rows without NAs")

    xdat <- xdat[keep.rows,,drop = FALSE]
    ydat <- ydat[keep.rows]

    if(!miss.z)
      zdat <- zdat[keep.rows,, drop = FALSE]
    
    nrow = dim(xdat)[1]
    ncol = dim(xdat)[2]

    ## at this stage, data to be sent to the c routines must be converted to
    ## numeric type.

    if (is.factor(ydat))
      ydat <- dlev(ydat)[as.integer(ydat)]
    else
      ydat <- as.double(ydat)

    xdat <- toMatrix(xdat)

    ## if (!miss.z)
    ##  zdat <- toMatrix(zdat)
    
    ## bad data
    if (qr(xdat)$rank < ncol(xdat)){
      stop("columns of the independent variable (xdat) are linearly dependent") 
    }

    n <- nrow(xdat)
    
    ## ... do bandwidth selection
    
    ## construct 'W' matrix
    ## in the future one will be able to use a switch to npksum
    ## to emulate W

    W <- cbind(1.0, xdat)
    yW <- cbind(ydat, W)
    
    if (miss.z){
      zdat <- xdat
      dati <- bws$xdati
    }
    else
      dati <- bws$zdati
    zdat.df <- if (is.data.frame(zdat)) zdat else as.data.frame(zdat)

    mysd <- EssDee(zdat[, dati$icon, drop = FALSE])
    nconfac <- n^(-1.0/(2.0*bws$ckerorder+bws$ncon))
    ncatfac <- n^(-2.0/(2.0*bws$ckerorder+bws$ncon))

    bws$sdev <- mysd
    bws$nconfac <- nconfac
    bws$ncatfac <- ncatfac
    apply_bw_to_scbw <- function(scbw, param) {
      scbw$sdev <- mysd
      scbw$nconfac <- nconfac
      scbw$ncatfac <- ncatfac
      .npscoef_apply_bw_to_scbw(scbw = scbw, param = param, nobs = n)
    }

    fast_largeh_tol <- npLargehRelTol()
    fast_disc_tol <- npDiscUpperRelTol()

    cont_utol <- switch(
      bws$ckertype,
      gaussian = sqrt(-2.0 * log(1.0 - fast_largeh_tol)),
      "truncated gaussian" = sqrt(-2.0 * log(1.0 - fast_largeh_tol)),
      epanechnikov = sqrt(fast_largeh_tol),
      uniform = 1.0 - 32.0 * .Machine$double.eps,
      0.0
    )

    cont_hmin <- numeric(0)
    if (any(dati$icon) && is.finite(cont_utol) && cont_utol > 0) {
      zcon <- zdat.df[, dati$icon, drop = FALSE]
      cont_hmin <- vapply(zcon, function(col) {
        vals <- as.double(col)
        vals <- vals[is.finite(vals)]
        if (!length(vals))
          return(Inf)
        diff(range(vals)) / cont_utol
      }, numeric(1))
    }

    disc_upper_tol <- function(upper) {
      max(fast_disc_tol * abs(upper),
          16.0 * .Machine$double.eps * max(1.0, abs(upper)))
    }

    uno_upper <- numeric(0)
    if (any(dati$iuno)) {
      uno_idx <- which(dati$iuno)
      uno_upper <- vapply(uno_idx, function(i) {
        uMaxL(dati$all.nlev[[i]], kertype = bws$ukertype)
      }, numeric(1))
    }

    ord_upper <- numeric(0)
    if (any(dati$iord)) {
      ord_idx <- which(dati$iord)
      ord_upper <- vapply(ord_idx, function(i) {
        oMaxL(dati$all.nlev[[i]], kertype = bws$okertype)
      }, numeric(1))
    }

    npscoef_fast_eligible <- function(sbw) {
      .npscoefbw_fast_eligible(sbw = sbw, eval.zdat = zdat.df)
    }
    objective.cache.enabled <- npObjectiveCacheEnabled()
    r.nn.cache.surface <- identical(bws$type %in% c("generalized_nn", "adaptive_nn"), TRUE) &&
      isTRUE(bws$ncon > 0L) &&
      isTRUE((bws$nuno + bws$nord) == 0L)
    r.exact.cache.surface <- identical(bws$type, "fixed") &&
      isTRUE(bws$ncon > 0L) &&
      isTRUE((bws$nuno + bws$nord) == 0L)
    r.objective.cache.kind <- if (isTRUE(r.nn.cache.surface)) {
      "nn"
    } else if (isTRUE(r.exact.cache.surface)) {
      "exact"
    } else {
      "none"
    }
    r.objective.cache.surface <- !identical(r.objective.cache.kind, "none")
    r.objective.cache.eligible <- isTRUE(bandwidth.compute) &&
      objective.cache.enabled &&
      r.objective.cache.surface
    r.objective.cache.stats <- list()
    r.objective.cache.disabled <- NULL
    if (isTRUE(bandwidth.compute) &&
        r.objective.cache.surface &&
        !objective.cache.enabled) {
      r.objective.cache.disabled <- .np_r_nn_cache_new(FALSE)
    }
    r_objective_cache_new <- function() {
      if (!r.objective.cache.eligible && is.null(r.objective.cache.disabled))
        return(NULL)
      key.length <- if (identical(r.objective.cache.kind, "nn")) bws$ncon else bws$ndim
      .np_r_nn_cache_new(r.objective.cache.eligible, key.length = key.length)
    }
    r_objective_cache_record <- function(cache) {
      st <- .np_r_nn_cache_stats(cache)
      if (!is.null(st))
        r.objective.cache.stats[[length(r.objective.cache.stats) + 1L]] <<- st
      invisible(NULL)
    }
    r_exact_cache_key <- function(x) {
      paste(sprintf("%a", as.double(x)), collapse = "\r")
    }
    r_objective_cache_lookup <- function(cache, sbw) {
      if (!is.environment(cache) || !isTRUE(cache$enabled))
        return(list(hit = FALSE, token = NULL, value = NULL))
      if (identical(r.objective.cache.kind, "nn"))
        return(.np_r_nn_cache_get(cache, as.integer(sbw$bw[sbw$icon])))
      if (identical(r.objective.cache.kind, "exact")) {
        token <- r_exact_cache_key(sbw$bw)
        return(.np_r_nn_cache_get_token(cache, token))
      }
      list(hit = FALSE, token = NULL, value = NULL)
    }
    r_objective_cache_store <- function(cache, token, value) {
      if (is.finite(value) && value < maxPenalty)
        .np_r_nn_cache_put(cache, token, value)
      invisible(NULL)
    }

    solve_cv_moment_system <- function(tyw, tww, W.eval.design, maxPenalty, Wz.eval = NULL) {
      neval <- ncol(tyw)
      ncoef <- nrow(tyw)
      pcoef <- ncol(W.eval.design)
      coef.out <- matrix(maxPenalty, nrow = pcoef, ncol = neval)
      ridge.grid <- npRidgeSequenceAdditive(n.train = n, cap = 1.0)
      ridge <- rep.int(ridge.grid[1L], neval)
      ridge.idx <- rep.int(1L, neval)
      doridge <- rep.int(TRUE, neval)

      while(any(doridge)){
        iloo <- seq_len(neval)[doridge]
        for (ii in iloo) {
          doridge[ii] <- FALSE
          ridge.val <- ridge[ii]*tyw[,ii][1]/NZD(tww[,,ii][1,1])
          theta.ii <- tryCatch(
            solve(tww[,,ii] + diag(rep(ridge[ii], ncoef)),
                  tyw[,ii] + c(ridge.val, rep(0, ncoef - 1))),
            error = function(e) e
          )
          if (inherits(theta.ii, "error")) {
            ridge.idx[ii] <- ridge.idx[ii] + 1L
            if (ridge.idx[ii] <= length(ridge.grid)) {
              ridge[ii] <- ridge.grid[ridge.idx[ii]]
              doridge[ii] <- TRUE
            }
            theta.ii <- rep(maxPenalty, ncoef)
          }

          if (is.null(Wz.eval)) {
            coef.out[,ii] <- theta.ii
          } else {
            coef.out[,ii] <- as.vector(crossprod(
              Wz.eval[ii,],
              matrix(theta.ii, nrow = ncol(Wz.eval), ncol = pcoef)
            ))
          }
        }
      }

      coef.out
    }

    cat.profile.cv <- NULL
    on.exit({ cat.profile.cv <- NULL }, add = TRUE)
    get_cat_profile_cv <- function() {
      if (is.null(cat.profile.cv)) {
        train.codes <- .np_cat_profile_code_matrix(zdat.df)
        train.keys <- .np_cat_profile_keys(train.codes)
        profile.keys <- unique(train.keys)
        train.id <- match(train.keys, profile.keys)
        train.rep <- match(profile.keys, train.keys)
        train.profile.codes <- train.codes[train.rep, , drop = FALSE]
        train.profile.dat <- zdat.df[train.rep, , drop = FALSE]
        yW.local <- cbind(ydat, W)
        p <- ncol(yW.local)
        cross.profile <- matrix(0.0, nrow = length(profile.keys), ncol = p * p)
        for (j in seq_len(p)) {
          for (k in seq_len(p)) {
            cross.profile[, (j - 1L) * p + k] <-
              .np_cat_profile_rowsum(yW.local[, j] * yW.local[, k],
                                     train.id, length(profile.keys))[, 1L]
          }
        }
        profile.index <- split(seq_along(train.id), train.id)
        profile.rows <- lapply(profile.index, function(idx) {
          Wg <- W[idx, , drop = FALSE]
          list(
            idx = idx,
            Wg = Wg,
            Wgy = Wg * ydat[idx],
            w1sq = Wg[, 1L] * Wg[, 1L]
          )
        })
        cat.profile.cv <<- list(
          train.id = train.id,
          profile.index = profile.index,
          profile.rows = profile.rows,
          train.profile.codes = train.profile.codes,
          train.profile.dat = train.profile.dat,
          G = length(profile.keys),
          yW = yW.local,
          p = p,
          cross.profile = cross.profile,
          ridge.grid = npRidgeSequenceAdditive(n.train = n, cap = 1.0)
        )
      }
      cat.profile.cv
    }

    lc_cat_profile_loo_mean <- function(sbw) {
      cp <- get_cat_profile_cv()
      train.id <- cp$train.id
      profile.index <- cp$profile.index
      profile.rows <- cp$profile.rows
      train.profile.codes <- cp$train.profile.codes
      train.profile.dat <- cp$train.profile.dat
      G <- cp$G
      yW.local <- cp$yW
      p <- cp$p
      cross.profile <- cp$cross.profile
      ridge.grid <- cp$ridge.grid

      L.profile <- .np_regression_cat_profile_kernel_matrix(
        eval.codes = train.profile.codes,
        train.codes = train.profile.codes,
        xdat = train.profile.dat,
        bws = sbw
      )

      flat.profile <- L.profile %*% cross.profile
      self.weight.profile <- L.profile[cbind(seq_len(G), seq_len(G))]
      mean.loo <- rep(maxPenalty, n)
      nc <- ncol(W)

      solve_one <- function(ii) {
        gg <- train.id[ii]
        mat.full <- matrix(flat.profile[gg, ], nrow = p, ncol = p)
        mat.full <- mat.full - self.weight.profile[gg] *
          tcrossprod(yW.local[ii, ])
        tyw.ii <- mat.full[-1L, 1L]
        tww.ii <- mat.full[-1L, -1L, drop = FALSE]

        ridge.idx <- 1L
        ridge <- ridge.grid[ridge.idx]
        repeat {
          ridge.val <- ridge * tyw.ii[1L] / NZD(tww.ii[1L, 1L])
          beta.ii <- tryCatch(
            solve(tww.ii + diag(rep(ridge, nc)),
                  tyw.ii + c(ridge.val, rep(0, nc - 1L))),
            error = function(e) e
          )
          if (!inherits(beta.ii, "error")) {
            return(as.double(W[ii,, drop = FALSE] %*% beta.ii))
          }
          ridge.idx <- ridge.idx + 1L
          if (ridge.idx > length(ridge.grid))
            break
          ridge <- ridge.grid[ridge.idx]
        }
        maxPenalty
      }

      ridge <- ridge.grid[1L]
      for (gg in seq_len(G)) {
        row.state <- profile.rows[[gg]]
        idx <- row.state$idx
        mat.full <- matrix(flat.profile[gg, ], nrow = p, ncol = p)
        tyw.full <- mat.full[-1L, 1L]
        tww.full <- mat.full[-1L, -1L, drop = FALSE]
        inv.full <- tryCatch(
          solve(tww.full + diag(rep(ridge, nc))),
          error = function(e) e
        )
        if (inherits(inv.full, "error")) {
          mean.loo[idx] <- vapply(idx, solve_one, numeric(1))
          next
        }

        Wg <- row.state$Wg
        sg <- self.weight.profile[gg]
        rhs <- matrix(tyw.full, nrow = length(idx), ncol = nc, byrow = TRUE)
        rhs <- rhs - sg * row.state$Wgy
        tww11 <- tww.full[1L, 1L] - sg * row.state$w1sq
        rhs[, 1L] <- rhs[, 1L] + ridge * rhs[, 1L] / NZD(tww11)

        u <- Wg %*% inv.full
        denom <- 1.0 - sg * rowSums(u * Wg)
        base <- rhs %*% inv.full
        alpha <- rowSums(u * rhs)
        beta <- base + (sg * alpha / denom) * u
        pred <- rowSums(Wg * beta)
        bad <- !is.finite(pred) | !is.finite(denom) |
          abs(denom) < sqrt(.Machine$double.eps)
        if (any(bad))
          pred[bad] <- vapply(idx[bad], solve_one, numeric(1))
        mean.loo[idx] <- pred
      }

      mean.loo
    }

    lc_cat_profile_partial_sums <- function(wj, partial.y) {
      cp <- get_cat_profile_cv()
      list(
        num.profile = .np_cat_profile_rowsum(partial.y * wj, cp$train.id, cp$G)[, 1L],
        den.profile = .np_cat_profile_rowsum(wj * wj, cp$train.id, cp$G)[, 1L]
      )
    }

    lc_cat_profile_partial_loo <- function(sbw, wj, partial.y, profile.sums = NULL) {
      cp <- get_cat_profile_cv()
      train.id <- cp$train.id
      train.profile.codes <- cp$train.profile.codes
      train.profile.dat <- cp$train.profile.dat
      G <- cp$G

      L.profile <- .np_regression_cat_profile_kernel_matrix(
        eval.codes = train.profile.codes,
        train.codes = train.profile.codes,
        xdat = train.profile.dat,
        bws = sbw
      )

      if (is.null(profile.sums))
        profile.sums <- lc_cat_profile_partial_sums(wj = wj, partial.y = partial.y)
      num.profile <- profile.sums$num.profile
      den.profile <- profile.sums$den.profile
      num <- as.vector(L.profile %*% num.profile)[train.id]
      den <- as.vector(L.profile %*% den.profile)[train.id]
      self.weight <- L.profile[cbind(seq_len(G), seq_len(G))][train.id]
      num <- num - self.weight * partial.y * wj
      den <- den - self.weight * wj * wj

      wj * num / NZD(den)
    }

    use_cat_profile_cv_lc <- function(sbw) {
      identical(reg.engine, "lc") &&
        identical(sbw$type, "fixed") &&
        npUseCategoricalCompress(ncon = sbw$ncon,
                                 ncat = sbw$nuno + sbw$nord) &&
        !miss.z &&
        isTRUE(sbw$ncon == 0L) &&
        isTRUE((sbw$nuno + sbw$nord) > 0L)
    }

    lp_full_coef <- function(sbw, leave.one.out.eval) {
      lp_state <- .npscoef_lp_state(
        bws = sbw,
        tzdat = zdat.df,
        ezdat = zdat.df,
        leave.one.out = leave.one.out.eval,
        where = "npscoefbw"
      )
      tensor.train <- .npscoef_row_tensor_design(W, lp_state$W.train)
      ytensor <- cbind(ydat, tensor.train)
      ksum.args <- list(
        txdat = lp_state$z.train,
        tydat = ytensor,
        weights = ytensor,
        bws = lp_state$rbw,
        leave.one.out = leave.one.out.eval,
        bandwidth.divide = TRUE
      )
      main.ks <- do.call(npksum, ksum.args)$ksum
      tyw <- main.ks[-1L, 1L, , drop = FALSE]
      if (length(dim(tyw)) == 3L)
        dim(tyw) <- c(dim(tyw)[1L], dim(tyw)[3L])
      tww <- main.ks[-1L, -1L, , drop = FALSE]
      solve_cv_moment_system(
        tyw = tyw,
        tww = tww,
        W.eval.design = W,
        Wz.eval = lp_state$W.eval,
        maxPenalty = maxPenalty
      )
    }

    lp_partial_coef <- function(sbw, wj, partial.y, leave.one.out.eval) {
      lp_state <- .npscoef_lp_state(
        bws = sbw,
        tzdat = zdat.df,
        ezdat = zdat.df,
        leave.one.out = leave.one.out.eval,
        where = "npscoefbw"
      )
      U <- lp_state$W.train * wj
      yU <- cbind(partial.y, U)
      ksum.args <- list(
        txdat = lp_state$z.train,
        tydat = yU,
        weights = yU,
        bws = lp_state$rbw,
        leave.one.out = leave.one.out.eval,
        bandwidth.divide = TRUE
      )
      main.ks <- do.call(npksum, ksum.args)$ksum
      tyw <- main.ks[-1L, 1L, , drop = FALSE]
      if (length(dim(tyw)) == 3L)
        dim(tyw) <- c(dim(tyw)[1L], dim(tyw)[3L])
      tww <- main.ks[-1L, -1L, , drop = FALSE]
      as.vector(solve_cv_moment_system(
        tyw = tyw,
        tww = tww,
        W.eval.design = matrix(1.0, nrow = n, ncol = 1L),
        Wz.eval = lp_state$W.eval,
        maxPenalty = maxPenalty
      ))
    }

    total.time <-
      system.time({
        if (bandwidth.compute){
          maxPenalty <- sqrt(.Machine$double.xmax)
          cv_state <- new.env(parent = emptyenv())
          cv_state$fast_total <- 0L
          cv_state$objective_fast <- FALSE
          cv_state$optim_progress <- NULL
          cv_state$optim_eval <- 0L
          cv_state$multistart_index <- NA_integer_
          cv_state$partial_progress <- NULL
          cv_state$partial_eval <- 0L
          cv_state$backfit_iteration <- NA_integer_
          cv_state$partial_index <- NA_integer_

          cv_progress_detail <- function(ridging = FALSE) {
            detail <- sprintf("multistart %d", cv_state$multistart_index)
            if (isTRUE(ridging)) {
              paste(detail, "near-singular system encountered, ridging", sep = ", ")
            } else {
              detail
            }
          }

          cv_progress_begin <- function() {
            cv_state$optim_eval <- 0L
            cv_state$optim_progress <- .np_progress_begin("Optimizing smooth coefficient bandwidth")
            invisible(NULL)
          }

          cv_progress_step <- function(ridging = FALSE) {
            cv_state$optim_eval <- cv_state$optim_eval + 1L
            .np_progress_bandwidth_activity_step(done = cv_state$optim_eval)
            cv_state$optim_progress <- .np_progress_step(
              state = cv_state$optim_progress,
              done = cv_state$optim_eval,
              detail = cv_progress_detail(ridging = ridging)
            )
            invisible(NULL)
          }

          cv_progress_end <- function(state) {
            if (is.null(state))
              return(invisible(NULL))

            if (isTRUE(state$known_total) && identical(state$last_done, state$total))
              return(invisible(NULL))

            state$last_emit <- -Inf
            .np_progress_end(state)
            invisible(NULL)
          }

          cv_progress_finish <- function(ridging = FALSE) {
            if (is.null(cv_state$optim_progress))
              return(invisible(NULL))

            cv_state$optim_progress$last_emit <- -Inf
            cv_state$optim_progress <- .np_progress_end(
              cv_state$optim_progress,
              detail = cv_progress_detail(ridging = ridging)
            )
            cv_state$optim_progress <- NULL
            invisible(NULL)
          }

          partial_progress_detail <- function(fv = NULL) {
            detail <- sprintf(
              "backfitting iteration %d of %d, partial residual %d of %d",
              cv_state$backfit_iteration,
              cv.num.iterations,
              cv_state$partial_index,
              ncol(W)
            )
            if (!is.null(fv)) {
              detail <- paste(
                detail,
                sprintf(
                  "fval %s",
                  format(signif(fv, digits = getOption("digits", 7L)), trim = TRUE)
                ),
                sep = ", "
              )
            }
            detail
          }

          partial_progress_begin <- function(iteration, partial.index) {
            cv_state$backfit_iteration <- iteration
            cv_state$partial_index <- partial.index
            cv_state$partial_eval <- 0L
            cv_state$partial_progress <- .np_progress_begin("Optimizing partial residual bandwidth")
            invisible(NULL)
          }

          partial_progress_step <- function(fv) {
            cv_state$partial_eval <- cv_state$partial_eval + 1L
            cv_state$partial_progress <- .np_progress_step(
              state = cv_state$partial_progress,
              done = cv_state$partial_eval,
              detail = partial_progress_detail(fv = fv)
            )
            invisible(NULL)
          }

          partial_progress_finish <- function(fv = NULL) {
            if (is.null(cv_state$partial_progress))
              return(invisible(NULL))

            cv_state$partial_progress$last_emit <- -Inf
            cv_state$partial_progress <- .np_progress_end(
              cv_state$partial_progress,
              detail = partial_progress_detail(fv = fv)
            )
            cv_state$partial_progress <- NULL
            invisible(NULL)
          }

          overall.cache <- NULL
          overall.cv.ls <- function(param) {
            cv_state$objective_fast <- FALSE
            sbw <- apply_bw_to_scbw(bws, param)
            if (!validateBandwidthTF(sbw) ||
                (!is.null(fixed.lower) && any(param < fixed.lower)) ||
                ((bws$nord+bws$nuno > 0) && any(param[!bws$icon] > 2.0*x.scale[!bws$icon])))
              return(maxPenalty)
            cache.hit <- r_objective_cache_lookup(overall.cache, sbw)
            if (isTRUE(cache.hit$hit)) {
              cv_progress_step()
              cv_state$fast_total <- cv_state$fast_total + 1L
              return(cache.hit$value)
            }
            cv_state$objective_fast <- npscoef_fast_eligible(sbw) ||
              use_cat_profile_cv_lc(sbw)

            if (identical(reg.engine, "lc")) {
              if (use_cat_profile_cv_lc(sbw)) {
                mean.loo <- lc_cat_profile_loo_mean(sbw)
              } else {
                tww <- npksum(txdat = zdat, tydat = yW, weights = yW, bws = sbw,
                              leave.one.out = TRUE)$ksum

                mean.loo <- rep(maxPenalty,n)
                ridge.grid <- npRidgeSequenceAdditive(n.train = n, cap = 1.0)
                ridge <- rep.int(ridge.grid[1L], n)
                ridge.idx <- rep.int(1L, n)
                doridge <- rep.int(TRUE, n)

                nc <- ncol(tww[-1,-1,1])

                while(any(doridge)){
                  iloo <- which(doridge)
                  for (ii in iloo) {
                    doridge[ii] <- FALSE
                    ridge.val <- ridge[ii]*tww[-1,1,ii][1]/NZD(tww[-1,-1,ii][1,1])
                    beta.ii <- tryCatch(
                      solve(tww[-1,-1,ii] + diag(rep(ridge[ii], nc)),
                            tww[-1,1,ii] + c(ridge.val, rep(0, nc - 1))),
                      error = function(e) e
                    )
                    if (inherits(beta.ii, "error")) {
                      ridge.idx[ii] <- ridge.idx[ii] + 1L
                      if (ridge.idx[ii] <= length(ridge.grid)) {
                        ridge[ii] <- ridge.grid[ridge.idx[ii]]
                        doridge[ii] <- TRUE
                        next
                      }
                      mean.loo[ii] <- NA_real_
                      next
                    }
                    mean.loo[ii] <- W[ii,, drop = FALSE] %*% beta.ii
                  }
                }
              }
            } else {
              coef.loo <- lp_full_coef(sbw = sbw, leave.one.out.eval = TRUE)
              mean.loo <- rowSums(W * t(coef.loo))
            }

            if (!all(is.finite(mean.loo))) {
              cv_progress_step(ridging = TRUE)
              if (isTRUE(cv_state$objective_fast))
                cv_state$fast_total <- cv_state$fast_total + 1L
              r_objective_cache_store(overall.cache, cache.hit$token, maxPenalty)
              return(maxPenalty)
            }

            if(!any(mean.loo == maxPenalty)){
              fv <- sum((ydat-mean.loo)^2)/n
              cv_progress_step()
            } else {
              cv_progress_step(ridging = TRUE)
              fv <- maxPenalty
            }

            if (isTRUE(cv_state$objective_fast))
              cv_state$fast_total <- cv_state$fast_total + 1L
            r_objective_cache_store(overall.cache, cache.hit$token, fv)

            return((if (is.finite(fv)) fv else maxPenalty))

          }

          scoef.loo.args <- list(
            bws = bws, txdat = xdat, tydat = ydat,
            leave.one.out = TRUE, iterate = TRUE,
            maxiter = backfit.maxiter, tol = backfit.tol,
            betas = TRUE, errors = FALSE,
            .np_fit_progress_allow = FALSE
          )
          if (!miss.z)
            scoef.loo.args$tzdat <- zdat

          current.partial.profile <- NULL
          current.partial.cache <- NULL
          
          partial.cv.ls <- function(param, partial.index) {
            cv_state$objective_fast <- FALSE
            sbw <- apply_bw_to_scbw(bws, param)

            if (!validateBandwidthTF(sbw) ||
                (!is.null(fixed.lower) && any(param < fixed.lower)) ||
                ((bws$nord+bws$nuno > 0) && any(param[!bws$icon] > 2.0*x.scale[!bws$icon])))
              return(maxPenalty)
            cache.hit <- r_objective_cache_lookup(current.partial.cache, sbw)
            if (isTRUE(cache.hit$hit)) {
              cv_state$fast_total <- cv_state$fast_total + 1L
              partial_progress_step(fv = cache.hit$value)
              return(cache.hit$value)
            }
            cv_state$objective_fast <- npscoef_fast_eligible(sbw) ||
              use_cat_profile_cv_lc(sbw)
            
            if (backfit.iterate){
              local.bws <- bws
              if (is.null(local.bws$bw.fitted)) {
                local.bws$bw.fitted <- matrix(
                  local.bws$bw,
                  nrow = length(local.bws$bw),
                  ncol = n.part
                )
              }
              local.bws$bw.fitted[, partial.index] <- sbw$bw
              local.args <- scoef.loo.args
              local.args$bws <- local.bws
              scoef.loo <- do.call(npscoef, local.args)
              partial.loo <- W[,partial.index]*scoef.loo$beta[,partial.index]
            } else {
              wj <- W[,partial.index]
              if (identical(reg.engine, "lc")) {
                if (use_cat_profile_cv_lc(sbw)) {
                  partial.loo <- lc_cat_profile_partial_loo(
                    sbw = sbw,
                    wj = wj,
                    partial.y = partial.orig,
                    profile.sums = current.partial.profile
                  )
                } else {
                  tww <- npksum(txdat=zdat,
                                tydat=cbind(partial.orig * wj, wj * wj),
                                weights=cbind(partial.orig * wj, 1),
                                bws=sbw,
                                leave.one.out=TRUE)$ksum

                  partial.loo <- wj * tww[1,2,]/NZD(tww[2,2,])
                }
              } else {
                partial.loo <- wj * lp_partial_coef(
                  sbw = sbw,
                  wj = wj,
                  partial.y = partial.orig,
                  leave.one.out.eval = TRUE
                )
              }
            }
            

            fv <- sum((partial.orig - partial.loo)^2)/n

            if (isTRUE(cv_state$objective_fast))
              cv_state$fast_total <- cv_state$fast_total + 1L
            r_objective_cache_store(current.partial.cache, cache.hit$token, fv)

            partial_progress_step(fv = fv)
            return((if (is.finite(fv)) fv else maxPenalty))
          }

          ## Now we implement multistarting

          fval.min <- .Machine$double.xmax
          have_best <- FALSE
          numimp <- 0
          value.overall <- numeric(nmulti)
          num.feval.overall <- 0
          overall.cache <- r_objective_cache_new()

          x.scale <- sapply(seq_len(bws$ndim), function(i){
            if (dati$icon[i]){
              return(1.059224*((if (bws$scaling) 1.0 else mysd[sum(dati$icon[seq_len(i)])]*nconfac)))
            }
            
            if (dati$iord[i])
              return(0.5*oMaxL(dati$all.nlev[[i]], kertype = bws$okertype)*
                     (if (bws$scaling) ncatfac else 1.0))
            
            if (dati$iuno[i])
              return(0.5*uMaxL(dati$all.nlev[[i]], kertype = bws$ukertype)*
                     (if (bws$scaling) ncatfac else 1.0))       
          })
          fixed.lower <- if (identical(bws$type, "fixed")) {
            out <- rep.int(0, length(x.scale))
            out[dati$icon] <- x.scale[dati$icon] * start.controls$scale.factor.search.lower
            out
          } else {
            NULL
          }

          optim.control <- list(abstol = optim.abstol,
                                reltol = optim.reltol,
                                maxit = optim.maxit)

          for (i in seq_len(nmulti)) {
            cv_state$multistart_index <- i
            cv_progress_begin()
            
            if (i == 1) {
              tbw <- .npscoef_default_start_bandwidth(
                param = x.scale,
                bwtype = bws$type,
                nobs = n,
                start.controls = start.controls,
                icon = dati$icon,
                iord = dati$iord,
                iuno = dati$iuno
              )
              if (all(bws$bw != 0) &&
                  .npscoef_candidate_is_admissible(param = bws$bw, bwtype = bws$type, nobs = n,
                                                   lower = fixed.lower)) {
                tbw <- .npscoef_finalize_bandwidth(
                  param = bws$bw,
                  bwtype = bws$type,
                  nobs = n,
                  lower = fixed.lower,
                  where = "npscoefbw"
                )
              }
            } else {
              tbw <- .npscoef_random_start_bandwidth(
                param = x.scale,
                bwtype = bws$type,
                nobs = n,
                start.controls = start.controls,
                icon = dati$icon,
                iord = dati$iord,
                iuno = dati$iuno
              )
            }

            suppressWarnings(optim.return <- optim(tbw,
                                                   fn = overall.cv.ls,
                                                   method = optim.method,
                                                   control = optim.control))
            if(!is.null(optim.return$counts) && length(optim.return$counts) > 0)
              num.feval.overall <- num.feval.overall + optim.return$counts[1]
            attempts <- 0
            while((optim.return$convergence != 0) && (attempts <= optim.maxattempts)) {
              attempts <- attempts + 1
              tbw <- .npscoef_random_start_bandwidth(
                param = x.scale,
                bwtype = bws$type,
                nobs = n,
                start.controls = start.controls,
                icon = dati$icon,
                iord = dati$iord,
                iuno = dati$iuno
              )
              optim.control <- lapply(optim.control, '*', 10.0)
              suppressWarnings(optim.return <- optim(tbw,
                                                     fn = overall.cv.ls,
                                                     method = optim.method,
                                                     control = optim.control))
              if(!is.null(optim.return$counts) && length(optim.return$counts) > 0)
                num.feval.overall <- num.feval.overall + optim.return$counts[1]

            }

            cv_progress_finish()

            value.overall[i] <- optim.return$value

            if (.npscoef_candidate_is_admissible(
              param = optim.return$par,
              bwtype = bws$type,
              nobs = n,
              lower = fixed.lower
            ) && (!have_best || optim.return$value < fval.min)) {
              param <- .npscoef_finalize_bandwidth(
                param = optim.return$par,
                bwtype = bws$type,
                nobs = n,
                lower = fixed.lower,
                where = "npscoefbw"
              )
              min.overall <- optim.return$value
              fval.min <- min.overall ## Added by jracine Jul 22 2010
              numimp.overall <- numimp + 1
              best.overall <- i
              have_best <- TRUE
            }

            .np_progress_bandwidth_multistart_step(done = i, total = nmulti)
          }
          r_objective_cache_record(overall.cache)

          if (!have_best) {
            if (identical(bws$type, "fixed")) {
              stop("npscoefbw: no feasible fixed bandwidths found", call. = FALSE)
            }
            stop("npscoefbw: no feasible bandwidths found", call. = FALSE)
          }

          param.overall <- bws$bw <- .npscoef_finalize_bandwidth(
            param = param,
            bwtype = bws$type,
            nobs = n,
            where = "npscoefbw"
          )
          bws <- apply_bw_to_scbw(bws, bws$bw)

          if(cv.iterate){
            n.part <- (ncol(xdat)+1)
            backfit.progress <- .np_progress_begin(
              "Backfitting smooth coefficient bandwidth",
              total = cv.num.iterations
            )
            on.exit(cv_progress_end(backfit.progress), add = TRUE)
            bws$bw.fitted <- matrix(data = bws$bw, nrow = length(bws$bw), ncol = n.part)
            ## obtain matrix of alpha.hat | h0 and beta.hat | h0

            scoef.args <- list(
              bws = bws,
              txdat = xdat,
              tydat = ydat,
              iterate = FALSE,
              betas = TRUE,
              errors = FALSE,
              .np_fit_progress_allow = FALSE
            )
            if (!miss.z)
              scoef.args$tzdat <- zdat
            scoef <- do.call(npscoef, scoef.args)
            
            resid.full <- ydat - scoef$mean

            
            for (i in seq_len(cv.num.iterations)) {
              backfit.progress <- .np_progress_step(
                state = backfit.progress,
                done = i,
                detail = sprintf("iteration %d of %d", i, cv.num.iterations)
              )

              for (j in seq_len(n.part)) {
                ## estimate partial residuals
                partial.orig <- W[,j] * scoef$beta[,j] + resid.full
                current.partial.profile <- if (identical(reg.engine, "lc") &&
                    npUseCategoricalCompress(ncon = bws$ncon,
                                             ncat = bws$nuno + bws$nord) &&
                    !miss.z &&
                    isTRUE(bws$ncon == 0L) &&
                    isTRUE((bws$nuno + bws$nord) > 0L)) {
                  lc_cat_profile_partial_sums(wj = W[, j], partial.y = partial.orig)
                } else {
                  NULL
                }
                partial_progress_begin(iteration = i, partial.index = j)
                current.partial.cache <- r_objective_cache_new()
                
                ## minimise
                partial.start <- bws$bw.fitted[, j]
                suppressWarnings(optim.return <-
                                 optim(partial.start, fn = partial.cv.ls,
                                       method = optim.method,
                                       control = optim.control,
                                       partial.index = j))
                if(!is.null(optim.return$counts) && length(optim.return$counts) > 0)
                  num.feval.overall <- num.feval.overall + optim.return$counts[1]
                partial_progress_finish(fv = optim.return$value)
                r_objective_cache_record(current.partial.cache)
                current.partial.cache <- NULL
                current.partial.profile <- NULL
                
                ## grab parameter
                bws$bw.fitted[,j] <- optim.return$par

                if (backfit.iterate){
                  ## re-estimate all betas
                  scoef.args <- list(
                    bws = bws, txdat = xdat, tydat = ydat,
                    iterate = TRUE, maxiter = backfit.maxiter,
                    tol = backfit.tol, betas = TRUE, errors = FALSE,
                    .np_fit_progress_allow = FALSE
                  )
                  if (!miss.z)
                    scoef.args$tzdat <- zdat
                  scoef <- do.call(npscoef, scoef.args)
                  resid.full <- ydat - scoef$mean
                } else {
                  bws$bw <- bws$bw.fitted[,j]
                  ## estimate new beta.hats

                  bws <- apply_bw_to_scbw(bws, bws$bw)

                  if (identical(reg.engine, "lc")) {
                    wj <- W[,j]
                    tww <- npksum(txdat=zdat,
                                  tydat=cbind(partial.orig * wj, wj * wj),
                                  weights=cbind(partial.orig * wj, 1),
                                  bws=bws)$ksum
                    scoef$beta[,j] <- tww[1,2,]/NZD(tww[2,2,])
                  } else {
                    wj <- W[,j]
                    scoef$beta[,j] <- lp_partial_coef(
                      sbw = bws,
                      wj = wj,
                      partial.y = partial.orig,
                      leave.one.out.eval = FALSE
                    )
                  }
                  
                  bws$bw <- param.overall
                  bws <- apply_bw_to_scbw(bws, bws$bw)
                  ## estimate new full residuals 
                  resid.full <- partial.orig - W[,j] * scoef$beta[,j]
                }
              }
            }
            scoef.loo.args <- list(
              bws = bws, txdat = xdat, tydat = ydat,
              iterate = TRUE, maxiter = backfit.maxiter,
              tol = backfit.tol, leave.one.out = TRUE,
              errors = FALSE,
              .np_fit_progress_allow = FALSE
            )
            if (!miss.z)
              scoef.loo.args$tzdat <- zdat
            scoef.loo <- do.call(npscoef, scoef.loo.args)$mean
            bws$fval.fitted <- sum((ydat - scoef.loo)^2)/n
          }

          bws$fval = min.overall
          bws$ifval = best.overall
          bws$num.feval = num.feval.overall
          bws$num.feval.fast = cv_state$fast_total
          if (length(r.objective.cache.stats)) {
            bws$nn.cache <- .np_r_nn_cache_combine_stats(r.objective.cache.stats)
          } else {
            bws$nn.cache <- .np_r_nn_cache_stats(r.objective.cache.disabled)
          }
          bws$numimp = numimp.overall
          bws$fval.vector = value.overall
        }
      })[["elapsed"]]
    
    bws$sfactor <- bws$bandwidth <- bws$bw
    nfactor <- nrow^(-2.0/(2.0*bws$ckerorder+bws$ncon))
    dfactor <- EssDee(zdat[, dati$icon, drop = FALSE])*nrow^(-1.0/(2.0*bws$ckerorder+sum(dati$icon)))

    if (bws$scaling) {
      bws$bandwidth[dati$icon] <- bws$bandwidth[dati$icon]*dfactor

      if(bws$nuno > 0)
        bws$bandwidth[dati$iuno] <- bws$bandwidth[dati$iuno]*nfactor

      if(bws$nord > 0)
        bws$bandwidth[dati$iord] <- bws$bandwidth[dati$iord]*nfactor
      
    } else {
      bws$sfactor[dati$icon] <- bws$sfactor[dati$icon]/dfactor

      if(bws$nuno > 0)
        bws$sfactor[dati$iuno] <- bws$sfactor[dati$iuno]/nfactor

      if(bws$nord > 0)
        bws$sfactor[dati$iord] <- bws$sfactor[dati$iord]/nfactor
    }

    ## Restore seed

    .np_seed_exit(seed.state, remove_if_absent = TRUE)
    nn.cache <- bws$nn.cache
    
    bws <- scbandwidth(bw = bws$bw,
                       regtype = regtype,
                       basis = if (is.null(bws$basis)) "glp" else bws$basis,
                       degree = bws$degree,
                       bernstein.basis = bws$bernstein.basis,
                       bwmethod = bws$method,
                       bwscaling = bws$scaling,
                       bwtype = bws$type,
                       ckertype = bws$ckertype,
                       ckerorder = bws$ckerorder,
                       ckerbound = bws$ckerbound,
                       ckerlb = bws$ckerlb,
                       ckerub = bws$ckerub,
                       ukertype = bws$ukertype,
                       okertype = bws$okertype,
                       fval = bws$fval,
                       ifval = bws$ifval,
                       num.feval = bws$num.feval,
                       num.feval.fast = bws$num.feval.fast,
                       numimp = bws$numimp,
                       fval.vector = bws$fval.vector,
                       bw.fitted = bws$bw.fitted,
                       fval.fitted = bws$fval.fitted,
                       nobs = bws$nobs,
                       xdati = bws$xdati,
                       ydati = bws$ydati,
                       zdati = bws$zdati,
                       xnames = bws$xnames,
                       ynames = bws$ynames,
                       znames = bws$znames,
                       sfactor = bws$sfactor,
                       bandwidth = bws$bandwidth,
                       sdev = bws$sdev,
                       nconfac = bws$nconfac,
                       ncatfac = bws$ncatfac,
                       rows.omit = rows.omit,
                       bandwidth.compute = bandwidth.compute,
                       optim.method = optim.method,
                       total.time = total.time)
    bws$nn.cache <- nn.cache
    bws <- npSetScaleFactorSearchLower(bws, scale.factor.search.lower)

    bws
  }

.npscoefbw_build_scbandwidth <- function(xdat,
                                         ydat,
                                         zdat,
                                         bws,
                                         bandwidth.compute,
                                         reg.args) {
  miss.z <- is.null(zdat)
  zdati <- if (miss.z) NULL else untangle(zdat)
  znames <- if (miss.z) NULL else names(zdat)

  sbw.args <- c(
    list(
      bw = bws,
      nobs = dim(xdat)[1],
      xdati = untangle(xdat),
      ydati = untangle(data.frame(ydat)),
      zdati = zdati,
      xnames = names(xdat),
      ynames = deparse(substitute(ydat)),
      znames = znames,
      bandwidth.compute = bandwidth.compute
    ),
    reg.args
  )

  out <- do.call(scbandwidth, sbw.args)
  if (!is.null(reg.args$scale.factor.search.lower))
    out$scale.factor.search.lower <- npResolveScaleFactorLowerBound(
      reg.args$scale.factor.search.lower
    )
  out
}

.npscoefbw_run_fixed_degree <- function(xdat, ydat, zdat, bws, reg.args, opt.args) {
  tbw <- .npscoefbw_build_scbandwidth(
    xdat = xdat,
    ydat = ydat,
    zdat = zdat,
    bws = bws,
    bandwidth.compute = opt.args$bandwidth.compute,
    reg.args = reg.args
  )

  scbw.args <- c(list(xdat = xdat, ydat = ydat, bws = tbw), opt.args)
  if (!is.null(zdat))
    scbw.args$zdat <- zdat
  do.call(npscoefbw.scbandwidth, scbw.args)
}

.npscoefbw_nomad_controls <- function(search.engine) {
  .np_degree_search_engine_controls(search.engine)
}

.npscoefbw_fast_eligible <- function(sbw, eval.zdat) {
  if (!identical(sbw$type, "fixed"))
    return(FALSE)

  tdati <- if (is.null(sbw$zdati)) sbw$xdati else sbw$zdati
  eval.zdat <- toFrame(eval.zdat)

  ckerbound <- if (is.null(sbw$ckerbound) || !length(sbw$ckerbound)) {
    "none"
  } else {
    as.character(sbw$ckerbound)[1L]
  }
  if (!identical(ckerbound, "none"))
    return(FALSE)

  ckertype <- as.character(sbw$ckertype)[1L]
  ckerorder <- as.integer(sbw$ckerorder)[1L]
  if (identical(ckertype, "truncated gaussian"))
    return(FALSE)
  if (!identical(ckertype, "uniform") &&
      (!is.finite(ckerorder) || ckerorder != 2L))
    return(FALSE)

  if (any(tdati$icon) && !npLogicalOption("np.largeh", TRUE))
    return(FALSE)
  if ((any(tdati$iuno) || any(tdati$iord)) &&
      !npLogicalOption("np.largelambda", TRUE))
    return(FALSE)

  fast_largeh_tol <- npLargehRelTol()
  fast_disc_tol <- npDiscUpperRelTol()

  cont_utol <- switch(
    ckertype,
    gaussian = sqrt(-2.0 * log(1.0 - fast_largeh_tol)),
    epanechnikov = sqrt(fast_largeh_tol),
    uniform = 1.0 - 32.0 * .Machine$double.eps,
    0.0
  )

  cont_hmin <- numeric(0)
  if (any(sbw$icon) && is.finite(cont_utol) && cont_utol > 0) {
    zcon <- eval.zdat[, sbw$icon, drop = FALSE]
    cont_hmin <- vapply(zcon, function(col) {
      vals <- as.double(col)
      if (!length(vals) || any(!is.finite(vals)))
        return(Inf)
      diff(range(vals)) / cont_utol
    }, numeric(1))
  }

  disc_upper_tol <- function(upper) {
    max(fast_disc_tol * abs(upper),
        16.0 * .Machine$double.eps * max(1.0, abs(upper)))
  }

  uno_upper <- numeric(0)
  if (any(sbw$iuno)) {
    uno_idx <- which(sbw$iuno)
    uno_upper <- vapply(uno_idx, function(i) {
      uMaxL(tdati$all.nlev[[i]], kertype = sbw$ukertype)
    }, numeric(1))
  }

  ord_upper <- numeric(0)
  if (any(sbw$iord)) {
    ord_idx <- which(sbw$iord)
    ord_upper <- vapply(ord_idx, function(i) {
      oMaxL(tdati$all.nlev[[i]], kertype = sbw$okertype)
    }, numeric(1))
  }

  bwv <- sbw$bandwidth[[1L]]
  if (!length(bwv) || length(bwv) != length(sbw$icon))
    return(FALSE)

  if (any(sbw$icon)) {
    bw_cont <- bwv[sbw$icon]
    if (any(!is.finite(bw_cont)) || any(bw_cont <= 0) ||
        any(bw_cont < cont_hmin))
      return(FALSE)
  }

  if (any(sbw$iuno)) {
    bw_uno <- bwv[sbw$iuno]
    ok_uno <- mapply(function(bw, upper) {
      is.finite(bw) && abs(bw - upper) <= disc_upper_tol(upper)
    }, bw = bw_uno, upper = uno_upper, SIMPLIFY = TRUE, USE.NAMES = FALSE)
    if (!all(ok_uno))
      return(FALSE)
  }

  if (any(sbw$iord)) {
    bw_ord <- bwv[sbw$iord]
    ok_ord <- mapply(function(bw, upper) {
      is.finite(bw) && abs(bw - upper) <= disc_upper_tol(upper)
    }, bw = bw_ord, upper = ord_upper, SIMPLIFY = TRUE, USE.NAMES = FALSE)
    if (!all(ok_ord))
      return(FALSE)
  }

  TRUE
}

.npscoefbw_eval_only <- function(xdat,
                                 ydat,
                                 zdat,
                                 bws,
                                 invalid.penalty = c("baseline", "large"),
                                 penalty.multiplier = 10) {
  invalid.penalty <- match.arg(invalid.penalty)
  base.penalty <- switch(
    invalid.penalty,
    baseline = if (is.finite(bws$fval[1L])) as.numeric(bws$fval[1L]) else 1,
    large = 1
  )
  base.penalty <- max(abs(base.penalty), 1)
  penalty <- penalty.multiplier * base.penalty

  fit <- tryCatch(
    npscoef(
      bws = bws,
      txdat = xdat,
      tydat = ydat,
      tzdat = zdat,
      leave.one.out = TRUE,
      iterate = FALSE,
      betas = FALSE,
      errors = FALSE,
      .np_fit_progress_allow = FALSE
    ),
    error = function(e) e
  )

  if (inherits(fit, "error") || is.null(fit$mean) || any(!is.finite(fit$mean))) {
    return(list(objective = penalty, num.feval = 1L, num.feval.fast = 0L))
  }

  list(
    objective = as.numeric(mean((as.double(ydat) - as.double(fit$mean))^2)),
    num.feval = 1L,
    num.feval.fast = if (.npscoefbw_fast_eligible(bws, eval.zdat = if (is.null(zdat)) xdat else zdat)) 1L else 0L
  )
}

.npscoefbw_nomad_search <- function(xdat,
                                    ydat,
                                    zdat,
                                    bws,
                                    reg.args,
                                    opt.args,
                                    degree.search,
                                    nomad.inner.nmulti = 0L,
                                    nomad.opts = list(),
                                    source = "explicit",
                                    reason = NULL,
                                    progress_label = NULL) {
  if (isTRUE(degree.search$verify))
    stop("automatic degree search with search.engine='nomad' does not support degree.verify")
  if (is.null(opt.args$nomad.opts) && length(nomad.opts))
    opt.args$nomad.opts <- nomad.opts

  if (!identical(opt.args$bandwidth.compute, TRUE))
    stop("automatic degree search with search.engine='nomad' requires bandwidth.compute=TRUE")

  eval.zdat <- if (is.null(zdat)) xdat else zdat

  template.reg.args <- reg.args
  template.reg.args$regtype <- "lp"
  template.reg.args$degree <- as.integer(degree.search$start.degree)
  template.reg.args$bernstein.basis <- degree.search$bernstein.basis
  template.type <- if (is.recursive(bws) && !is.null(bws$type) && length(bws$type)) {
    as.character(bws$type[1L])
  } else if (!is.null(template.reg.args$bwtype) && length(template.reg.args$bwtype)) {
    as.character(template.reg.args$bwtype[1L])
  } else {
    "fixed"
  }
  template.bws <- bws
  if (!identical(template.type, "fixed") &&
      is.numeric(template.bws) &&
      length(template.bws) > 0L &&
      all(template.bws == 0)) {
    nn.start <- max(2L, min(as.integer(NROW(eval.zdat)) - 1L, as.integer(round(sqrt(NROW(eval.zdat))))))
    template.bws <- rep.int(as.double(nn.start), length(template.bws))
  }

  template <- .npscoefbw_build_scbandwidth(
    xdat = xdat,
    ydat = ydat,
    zdat = zdat,
    bws = template.bws,
    bandwidth.compute = FALSE,
    reg.args = template.reg.args
  )

  if (!(template$type %in% c("fixed", "generalized_nn", "adaptive_nn")))
    stop("automatic degree search with search.engine='nomad' requires bwtype='fixed', 'generalized_nn', or 'adaptive_nn'")
  setup <- .npregbw_nomad_bw_setup(xdat = eval.zdat, template = template, allow.extended.nn = TRUE)
  ncon <- length(setup$cont_idx)
  ncat <- length(setup$cat_idx)
  ndeg <- length(degree.search$start.degree)
  nomad.nmulti <- if (is.null(opt.args$nmulti)) npDefaultNmulti(NCOL(eval.zdat)) else npValidateNmulti(opt.args$nmulti[1L])

  bw_bounds <- .npregbw_nomad_bw_bounds(template = template, setup = setup)
  opt.value <- function(name, default) {
    if (is.null(opt.args[[name]])) default else opt.args[[name]]
  }
  bw_start_bounds <- .np_nomad_bw_restart_start_bounds(
    bounds = bw_bounds,
    setup = setup,
    opt.value = opt.value,
    where = "npscoefbw"
  )

  x0 <- c(
    .npregbw_nomad_complete_bw_start_point(
      point = if (all(template$bw == 0)) NULL else .npregbw_nomad_bw_to_point(template$bw, template = template, setup = setup),
      bounds = bw_bounds,
      setup = setup,
      initial = bw_start_bounds$initial,
      where = "npscoefbw"
    ),
    as.integer(degree.search$start.degree)
  )
  lb <- c(bw_bounds$lower, degree.search$lower)
  ub <- c(bw_bounds$upper, degree.search$upper)
  bbin <- c(bw_bounds$bbin, rep.int(1L, ndeg))
  coordinate.roles <- .np_nomad_coordinate_roles(bw_bounds, degree.search)
  baseline.record <- NULL
  nomad.num.feval.total <- 0
  nomad.num.feval.fast.total <- 0

  .np_nomad_baseline_note(degree.search$start.degree)

  eval_fun <- function(point) {
    point <- as.numeric(point)
    degree <- as.integer(round(point[ncon + ncat + seq_len(ndeg)]))
    degree <- .np_degree_clip_to_grid(degree, degree.search$candidates)
    bw_vec <- .npregbw_nomad_point_to_bw(point[seq_len(ncon + ncat)], template = template, setup = setup)

    eval.reg.args <- reg.args
    eval.reg.args$regtype <- "lp"
    eval.reg.args$degree <- degree
    eval.reg.args$bernstein.basis <- degree.search$bernstein.basis
    eval.reg.args$bwtype <- template$type

    tbw <- .npscoefbw_build_scbandwidth(
      xdat = xdat,
      ydat = ydat,
      zdat = zdat,
      bws = bw_vec,
      bandwidth.compute = FALSE,
      reg.args = eval.reg.args
    )

    out <- .npscoefbw_eval_only(
      xdat = xdat,
      ydat = ydat,
      zdat = zdat,
      bws = tbw,
      invalid.penalty = "baseline",
      penalty.multiplier = if (is.null(opt.args$penalty.multiplier)) 10 else opt.args$penalty.multiplier
    )
    nomad.num.feval.total <<- nomad.num.feval.total + as.numeric(out$num.feval[1L])
    nomad.num.feval.fast.total <<- nomad.num.feval.fast.total + as.numeric(out$num.feval.fast[1L])

    list(
      objective = out$objective,
      degree = degree,
      num.feval = out$num.feval
    )
  }

  build_payload <- function(point, best_record, solution, interrupted) {
    point <- as.numeric(point)
    degree <- as.integer(best_record$degree)
    bw_vec <- .npregbw_nomad_point_to_bw(point[seq_len(ncon + ncat)], template = template, setup = setup)
    powell.elapsed <- NA_real_

    build_direct_payload <- function() {
      final.reg.args <- reg.args
      final.reg.args$regtype <- "lp"
      final.reg.args$degree <- degree
      final.reg.args$bernstein.basis <- degree.search$bernstein.basis
      final.reg.args$bwtype <- template$type

      tbw <- .npscoefbw_build_scbandwidth(
        xdat = xdat,
        ydat = ydat,
        zdat = zdat,
        bws = bw_vec,
        bandwidth.compute = FALSE,
        reg.args = final.reg.args
      )
      tbw$fval <- as.numeric(best_record$objective)
      tbw$ifval <- as.numeric(best_record$objective)
      tbw$num.feval <- as.numeric(nomad.num.feval.total)
      tbw$num.feval.fast <- as.numeric(nomad.num.feval.fast.total)
      tbw$numimp <- 0
      tbw$fval.vector <- as.numeric(best_record$objective)
      tbw$total.time <- NA_real_
      if (!is.null(tbw$method) && length(tbw$method))
        tbw$pmethod <- bwmToPrint(as.character(tbw$method[1L]))
      tbw
    }

    direct.payload <- build_direct_payload()
    direct.objective <- as.numeric(best_record$objective)

    if (identical(degree.search$engine, "nomad+powell")) {
      hot.reg.args <- reg.args
      hot.reg.args$regtype <- "lp"
      hot.reg.args$degree <- degree
      hot.reg.args$bernstein.basis <- degree.search$bernstein.basis
      hot.reg.args$bwtype <- template$type
      hot.opt.args <- .np_nomad_powell_hotstart_opt_args(
        opt.args,
        strategy = "single_iteration",
        remin = isTRUE(opt.args$powell.remin)
      )
      hot.opt.args$bwsolver <- NULL
      powell.start <- proc.time()[3L]
      hot.payload <- .np_nomad_with_powell_progress(
        degree = degree,
        best_record = best_record,
        expr = local({
          .npscoefbw_run_fixed_degree(
            xdat = xdat,
            ydat = ydat,
            zdat = zdat,
            bws = bw_vec,
            reg.args = hot.reg.args,
            opt.args = hot.opt.args
          )
        })
      )
      powell.elapsed <- proc.time()[3L] - powell.start
      direct.payload$num.feval <- as.numeric(direct.payload$num.feval[1L]) + as.numeric(hot.payload$num.feval[1L])
      direct.payload$num.feval.fast <- as.numeric(direct.payload$num.feval.fast[1L]) + as.numeric(hot.payload$num.feval.fast[1L])
      hot.payload$num.feval <- direct.payload$num.feval
      hot.payload$num.feval.fast <- direct.payload$num.feval.fast
      if (!is.null(hot.payload$method) && length(hot.payload$method))
        hot.payload$pmethod <- bwmToPrint(as.character(hot.payload$method[1L]))
      hot.objective <- as.numeric(hot.payload$fval[1L])
      if (is.finite(hot.objective) &&
          .np_degree_better(hot.objective, direct.objective, direction = "min")) {
        return(list(payload = hot.payload, objective = hot.objective, powell.time = powell.elapsed))
      }
    }

    list(payload = direct.payload, objective = direct.objective, powell.time = powell.elapsed)
  }

  .np_nomad_search(
    engine = degree.search$engine,
    baseline_record = baseline.record,
    start_degree = degree.search$start.degree,
    x0 = x0,
    bbin = bbin,
    lb = lb,
    ub = ub,
    eval_fun = eval_fun,
    build_payload = build_payload,
    direction = "min",
    objective_name = "fval",
    nmulti = nomad.nmulti,
    nomad.inner.nmulti = nomad.inner.nmulti,
    random.seed = if (!is.null(opt.args$random.seed)) opt.args$random.seed else 42L,
    remin = isTRUE(opt.args$nomad.remin),
    nomad.opts = if (is.null(opt.args$nomad.opts)) list() else opt.args$nomad.opts,
    native.r.bridge = TRUE,
    source = source,
    reason = reason,
    progress_label = progress_label,
    start.lower = c(bw_start_bounds$lower, degree.search$lower),
    start.upper = c(bw_start_bounds$upper, degree.search$upper),
    coordinate.roles = coordinate.roles,
    degree_spec = list(
      initial = degree.search$start.degree,
      lower = degree.search$lower,
      upper = degree.search$upper,
      basis = degree.search$basis,
      nobs = degree.search$nobs,
      user_supplied = degree.search$start.user
    )
  )
}

.npscoefbw_degree_search_controls <- function(regtype,
                                              regtype.named,
                                              cv.iterate,
                                              cv.iterate.named,
                                              bandwidth.compute,
                                              ncon,
                                              nobs,
                                              basis,
                                              degree.select,
                                              search.engine,
                                              degree.min,
                                              degree.max,
                                              degree.start,
                                              degree.restarts,
                                              degree.max.cycles,
                                              degree.verify,
                                              bernstein.basis,
                                              bernstein.named,
                                              nomad.source = "explicit",
                                              nomad.auto.filled = character()) {
  degree.select <- match.arg(degree.select, c("manual", "coordinate", "exhaustive"))
  if (identical(degree.select, "manual"))
    return(NULL)
  resolved <- .np_degree_resolve_auto_engine(
    search.engine = search.engine,
    degree.select = degree.select,
    ncon = ncon,
    source = nomad.source,
    auto.filled = nomad.auto.filled
  )
  search.engine <- .npscoefbw_nomad_controls(resolved$search.engine)
  degree.select <- resolved$degree.select

  regtype.requested <- if (isTRUE(regtype.named)) match.arg(regtype, c("lc", "ll", "lp")) else "lc"
  if (!identical(regtype.requested, "lp"))
    stop("automatic degree search currently requires regtype='lp'")
  if (!isTRUE(bandwidth.compute))
    stop("automatic degree search requires bandwidth.compute=TRUE")
  if (isTRUE(cv.iterate.named) && isTRUE(cv.iterate))
    stop("automatic degree search currently requires cv.iterate=FALSE")
  if (ncon < 1L)
    stop("automatic degree search requires at least one continuous smoothing predictor")

  bern.auto <- if (isTRUE(bernstein.named)) bernstein.basis else TRUE
  bern.auto <- npValidateGlpBernstein(regtype = "lp", bernstein.basis = bern.auto)

  bounds <- .np_degree_normalize_bounds(
    ncon = ncon,
    degree.min = degree.min,
    degree.max = degree.max,
    default.max = 3L
  )

  baseline.degree <- rep.int(0L, ncon)
  default.start.degree <- if (identical(search.engine, "cell")) {
    baseline.degree
  } else {
    rep.int(1L, ncon)
  }
  start.degree <- if (is.null(degree.start)) {
    pmax(bounds$lower, pmin(bounds$upper, default.start.degree))
  } else {
    start.raw <- npValidateGlpDegree(regtype = "lp", degree = degree.start, ncon = ncon, argname = "degree.start")
    out.of.range <- vapply(seq_len(ncon), function(j) !(start.raw[j] %in% bounds$candidates[[j]]), logical(1))
    if (any(out.of.range))
      stop("degree.start must lie within the searched degree candidates for every continuous smoothing predictor")
    start.raw
  }

  list(
    method = if (identical(search.engine, "cell")) degree.select else search.engine,
    engine = search.engine,
    candidates = bounds$candidates,
    lower = bounds$lower,
    upper = bounds$upper,
    grid.size = bounds$grid.size,
    singleton = bounds$singleton,
    fixed.degree = bounds$fixed.degree,
    baseline.degree = baseline.degree,
    start.degree = start.degree,
    start.user = !is.null(degree.start),
    basis = if (missing(basis) || is.null(basis)) "glp" else as.character(basis[1L]),
    nobs = as.integer(nobs[1L]),
    restarts = npValidateNonNegativeInteger(degree.restarts, "degree.restarts"),
    max.cycles = npValidatePositiveInteger(degree.max.cycles, "degree.max.cycles"),
    verify = npValidateScalarLogical(degree.verify, "degree.verify"),
    bernstein.basis = bern.auto,
    source = resolved$source,
    reason = resolved$reason
  )
}

.npscoefbw_attach_degree_search <- function(bws, search_result) {
  metadata <- .np_degree_search_metadata(search_result, default_direction = "min")

  if (isTRUE(search_result$native) &&
      isTRUE(getOption("np.developer.native.nomad.diagnostics", FALSE)) &&
      !is.null(search_result$native.diagnostics)) {
    attr(bws, "native.nomad.diagnostics") <- search_result$native.diagnostics
  }

  if (!is.null(search_result$nomad.time))
    bws$nomad.time <- as.numeric(search_result$nomad.time[1L])
  if (!is.null(search_result$powell.time))
    bws$powell.time <- as.numeric(search_result$powell.time[1L])
  if (!is.null(search_result$optim.time) && is.finite(search_result$optim.time))
    bws$total.time <- as.numeric(search_result$optim.time[1L])
  bws <- .np_attach_nomad_restart_summary(bws, search_result)
  bws$degree.search <- metadata
  bws
}

npscoefbw.default <-
  function(xdat = stop("invoked without data 'xdat'"),
           ydat = stop("invoked without data 'ydat'"),
           zdat = NULL,
           bws,
           backfit.iterate,
           backfit.maxiter,
           backfit.tol,
           bandwidth.compute = TRUE,
           basis,
           bernstein.basis,
           bwmethod,
           bwscaling,
           bwtype,
           ckerbound,
           ckerlb,
           ckerorder,
           ckertype,
           ckerub,
           cv.iterate,
           cv.num.iterations,
           degree,
           degree.select = c("manual", "coordinate", "exhaustive"),
           search.engine = c("nomad+powell", "cell", "nomad"),
           nomad = FALSE,
           nomad.nmulti = 0L,
           degree.min = NULL,
           degree.max = NULL,
           degree.start = NULL,
           degree.restarts = 0L,
           degree.max.cycles = 20L,
           degree.verify = FALSE,
           nmulti,
           nomad.remin = FALSE,
           powell.remin = TRUE,
           okertype,
           optim.abstol,
           optim.maxattempts,
           optim.maxit,
           optim.method,
           optim.reltol,
           random.seed,
           regtype,
           ukertype,
           scale.factor.init.lower = 0.1,
           scale.factor.init.upper = 2.0,
           scale.factor.init = 0.5,
           lbd.init = 0.5,
           hbd.init = 1.5,
           dfac.init = 1.0,
           scale.factor.search.lower = NULL,
           ...,
           nomad.opts = list()){

    nomad.opts <- .np_nomad_normalize_user_opts(nomad.opts, "npscoefbw")
    dots <- list(...)
    if (length(nomad.opts))
      dots$nomad.opts <- nomad.opts
    npRejectUnsupportedBwsolver(dots, "npscoefbw")

    if (!missing(bwmethod) && identical(match.arg(bwmethod, c("cv.ls", "manual")), "manual") &&
        missing(bws))
      stop("bwmethod='manual' requires argument 'bws'")

    miss.z <- missing(zdat)
    xdat <- toFrame(xdat)
    
    if (!(is.vector(ydat) || is.factor(ydat)))
      stop("'ydat' must be a vector or a factor")

    if(!miss.z)
      zdat <- toFrame(zdat)

    mc <- match.call(expand.dots = FALSE)
    mc.names <- names(mc)
    nomad.shortcut <- .np_prepare_nomad_shortcut(
      nomad = nomad,
      call_names = mc.names,
      preset = list(
        regtype = "lp",
        search.engine = "nomad+powell",
        degree.select = "coordinate",
        bernstein.basis = TRUE,
        degree.min = 0L,
        degree.max = 10L,
        degree.verify = FALSE,
        bwtype = "fixed"
      ),
      values = list(
        regtype = if ("regtype" %in% mc.names) regtype else NULL,
        search.engine = if ("search.engine" %in% mc.names) search.engine else NULL,
        degree.select = if ("degree.select" %in% mc.names) degree.select else NULL,
        bernstein.basis = if ("bernstein.basis" %in% mc.names) bernstein.basis else NULL,
        degree.min = if ("degree.min" %in% mc.names) degree.min else NULL,
        degree.max = if ("degree.max" %in% mc.names) degree.max else NULL,
        degree.verify = if ("degree.verify" %in% mc.names) degree.verify else NULL,
        bwtype = if ("bwtype" %in% mc.names) bwtype else NULL,
        degree = if ("degree" %in% mc.names) degree else NULL
      ),
      where = "npscoefbw"
    )

    if (isTRUE(nomad.shortcut$enabled)) {
      if ("degree" %in% mc.names)
        stop("nomad=TRUE does not support an explicit degree; remove degree or set nomad=FALSE")
      if ("regtype" %in% mc.names &&
          !identical(as.character(match.arg(nomad.shortcut$values$regtype, c("lc", "ll", "lp")))[1L], "lp"))
        stop("nomad=TRUE requires regtype='lp'")
      if ("bwtype" %in% mc.names &&
          !(as.character(match.arg(nomad.shortcut$values$bwtype, c("fixed", "generalized_nn", "adaptive_nn")))[1L] %in%
              c("fixed", "generalized_nn", "adaptive_nn")))
        stop("nomad=TRUE requires bwtype='fixed', 'generalized_nn', or 'adaptive_nn'")
      if ("degree.select" %in% mc.names &&
          identical(as.character(match.arg(nomad.shortcut$values$degree.select, c("manual", "coordinate", "exhaustive")))[1L], "manual"))
        stop("nomad=TRUE requires automatic degree search; use degree.select='coordinate' or 'exhaustive'")
      if (!identical(nomad.shortcut$metadata$source, "auto") &&
          "search.engine" %in% mc.names &&
          !(as.character(match.arg(nomad.shortcut$values$search.engine, c("nomad+powell", "cell", "nomad")))[1L] %in%
              c("nomad", "nomad+powell")))
        stop("nomad=TRUE requires search.engine='nomad' or 'nomad+powell'")
      if ("degree.verify" %in% mc.names &&
          isTRUE(npValidateScalarLogical(nomad.shortcut$values$degree.verify, "degree.verify")))
        stop("nomad=TRUE currently requires degree.verify=FALSE")
    }

    regtype.named <- isTRUE(nomad.shortcut$enabled) || any(mc.names == "regtype")
    bernstein.named <- isTRUE(nomad.shortcut$enabled) || any(mc.names == "bernstein.basis")
    cv.iterate.named <- any(mc.names == "cv.iterate")
    regtype.value <- if (!is.null(nomad.shortcut$values$regtype)) nomad.shortcut$values$regtype else "lc"
    degree.select.value <- if (!is.null(nomad.shortcut$values$degree.select)) nomad.shortcut$values$degree.select else "manual"
    degree.search <- .npscoefbw_degree_search_controls(
      regtype = regtype.value,
      regtype.named = regtype.named,
      cv.iterate = cv.iterate,
      cv.iterate.named = cv.iterate.named,
      bandwidth.compute = bandwidth.compute,
      ncon = sum(if (miss.z) untangle(xdat)$icon else untangle(zdat)$icon),
      nobs = NROW(xdat),
      basis = if ("basis" %in% mc.names) basis else "glp",
      degree.select = degree.select.value,
      search.engine = if (!is.null(nomad.shortcut$values$search.engine)) nomad.shortcut$values$search.engine else "nomad+powell",
      degree.min = nomad.shortcut$values$degree.min,
      degree.max = nomad.shortcut$values$degree.max,
      degree.start = if ("degree.start" %in% mc.names) degree.start else NULL,
      degree.restarts = if ("degree.restarts" %in% mc.names) degree.restarts else 0L,
      degree.max.cycles = if ("degree.max.cycles" %in% mc.names) degree.max.cycles else 20L,
      degree.verify = if (!is.null(nomad.shortcut$values$degree.verify)) nomad.shortcut$values$degree.verify else FALSE,
      bernstein.basis = if (!is.null(nomad.shortcut$values$bernstein.basis)) nomad.shortcut$values$bernstein.basis else bernstein.basis,
      bernstein.named = bernstein.named,
      nomad.source = nomad.shortcut$metadata$source,
      nomad.auto.filled = nomad.shortcut$metadata$auto.filled
    )
    nomad.inner.named <- "nomad.nmulti" %in% mc.names
    nomad.inner.nmulti <- if (nomad.inner.named) {
      npValidateNonNegativeInteger(nomad.nmulti, "nomad.nmulti")
    } else {
      0L
    }
    if (nomad.inner.named &&
        (is.null(degree.search) || !(degree.search$engine %in% c("nomad", "nomad+powell")))) {
      stop("nomad.nmulti is only supported when regtype='lp', automatic degree search is active, and search.engine is 'nomad' or 'nomad+powell'")
    }
    degree.setup <- npSetupGlpDegree(
      regtype = regtype.value,
      degree = if ("degree" %in% mc.names) degree else NULL,
      ncon = sum(if (miss.z) untangle(xdat)$icon else untangle(zdat)$icon),
      degree.select = degree.select.value
    )
    scale.factor.search.lower <- npResolveScaleFactorLowerBound(scale.factor.search.lower)

    ## first grab dummy args for scbandwidth() and perform 'bootstrap'
    ## bandwidth call

    margs <- c("regtype", "basis", "degree", "bernstein.basis",
               "bwmethod", "bwscaling", "bwtype", "ckertype", "ckerorder",
               "ckerbound", "ckerlb", "ckerub", "ukertype", "okertype",
               "scale.factor.search.lower")

    m <- match(margs, mc.names, nomatch = 0)
    any.m <- any(m != 0)

    sbw.args <- list(
      bw = bws,
      nobs = dim(xdat)[1],
      xdati = untangle(xdat),
      ydati = untangle(data.frame(ydat)),
      zdati = untangle(zdat),
      xnames = names(xdat),
      ynames = deparse(substitute(ydat)),
      znames = names(zdat),
      bandwidth.compute = bandwidth.compute
    )
    if (any.m) {
      nms <- mc.names[m]
      sbw.args[nms] <- mget(nms, envir = environment(), inherits = FALSE)
    }
    if (!("degree" %in% names(sbw.args)) && !is.null(degree.setup))
      sbw.args$degree <- degree.setup
    reg.args <- sbw.args[setdiff(names(sbw.args), c("bw", "nobs", "xdati", "ydati", "zdati", "xnames", "ynames", "znames", "bandwidth.compute"))]
    if (!is.null(degree.search))
      reg.args$bernstein.basis <- degree.search$bernstein.basis
    tbw <- do.call(scbandwidth, sbw.args)
    tbw <- npSetScaleFactorSearchLower(tbw, scale.factor.search.lower)

    ## next grab dummies for actual bandwidth selection and perform call
    margs <- c("zdat",
               "nmulti",
               "powell.remin",
               "random.seed",
               "nomad.opts",
               "scale.factor.init.lower", "scale.factor.init.upper", "scale.factor.init",
               "lbd.init", "hbd.init", "dfac.init",
               "scale.factor.search.lower",
               "cv.iterate",
               "cv.num.iterations",
               "backfit.iterate",
               "backfit.maxiter",
               "backfit.tol",
               "optim.method", "optim.maxattempts",
               "optim.reltol", "optim.abstol", "optim.maxit")
    m <- match(margs, mc.names, nomatch = 0)
    any.m <- any(m != 0)

    if (any.m) {
      nms <- mc.names[m]
      opt.args <- mget(nms, envir = environment(), inherits = FALSE)
    } else {
      opt.args <- list()
    }
    opt.args <- c(list(bandwidth.compute = bandwidth.compute), opt.args)
    if ("nomad.opts" %in% names(dots))
      opt.args$nomad.opts <- dots$nomad.opts
    reg.args$scale.factor.search.lower <- scale.factor.search.lower
    opt.args$scale.factor.search.lower <- scale.factor.search.lower

    if (!is.null(degree.search)) {
      eval_fun <- function(degree.vec) {
        cell.reg.args <- reg.args
        cell.reg.args$regtype <- "lp"
        cell.reg.args$degree <- as.integer(degree.vec)
        cell.reg.args$bernstein.basis <- degree.search$bernstein.basis
        cell.bws <- .npscoefbw_run_fixed_degree(
          xdat = xdat,
          ydat = ydat,
          zdat = if (miss.z) NULL else zdat,
          bws = bws,
          reg.args = cell.reg.args,
          opt.args = opt.args
        )
        list(
          objective = as.numeric(cell.bws$fval[1L]),
          payload = cell.bws,
          num.feval = if (!is.null(cell.bws$num.feval)) as.numeric(cell.bws$num.feval[1L]) else NA_real_,
          nn.cache = cell.bws$nn.cache
        )
      }

      if (isTRUE(degree.search$singleton)) {
        search.result <- .np_degree_singleton_search_result(
          degree.search = degree.search,
          eval_result = eval_fun(degree.search$fixed.degree),
          direction = "min",
          objective_name = "fval"
        )
      } else if (identical(degree.search$engine, "cell")) {
        search.result <- .np_degree_search(
          method = degree.search$method,
          candidates = degree.search$candidates,
          baseline_degree = degree.search$baseline.degree,
          start_degree = degree.search$start.degree,
          restarts = degree.search$restarts,
          max_cycles = degree.search$max.cycles,
          verify = degree.search$verify,
          eval_fun = eval_fun,
          direction = "min",
          trace_level = "full",
          source = degree.search$source,
          reason = degree.search$reason,
          objective_name = "fval"
        )
      } else {
        search.result <- .npscoefbw_nomad_search(
          xdat = xdat,
          ydat = ydat,
          zdat = if (miss.z) NULL else zdat,
          bws = bws,
          reg.args = reg.args,
          opt.args = opt.args,
          degree.search = degree.search,
          nomad.inner.nmulti = nomad.inner.nmulti,
          nomad.opts = if (is.null(opt.args$nomad.opts)) list() else opt.args$nomad.opts,
          source = degree.search$source,
          reason = degree.search$reason,
          progress_label = .np_degree_search_label(degree.search$engine, degree.search$source)
        )
      }
      tbw <- .npscoefbw_attach_degree_search(
        bws = search.result$best_payload,
        search_result = search.result
      )
    } else {
      scbw.args <- c(list(xdat = xdat, ydat = ydat, bws = tbw), opt.args)
      if (!miss.z)
        scbw.args$zdat <- zdat
      tbw <- .np_progress_select_bandwidth_enhanced(
        "Selecting smooth coefficient bandwidth",
        do.call(npscoefbw.scbandwidth, scbw.args)
      )
    }

    environment(mc) <- parent.frame()
    tbw$call <- mc
    tbw <- .np_attach_nomad_shortcut(tbw, nomad.shortcut$metadata)

    return(tbw)
    
  }

Try the np package in your browser

Any scripts or data that you put into this service are public.

np documentation built on July 15, 2026, 1:07 a.m.