Initializing help system before first use

Constraints

Linear, quadratic, and nonlinear constraints can be specified as follows:

constraint (constraint, body, lb, ub, sense, rhs, name)

The parameters are:

  1. constraint is the full-form constraint, such as x1 + 2 * x2 <= 4;
  2. body is the body of the constraint, such as 3 * x1 + x2 (it may contain constants);
  3. lb is the lower bound on the body of the constraint;
  4. ub is the upper bound on the body of the constraint;
  5. sense is the sense of the constraint, one among xpress.leq, xpress.geq, xpress.eq, and xpress.range; in the first three cases, the parameter rhs must be specified; only in the fourth case must lb and ub be specified;
  6. rhs is the right-hand side of the constraint;
  7. name is the name of the constraint. Parameters lb, ub, and rhs must be constant.

A much more natural way to formulate a constraint is possible though:

myconstr = x1 + x2 * (x2 + 1) <= 4
myconstr2 = xp.exp(xp.sin(x1)) + x2 * (x2**5 + 1) <= 4

One or more constraints (or list of constraints) can be added to a problem via the addConstraint method:

m.addConstraint (myconstr)
m.addConstraint(v1 + xp.tan(v2) <= 3)
m.addConstraint(x[i] + y[i] <= 2 for i in range(10))
myconstr = x1 + x2 * (x2 + 1) <= 4
m.addConstraint(myconstr)

In order to help formulate compact problems, the Sum operator of the xpress module can be used to express sums of expressions. Its argument is a list of expressions:

m.addConstraint(xp.Sum([y[i] for i in range(10)]) <= 1)
m.addConstraint(xp.Sum([x[i]**5 for i in range(9)]) <= x[9])

When handling variables or expressions, it is advised to use the Sum operator in the Xpress module rather than the native Python operator, for reasons of efficiency.

As for variables, an object of type constraint allows for read/write access of its features via its members name, body, lb, and ub. The same caveat for variables holds here: any change to an object's members will only have an effect in the problems to which a constraint is added after the change.

A set of variables or constraint can also be created using Python's fundamental data structure: lists and dictionaries, as well as NumPy's arrays. As described in Section Hints for building models efficiently below, one can for example create a list of variables x[i], all with upper bound 10, indexed from 0 to k-1 as follows:

k=24
x = [xpress.var(ub=10) for _ in range(k)]

If a more elaborate indexing is required, dictionaries can be used. Suppose we want to create an integer variable x for each item in the list ['Seattle','Miami','Omaha','Charleston']. Then

L = ['Seattle','Miami','Omaha','Charleston']
x = {i: xpress.var(vartype=xpress.integer) for i in L}

This allows one to refer to such variables using the names in L, for instance x['Seattle'], x['Charleston'], etc.

Similarly, one can use lists and dictionaries to create constraints, like in the following example on lists:

L = range(20)
x = [xpress.var(ub=1) for i in L]
y = [xpress.var(vartype=xpress.binary) for i in L]
constr = [x[i] <= y[i] for in L]
p = xpress.problem()
p.addVariable(x,y)
p.addConstraint(constr)

Below is an example with dictionaries. Note that Python allows for conditional indexing on the two parameters i and j, and each constraint can be referred to with pairs of names, e.g. cliq['Seattle','Miami'].

L = ['Seattle','Miami','Omaha','Charleston']
x = {i: xpress.var(vartype=xpress.binary) for i in L}
cliq = {(i,j): x[i] + x[j] <= 1 for i in L for j in L if i != j}
p = xpress.problem()
p.addVariable(x)
p.addConstraint(cliq)

There is yet another function for creating an indexed set of variables: the function xpress.vars. It takes one or more lists, sets, or ranges, and produces as many variables as can be indexed with all combinations from the provided lists/sets. This allows for creating a set of variables with the same bounds and type and a similar name, in case the problem is written onto an MPS or LP file. Its syntax is as follows:

xpress.vars(*indices, name='x', lb=0, ub=xpress.infinity,
             threshold = -xpress.infinity, vartype=xpress.continuous)

The parameter *indices stands for one or more arguments, each a Python list, a Python set, or a positive integer. If *indices consists of one list, then the result contains one element for each element of the list. In case of more lists, sets, or ranges in *indices, the Cartesian product of these lists/sets provides the indexing space of the result. All other arguments are the same as for the declaration of a single variable. Here is an example of use:

myvar = xpress.vars(['a','b','c'], lb=-1, ub=+1)

The result is the three variables myvar['a'], myvar['b'], and myvar['c'], all with -1 as lower bound and +1 as upper bound. The following is an example of multi-indexed variables:

y = xpress.vars(['a','b','c','d'], [100, 120, 150], vartype=xpress.integer)

The result is the 12 variables y['a',100], y['a',120], y['a',150], y['b',100],..., y['d',150].

If argument name is not specified, a prefix "x" is used. The name of each variable resulting from a call to xpress.vars is the given prefix and the comma-separated list of index values between brackets, for example it will be "x(a,100)", "x(a,120)", "x(a,150)" for the example above. The call

x = xpress.vars(['a','b','c','d'], [100, 120, 150], name='var')

produces variables x['a',100] whose name is "var(a,120)", etc.

In the *indices argument, in lieu of a list or a set one can also specify an integer positive number k, which is interpreted as the range of numbers 0,1,...,k-1. Thus the call x = xpress.vars(5, 7, vartype = xpress.integer) creates 35 variables x[0,0], x[0,1], x[0,2],..., x[4,6].

The xpress.vars function, effectively, is a more readable way to create a Python dictionary of variables. The instruction

x = xpress.vars(['a','b','c','d'], [100, 120, 150], ub=20, name='newvar')

is equivalent to the following:

x = {(i,j): xpress.var(ub=20, name='newvar({0},{1})'.format(i,j))
           for i in ['a','b','c','d']
           for j in [100, 120, 150]}