alexiosg/rugarch

Problem with nloptr solver usage

RebelionTheGrey opened this issue · 6 comments

I have nloptr package installed on my R.

When I try to use ugarchfit with nloptr solver, I get this error each time:

Error in if (convergence != 0) warning("\nugarchfit-->warning: solver failer to converge.") : 
  argument is of length zero

Code to reproduce:

library(nloptr);
library(rugarch);

x0 <- c( -1.2, 1 )

opts <- list("algorithm"="NLOPT_LD_LBFGS", "xtol_rel"=1.0e-8)

# solve Rosenbrock Banana function
res <- nloptr( x0=x0, eval_f=eval_f, eval_grad_f=eval_grad_f, opts=opts)
print(res)

data(dmbp)
spec = ugarchspec()
fit = ugarchfit(data = dmbp[,1], spec = spec, solver = "nloptr")

fit

The full output for the code above:

> library(nloptr);
> library(rugarch);
> 
> x0 <- c( -1.2, 1 )
> 
> opts <- list("algorithm"="NLOPT_LD_LBFGS", "xtol_rel"=1.0e-8)
> 
> # solve Rosenbrock Banana function
> res <- nloptr( x0=x0, eval_f=eval_f, eval_grad_f=eval_grad_f, opts=opts)
> print(res)

Call:
nloptr(x0 = x0, eval_f = eval_f, eval_grad_f = eval_grad_f, opts = opts)


Minimization using NLopt version 2.7.1 

NLopt solver status: 1 ( NLOPT_SUCCESS: Generic success return value. )

Number of Iterations....: 56 
Termination conditions:  xtol_rel: 1e-08 
Number of inequality constraints:  0 
Number of equality constraints:    0 
Optimal value of objective function:  7.35727226897802e-23 
Optimal value of controls: 1 1


> 
> data(dmbp)
> spec = ugarchspec()
> fit = ugarchfit(data = dmbp[,1], spec = spec, solver = "nloptr")
Error in if (convergence != 0) warning("\nugarchfit-->warning: solver failer to converge.") : 
  argument is of length zero
> 
> fit
Error: object 'fit' not found
> 

First part of this code is an example from nloptr package for showing that nloptr package works correct, the second is just a little modified example from ugarchfit specification.

My system:
AMD Ryzen 5950X, 128 Gb DDR4, Windows 11 Pro,
R version 4.2.2 Patched (2022-12-14 r83479 ucrt) -- "Innocent and Trusting"
gcc (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders) 12.2.0
Compilation options: -m64 -O3 -w -march=native -mfpmath=both -mfma -mtune=native
nloptr package version - 2.0.3 (current).

I try to use this solver because sometimes solnp/gosolnp solvers freeze on some data - ugarchfit/arfimafit really freeze.

P.S. this kind of problem is just connected with only nloptr solver. For the same data ordinary solvers work fine.

Did you make any progress on this? I'm facing the same issue. I think it was introduced after 1.4.4 since my code worked with that. I see in the change log:

2022-01-18 Alexios Galanos alexios@4dscape.com
* DESCRIPTION (Version): New version is 1.4-6.
* Removed nloptr dependency

Looking at some change logs: 0c75d22 it looks like nloptr has been removed. I don't know why, it was more stable for me than the other solvers which often crashed my R sessions. This means there's no way to make this work without forking the code and adding the commented lines back in.

The only solution seems to be to revert to 1.4-4 https://cran.r-project.org/src/contrib/Archive/rugarch/

So far I am not able to make it work on R 4.3 though.

@RebelionTheGrey

Here is a hack I cooked up that should mimic the old package behavior. In the second example I add BFGS solver. I'm passing on all arguments so it will throw a warning that the bounds are not respected.

library(rugarch)

# define a new solver, here nloptr
.nloptrsolver = function(pars, fun, control, LB, UB, arglist){
  require(nloptr)
   ans = try(nloptr(x0 = pars, eval_f = fun,  eval_grad_f = NULL,
           eval_g_ineq = NULL, lb = LB, ub = UB, eval_jac_g_ineq = NULL,
           eval_g_eq = NULL, eval_jac_g_eq = NULL, opts = control, arglist = arglist),
       silent=TRUE)
   if(inherits(ans, "try-error")){
     sol = list()
     sol$convergence = 1
     sol$message = ans
     sol$par = rep(NA, length(pars))
     names(sol$par) = names(pars)
   } else{
     sol = list()
     sol$convergence = 0
     sol$message = ans$message
     sol$par = ans$solution
   }
   hess = NULL
   return(list(sol = sol, hess = hess))
}

  # define a new solver, here BFGS
.bfgssolver = function(pars, fun, gr, parscale, control, LB, UB, arglist){
  control$parscale = parscale
  ans = try(optim(par = pars, fn = fun, gr = gr, arglist = arglist,
      method = "BFGS", lower = LB, upper = UB, control = control,
      hessian = TRUE),silent=TRUE)
  if(inherits(ans, "try-error")){
    sol = list()
    sol$convergence = 1
    sol$message = ans
    sol$par = rep(NA, length(pars))
    names(sol$par) = names(pars)
  } else{
    sol = ans
  }
  hess = sol$hessian
  return(list(sol = sol, hess = hess))
}

  # modify the garch solver
  .garchsolver = function(solver, pars, fun, Ifn, ILB, IUB, gr, hessian, parscale,
      control, LB, UB, ux=NULL, ci=NULL, mu=NULL, arglist)
  {
    gocontrol = control
    control = getcontrol2(solver, control) # use new control function
    retval = switch(solver,
        hybrid = rugarch:::.hybridsolver(pars, fun, Ifn, ILB, IUB, gr, hessian, parscale, gocontrol, LB, UB, arglist), # use namespace and ::: otherwise it won't be found
        nlminb = rugarch:::.nlminbsolver(pars, fun, gr, hessian, parscale, control, LB, UB, arglist), # use namespace and ::: otherwise it won't be found
        solnp = rugarch:::.solnpsolver(pars, fun, Ifn, ILB, IUB, control, LB, UB, arglist), # use namespace and ::: otherwise it won't be found
        gosolnp = rugarch:::.gosolnpsolver(pars, fun, Ifn, ILB, IUB, gocontrol, LB, UB, arglist), # use namespace and ::: otherwise it won't be found
        lbfgs = rugarch:::.lbfgssolver(pars, fun, gr, parscale, control, LB, UB, arglist), # use namespace and ::: otherwise it won't be found
        bfgs = .bfgssolver(pars, fun, gr, parscale, control, LB, UB, arglist), # add here
        nloptr = .nloptrsolver(pars, fun, control, LB, UB, arglist)) # add here
    return(retval)
  }
    # assign it to name space to overwrite rugarch:::.garchsolver
    assignInNamespace(".garchsolver", .garchsolver, "rugarch")


getcontrol2 = function(solver, control)
{
  ans = switch(solver,
    nlminb = rugarch:::.nlminbctrl(control), # use namespace and ::: otherwise it won't be found
    solnp = rugarch:::.solnpctrl(control), # use namespace and ::: otherwise it won't be found
    gosolnp = rugarch:::.gosolnpctrl(control), # use namespace and ::: otherwise it won't be found
    lbfgs = rugarch:::.lbfgsctrl(control), # use namespace and ::: otherwise it won't be found
    bfgs = rugarch:::.lbfgsctrl(control), # use the same as L-BFGS-B controls, use namespace and ::: otherwise it won't be found
    nloptr = .nloptrctrl(control)) # add controls, newsly specified function
  return(ans)
}

.nloptrctrl = function(control){
 #solver = c("NLOPT_LN_COBYLA", "NLOPT_LN_BOBYQA", "NLOPT_LN_PRAXIS", "NLOPT_LN_NELDERMEAD", "NLOPT_LN_SBPLX",
 # and AUGLAG + solver
 # subsolvers:
 xsub = c("NLOPT_LN_COBYLA", "NLOPT_LN_BOBYQA", "NLOPT_LN_PRAXIS", "NLOPT_LN_NELDERMEAD", "NLOPT_LN_SBPLX")
 ans = list()
 params = unlist(control)
 if(is.null(params)) {
   ans$ftol_rel = 1e-8
   ans$xtol_rel = 1e-6
   ans$maxeval = 25000
   ans$print_level = 0
   mainsolver = "NLOPT_LN_SBPLX"
   subsolver = NULL
 } else{
   npar = tolower(names(unlist(control)))
   names(params) = npar
   if(any(substr(npar, 1, 8) == "ftol_rel")) ans$ftol_rel = as.numeric(params["ftol_rel"]) else ans$ftol_rel = 1e-8
   if(any(substr(npar, 1, 8) == "xtol_rel")) ans$xtol_rel = as.numeric(params["xtol_rel"]) else ans$xtol_rel = 1e-6
   if(any(substr(npar, 1, 7) == "maxeval")) ans$maxeval = as.numeric(params["maxeval"]) else ans$maxeval = 25000
   if(any(substr(npar, 1, 11) == "print_level")) ans$print_level = as.numeric(params["print_level"]) else ans$print_level = 0
   if(any(substr(npar, 1, 6) == "solver")){
     if(abs(as.integer(params["solver"]))>5){
       mainsolver = "NLOPT_LN_AUGLAG"
       subsolver = xsub[min(5,abs(as.integer(params["solver"]))-5 )]
     } else{
       mainsolver = xsub[min(5,abs(as.integer(params["solver"])))]
       subsolver = NULL
     }
   } else{
     mainsolver = "NLOPT_LN_SBPLX"
     subsolver = NULL
   }
 }
 if(is.null(subsolver)){
   ans$algorithm = mainsolver
 } else{
   ans$algorithm = mainsolver
   ans$local_opts$algorithm = subsolver
   ans$local_opts$ftol_abs = ans$ftol_rel
   ans$local_opts$xtol_rel = ans$xtol_rel
   ans$local_opts$maxeval = 2000
   ans$local_opts$print_level = 0

 }
 return(ans)
}

