Tutorial de GAMSPy: primer modelo agroeconómico en GAMSPy

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 Introducción

Esta guía está diseñada para ofrecerte una introducción a la formulación de modelos de programación matemática utilizando el programa GAMS en el entorno Python, es decir, utilizando GAMSPy. El objetivo es aprender a traducir un problema agroeconómico a un lenguaje de modelización capaz de resolverlo.

Para funciones más avanzadas, puedes explorar la lista de funciones completas user guide y la extensa model library.

Aunque no es obligatorio, tener un conocimiento básico de programación en Python y el uso de Pandas library te facilitará seguir este tutorial.

Puedes ver un ejemplo de modelo GAMS en Python en: https://gamspy.readthedocs.io/en/latest/user/notebooks/trnsport.html

y en google colab: https://colab.research.google.com/github/GAMS-dev/gamspy/blob/develop/docs/user/notebooks/trnsport_colab.ipynb

2 Objetivos de aprendizaje

  1. Formular matemáticamente un modelo de optimización lineal con restricciones de recursos (modelo agroeconómico a nivel de la explotación).
  2. Implementar y resolver modelos agroeconómicos utilizando GAMSPy (GAMS para Python).
  3. Interpretar los resultados del modelo: plan de producción óptimo (valores óptimos de las variables de decisión) y valores marginales (valores duales o precios sombra) de los recursos limitados.

3 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 los elementos deseados de GAMSPy
# OPCION 1: solo se importan algunos elementos (los utilizados en el modelo) > activando la siguiente línea de código y ejecutando:
#> from gamspy import Container, Set, Parameter, Variable, Equation, Model, Sum, Sense, Options

# OPCION 2: se importa toda la app (recomendable para modelos grandes) 
import gamspy as gp
# OjO: en este caso las instrucciones cambian a gp.Container....

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

4 Definición y formulación matemática del primer modelo de oferta agraria

4.1 Enunciado del problema

Comenzaremos con un primer modelo de programación lineal en lenguaje GAMS : modelo lineal muy simple con únicamente dos actividades de producción y tres restricciones.

Enunciado: “Un agricultor, que posee 50 hectáreas y dispone de 1800 horas de trabajo al año, puede producir dos cultivos: trigo y maíz. El trigo tiene un margen bruto de 450 euros por hectárea y el maíz de 1000 euros por hectárea. El trigo necesita 20 horas de trabajo al año y el maíz 60. El trigo necesita 2500 m3 de agua de riego al año y el maíz 5000. La disponibilidad anual de agua de riego está limitada a 200000 m3. El agricultor quiere determinar el plan de producción que le permita obtener el margen global de la explotación más elevado posible”.

Por consiguiente, el objetivo es encontrar la superficie a dedicar a cada cultivo de forma que el beneficio de la finca sea el máximo posible.

4.2 Formulación algebraica del modelo lineal

Los elementos de la formulación matemática son:

Índices (Sets):

  • \(c\) = cultivos, donde \(c\) = {\(trigo, maiz\)}
  • \(r\) = recursos, donde \(r\) = {\(tierra, trabajo, agua\)}

Datos (Parameters):

  • \(mb_{c}\) = margen bruto del cultivo \(c\) (eur/ha)
  • \(d_{r}\) = disponibilidad del recurso \(r\) (ud)
  • \(n_{c,r}\) = necesidades por unidad de cultivo \(c\) del recurso \(r\) (ud/ha)

Variables de decisión:

  • \(x_{c}\) = superficie de cultivo \(c\) (ha), donde \(x_{c} \ge 0\) para todo \(c\)

Variable objetivo:

  • \(z\) = beneficio de la explotación (eur)

Ecuaciones:

  • Función objetivo:   Maximizar \(z = \sum_c (mb_c \cdot x_c)\)

  • Restricciones de recursos:   \(\sum_c (n_{cr} \cdot x_c) \le d_r \quad \forall r\)

  • Condidiones de no-negatividad:   \(x_{c} \ge 0 \quad \forall c\)

Por consiguiente, la formulación algebraica del problema es:

\[ \begin{aligned} \text{Maximizar } & z = \sum_c (mb_c \cdot x_c) \\ \text{sujeto a } & \sum_c (n_{cr} \cdot x_c) \le d_r \quad \forall r \\ & x_{c} \ge 0 \quad \forall c \end{aligned} \]

5 Datos del primer modelo

Antes de formular en modelo de optimización, vamos a organizar nuestros datos utilizando pandas:

Tabla de necesidades de recursos:

# Necesidades de recursos por 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")

nec_recurso
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

Tabla de indicadores técnicos y económicos: rendimiento, precio, costes y margen bruto

# Datos de margen por cultivo
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")

dat_margen
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 precio, rendimiento, coste y margen:

# tablas individualizada para cada indicador y solo con dos columnas

precio = dat_margen[dat_margen["indicador"] == "precio"].drop(columns=['indicador'])
rendim = dat_margen[dat_margen["indicador"] == "rendim"].drop(columns=['indicador'])
costes = dat_margen[dat_margen["indicador"] == "costes"].drop(columns=['indicador'])
margen = dat_margen[dat_margen["indicador"] == "margen"].drop(columns=['indicador'])

margen
valor
cultivo
trigo 450
maiz 1000

Tabla de disponibilidades de recursos:

dis_recurso = pd.DataFrame(
    [["tierra", 50], ["trabajo", 1800], ["agua", 200]], columns=["recurso", "valor"]
).set_index("recurso")

dis_recurso
valor
recurso
tierra 50
trabajo 1800
agua 200

6 Elementos 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).

6.1 Container

En primer lugar, creamos un Container (contenedor) para encapsular toda la información relevante de nuestro Modelo GAMSPy. Este Container actúa como un centro de información, recopilando datos esenciales, conjuntos, parámetros, variables y restricciones, proporcionando una estructura clara para nuestro problema de optimización.

# Creamos un contenedor m para el modelo
m = gp.Container()

6.2 Sets

Los Sets son uno de los bloques fundamentales de un modelo GAMSPy, correspondiendo a los índices de la notación algebraica. En el modelo de oferta, definimos los siguientes índices:

  • \(c\) = cultivos
  • \(r\) = recursos

Para una guía detallada sobre el uso de Sets, consultad la set section de la guía de usuario.

Hay dos formas de declarar sets:

  1. Primero declaración y después asignación de valores en una instrucción separada.
  2. Combinar la declaración y asignación en una única instrucción.

6.2.1 Declaration y asignación separada

Para el set c utilizamos la forma separada:

# Creamos un conjunto c de cultivos
c = gp.Set(container=m, name="c", description="cultivos")

# Asignamos los elementos del conjunto c a partir del índice de la tabla margen
c.setRecords(margen.index)

# Visualizamos los elementos del conjunto c
c.records
uni element_text
0 trigo
1 maiz

6.2.2 Declaración y asignación combinada

Para el set r utilizamos la forma combinada:

# Creamos un conjunto r de recursos y asignamos los elementos del conjunto r a partir del índice de la tabla dis_recurso
r = gp.Set(container=m, name="r", description="recursos", records=dis_recurso.index)

# Otra forma de asignar los elementos del conjunto r es escribirlos directamente 
#> r = gp.Set(container=m, name="r", description="recursos", records=["tierra", "trabajo", "agua"])

# Visualizamos los elementos del conjunto r
r.records
uni element_text
0 tierra
1 trabajo
2 agua

De esta forma, hemos definido dos Set, \(c\) y \(r\). Es el equivalente a la notación algebraica:

\(c\) = {\(trigo, maiz\)}

\(r\) = {\(tierra, trabajo, agua\)}

Además, hemos introducido la descripción de cada uno, facilitando la comprensión del código del modelo.

Para visualizar el contenido de un set podemos usar <set name>.records.

# Visualizamos los elementos del conjunto c
c.records
uni element_text
0 trigo
1 maiz

6.3 Parameters

