Equation#
Introduction#
Equations in GAMSPy are associated with the symbolic algebraic relationships
that will be used to generate the constraints in a model. The algebraic
relationships are defined by using constants, mathematical operators,
functions, sets, parameters and variables. As with variables,
an equation may be defined over a group of sets and in turn map into several
individual constraints associated with the elements of those sets.
Equations are specified in two steps. First they have to be declared, afterwards
they get a definition. Finally, in order
to be considered, they have to be added to an instance of gamspy.Model through
the equations
argument of its constructor. A handy shortcut to retrieve all equations
contained in a gamspy.Container is the
getEquations()
method.
Equation Declaration#
A GAMSPy equation must be declared before it may
be referenced by creating an instance of Equation
.
The declaration of an equation is similar to a set, parameter,
or variable, in that it requires a container, an optional name, a domain (if applicable),
and an optional description. For a complete list of available arguments, see
the Equation reference
.
Below is an example of equation declarations adapted from trnsport.py. for illustration::
from gamspy import Container, Set, Equation
m = Container()
i = Set(m, name="i", records=["seattle", "san-diego"])
j = Set(m, name="j", records=["new-york", "chicago", "topeka"])
supply = Equation(
m,
name="supply",
domain=[i],
description="observe supply limit at plant i",
)
demand = Equation(
m,
name="demand",
domain=[j],
description="satisfy demand at market j",
)
First a gamspy.Container
is created and two sets i
and j
are added.
Those will be used as domain for the equations about to be declared.
Two instances of gamspy.Equation
are created and assigned to Python
variables for later use. In this case both equations are one dimensional,
and use the sets i
(supply
) and j
(demand
) as domain.
In typical circumstances an indexed equation declaration implies that a block
of constraints will be generated. For example, equation supply
implies that
two constraints will be generated, one for each element of the set i
.
Equation Definition#
After declaring equations they have to be defined. The definition of an
equation specifies its algebraic structure by using sets, parameters,
logical operators and functions. A definition is made
by assigning an expression of the form expression [==|>=|<=] expression
to the Python variable that references the Equation
instance. For
indexed equations, the index operator is used to specify the domain:
equation[index_list] = expression [==|>=|<=] expression
The index_list
consists of one or multiple sets which correspond to the
sets that were used when the equation was declared using the domain
argument
of the Equation
constructor. One or more logical conditions are optional.
After the assignment operator =
, the left hand side of the equation follows.
It is an arbitrary algebraic expression which may include variables, parameters,
functions, and constants among other items. The left hand side is followed by one
of the supported relational operators which define the relation between the left hand side
and the right hand side of the equation:
==
: Equal>=
: Greater than or equal<=
: Less than or equal
Note that other operators like <
, >
or !=
are not supported. Furthermore
the operator is only significant in case of equations of type="regular"
which is
the default. See the Equation Types section for more details about the available
equation types.
A zero dimensional or scalar equation which is not declared over one or multiple sets
has to use the ellipsis literal [...]
instead of the indexing operator like
follows:
equation[...] = expression [==|>=|<=] expression
Note
Note that each equation has to be declared before it can be defined.
Scalar Equations#
A scalar equation will produce one equation in the associated optimization problem. The following is an example of a scalar equation definition from the ramsey.py. model:
utility[...] = W == Sum(t, beta[t] * gams_math.log(C[t]))
The equation utility
defined above is an example of a scalar equation that uses the scalar
variable W
. In addition, scalar equations may contain indexed variables like C
.
However, they must occur with an indexed operator such as Sum
or Product
, unless the indexed
variables refer to a singleton set (a set with only one element).
Indexed Equations#
All the set references in scalar equations are within the scope of indexed operators or
they refer to singleton sets, thus many variable, set and parameter references can be
included in one equation. In addition, GAMSPy also allows for equations to be defined
over a domain, thereby developing a compact representation for constraints. The
index sets to the left of the Python assignment operator =
are called the domain
of definition of the equation.
Note
GAMSPy equations can have up to 20 dimensions.
Domain checking ensures that the domain over which an equation is defined is the set (or the sets) or a subset of the set (or the sets) over which the equation was declared.
As a corollary, domain checking also catches the error of the indices being listed in an inconsistent order. For example, declaring an equation with
domain=[s, t]
and then naming it in the definition asmyequation[t, s]
causes an error (unlesss
andt
are aliases of the same set).
The following is an example of indexed equation definitions, again taken from the
trnsport.py model. Besides the already introduced sets i
and j
, parameters a
and b
are used as well as the Sum
operator:
from gamspy Parameter, Sum
a = Parameter(m, domain=i, records=[["seattle", 350], ["san-diego", 600]])
b = Parameter(m, domain=j, records=[["new-york", 325], ["chicago", 300], ["topeka", 275]])
supply[i] = Sum(j, x[i, j]) <= a[i]
demand[j] = Sum(i, x[i, j]) >= b[j]
Given the set i
containing the elements "seattle"
and "san-diego"
, the
following two individual equations are generated for supply
:
supply["seattle"] = Sum(j, x["seattle", j]) <= a["seattle"]
supply["san-diego"] = Sum(j, x["san-diego", j]) <= a["san-diego"]
For the equation demand
, the number of generated constraints in three:
demand["new-york"] = Sum(i, x[i, "new-york"]) >= b["new-york"]
demand["chicago"] = Sum(i, x[i, "chicago"]) >= b["chicago"]
demand["topeka"] = Sum(i, x[i, "topeka"]) >= b["topeka"]
Combining Equation Declaration and Definition#
Sometimes it can be handy to combine an equation declaration and definition.
This is possible by using the optional definition
argument of
the Equation
constructor. A combined declaration and definition of the
preceding example would look like follows:
supply = Equation(
m,
name="supply",
domain=i,
description="observe supply limit at plant i",
definition=Sum(j, x[i, j]) <= a[i],
)
demand = Equation(
m,
name="demand",
domain=j,
description="satisfy demand at market j",
definition=Sum(i, x[i, j]) >= b[j],
)
Note
The arrangement of the terms in the equation is a matter of choice, but often a particular one is chosen because it makes the model easier to understand.
Using Labels Explicitly in Equations#
Sometimes it can be necessary to refer to specific set elements in equations.
This can be done as with parameters - by using quotes or double quotes around
the label. Consider the following example from the model cta.py where
the label "total"
is used on the second index position of the variable t
explicitly:
addrow[i, k] = Sum(v[i, j, k], t[v]) == 2 * t[i, "total", k]
Logic Equations#
Logic equations defined by using type="boolean"
in the Equation
constructor
use boolean algebra and have to evaluate to True
(or 1
) to be feasible if ==
is not provided.
If == 0 (or == 1) is given in the equation definition this requires the expression to be evaluaed to
False
(or 0
) (or True
(or 1
)) to be feasible.
Most boolean functions can be used with the a Python operator as well as an equivalent method
from gamspy.math
, but some do exist in the latter only. The following
table gives an overview of the available boolean functions in GAMSPy:
Function |
Operator |
Evaluation |
---|---|---|
Negation |
|
|
Logical conjunction |
|
|
Logical disjunction |
|
|
Exclusive disjunction |
|
|
Material implication |
|
|
Material equivalence |
|
|
Equation Types#
Equations can have different types. Most of the time, the default type="regular"
is sufficient, but there are other types for specific needs
and modelling practices. The following table gives an overview of the available
equation types in GAMSPy:
Type |
Description |
---|---|
|
This is the default equation type which is suitible for ordinary equations using the |
|
No relationship implied between left-hand side and right-hand side. This equation type is ideally suited for use in MCP models and in variational inequalities. This equation type requires ==. |
|
Equation is defined by external programs. This equation type requires ==. See the section External Equations in the documentation. |
|
Boolean equations. See the section Logic Equations. |
Expressions in Equation Definitions#
The arithmetic operators and some of the functions provided by GAMSPy may be used in equation definitions. But also certain native Python operators can be used. Consider the following example adapted from the model ramsey.py demonstrating the use of parentheses and exponentiation:
production[t] = Y[t] == a * (K[t] ** b) * (L[t] ** (1 - b))
The list of Python operators that can be used to create expressions:
Operator |
Explanation |
---|---|
+ |
Addition |
- |
Subtraction |
/ |
Division |
* |
Multiplication |
** |
Power |
% |
Mod |
& |
Logical And |
| |
Logical Or |
^ |
Logical Xor |
< |
Lower than |
<= |
Lower than or equal |
> |
Higher than |
>= |
Higher than or equal |
== |
Equal |
!= |
Not equal |
~ |
Not |
@ |
Matmul |
- |
Negated |
Functions in Equation Definitions#
The functions provided by GAMSPy can be found in gamspy.math
.
Note that some functions like uniform
and
normal
are not allowed in equation definitions. Further
information about the math functions can be found functions
documentation of GAMS. The use of the other functions is determined by the type of arguments in the model. There are two types of arguments:
Exogenous arguments: The arguments are known. Parameters and constants are used as arguments. Variable attributes (for example,
.l
and.m
attributes) are also allowed, but can be confusing to the reader of the model algebra. The expression is evaluated once when the model is being set up.Endogenous arguments: The arguments are variables and therefore unknown at the time of model setup. The function will be evaluated many times at intermediate points while the model is being solved. Note that the occurrence of any function with endogenous arguments implies that the model is not linear.
There are two types of functions allowing endogenous arguments: smooth functions
and discontinuous functions. Smooth functions are continuous functions with
continuous derivatives (like sin
,
exp
, log
). Discontinuous functions
include continuous functions with discontinuous derivatives
(like Max
, Min
, abs
)
and discontinuous functions (like ceil
, sign
).
Smooth functions may be used routinely in nonlinear models. However, discontinuous
functions may cause numerical problems and should be used only if unavoidable,
and only in a special model type called DNLP
. For more details on model types see
Model documentation.
Note
The best way to model discontinuous functions is with binary variables.
The result is a model of the type MINLP
. The GAMS model
absmip
demonstrates this formulation technique for the functions abs
, Min
,
Max
, and sign
.
Preventing Undefined Operations in Equations#
Some operations are not defined at particular values of the arguments. Two examples
are division by 0
and the log
of 0
. While this can easily be identified
at model setup for exogenous functions and expressions, it is a lot more difficult
when the terms involve variables. The expression may be evaluated many times when
the problem is being solved and the undefined result may arise only under certain
cases. One way to avoid an expression becoming undefined is adding bounds to the
respective variables. Consider the following example from the ramsey.py.
model:
C.lo[t] = 1e-3
utility[...] = W == Sum(t, beta[t] * gams_math.log(C[t]))
Specifying a lower bound for C[t]
that is slightly larger than 0
prevents the log
function from becoming undefined.
Equation Attributes#
Similar to variables, equations have five attributes. Five values are
associated with each unique label combination of every equation. They
are denoted by the properties .l
, .m
, .lo
, .up
and
.scale
. A list of the attributes and their description is given in
the following table:
Attribute |
Description |
---|---|
|
Lower bound. Negative infinity for |
|
Upper bound. Positive infinity for |
|
Level of the equation. This attribute is reset to a new value when a model containing the equation is solved. It is the calculation of the left hand side using the level of all terms involving variables. |
|
Marginal value for equation. This attribute is reset to a new value when a model containing the equation is solved. The marginal value for an equation is also known as the shadow price for the equation and in general not defined before solution but if present it can help to provide a basis for the model |
|
Numerical scaling factor that scales all coefficients in the equation.
This is only used when the model attribute |
|
This attribute allows to assign equations to stages in a stochastic
program or other block structured model. Similar to variable attributes
|
Note that all properties except for .scale
and .stage
contain the
attribute values of equations after a solution of the model has been obtained.
For some solvers it can be useful to specify marginal values .m
and level
values .l
on input to provide starting information. Also note that the
marginal value is also known as the dual or shadow price. Roughly speaking, the
marginal value .m
of an equation is the amount by which the value of the
objective variable would change if the equation level were moved one unit.
Equation attributes may be referenced in assignment statements and can be assigned to
to specify starting values. In addition, they serve for scaling purposes and for reporting
after a model was solved. Here the attributes are not accessed via the Python
properties, but are contained in the data of the equation itself which can be
retrieved via the records
property as the following example shows:
transport = Model(
m,
name="transport",
equations=m.getEquations(),
problem="LP",
sense=Sense.MIN,
objective=Sum((i, j), c[i, j] * x[i, j]),
)
transport.solve()
In [1]: supply.records
Out[1]:
i level marginal lower upper scale
0 seattle 350.0 -0.0 -inf 350.0 1.0
1 san-diego 550.0 0.0 -inf 600.0 1.0
The level values of the equation supply
are displayed. As expected, there
are two level values, one for each member of the set i
over which the
equation supply
was defined.
In addition to the equation attributes introduced above, there are a number of equation attributes that cannot be assigned but may be used in computations. They are given in the following table:
Attribute |
Description |
---|---|
|
The difference between the lower and upper bounds of an equation. |
|
Slack from equation lower bound. This is defined as the greater of two values: zero or the difference between the level value and the lower bound of an equation. |
|
Slack from equation upper bound. This is defined as the greater of two values: zero or the difference between the upper bound and the level value of an equation. |
|
Minimum slack from equation bound. This is defined as the minimum of two values: the slack from equation lower bound and the slack from equation upper bound. |
|
Infeasibility: Amount by which an equation is infeasible falling below its lower bound
or above its upper bound. This is defined as |
Equation attributes can be assigned just like Variable attributes. For example to assign an initial value to a scalar equation:
import gamspy as gp
m = gp.Container()
e = gp.Equation(m)
e.l = 5
or to assign an initial value to an equation with non-scalar domain:
import gamspy as gp
m = gp.Container()
i = gp.Set(m, "i", records=range(5))
e = gp.Equation(m, domain=i)
e.l[i] = 5
Inspecting Generated Equations#
The generated equations can be inspected by using getEquationListing()
function after solving the model. Note that by studying the equation listing the user may determine whether the
model generated by GAMS is the the model that the user has intended - an extremely important question. The equation
listing can be filtered with filters
argument, the number of equations returned can be limited with n
argument,
and Infeasibilities above a certain threshold can be filtered with infeasibility_threshold
argument.
For example, in Mexico Steel sector model
market requirements equation mr
is defined over markets j
which contain 3 elements and commodities cf
which contain
one element. If one prints the equation listing directly, getEquationListing
would return all three generated equations.
model.solve(options=gp.Options(equation_listing_limit=100))
In [1]: mr.getEquationListing()
Out[1]:
mr(steel,mexico-df).. x(steel,ahmsa,mexico-df) + x(steel,fundidora,mexico-df) + x(steel,sicartsa,mexico-df) + x(steel,hylsa,mexico-df) + x(steel,hylsap,mexico-df) + v(steel,mexico-df) =G= 4.01093 ; (LHS = 0, INFES = 4.01093 ****)
mr(steel,monterrey).. x(steel,ahmsa,monterrey) + x(steel,fundidora,monterrey) + x(steel,sicartsa,monterrey) + x(steel,hylsa,monterrey) + x(steel,hylsap,monterrey) + v(steel,monterrey) =G= 2.18778 ; (LHS = 0, INFES = 2.18778 ****)
mr(steel,guadalaja).. x(steel,ahmsa,guadalaja) + x(steel,fundidora,guadalaja) + x(steel,sicartsa,guadalaja) + x(steel,hylsa,guadalaja) + x(steel,hylsap,guadalaja) + v(steel,guadalaja) =G= 1.09389 ; (LHS = 0, INFES = 1.09389 ****)
One can alternatively filter certain equations by using the filters
argument. For example, if one only wants to see
the equations for monterrey market, they can provide the elements as follows:
In [1]: mr.getEquationListing(filters=[[], ['monterrey']])
Out[1]:
mr(steel,monterrey).. x(steel,ahmsa,monterrey) + x(steel,fundidora,monterrey) + x(steel,sicartsa,monterrey) + x(steel,hylsa,monterrey) + x(steel,hylsap,monterrey) + v(steel,monterrey) =G= 2.18778 ; (LHS = 0, INFES = 2.18778 ****)
filters
argument is a list of lists where each list specifies the elements to be gathered.
If an empty list is given as in the example above, it means all elements.
Number of equations returned can be filtered with n
argument. For example, if n
is set to 1,
the function return only the first equation.
Note
The equation listing provides information about the value of the left hand side (LHS
) and the
infeasbility max[0, lower-level, level-upper]
(INFES
) of the equations. This information is based on
the input point, not the solution that is calculated by the solve.
If one wants to ignore equations that have an infeasibility below a certain threshold, one can
specify the infeasibility_threshold
argument. Any equation that has infeasibility smaller than
infeasibility_threshold will be filtered out.
Note
Length of the filters
argument must be equal to the dimension of the equation.