Initializing help system before first use

Querying a problem

It is useful, after solving a problem, to obtain the value of an optimal solution. After solving a continuous or mixed integer problem, the two methods getSolution and getSlack return the vector (of portions thereof) of an optimal solution or the slack of the constraints, respectively. If an optimal solution was not found but a feasible solution is available, these methods will return data based on this solution.

Both getSolution and getSlack can be used in multiple ways: if no argument is passed, the whole solution or slack vector is returned. If a range of indices is passed, a list of values is returned corresponding to the range specified. One can also pass a list of variables to obtain the value of these variables in the solution found.

For getSolution only, there are more possible ways to call it: indices, strings, expressions are the basic types. An index ind will yield the value of the variable whose index in that problem (i.e. the order in which it was added to the problem) is ind; if the index is out of range, an error will occur. A string str will yield the value of the variable whose name is equal to str, if such variable exists, otherwise an error will occur. Finally, an expression, which can be just a variable, will yield the value of the expression given the current solution.

These basic types can be combined, even on multiple levels, with Python's fundamental aggregate types: getSolution can be passed a list, a dictionary, a tuple, or any sequence, including NumPy arrays, of indices, strings, expressions, and other aggregate objects thereof. The result will have the same structure as the argument passed (list, dictionary, etc.) containing the value corresponding to the passed expressions, variable indices, or variable names.

The uses of getSolution are exemplified in the following code:

import xpress as xp
import numpy as np

v1 = xp.var(name='Var1')
x = [xp.var(lb=-1, ub=1, vartype=xp.integer) for i in range(10)]

m = xp.problem()

m.addVariable(v1, x)

[...] # add constraints and objective

m.solve()

print(m.getSolution ())            # Prints a list with an optimal solution
print("v1 is", m.getSolution(v1))  # Only prints the value of v1
a = m.getSolution(x)               # Gets the values of all variables in the vector x
b = m.getSolution(range(4))        # Gets the value of v1 and x[0], x[1], x[2], i.e.
                                   # the first four variables of the problem
c = m.getSolution('Var1')          # Gets the value of v1 by its name
e = m.getSolution({1: x, 2: 0,
                   3: 'Var1'})     # Returns a dictionary containing the same keys as
                                   # in the arguments and the values of the
                                   # variables/expressions passed
d = m.getSolution(v1 + 3*x)        # Gets the value of an expression under the
                                   # current solution
e = m.getSolution(np.array(x))     # Gets a NumPy array with the solution of x

Consider the last eight instructions. The first of them returns a Python list of ncol floating point scalars, where ncol is the number of variables of the problem (nrow is the number of constraints, the size of the vector returned by getSlack) containing the full solution. The second example retrieves the value of the single variable v1.

The third example returns an array of the same size as x with the value of all variables of the list x. The fourth example shows that a range of indices can be specified in order to obtain a vector of values without specifying the corresponding variables. Recall that the column and row indices begin at 0. The fifth line shows that a variable can be passed by name, while the sixth line shows that passing a dictionary with variables, expression, indices, or variable names returns a dictionary with the same keys as the dictionary passed, but with its values set to the values of the corresponding variables/expressions.

The seventh line shows how to request the value of an expression when evaluated with the current solution found for the problem, and the eight line is equivalent to m.getSolution(x) but the returned object is a NumPy array with the solution (this can be useful when using NumPy with large vectors both for defining a problem and handling solution vectors).

The method getSlack works only with indices, constraint names, constraint objects, and lists of indices/names/constraints. The following examples illustrate a few possible uses.

import xpress as xp

N = 10

x  = [xp.var(vartype=xp.binary) for i in range(N)]

m  = xp.problem()

m.addVariable(x)

con1 = xp.Sum(x[i] * i for i in range(N)) <= N)
con2 = (x[i] >= x[i+1] for i in range(N-1))

m.addConstraints(con1, con2)
m.setObjective(xp.Sum(x[i] for i in range(N))
m.solve()

print(m.getSlack())                   # prints a list of slacks for all N constraints
print("slack_1 is", m.getSlack(con1)) # only prints the slack of con1

a = m.getSlack(con2)                  # gets the slack of N-1 constraints con2 as a list of floats
b = m.getSlack(range(2))              # gets the slack of con1 and con2[0]

In addition, for problems with only continuous variables, the two methods getDual and getRCost return the the vector (or a portion thereof) of dual variables and reduced costs, respectively. Their usage is similar to that for getSolution and getSlack.

Note that the inner workings of the Python interface obtain a copy of the whole solution, slack, dual, or reduced cost vectors, even if only one element is requested. It is therefore advisable that instead of repeated calls (for instance, in a loop) to getSolution, getSlack, etc. only one call is made and the result is stored in a list to be consulted in the loop. Hence, in the following example:

import xpress as xp

n = 10000
N = range(n)

x = [xp.var() for i in N]

p = xp.problem()

p.addVariable(x)
m.addConstraints(xp.Sum(x[i] * i for i in N) <= n))
m.setObjective(xp.Sum(x[i] for i in N)
m.solve()

for i in N:
    if m.getSolution(x[i]) > 1e-3:
        print(i)

the last three lines should be substituted as follows, as this will prevent repeatedly copying a large (10,000) vector:

sol = m.getSolution()

for i in N:
    if sol[i] > 1e-3:
        print(i)

A very similar function of the class problem is evaluate, which allows for running all of the above evaluation functions while passing, rather than the solution currently available for the problem, any vector or any dictionary assigning a float to the variables used in the expressions.

© 2001-2019 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.