set.seed(1)
testdata = rnorm(100)

test1 = ugarchfit(data =testdata, spec = ugarchspec(), solver = "nloptr", solver.control=list(control=list(solver=1)))
test2 = ugarchfit(data =testdata, spec = ugarchspec(), solver = "bfgs")
test3 = ugarchfit(data =testdata, spec = ugarchspec(), solver = "hybrid")

@BPJandree, @RebelionTheGrey does this commit resolve this issue? 008125a

If so then please close the issue.

@travis-leith

from a quick reading of the code changes in 008125a this seems to work yes.

I ran a quick test on

sionInfo()
R version 4.1.2 (2021-11-01)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 22621)

Matrix products: default

locale:
[1] LC_COLLATE=English_Netherlands.1252  LC_CTYPE=English_Netherlands.1252    LC_MONETARY=English_Netherlands.1252 LC_NUMERIC=C                        
[5] LC_TIME=English_Netherlands.1252    

attached base packages:
[1] parallel  stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] rugarch_1.5-1

loaded via a namespace (and not attached):
 [1] zoo_1.8-12                  remotes_2.4.2               ks_1.14.1                   purrr_1.0.1                 lattice_0.20-45             vctrs_0.6.1                
 [7] miniUI_0.1.1.1              usethis_2.1.6               htmltools_0.5.2             SkewHyperbolic_0.4-2        rlang_1.1.0                 pkgbuild_1.4.3             
[13] pracma_2.4.4                urlchecker_1.0.1            later_1.3.0                 nloptr_2.0.3                glue_1.6.2                  sessioninfo_1.2.2          
[19] lifecycle_1.0.4             stringr_1.5.0               devtools_2.4.5              htmlwidgets_1.5.4           mvtnorm_1.2-4               memoise_2.0.1              
[25] callr_3.7.3                 fastmap_1.1.0               httpuv_1.6.5                ps_1.7.5                    curl_4.3.2                  xts_0.13.1                 
[31] Rcpp_1.0.10                 KernSmooth_2.23-20          xtable_1.8-4                promises_1.2.0.1            cachem_1.0.6                desc_1.4.3                 
[37] DistributionUtils_0.6-1     pkgload_1.3.2               truncnorm_1.0-9             mime_0.12                   fs_1.5.2                    spd_2.0-1                  
[43] digest_0.6.31               stringi_1.7.6               processx_3.8.3              shiny_1.7.1                 numDeriv_2016.8-1.1         grid_4.1.2                 
[49] cli_3.6.1                   tools_4.1.2                 magrittr_2.0.3              Rsolnp_1.16                 profvis_0.3.7               ellipsis_0.3.2             
[55] GeneralizedHyperbolic_0.8-6 MASS_7.3-54                 Matrix_1.3-4                R6_2.5.1                    mclust_6.0.1                compiler_4.1.2     

using just:

set.seed(1)
testdata = rnorm(100)

test1 = ugarchfit(data =testdata, spec = ugarchspec(), solver = "nloptr", solver.control=list(control=list(solver=1)))
test2 = ugarchfit(data =testdata, spec = ugarchspec(), solver = "nloptr", solver.control=list(control=list(solver=2)))

This both works.

I then tried:

# define a new solver, here nloptr
.nloptrsolver = function(pars, fun, control, LB, UB, arglist){
  require(nloptr)
   ans = try(nloptr(x0 = pars, eval_f = fun,  eval_grad_f = NULL,
           eval_g_ineq = NULL, lb = LB, ub = UB, eval_jac_g_ineq = NULL,
           eval_g_eq = NULL, eval_jac_g_eq = NULL, opts = control, arglist = arglist),
       silent=TRUE)
   if(inherits(ans, "try-error")){
     sol = list()
     sol$convergence = 1
     sol$message = ans
     sol$par = rep(NA, length(pars))
     names(sol$par) = names(pars)
   } else{
     sol = list()
     sol$convergence = 0
     sol$message = ans$message
     sol$par = ans$solution
   }
   hess = NULL
   return(list(sol = sol, hess = hess))
}

  # define a new solver, here BFGS
.bfgssolver = function(pars, fun, gr, parscale, control, LB, UB, arglist){
  control$parscale = parscale
  ans = try(optim(par = pars, fn = fun, gr = gr, arglist = arglist,
      method = "BFGS", lower = LB, upper = UB, control = control,
      hessian = TRUE),silent=TRUE)
  if(inherits(ans, "try-error")){
    sol = list()
    sol$convergence = 1
    sol$message = ans
    sol$par = rep(NA, length(pars))
    names(sol$par) = names(pars)
  } else{
    sol = ans
  }
  hess = sol$hessian
  return(list(sol = sol, hess = hess))
}

  # modify the garch solver
  .garchsolver = function(solver, pars, fun, Ifn, ILB, IUB, gr, hessian, parscale,
      control, LB, UB, ux=NULL, ci=NULL, mu=NULL, arglist)
  {
    gocontrol = control
    control = getcontrol2(solver, control) # use new control function
    retval = switch(solver,
        hybrid = rugarch:::.hybridsolver(pars, fun, Ifn, ILB, IUB, gr, hessian, parscale, gocontrol, LB, UB, arglist), # use namespace and ::: otherwise it won't be found
        nlminb = rugarch:::.nlminbsolver(pars, fun, gr, hessian, parscale, control, LB, UB, arglist), # use namespace and ::: otherwise it won't be found
        solnp = rugarch:::.solnpsolver(pars, fun, Ifn, ILB, IUB, control, LB, UB, arglist), # use namespace and ::: otherwise it won't be found
        gosolnp = rugarch:::.gosolnpsolver(pars, fun, Ifn, ILB, IUB, gocontrol, LB, UB, arglist), # use namespace and ::: otherwise it won't be found
        lbfgs = rugarch:::.lbfgssolver(pars, fun, gr, parscale, control, LB, UB, arglist), # use namespace and ::: otherwise it won't be found
        bfgs = .bfgssolver(pars, fun, gr, parscale, control, LB, UB, arglist), # add here
        nloptr = .nloptrsolver(pars, fun, control, LB, UB, arglist)) # add here
    return(retval)
  }
    # assign it to name space to overwrite rugarch:::.garchsolver
    assignInNamespace(".garchsolver", .garchsolver, "rugarch")


getcontrol2 = function(solver, control)
{
  ans = switch(solver,
    nlminb = rugarch:::.nlminbctrl(control), # use namespace and ::: otherwise it won't be found
    solnp = rugarch:::.solnpctrl(control), # use namespace and ::: otherwise it won't be found
    gosolnp = rugarch:::.gosolnpctrl(control), # use namespace and ::: otherwise it won't be found
    lbfgs = rugarch:::.lbfgsctrl(control), # use namespace and ::: otherwise it won't be found
    bfgs = rugarch:::.lbfgsctrl(control), # use the same as L-BFGS-B controls, use namespace and ::: otherwise it won't be found
    nloptr = .nloptrctrl(control)) # add controls, newsly specified function
  return(ans)
}

