# Instalamos la librería de GAMSPy (si no está instalada): ver instrucciones de instalación en https://gamspy.readthedocs.io/en/latest/user/installation.html
# En Google Colab siempre es necesario instalar GAMSPy > para ello activamos la siguiente línea de código y ejecutamos:
#! pip install gamspy
# Importamos GAMSPy
import gamspy as gpEjercicio 1: Instrucciones
Modelo de programación lineal
1 Instrucciones
1.1 Objetivos y pasos a seguir
- Comprender la formulación del modelo
oferta1que se presenta a continuación. La formulación es similar a la explicada en el Tutorial, pero las restricciones de recursos se han introducido por separado. - Crear el modelo
oferta2:oferta1con un cambio en la disponibilidad de trabajo, un aumento en 1 hora, para comprobar que el valor marginal del trabajo en la solución óptima mide el efecto sobre la variable objetivo de disponer de una unidad más del recurso. Generar un parámetro de resultados para comparar los resultados deoferta1yoferta2. - Crear el modelo
oferta3:oferta1con un cultivo adicicional (tomate), del que se conocen los datos. Generar un parámetro de resultados para comparar los resultados deoferta1yoferta2.
1.2 Configuración de este cuaderno
Instalación (si es necesario) y carga de GAMSPy
Configuración de Python y Jupyter Notebook
# En Google Colab no es nedesario instalar librerías de PYTHON
# En VS Code, es necesario instalar PYTHON si no está instalado. Si no están instaladas las librerías nbconvert o ipykernel, las instalamos. Desde el terminal:
# pip install pandas jupyter
# pip install -U ipykernel
# Importamos las librerías necesarias para tratamiento de datos
import pandas as pd
import numpy as np2 Definición y formulación matemática del modelo oferta1
2.1 Enunciado del problema
El modelo de oferta agraria oferta1 puede describirse como: un agricultor posee 50 hectáreas, dispone de 1800 horas de trabajo al año y tiene una disponibilidad de agua anual para toda la explotación de 200000 m3. El agricultor puede elegir entre varios cultivos y conoce las necesidades de recursos por hectárea, así como otros parámetros técnico-económicos (precio, rendimiento, costes y margen bruto). El objetivo del agricultor es determinar el plan de producción que le permita obtener el máximo beneficio posible.
2.2 Formulación algebraica del modelo lineal
Los elementos de la formulación matemática son:
Índices (Sets):
- \(c\) = cultivos
- \(t\) = técnicas
Datos (Parameters):
- \(dti\) = disponibilidad de tierra (ha)
- \(dtr\) = disponibilidad de trabajo (horas)
- \(dag\) = disponibilidad de agua (miles m3)
- \(ntr_{c}\) = necesidades de trabajo del cultivo \(c\) (horas/ha)
- \(nag_{c}\) = necesidades de agua del cultivo \(c\) (miles m3/ha)
- \(prec_{c}\) = precio del cultivo \(c\) (eur/t)
- \(rend_{c}\) = rendimiento del cultivo \(c\) (t/ha)
- \(cost_{c}\) = costes directos del cultivo \(c\) (t/ha)
- \(mb_{c}\) = margen bruto del cultivo \(c\) (eur/ha)
Variables de decisión:
- \(x_{c}\) = superficie del cultivo \(c\) (ha), donde \(x_{c} \ge 0\) para todo \(c\)
Variable objetivo:
- \(z\) = beneficio de la explotación (euros)
La formulación algebraica del problema es:
\[ \begin{aligned} \text{Maximizar } & z = \sum_c (mb_c \cdot x_c) \\ \text{sujeto a } & \sum_c (x_c) \le dti \\ & \sum_c (ntr_{c} \cdot x_c) \le dtr \\ & \sum_c (nag_{c} \cdot x_c) \le dag \\ & x_{c} \ge 0 \quad \forall c \end{aligned} \]
2.3 Datos del modelo oferta1
Antes de formular en modelo de optimización, vamos a organizar nuestros datos utilizando pandas:
# Tabla de necesidades de recursos por hectárea de cultivo:
nec_recurso = pd.DataFrame(
[["trigo", "tierra", 1], ["trigo", "trabajo", 20], ["trigo", "agua", 2.5],
["maiz", "tierra", 1], ["maiz", "trabajo", 60], ["maiz", "agua", 5.0]],
columns=["cultivo", "recurso", "valor"]
).set_index("cultivo")
# Tabla de indicadores técnicos y económicos: rendimiento, precio, costes y margen bruto:
dat_margen = pd.DataFrame(
[["trigo", "precio", 150], ["maiz", "precio", 150],
["trigo", "rendim", 5], ["maiz", "rendim", 10],
["trigo", "costes", 300], ["maiz", "costes", 500],
["trigo", "margen", 450], ["maiz", "margen", 1000]],
columns=["cultivo", "indicador", "valor"]
).set_index("cultivo")
# Tabla de disponibilidades de recursos:
dis_recurso = pd.DataFrame(
[["tierra", 50], ["trabajo", 1800], ["agua", 200]], columns=["recurso", "dr"]
).set_index("recurso")
nec_recurso, dat_margen( recurso valor
cultivo
trigo tierra 1.0
trigo trabajo 20.0
trigo agua 2.5
maiz tierra 1.0
maiz trabajo 60.0
maiz agua 5.0,
indicador valor
cultivo
trigo precio 150
maiz precio 150
trigo rendim 5
maiz rendim 10
trigo costes 300
maiz costes 500
trigo margen 450
maiz margen 1000)
Creación de parámetros individualizados para necesidades de recursos, precio, rendimiento, coste y margen:
# tablas individualizada para cada indicador y solo con dos columnas
dat_ntr = nec_recurso[nec_recurso["recurso"] == "trabajo"].drop(columns=['recurso'])
dat_nag = nec_recurso[nec_recurso["recurso"] == "agua"].drop(columns=['recurso'])
dat_precio = dat_margen[dat_margen["indicador"] == "precio"].drop(columns=['indicador'])
dat_rendim = dat_margen[dat_margen["indicador"] == "rendim"].drop(columns=['indicador'])
dat_costes = dat_margen[dat_margen["indicador"] == "costes"].drop(columns=['indicador'])
dat_margen = dat_margen[dat_margen["indicador"] == "margen"].drop(columns=['indicador'])
dat_ntr, dat_nag( valor
cultivo
trigo 20.0
maiz 60.0,
valor
cultivo
trigo 2.5
maiz 5.0)
2.4 Formulación del modelo en GAMSPy
El problema de oferta puede descomponerse en los siguientes bloques: conjuntos, parámetros, variables y ecuaciones. Para definir el modelo en GAMSPy, tenemos que declarar todos los símbolos que lo componen. Para ello, comenzamos creando un container que incluirá todos los elementos del modelo. A continuación, creamos los símbolos de tipo set (conjuntos), parameter (parámetros), variable (variables), equation (ecuaciones) y model (nombre del modelo).
2.4.1 Introducción de datos
Comenzamos creando un container y definiendo los conjuntos (subíndices):
# Container: creamos un contenedor m para el modelo
m = gp.Container()
# Sets: definimos los conjuntos de recursos y cultivos
c = gp.Set(container=m, name="c", description="cultivos", records=dat_precio.index)
# Visualizamos los elementos del conjunto c
c.records| uni | element_text | |
|---|---|---|
| 0 | trigo | |
| 1 | maiz |
Añadimos los parámetros, con datos introducidos directamente (disponibilidad de recursos) o desde los df:
# PARAMETROS (desde los datos)
# A nivel de finca (no tienen dimensión c,t...)
dti = gp.Parameter(m, name="dti", description="superficie explotacion (ha)")
dtr = gp.Parameter(m, name="dtr", description="trabajo familiar anual (h)")
dag = gp.Parameter(m, name="dag", description="disponibilidad de agua de riego (miles m3)")
dti.setRecords(50)
dtr.setRecords(1800)
dag.setRecords(200)
# Indicadores por cultivo
ntr = gp.Parameter(m, name="ntr", domain=[c], description="necesidad de trabajo (h/ha)", records=dat_ntr.reset_index())
nag = gp.Parameter(m, name="nag", domain=[c], description="necesidad de agua de riego (miles m3/ha)", records=dat_nag.reset_index())
precio = gp.Parameter(m, name="precio", domain=[c], description="precio de venta (EUR/ton)", records=dat_precio.reset_index())
rendim = gp.Parameter(m, name="rendim", domain=[c], description="rendimiento (ton/ha)", records=dat_rendim.reset_index())
costes = gp.Parameter(m, name="costes", domain=[c], description="coste directo total (EUR/ha)", records=dat_costes.reset_index())
# PARAMETROS calculados
# Margen bruto del cultivo
mb = gp.Parameter(m, name="mb", domain=[c], description="margen bruto del cultivo")
# Calculamos mb a partir de los parámetros precio, rendim y costes
mb[c] = precio[c]*rendim[c] - costes[c]
# Nota: el margen bruto se calcula como ingresos por hectárea (precio por rendimiento) menos costes directos por hectárea.
# Visualizamos los datos del parámetro mb
mb.records| c | value | |
|---|---|---|
| 0 | trigo | 450.0 |
| 1 | maiz | 1000.0 |
2.4.2 Variables, ecuaciones y modelo
Definimos las variables:
# Variables de decisión (superficie de cultivo)
x = gp.Variable(
container=m,
name="x",
domain=[c],
type="Positive",
description="superficie de cultivo (ha)",
)
# Variable objetivo: beneficio de la explotación (siempre tiene que ser de tipo "Free")
z = gp.Variable(
container=m,
name="z",
domain=None,
type="Free",
description="margen global (eur)",
)
# Nota: la variable objetivo z tiene que ser de tipo "Free" y no tiene dominio (es una variable escalar, no depende de ningún conjunto).Puesto que las variables de decisión x se han definido como positive, ya estamos teniendo en cuenta las condiciones de no negatividad.
Introducimos las ecuaciones y damos nombre al modelo:
# ECUACIONES
MARGEN = gp.Equation(m, name="MARGEN", description="definicion del margen global")
TIERRA = gp.Equation(m, name="TIERRA", description="restriccion de tierra")
TRABAJO = gp.Equation(m, name="TRABAJO", description="restriccion de trabajo por periodo")
AGUA = gp.Equation(m, name="AGUA", description="restriccion de agua")
# Función objetivo: margen global de la explotación
MARGEN[...] = (
z == gp.Sum([c], (mb[c] * x[c]))
)
# Condiciones que deben cumplir las variables (restricciones)
TIERRA[...] = (
gp.Sum([c], x[c]) <= dti
)
TRABAJO[...] = (
gp.Sum([c], ntr[c] * x[c]) <= dtr
)
AGUA[...] = (
gp.Sum([c], nag[c] * x[c]) <= dag
)
# MODELO
# Problema LP; objetivo = Z; sentido = MAX
oferta1 = gp.Model(
m,
name="oferta1",
equations=[MARGEN, TIERRA, TRABAJO, AGUA], # incluye las ecuaciones
problem="LP",
sense="MAX",
objective=z,
)2.5 Resolución del modelo y parámetro de resultados
Resolvemos el modelo:
# Ejecutamos el modelo
oferta1.solve()| Solver Status | Model Status | Objective | Num of Equations | Num of Variables | Model Type | Solver | Solver Time | |
|---|---|---|---|---|---|---|---|---|
| 0 | Normal | OptimalGlobal | 33500.0 | 4 | 3 | LP | CPLEX | 0.0 |
Analizamos los resultados (valores de las variables de decisión y de la función objetivo):
x.records.set_index(["c"])| level | marginal | lower | upper | scale | |
|---|---|---|---|---|---|
| c | |||||
| trigo | 30.0 | 0.0 | 0.0 | inf | 1.0 |
| maiz | 20.0 | 0.0 | 0.0 | inf | 1.0 |
z.records| level | marginal | lower | upper | scale | |
|---|---|---|---|---|---|
| 0 | 33500.0 | 0.0 | -inf | inf | 1.0 |
Analizamos los valores de las restricciones de recursos en la solución del modelo, tanto los niveles de uso de recursos como los valores marginales de los recursos.
TIERRA.records| level | marginal | lower | upper | scale | |
|---|---|---|---|---|---|
| 0 | 50.0 | 175.0 | -inf | 50.0 | 1.0 |
TRABAJO.records| level | marginal | lower | upper | scale | |
|---|---|---|---|---|---|
| 0 | 1800.0 | 13.75 | -inf | 1800.0 | 1.0 |
AGUA.records| level | marginal | lower | upper | scale | |
|---|---|---|---|---|---|
| 0 | 175.0 | 0.0 | -inf | 200.0 | 1.0 |
Podemos crear un parámetro para extraer y visualizar los resultados que nos interesen.
# RESULTADOS (plan de producción y uso de recursos)
oferta1_resultados = gp.Parameter(container=m, name="oferta1_resultados", domain=["*","*","*"])
oferta1_resultados["Uso_recurso","tierra",c] = x.l[c]
oferta1_resultados["Uso_recurso","trabajo",c] = ntr[c] * x.l[c]
oferta1_resultados["Uso_recurso","agua",c] = nag[c] * x.l[c]
oferta1_resultados["Uso_recurso","tierra","total"] = gp.Sum(c, x.l[c])
oferta1_resultados["Uso_recurso","trabajo","total"] = gp.Sum(c, ntr[c] * x.l[c])
oferta1_resultados["Uso_recurso","agua","total"] = gp.Sum(c, nag[c] * x.l[c])
oferta1_resultados["Valor_marginal","tierra","total"] = TIERRA.m
oferta1_resultados["Valor_marginal","trabajo","total"] = TRABAJO.m
oferta1_resultados["Valor_marginal","agua","total"] = AGUA.m
oferta1_resultados["Margen","global","total"] = z.l
oferta1_resultados["Margen","porha","total"] = z.l/gp.Sum(c, x.l[c])
# verificar que se han guardado los resultados en el parámetro de resultados
oferta1_resultados.records| uni_0 | uni_1 | uni_2 | value | |
|---|---|---|---|---|
| 0 | Uso_recurso | tierra | trigo | 30.00 |
| 1 | Uso_recurso | tierra | maiz | 20.00 |
| 2 | Uso_recurso | tierra | total | 50.00 |
| 3 | Uso_recurso | trabajo | trigo | 600.00 |
| 4 | Uso_recurso | trabajo | maiz | 1200.00 |
| 5 | Uso_recurso | trabajo | total | 1800.00 |
| 6 | Uso_recurso | agua | trigo | 75.00 |
| 7 | Uso_recurso | agua | maiz | 100.00 |
| 8 | Uso_recurso | agua | total | 175.00 |
| 9 | Valor_marginal | tierra | total | 175.00 |
| 10 | Valor_marginal | trabajo | total | 13.75 |
| 11 | Margen | global | total | 33500.00 |
| 12 | Margen | porha | total | 670.00 |