Ejercicio 1: Instrucciones

Modelo de programación lineal

Autor/a
Afiliación

Maria Blanco

Universidad Politécnica de Madrid

Fecha de publicación

14 de septiembre de 2026

1 Instrucciones

1.1 Objetivos y pasos a seguir

  1. Comprender la formulación del modelo oferta1 que 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.
  2. Crear el modelo oferta2: oferta1 con 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 de oferta1 y oferta2.
  3. Crear el modelo oferta3: oferta1 con un cultivo adicicional (tomate), del que se conocen los datos. Generar un parámetro de resultados para comparar los resultados de oferta1 y oferta2.

1.2 Configuración de este cuaderno

Instalación (si es necesario) y carga de GAMSPy

# 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 gp

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 np

2 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

3 Definición y formulación matemática del modelo oferta2

4 Definición y formulación matemática del modelo oferta3