ROI

El paquete ROI (= R Optimization Infrastructure) nos proporciona una infraestructura ampliable para modelado de problemas de optimización (lineal, cuadrática, cónica y no lineal en generalgeneral). Además, administra múltiples solvers, reformulaciones, colecciones de problemas y funciones para leer y escribir problemas de optimización en varios formatos.

El paper de 2019 es muy recomendable para iniciarse en ROI y para profundizar luego en él. En su Apartado 2 encontrarás un resumen de teoría de optimización, en el 3 tienes una interesante panorámica del software de optimización disponible en R, en los 4 y 5 dispones de la descripción de la infraestructura y la librería. El paper termina con un apartado 6 dedicado a ejemplos.

En definitiva, la principal ventaja de ROI es que es a la optimización lo que caret fue a machine learning: un wrapper que unifica la interfaz de programación independientemente del solver.

Te muestro a continuación un pequeño ejemplo de su uso.

Vamos a utilizarlo con este sencillo problema de optimización lineal:

Lo primero que hacemos es definir el problema como un objeto OP (Optimization Problem):

library(ROI)
library(tidyverse)

A <- rbind(c(5, 7, 2), c(3, 2, -9), c(1, 3, 1))
dir <- c("<=", "<=", "<=")
rhs <- c(61, 35, 31)

lp <- OP(objective   = L_objective(c(3, 7, -12)),
         constraints = L_constraint(A, dir = dir, rhs = rhs),
         types       = NULL, # NULL ="C", "I", "B"
         bounds      = V_bound(li = 3, ui = 3, lb = -10, ub = 10, nobj = 3),
         maximum     = TRUE)

Alternativamente lo podríamos definir así:

# ALTERNATIVA:

lp <- OP()

objective(lp)   <- L_objective(c(3, 7, -12)) # 3x_1 + 7x_2 -12x_3
constraints(lp) <- L_constraint(A, dir = c("<=", "<=", "<="), rhs = rhs)
bounds(lp)      <- V_bound(li = 3, ui = 3, lb = -10, ub = 10, nobj = 3)
# types(lp)
maximum(lp)     <- TRUE

Este es el problema que hemos definido:

lp
## ROI Optimization Problem:
## 
## Maximize a linear objective function of length 3 with
## - 3 continuous objective variables,
## 
## subject to
## - 3 constraints of type linear.
## - 1 lower and 1 upper non-standard variable bound.

Como ves, ROI identifica el tipo de problema que le hemos definido. Esto facilita que, a continuación, busquemos un solver apropiado para este problema.

La infraestructura ROI tiene disponibles los siguientes solvers:

ROI_available_solvers(lp)[, c("Package", "Repository")] %>% head()
##              Package                 Repository
## 1 ROI.plugin.alabama https://CRAN.R-project.org
## 2     ROI.plugin.clp https://CRAN.R-project.org
## 3   ROI.plugin.cplex https://CRAN.R-project.org
## 5    ROI.plugin.ecos https://CRAN.R-project.org
## 6    ROI.plugin.glpk https://CRAN.R-project.org
## 7 ROI.plugin.lpsolve https://CRAN.R-project.org

(se muestran solo 6, pero hay bastantes más).

¿Cuáles tenemos instalados en nuestro entorno?

ROI_installed_solvers()
##                nlminb               alabama               deoptim 
##   "ROI.plugin.nlminb"  "ROI.plugin.alabama"  "ROI.plugin.deoptim" 
##                  glpk               lpsolve                nloptr 
##     "ROI.plugin.glpk"  "ROI.plugin.lpsolve"   "ROI.plugin.nloptr" 
##              quadprog 
## "ROI.plugin.quadprog"

Si el que necesitas no está instalado:

install.packages("ROI.plugin.glpk")

De los que tenemos instalados, ¿cuáles están ya registrados (porque ya los hemos utilizado anteriormente) ?

ROI_registered_solvers() %>% head(15)
##               nlminb              alabama             deoptimr 
##  "ROI.plugin.nlminb" "ROI.plugin.alabama" "ROI.plugin.deoptim" 
##              deoptim                 glpk              lpsolve 
## "ROI.plugin.deoptim"    "ROI.plugin.glpk" "ROI.plugin.lpsolve" 
##        nloptr.bobyqa        nloptr.crs2lm        nloptr.direct 
##  "ROI.plugin.nloptr"  "ROI.plugin.nloptr"  "ROI.plugin.nloptr" 
##       nloptr.directL         nloptr.lbfgs    nloptr.neldermead 
##  "ROI.plugin.nloptr"  "ROI.plugin.nloptr"  "ROI.plugin.nloptr" 
##        nloptr.newuoa         nloptr.sbplx         nloptr.stogo 
##  "ROI.plugin.nloptr"  "ROI.plugin.nloptr"  "ROI.plugin.nloptr"

Para registrarlo la primera vez que lo usas lo único que hay que hacer es cargar la librería del plugin:

library("ROI.plugin.glpk")

Ahora preguntamos a ROI qué solvers son aplicables a este problema:

ROI_applicable_solvers(lp)
## [1] "alabama"       "glpk"          "lpsolve"       "nloptr.cobyla"
## [5] "nloptr.mma"    "nloptr.auglag" "nloptr.isres"  "nloptr.slsqp"

Con el solver que elijamos ya podemos resolver el problema:

(lp_sol <- ROI_solve(lp, solver = "glpk"))
## Optimal solution found.
## The objective value is: 8.670149e+01

Y ver sus los resultados. El óptimo se encuentra en:

solution(lp_sol)
## [1]  0.000000  9.238806 -1.835821

Y el valor de la función objetivo optimizada:

objective(lp)(solution(lp_sol))
## [1] 86.70149

También podemos ver el status del solver:

lp_sol$status
## $code
## [1] 0
## 
## $msg
##   solver glpk
##     code 5
##   symbol GLP_OPT
##  message Solution is optimal.
## roi_code 0
lp_sol$message
## $optimum
## [1] 86.70149
## 
## $solution
## [1]  0.000000  9.238806 -1.835821
## 
## $status
## [1] 5
## 
## $solution_dual
## [1] -4.298507  0.000000  0.000000
## 
## $auxiliary
## $auxiliary$primal
## [1] 61.0000 35.0000 25.8806
## 
## $auxiliary$dual
## [1] 0.5820896 1.4626866 0.0000000
## 
## 
## $sensitivity_report
## [1] NA

Y hasta aquí el ejemplo. Como ves, la dinámica de uso es bastante clara y cómoda. Los problemas de optimización reales no son tan simples como este ejemplo, por supuesto. Pero usar ROI nos permite centrarnos en plantear y solucionar el problema sin malgastar demasiado esfuerzo en la programación de la solución.

Por último, te dejo un par de enlaces que espero te sean útiles:

Y un video que cuenta más o menos lo mismo que este post: