El objetivo de este tutorial es que aprendan a definir y solucionar problemas de programación dinámica en R.

Definición del problema

A continuación resolveremos un problema tipo knapsack de programación dinámica determinística. El problema está descrito en el archivo Complementaria 12 (Q).pdf en SICUA+. Para implementar la solución de este problema haremos uso de matrices en R.

Definimos entonces las épocas de decisión, la variable de estado y el espacio de estados:

\[E = \{ \text{Bloqueador, Agua, Enlatados} \}\]

\[X_n: \text{Espacio disponible en la maleta al inicio de la época de decisión }n\] \[S_x = \{ 3000,2500,2000,1500,1000,500,0\}\]

Vemos que el espacio de estados está definido por múltiplos de 500, pues nunca tomará un valor diferente de acuerdo a los pesos de los diferentes objetos (todos son múltiplos de 500).

El valor de las ganancias inmediatas está dado por los beneficios obtenidos al incluir cada objeto en la mochila.

Ahora bien, definimos todos los elementos del problema en R

e = c(1,2,3)                                #Épocas (1: Bloqueador, 2: Agua, 3: Enlatados)
s = c(3000,2500,2000,1500,1000,500,0)       #Espacio de estados
w = c(500,1000,1500)                        #Pesos
b = c(8,15,20)                              #Beneficios
x = 3000                                    #Espacio disponible en la mochila

Comenzamos a hacer la recursión por la última etapa de decisión Enlatados. Sabemos para esta etapa que el número máximo de enlatados que podríamos ingresar a la mochila es 2 \(\left(\frac{3000}{1500} = 2\right)\), y, de tener espacio, el mínimo es 1, pues al ser la última etapa intentamos llenar la mochila para obtener el máximo beneficio.

fenl = cbind(s, rep(0,length(s)),rep(0,length(s)),rep(0,length(s)), rep(0,length(s)))
colnames(fenl) = c("Estados", "A:1", "A:2", "ValueMax", "Decisión a tomar")
fenl[,2] = ifelse(w[3]*1>fenl[,1],0,1*b[3])
fenl[,3] = ifelse(w[3]*2>fenl[,1],0,2*b[3])
fenl[,4] = apply(fenl[,2:3],1,max)
fenl[,5] = apply(fenl[,2:3],1,which.max)
fenl
##      Estados A:1 A:2 ValueMax Decisión a tomar
## [1,]    3000  20  40       40                2
## [2,]    2500  20   0       20                1
## [3,]    2000  20   0       20                1
## [4,]    1500  20   0       20                1
## [5,]    1000   0   0        0                1
## [6,]     500   0   0        0                1
## [7,]       0   0   0        0                1

Así, tenemos una matriz con el valor de la recursión y la decisión a tomar para cada posible estado en el que se puede estar en la última etapa de decisión. Así, continuamos con la penúltima etapa de decisión Agua. Para este objeto podemos llevar máximo 3 objetos cuando tenemos suficiente espacio \(\left( \frac{3000}{1000}=3\right)\) y mínimo 0. Por ende, el espacio de decisiones depende del valor de la variable de estado en esta época.

fagu = cbind(s, rep(0,length(s)),rep(0,length(s)),rep(0,length(s)),rep(0,length(s)),rep(0,length(s)), rep(0,length(s)))
colnames(fagu) = c("Estados", "A:0", "A:1", "A:2", "A:3", "ValueMax", "Decisión a tomar")
fagu[,2] = 0*b[2] + fenl[,4]
fagu[,3] = ifelse(w[2]*1>fagu[,1],0,1*b[2])+ifelse(fagu[,1] - w[2]*1<0,0,fenl[match(fagu[,1]-w[2]*1,fenl),4])
fagu[,4] = ifelse(w[2]*2>fagu[,1],0,2*b[2])+ifelse(fagu[,1] - w[2]*2<0,0,fenl[match(fagu[,1]-w[2]*2,fenl),4])
fagu[,5] = ifelse(w[2]*3>fagu[,1],0,3*b[2])+ifelse(fagu[,1] - w[2]*3<0,0,fenl[match(fagu[,1]-w[2]*3,fenl),4])
fagu[,6] = apply(fagu[,2:5],1,max)
fagu[,7] = apply(fagu[,2:5],1,which.max)-1
fagu
##      Estados A:0 A:1 A:2 A:3 ValueMax Decisión a tomar
## [1,]    3000  40  35  30  45       45                3
## [2,]    2500  20  35  30   0       35                1
## [3,]    2000  20  15  30   0       30                2
## [4,]    1500  20  15   0   0       20                0
## [5,]    1000   0  15   0   0       15                1
## [6,]     500   0   0   0   0        0                0
## [7,]       0   0   0   0   0        0                0

Finalmente, hacemos la última recursión para la primera decisión que se toma: Bloqueador. Aquí, sabemos que el estado inicial es 3000, por lo que sólo tenemos que evaluar las decisiones en este estado. Además, tenemos que el número máximo de objetos que podemos llevar es de \(\left( \frac{3000}{500}= 6\right)\).

fblo =c(3000, 0,0,0,0,0,0,0)
fblo[2] = 0*b[1]+fagu[match(fblo[1]-w[1]*0,fagu),6]
fblo[3] = 1*b[1]+fagu[match(fblo[1]-w[1]*1,fagu),6]
fblo[4] = 2*b[1]+fagu[match(fblo[1]-w[1]*2,fagu),6]
fblo[5] = 3*b[1]+fagu[match(fblo[1]-w[1]*3,fagu),6]
fblo[6] = 4*b[1]+fagu[match(fblo[1]-w[1]*4,fagu),6]
fblo[7] = 5*b[1]+fagu[match(fblo[1]-w[1]*5,fagu),6]
fblo[8] = 6*b[1]+fagu[match(fblo[1]-w[1]*6,fagu),6]
fblo
## [1] 3000   45   43   46   44   47   40   48
print(paste0("Mi beneficio será:" ,max(fblo[2:8])))
## [1] "Mi beneficio será:48"
print(paste0("Debo empacar ",which.max(fblo[2:8])-1, " bloqueadores"))
## [1] "Debo empacar 6 bloqueadores"

De este modo, la estrategia para maximizar el beneficio es:

  1. Bloqueador: Empacar 6.
  2. Agua: Empacar 0.
  3. Enlatados: Empacar 0.

El beneficio al implementar esta política de decisión es de 48.

Análisis de sensibilidad

Ahora bien, en el literal b se plantea una evaluación del modelo cambiando el parámetro de entrada del beneficio del bloqueador. Esto equivale a cambiar en nuestro modelo el parámetro \(b[2] = 5\) y volver a correr la programación dinámica.

e = c(1,2,3)                                #Épocas (1: Bloqueador, 2: Agua, 3: Enlatados)
s = c(3000,2500,2000,1500,1000,500,0)       #Espacio de estados
w = c(500,1000,1500)                        #Pesos
b = c(5,15,20)                              #Beneficios
x = 3000                                    #Espacio disponible en la mochila

Si existe una disminución de 3 unidades en el beneficio inmediato obtenido por empacar cada bloqueador, la estrategia que maximiza el beneficio es:

  1. Bloqueador: Empacar 0.
  2. Agua: Empacar 3.
  3. Enlatados: Empacar 0.

En este caso, el beneficio total al implementar esta política de decisión es de 45. Se concluye entonces que el beneficio total obtenido disminuye en 3 unidades al disminuir en 3 unidades la ganancia inmediata de empacar un bloqueador y la política de decisión cambia.

Ejercicios

  1. Implemente la estimación de la función recursiva en una function() en R para hacer que el código sea más eficiente y replicable.
  2. ¿Cuál es la política de decisión y el beneficio inmediato si los parámetros del modelo son los siguientes?

Peso total de la mochila: 3000.

Objeto Beneficio Peso
Agua 18 1200
Bloqueador 7 400
Enlatados 12 8000