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 gamspy.Equation
.
The declaration of an equation is similar to a set, parameter,
or variable, in that it requires a container, a name, a domain (if applicable),
and an optional description. For a complete list of available arguments, see
the Equation reference
.
An example for equation declarations adapted from trnsport.py is shown below 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 gamspy.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
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). For more information, see section Domain Checking in the GAMS documentation.
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
capacities = [["seattle", 350], ["san-diego", 600]]
demands = [["new-york", 325], ["chicago", 300], ["topeka", 275]]
a = Parameter(m, name="a", domain=[i], records=capacities)
b = Parameter(m, name="b", domain=[j], records=demands)
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:
from gamspy import Container, Equation, Sum
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. 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. |
|
Equation is defined by external programs. See the section External Equations in the GAMS documentation. |
|
Conic constraint. See the section Conic Programming in the GAMS 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))
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.
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 variable attributes (for example,
.l
and.m
attributes) are used as arguments. The expression is evaluated once when the model is being set up and most mathematical functions are allowed.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
. See also section Reformulating DNLP Models in the GAMS documentation.
We strongly discourage the use of the DNLP
model type.
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] = 0.001
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:
Equation Attribute |
Property |
Description |
---|---|---|
Lower bound |
|
Negative infinity for |
Upper bound |
|
Positive infinity for |
Equation level |
|
Level of the equation in the current solution, equal to the level of all terms involving variables. |
Marginal |
|
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 |
Scale factor |
|
Numerical scaling factor that scales all coefficients in the equation.
This is only used when the model attribute |
Stage |
|
This attribute allows to assign equations to stages in a stochastic
program or other block structured model. Its current use is limited to
2-stage stochastic programs solved with |
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 expressions and can be used 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()
print(supply.records)
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:
Equation Attribute |
Property |
Description |
---|---|---|
Range |
|
The difference between the lower and upper bounds of an equation. |
Slack lower bound |
|
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 upper bound |
|
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. |
Slack |
|
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 max(0, lower bound - level, level - upper bound). |