Para declarar los parámetros, utilizamos la palabra clave Parameter. Cada parámetro debe tener un nombre (identificador) y puede tener una descripción. Se deben indicar las dimensiones, como en \(mb_c\) donde la dimensión es \(c\). Para indicar las dimensiones, utilizamos el atributo domain e indicamos los Setcorrespondientes.

Al igual que cuando creamos los conjuntos, para los parámetros también podemos combinar o separar los pasos de declaración y asignación. Por comodidad, procederemos combinando la declaración del parámetro y la asignación de sus datos.

6.3.1 Introducción directa de datos

Los datos se pueden asignar directamente, a partir de una tabla de datos, o podemos calcularlos (subsección siguiente)

# Creamos un parámetro  mb de margen bruto del cultivo, con dominio c y asignamos los datos a partir de la tabla margen
mb = gp.Parameter(
    container=m,
    name="mb",
    domain=c,
    description="margen bruto del cultivo",
    records=margen.reset_index(),
)

# Otra forma de asignar los datos del parámetro mb es escribirlos directamente
#> mb = gp.Parameter(container=m, name="mb", domain=c, description="margen bruto del cultivo", records=[["trigo", 450], ["maiz", 1000]])

# Visualizamos los datos del parámetro mb
mb.records
cultivo value
0 trigo 450.0
1 maiz 1000.0
# Creamos un parámetro de disponibilidad de recursos, con dominio r y asignamos los datos a partir de la tabla dis_recurso
dr = gp.Parameter(
    container=m,
    name="dr",
    domain=r,
    description="disponibilidad del recurso r",
    records=dis_recurso.reset_index(),
)
dr.records
recurso value
0 tierra 50.0
1 trabajo 1800.0
2 agua 200.0
# Creamos un parámetro de necesidades de recursos, con dominio [c,r] y asignamos los datos a partir de la tabla nec_recurso
nr = gp.Parameter(
    container=m,
    name="nr",
    domain=[c, r],
    description="necesidades del recurso r por unidad de cultivo c",
    records=nec_recurso.reset_index(),
)
nr.records
cultivo recurso value
0 trigo tierra 1.0
1 trigo trabajo 20.0
2 trigo agua 2.5
3 maiz tierra 1.0
4 maiz trabajo 60.0
5 maiz agua 5.0

6.3.2 Parámetros calculados

Hemos introducido los datos de margen directamente. Pero podría tratarse de un parámetro calculado. Por ejemplo, si nos dan los datos de precio, rendimiento y costes, podemos calcular el margen bruto como ingresos por hectárea menos costes directos por hectárea:

# Creamos un parámetro de precio del producto, con dominio c y asignamos los datos a partir de la tabla precio
precio = gp.Parameter(
    container=m,
    name="precio",
    domain=c,
    description="precio del producto (eur/ton)",
    records=precio.reset_index(),
)
precio.records
cultivo value
0 trigo 150.0
1 maiz 150.0
# Creamos un parámetro de rendimiento del cultivo, con dominio c y asignamos los datos a partir de la tabla rendim
rendim = gp.Parameter(
    container=m,
    name="rendim",
    domain=c,
    description="rendimiento del cultivo (ton/ha)",
    records=rendim.reset_index(),
)
rendim.records
cultivo value
0 trigo 5.0
1 maiz 10.0
# Creamos un parámetro de costes directos de producción, con dominio c y asignamos los datos a partir de la tabla costes
costes = gp.Parameter(
    container=m,
    name="costes",
    domain=c,
    description="costes directos de produccion (eur/ha)",
    records=costes.reset_index(),
)
costes.records
cultivo value
0 trigo 300.0
1 maiz 500.0

Y a continuación, generamos el parámetro de margen:

# El parámetro margen ya está creado antes en el container m: aquí lo recalculamos a partir de los parámetros precio, rendim y costes
mb[c] = precio[c]*rendim[c] - costes[c]

mb.records

# Nota: el margen bruto se calcula como ingresos por hectárea (precio por rendimiento) menos costes directospor hectárea. El margen bruto es el beneficio que se obtiene por cada hectárea de cultivo, antes de considerar otros costes fijos o variables adicionales (trabajo, capital).
c value
0 trigo 450.0
1 maiz 1000.0

Más información sobre el uso de parámetos puede encontrarse en la parameter section de la guía de usuario.

6.4 Variables

Las variables GAMSPy se declaran con la palabra clave Variable. Cada Variable tiene un nombre (identificador), un dominio si es necesario, un tipo y, opcionalmente, una descripción.

Más información sobre variables puede consultarse en la variable section de la guía de usuario.

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

6.5 Ecuaciones

Una ecuación GAMSPy se declara con la palabra clave Equation. Las ecuaciones deben ser declaradas y formuladas en dos sentencias separadas. Primero debemos declarar la ecuación, utilizando la palabra clave Equation seguida del nombre (identificador), dominio si es necesario y, opcionalmente, una descripción.

6.5.1 Restricciones del modelo

El modelo de oferta tiene restricciones de recursos que, en notación algebraica, se escriben:

\(\sum_c (n_{cr} \cdot x_c) \le d_r \quad \forall r\)

Para definir estas ecuaciones en GAMSPy, en primer lugar declaramos las ecuaciones:

# Creamos las ecuaciones de restricciones de recursos, con dominio r 
RECURSOS = gp.Equation(
    container=m, name="RECURSOS", domain=r, description="restricciones de recursos"
)

A continuación, definimos las ecuaciones, incluyendo los siguientes componentes: 1. La variable Python de la Equation a definir 2. El dominio (en su caso) 3. Las condiciones a aplicar al dominio (en su caso) 4. El signo = 5. El lado de la izquierda de la ecuación (LHS) 6. El operador relacional (==, <=, >=) 7. El lado de la derecha de la ecuación (RHS)

Más información sobre ecuaciones puede encontrarse en la equation section de la guía de usuario.

Para las restricciones de recursos, la ecuación se define como sigue:

# Formulamos las ecuaciones de restricciones de recursos, con dominio r
RECURSOS[r] = (gp.Sum(c, nr[c, r] * x[c]) <= dr[r])

# OjO: utilizar [] para las dimensiones de las variables/parámetros y () para el sumatorio.
# Nota: es recomendable poner toda la ecuación entre paréntesis ( )

# Comprobamos que la definición de las restricciones de recursos es correcta
print(RECURSOS.getDefinition())
RECURSOS(r) .. sum(c,nr(c,r) * x(c)) =l= dr(r);

Las restricciones de no negatividad ya se han tenido en cuenta, puesto que hemos definido la variable x como variable positiva.

6.6 Función objectivo

La función objetivo de un modelo en GAMSPy puede formularse como cualquier otra ecuación. También es posible utilizar directamente la función obj.

En este ejemplo, formularemos una ecuación denominada MARGEN.

# Declaración de la función objetivo:
MARGEN = gp.Equation(m, name="MARGEN", description="definicion del margen global")

# Formulación de la función objetivo:
MARGEN[...] = (z == gp.Sum(c, mb[c] * x[c]))

# OjO: utilizar [...] si la ecuación no tiene dimensiones.

7 Denominación y definición del modelo

Un modelo en GAMSPy tiene un nombre (identificador) y engloba una función objetivo, unas restricciones, un sentido (minimize, maximize, feasibility), y un tipo de problema. También está asociado a un Container.

Para definir el modelo de oferta como un modelo GAMSPy, utilizamos la palabra clave Model, asociamos el modelo a un Container (populado con símbolos y datos), le damos un nombre “oferta”, específicamos las ecuaciones, definimos el tipo como programación lineal (LP), específicamos el sentido de la optimización (Sense.MIN), e indicamos la variable o expresión a optimizar.

GAMSPy permite dos opciones para asignar ecuaciones a un modelo: 1. Usando la lista de ecuaciones, 2. Usando todas las ecuaciones del archivo con el término m.getEquations().

7.1 Definición del modelo a través de la lista de ecuaciones

Es útil si queremos definir varios modelos en el mismo archivo, es decir, si queremos utilizar un subconjunto de las ecuaciones del Container.

En el modelo de oferta sería:

modelo1 = gp.Model(
    m,
    name="modelo1",
    equations=[RECURSOS,MARGEN],
    problem="LP",
    sense="MAX",
    objective=z,
)

7.2 Definición del modelo incluyendo todas las ecuaciones del archivo

Utilizar m.getEquations() es conveniente si queremos incluir todas las ecuaciones del Container en el modelo.

En el modelo de oferta sería:

modelo1_bis = gp.Model(
    m,
    name="modelo1_bis",
    equations=m.getEquations(),
    problem="LP",
    sense="MAX",
    objective=z,
)

Más información sobre el uso de Model puede encontrarse en la model section de la guía de usuario.

8 Resolución del modelo

8.1 Comando solve

Una vez definido el modelo, está listo para ser resuelto. El comando solve() activa la generación de la instancia específica del modelo, crea estructuras de datos adecuadas para el solucionador e invoca al solucionador. Para ello utilizamos el nombre del modelo seguido de .solve.

Opcionalmente, para ver el resultado en la consola, podemos utilizar la librería sys, pasando el atributo output=sys.stdout al oferta.solve().

# Ejecutamos el modelo y mostramos los resultados
import sys

modelo1.solve(output=sys.stdout)
--- Job _UOub9lAsQm_wOpg9a48FGQ.gms Start 09/14/26 01:32:38 52.5.0 9bc771bf WEX-WEI x86 64bit/MS Windows
--- Applying:
    C:\agami\bioeco\bio_models\agrimod_vs\.gamspy_venv\Lib\site-packages\gamspy_base\gmsprmNT.txt
--- GAMS Parameters defined
    LP cplex
    Input C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\_UOub9lAsQm_wOpg9a48FGQ.gms
    Output C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\_UOub9lAsQm_wOpg9a48FGQ.lst
    ScrDir C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\tmpo7o4d8ee\
    SysDir C:\agami\bioeco\bio_models\agrimod_vs\.gamspy_venv\Lib\site-packages\gamspy_base\
    LogOption 3
    Trace C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\_UOub9lAsQm_wOpg9a48FGQ.txt
    License C:\Users\Maria\Documents\GAMSPy\gamspy_license.txt
    OptFile 0
    OptDir C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\
    LimRow 0
    LimCol 0
    TraceOpt 3
    GDX C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\_UOub9lAsQm_wOpg9a48FGQout.gdx
    SolPrint 0
    SolveLink 2
    PreviousWork 1
    gdxSymbols newOrChanged
System information: 12 physical cores and 32 Gb physical memory detected
--- Starting compilation
--- _UOub9lAsQm_wOpg9a48FGQ.gms(71) 4 Mb
--- Starting execution: elapsed 0:00:00.005
--- Generating LP model modelo1
--- _UOub9lAsQm_wOpg9a48FGQ.gms(157) 4 Mb
---   4 rows  3 columns  9 non-zeroes
--- Range statistics (absolute non-zero finite values)
--- RHS       [min, max] : [ 5.000E+01, 1.800E+03] - Zero values observed as well
--- Bound     [min, max] : [        NA,        NA] - Zero values observed as well
--- Matrix    [min, max] : [ 1.000E+00, 1.000E+03]
--- Executing CPLEX (Solvelink=2): elapsed 0:00:00.018

IBM ILOG CPLEX   52.5.0 9bc771bf Jan 28, 2026          WEI x86 64bit/MS Window

--- GAMS/CPLEX licensed for continuous and discrete problems.
--- GMO setup time: 0.00s
--- GMO memory 0.50 Mb (peak 0.50 Mb)
--- Dictionary memory 0.00 Mb
--- Cplex 22.1.2.0 link memory 0.00 Mb (peak 0.00 Mb)
--- Starting Cplex

Version identifier: 22.1.2.0 | 2024-11-25 | 0edbb82fd
CPXPARAM_Advance                                 0
CPXPARAM_Simplex_Display                         2
CPXPARAM_MIP_Display                             4
CPXPARAM_MIP_Pool_Capacity                       0
CPXPARAM_MIP_Tolerances_AbsMIPGap                0
Tried aggregator 1 time.
LP Presolve eliminated 0 rows and 1 columns.
Reduced LP has 3 rows, 2 columns, and 6 nonzeros.
Presolve time = 0.00 sec. (0.00 ticks)