.nloptrctrl = function(control){
 #solver = c("NLOPT_LN_COBYLA", "NLOPT_LN_BOBYQA", "NLOPT_LN_PRAXIS", "NLOPT_LN_NELDERMEAD", "NLOPT_LN_SBPLX",
 # and AUGLAG + solver
 # subsolvers:
 xsub = c("NLOPT_LN_COBYLA", "NLOPT_LN_BOBYQA", "NLOPT_LN_PRAXIS", "NLOPT_LN_NELDERMEAD", "NLOPT_LN_SBPLX")
 ans = list()
 params = unlist(control)
 if(is.null(params)) {
   ans$ftol_rel = 1e-8
   ans$xtol_rel = 1e-6
   ans$maxeval = 25000
   ans$print_level = 0
   mainsolver = "NLOPT_LN_SBPLX"
   subsolver = NULL
 } else{
   npar = tolower(names(unlist(control)))
   names(params) = npar
   if(any(substr(npar, 1, 8) == "ftol_rel")) ans$ftol_rel = as.numeric(params["ftol_rel"]) else ans$ftol_rel = 1e-8
   if(any(substr(npar, 1, 8) == "xtol_rel")) ans$xtol_rel = as.numeric(params["xtol_rel"]) else ans$xtol_rel = 1e-6
   if(any(substr(npar, 1, 7) == "maxeval")) ans$maxeval = as.numeric(params["maxeval"]) else ans$maxeval = 25000
   if(any(substr(npar, 1, 11) == "print_level")) ans$print_level = as.numeric(params["print_level"]) else ans$print_level = 0
   if(any(substr(npar, 1, 6) == "solver")){
     if(abs(as.integer(params["solver"]))>5){
       mainsolver = "NLOPT_LN_AUGLAG"
       subsolver = xsub[min(5,abs(as.integer(params["solver"]))-5 )]
     } else{
       mainsolver = xsub[min(5,abs(as.integer(params["solver"])))]
       subsolver = NULL
     }
   } else{
     mainsolver = "NLOPT_LN_SBPLX"
     subsolver = NULL
   }
 }
 if(is.null(subsolver)){
   ans$algorithm = mainsolver
 } else{
   ans$algorithm = mainsolver
   ans$local_opts$algorithm = subsolver
   ans$local_opts$ftol_abs = ans$ftol_rel
   ans$local_opts$xtol_rel = ans$xtol_rel
   ans$local_opts$maxeval = 2000
   ans$local_opts$print_level = 0

 }
 return(ans)
}

set.seed(1)

test3 = ugarchfit(data =testdata, spec = ugarchspec(), solver = "nloptr", solver.control=list(control=list(solver=1)))

And compared outputs (truncated here):

> test1

*---------------------------------*
*          GARCH Model Fit        *
*---------------------------------*

Conditional Variance Dynamics   
-----------------------------------
GARCH Model     : sGARCH(1,1)
Mean Model      : ARFIMA(1,0,1)
Distribution    : norm 

Optimal Parameters
------------------------------------
        Estimate  Std. Error    t value Pr(>|t|)
mu      0.109100    0.090130   1.210475  0.22610
ar1    -0.910069    0.175661  -5.180834  0.00000
ma1     0.928281    0.154229   6.018838  0.00000
omega   0.001397    0.039392   0.035457  0.97172
alpha1  0.000000    0.050613   0.000000  1.00000
beta1   0.998556    0.007986 125.030966  0.00000

Robust Standard Errors:
        Estimate  Std. Error   t value Pr(>|t|)
mu      0.109100    0.075885   1.43771  0.15052
ar1    -0.910069    0.072765 -12.50698  0.00000
ma1     0.928281    0.070429  13.18039  0.00000
omega   0.001397    0.037176   0.03757  0.97003
alpha1  0.000000    0.048317   0.00000  1.00000
beta1   0.998556    0.001267 788.22578  0.00000

LogLikelihood : -130.5498 


> test3

*---------------------------------*
*          GARCH Model Fit        *
*---------------------------------*

Conditional Variance Dynamics   
-----------------------------------
GARCH Model     : sGARCH(1,1)
Mean Model      : ARFIMA(1,0,1)
Distribution    : norm 

Optimal Parameters
------------------------------------
        Estimate  Std. Error    t value Pr(>|t|)
mu      0.109100    0.090130   1.210475  0.22610
ar1    -0.910069    0.175661  -5.180834  0.00000
ma1     0.928281    0.154229   6.018838  0.00000
omega   0.001397    0.039392   0.035457  0.97172
alpha1  0.000000    0.050613   0.000000  1.00000
beta1   0.998556    0.007986 125.030966  0.00000

Robust Standard Errors:
        Estimate  Std. Error   t value Pr(>|t|)
mu      0.109100    0.075885   1.43771  0.15052
ar1    -0.910069    0.072765 -12.50698  0.00000
ma1     0.928281    0.070429  13.18039  0.00000
omega   0.001397    0.037176   0.03757  0.97003
alpha1  0.000000    0.048317   0.00000  1.00000
beta1   0.998556    0.001267 788.22578  0.00000

LogLikelihood : -130.5498 

This shows the solution of rugarch 1.5-1 nloptr is the same as the one with the "hack" I posted.

I think this issue can be closed.

Very best wishes

Sorry for the late reply to this thread. The reason I removed nloptr at the time was because it was in danger of being abandoned on CRAN (email from Brian Ripley on Jan 07 2022). In the meantime, someone stepped up to become a maintainer but I did not add it back as I'd started to focus more on the tsmodels/tsgarch package instead. I may add it back in the next release. Thanks for your understanding.
Alexios