Iteration      Dual Objective            In Variable           Out Variable
     1 sI            0.000000                x(maiz) RECURSOS(tierra) slack
     2           33500.000000               x(trigo)RECURSOS(trabajo) slack

--- LP status (1): optimal.
--- Cplex Time: 0.00sec (det. 0.01 ticks)


Optimal solution found
Objective:        33500.000000

--- Reading solution for model modelo1
--- Executing after solve: elapsed 0:00:00.083
--- _UOub9lAsQm_wOpg9a48FGQ.gms(216) 4 Mb
--- GDX File C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\_UOub9lAsQm_wOpg9a48FGQout.gdx
*** Status: Normal completion
--- Job _UOub9lAsQm_wOpg9a48FGQ.gms Stop 09/14/26 01:32:38 elapsed 0:00:00.092
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

8.2 Chequeando el modelo

Las ecuaciones y variables generadas pueden inspeccionarse para verificar la corrección del modelo generado. GAMSPy proporciona getEquationListing y getVariableListing para obtener las ecuaciones y variables generadas.

Para obtener los listados, primero necesitamos resolver el modelo con opciones de equation_listing_limit y variable_listing_limit.

modelo1.solve(output=sys.stdout, options=gp.Options(equation_listing_limit=10, variable_listing_limit=10))
--- _UOub9lAsQm_wOpg9a48FGQ.gms(216) 4 Mb
--- Job _UOub9lAsQm_wOpg9a48FGQ.gms Start 09/14/26 01:32:38 52.5.0 9bc771bf WEX-WEI x86 64bit/MS Windows
--- Applying:
    C:\agami\bioeco\bio_models\agrimod_vs\.gamspy_venv\Lib\site-packages\gamspy_base\gmsprmNT.txt
--- GAMS Parameters defined
    LP cplex
    Input C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\_UOub9lAsQm_wOpg9a48FGQ.gms
    Output C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\_UOub9lAsQm_wOpg9a48FGQ.lst
    ScrDir C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\tmpo7o4d8ee\
    SysDir C:\agami\bioeco\bio_models\agrimod_vs\.gamspy_venv\Lib\site-packages\gamspy_base\
    LogOption 3
    Trace C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\_UOub9lAsQm_wOpg9a48FGQ.txt
    License C:\Users\Maria\Documents\GAMSPy\gamspy_license.txt
    OptFile 0
    OptDir C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\
    LimRow 10
    LimCol 10
    TraceOpt 3
    GDX C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\_UOub9lAsQm_wOpg9a48FGQout.gdx
    SolPrint 0
    SolveLink 2
    PreviousWork 1
    gdxSymbols newOrChanged
System information: 12 physical cores and 32 Gb physical memory detected
--- Starting compilation
--- _UOub9lAsQm_wOpg9a48FGQ.gms(71) 4 Mb
--- Starting execution: elapsed 0:00:00.005
--- Generating LP model modelo1
--- _UOub9lAsQm_wOpg9a48FGQ.gms(228) 4 Mb
---   4 rows  3 columns  9 non-zeroes
--- Range statistics (absolute non-zero finite values)
--- RHS       [min, max] : [ 5.000E+01, 1.800E+03] - Zero values observed as well
--- Bound     [min, max] : [        NA,        NA] - Zero values observed as well
--- Matrix    [min, max] : [ 1.000E+00, 1.000E+03]
--- Executing CPLEX (Solvelink=2): elapsed 0:00:00.007

IBM ILOG CPLEX   52.5.0 9bc771bf Jan 28, 2026          WEI x86 64bit/MS Window

--- GAMS/CPLEX licensed for continuous and discrete problems.
--- GMO setup time: 0.00s
--- GMO memory 0.50 Mb (peak 0.50 Mb)
--- Dictionary memory 0.00 Mb
--- Cplex 22.1.2.0 link memory 0.00 Mb (peak 0.00 Mb)
--- Starting Cplex

Version identifier: 22.1.2.0 | 2024-11-25 | 0edbb82fd
CPXPARAM_Advance                                 2
CPXPARAM_Simplex_Display                         2
CPXPARAM_MIP_Display                             4
CPXPARAM_MIP_Pool_Capacity                       0
CPXPARAM_MIP_Tolerances_AbsMIPGap                0
Tried aggregator 1 time.
LP Presolve eliminated 0 rows and 1 columns.
Reduced LP has 3 rows, 2 columns, and 6 nonzeros.
Presolve time = 0.00 sec. (0.00 ticks)
Using devex.

--- LP status (1): optimal.
--- Cplex Time: 0.00sec (det. 0.01 ticks)


Optimal solution found
Objective:        33500.000000

--- Reading solution for model modelo1
--- Executing after solve: elapsed 0:00:00.075
--- _UOub9lAsQm_wOpg9a48FGQ.gms(287) 4 Mb
--- GDX File C:\Users\Maria\AppData\Local\Temp\tmp108du7g2\_UOub9lAsQm_wOpg9a48FGQout.gdx
*** Status: Normal completion
--- Job _UOub9lAsQm_wOpg9a48FGQ.gms Stop 09/14/26 01:32:38 elapsed 0:00:00.088
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
# Ahora podemos chequear las ecuaciones
print(modelo1.getEquationListing())
RECURSOS(tierra)..  x(trigo) + x(maiz) =L= 50 ; (LHS = 50)
RECURSOS(trabajo)..  20*x(trigo) + 60*x(maiz) =L= 1800 ; (LHS = 1800)
RECURSOS(agua)..  2.5*x(trigo) + 5*x(maiz) =L= 200 ; (LHS = 175)
MARGEN..  - 450*x(trigo) - 1000*x(maiz) + z =E= 0 ; (LHS = 0)
# Podemos chequear las variables
print(modelo1.getVariableListing())
x(trigo)
                (.LO, .L, .UP, .M = 0, 30, +INF, 0)
        1       RECURSOS(tierra)
       20       RECURSOS(trabajo)
        2.5     RECURSOS(agua)
     -450       MARGEN

x(maiz)
                (.LO, .L, .UP, .M = 0, 20, +INF, 0)
        1       RECURSOS(tierra)
       60       RECURSOS(trabajo)
        5       RECURSOS(agua)
    -1000       MARGEN

z
                (.LO, .L, .UP, .M = -INF, 33500, +INF, 0)
        1       MARGEN
# Podemos chequear una ecuación en particular
print(RECURSOS.getEquationListing())
RECURSOS(tierra)..  x(trigo) + x(maiz) =L= 50 ; (LHS = 50)
RECURSOS(trabajo)..  20*x(trigo) + 60*x(maiz) =L= 1800 ; (LHS = 1800)
RECURSOS(agua)..  2.5*x(trigo) + 5*x(maiz) =L= 200 ; (LHS = 175)
# Podemos chequear una variable en particular
print(x.getVariableListing())
x(trigo)
                (.LO, .L, .UP, .M = 0, 30, +INF, 0)
        1       RECURSOS(tierra)
       20       RECURSOS(trabajo)
        2.5     RECURSOS(agua)
     -450       MARGEN

x(maiz)
                (.LO, .L, .UP, .M = 0, 20, +INF, 0)
        1       RECURSOS(tierra)
       60       RECURSOS(trabajo)
        5       RECURSOS(agua)
    -1000       MARGEN

9 Análisis de resultados del modelo

9.1 Valores de las variables de decisión

Los valores de las variables en la solución del modelo pueden obtenerse utilizando <variable name>.records.

El level indica el nivel en la solución. Otros atributos de las variables son el valor marginal, los límiter inferior y superior y el factor de escalado.

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

9.2 Valor óptimo de la variable objetivo

El óptimo de la función objetivo puede obtenerse utilizando <model name>.objective_value.

modelo1.objective_value

# Nota: también podemos ver el óptimo de la función objetivo mostrando la variable z: z.records
33500.0

9.3 Valores de las restricciones de recursos

También es importante analizar 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.

RECURSOS.records.set_index(["r"])
level marginal lower upper scale
r
tierra 50.0 175.00 -inf 50.0 1.0
trabajo 1800.0 13.75 -inf 1800.0 1.0
agua 175.0 0.00 -inf 200.0 1.0

El valor marginal de un recurso limitado, también denominado valor dual o precio sombra, representa el valor interno del recurso en el margen (valor de la última unidad utilizada en la solución del modelo). Puede interpretarse como el cambio que experimentaría el valor de la variable objetivo si la disponibilidad de recurso aumenta en una unidad (manteniéndose constantes el resto de condiciones del modelo). Es decir, puede interpretarse como el precio máximo que el productor (dada la solución óptima) estaría dispuesto a pagar por disponer de una unidad adicional del recurso.

Los recursos utilizados en su totalidad tienen un valor marginal positivo. En la solución óptima, se utiliza toda la tierra y todo el trabajo disponibles. El valor marginal de la tierra es de 175 eur/ha y el valor marginal del trabajo es de 13.75 eur/hora. Disponer de una hectárea de tierra adicional permitiría al agricultor incrementar el beneficio en 175 euros (en la nueva solución óptima).

Los recursos que no se utilizan en su totalidad, tienen un valor marginal nulo. En la solución óptima, se utilizan 175000 de los 200000 m3 disponibles. Por consiguiente, el valor marginal del agua es cero. Disponer de 1000 m3 adicionales no tendría ningún efecto en la variable objetivo.

💡 Para pensar:

  1. Compara los márgenes brutos de cada cultivo. ¿Por qué el modelo no destina el 100% de la tierra al cultivo que ofrece más margen por hectárea (maíz)? Argumenta qué recursos son limitantes e impiden cultivar toda la superficie de maíz.
  2. Revisa los valores marginales (duales) de las restricciones de recursos. Si el agricultor tuviese la oportunidad de contratar trabajo asalariado al precio de 15 euros por hora de trabajo, ¿debería contratarlo?

9.4 Parámetros de resultados

Podemos crear parámetros para extraer y visualizar los resultados que nos interesen.

# RESULTADOS (plan de producción y uso de recursos)
modelo1_resultados = gp.Parameter(container=m, name="modelo1_resultados", domain=["*","*","*"])

modelo1_resultados["Uso_recurso",r,c] = nr[c,r] * x.l[c]
modelo1_resultados["Uso_recurso",r,"total"] = gp.Sum(c, nr[c, r] * x.l[c])
modelo1_resultados["Valor_marginal",r,"total"] = RECURSOS.m[r]
modelo1_resultados["Margen","global","total"] = z.l
modelo1_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
modelo1_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

9.5 Interpretación de valores marginales

Podemos realizar cambios en el modelo para comprender mejor el significado de los valores marginales de los recursos. Por ejemplo, podemos hacer una simulación incrementando la disponibilidad de tierra en una unidad.

Siempre es preferible realizar las simulaciones sin modificar los datos originales del modelo, de forma que podamos comparar resultados antes y después del cambio y que podamos volver a utilizar los datos originales.

En nuestro ejemplo:

# Redefinimos el valor del parámetro de disponibilidad de recursos, solo para el recurso "tierra", de 50 a 51 ha
dr["tierra"] = 51

# Volvemos a ejecutar el modelo para ver cómo cambia la solución óptima:
modelo1.solve()

# Visualizamos los resultados de la variable de decisión x (superficie de cultivo) 
x.records.set_index(["c"])
level marginal lower upper scale
c
trigo 31.5 0.0 0.0 inf 1.0
maiz 19.5 0.0 0.0 inf 1.0
# Visualizamos el resultado de la variable objetivo z (beneficio) 
z.records
level marginal lower upper scale
0 33675.0 0.0 -inf inf 1.0

En la nueva solución del modelo, la variable objetivo ha aumentado exactamente en 175 euros.

Es importante tener en cuenta que el valor marginal de los recursos cambia cuando cambian las condiciones (siempre debe interpretarse como valor en el margen).