14.3 Sensitivity Analysis

Given an optimization problem it is often useful to obtain information about how the optimal objective value changes when the problem parameters are perturbed. E.g, assume that a bound represents the capacity of a machine. Now, it may be possible to expand the capacity for a certain cost and hence it is worthwhile knowing what the value of additional capacity is. This is precisely the type of questions the sensitivity analysis deals with.

Analyzing how the optimal objective value changes when the problem data is changed is called sensitivity analysis.

References

The book [Chvatal83] discusses the classical sensitivity analysis in Chapter 10 whereas the book [RTV97] presents a modern introduction to sensitivity analysis. Finally, it is recommended to read the short paper [Wal00] to avoid some of the pitfalls associated with sensitivity analysis.

Warning

Currently, sensitivity analysis is only available for continuous linear optimization problems. Moreover, MOSEK can only deal with perturbations of bounds and objective function coefficients.

14.3.1 Sensitivity Analysis for Linear Problems

14.3.1.1 The Optimal Objective Value Function

Assume that we are given the problem

(14.3)z(lc,uc,lx,ux,c)=minimizecTxsubject tolcAxuc,lxxux,

and we want to know how the optimal objective value changes as lic is perturbed. To answer this question we define the perturbed problem for lic as follows

flic(β)=minimizecTxsubject tolc+βeiAxuc,lxxux,

where ei is the i-th column of the identity matrix. The function

(14.4)flic(β)

shows the optimal objective value as a function of β. Please note that a change in β corresponds to a perturbation in lic and hence (14.4) shows the optimal objective value as a function of varying lic with the other bounds fixed.

It is possible to prove that the function (14.4) is a piecewise linear and convex function, i.e. its graph may look like in Fig. 14.1 and Fig. 14.2.

_images/optimal_val_fun2.png

Fig. 14.1 β=0 is in the interior of linearity interval.

_images/optimal_val_fun.png

Fig. 14.2 β=0 is a breakpoint.

Clearly, if the function flic(β) does not change much when β is changed, then we can conclude that the optimal objective value is insensitive to changes in lic. Therefore, we are interested in the rate of change in flic(β) for small changes in β — specifically the gradient

flic(0),

which is called the shadow price related to lic. The shadow price specifies how the objective value changes for small changes of β around zero. Moreover, we are interested in the linearity interval

β[β1,β2]

for which

flic(β)=flic(0).

Since flic is not a smooth function flic may not be defined at 0, as illustrated in Fig. 14.2. In this case we can define a left and a right shadow price and a left and a right linearity interval.

The function flic considered only changes in lic. We can define similar functions for the remaining parameters of the z defined in (14.3) as well:

flic(β)=z(lc+βei,uc,lx,ux,c),i=1,,m,fuic(β)=z(lc,uc+βei,lx,ux,c),i=1,,m,fljx(β)=z(lc,uc,lx+βej,ux,c),j=1,,n,fujx(β)=z(lc,uc,lx,ux+βej,c),j=1,,n,fcj(β)=z(lc,uc,lx,ux,c+βej),j=1,,n.

Given these definitions it should be clear how linearity intervals and shadow prices are defined for the parameters uic etc.

14.3.1.1.1 Equality Constraints

In MOSEK a constraint can be specified as either an equality constraint or a ranged constraint. If some constraint eic is an equality constraint, we define the optimal value function for this constraint as

feic(β)=z(lc+βei,uc+βei,lx,ux,c)

Thus for an equality constraint the upper and the lower bounds (which are equal) are perturbed simultaneously. Therefore, MOSEK will handle sensitivity analysis differently for a ranged constraint with lic=uic and for an equality constraint.

14.3.1.2 The Basis Type Sensitivity Analysis

The classical sensitivity analysis discussed in most textbooks about linear optimization, e.g. [Chvatal83], is based on an optimal basis. This method may produce misleading results [RTV97] but is computationally cheap. This is the type of sensitivity analysis implemented in MOSEK.

We will now briefly discuss the basis type sensitivity analysis. Given an optimal basic solution which provides a partition of variables into basic and non-basic variables, the basis type sensitivity analysis computes the linearity interval [β1,β2] so that the basis remains optimal for the perturbed problem. A shadow price associated with the linearity interval is also computed. However, it is well-known that an optimal basic solution may not be unique and therefore the result depends on the optimal basic solution employed in the sensitivity analysis. If the optimal objective value function has a breakpoint for β=0 then the basis type sensitivity method will only provide a subset of either the left or the right linearity interval.

In summary, the basis type sensitivity analysis is computationally cheap but does not provide complete information. Hence, the results of the basis type sensitivity analysis should be used with care.

14.3.1.3 Example: Sensitivity Analysis

As an example we will use the following transportation problem. Consider the problem of minimizing the transportation cost between a number of production plants and stores. Each plant supplies a number of goods and each store has a given demand that must be met. Supply, demand and cost of transportation per unit are shown in Fig. 14.3.

_images/transportp2.png

Fig. 14.3 Supply, demand and cost of transportation.

If we denote the number of transported goods from location i to location j by xij, problem can be formulated as the linear optimization problem of minimizing

1x11+2x12+5x23+2x24+1x31+2x33+1x34

subject to

(14.5)x11+x12400,x23+x241200,x31+x33+x341000,x11+x31=800,x12=100,x23+x33=500,x24+x34=500,x11,x12,x23,x24,x31,x33,x340.

The sensitivity parameters are shown in Table 14.1 and Table 14.2.

Table 14.1 Ranges and shadow prices related to bounds on constraints and variables.

Con.

β1

β2

σ1

σ2

1

300.00

0.00

3.00

3.00

2

700.00

+

0.00

0.00

3

500.00

0.00

3.00

3.00

4

0.00

500.00

4.00

4.00

5

0.00

300.00

5.00

5.00

6

0.00

700.00

5.00

5.00

7

500.00

700.00

2.00

2.00

Var.

β1

β2

σ1

σ2

x11

300.00

0.00

0.00

x12

100.00

0.00

0.00

x23

0.00

0.00

0.00

x24

500.00

0.00

0.00

x31

500.00

0.00

0.00

x33

500.00

0.00

0.00

x34

0.000000

500.00

2.00

2.00


Table 14.2 Ranges and shadow prices related to the objective coefficients.

Var.

β1

β2

σ1

σ2

c1

3.00

300.00

300.00

c2

100.00

100.00

c3

2.00

0.00

0.00

c4

2.00

500.00

500.00

c5

3.00

500.00

500.00

c6

2.00

500.00

500.00

c7

2.00

0.00

0.00


Examining the results from the sensitivity analysis we see that for constraint number 1 we have σ1=3 and β1=300, β2=0.

If the upper bound on constraint 1 is decreased by

β[0,300]

then the optimal objective value will increase by the value

σ1β=3β.

14.3.2 Sensitivity Analysis with MOSEK

MOSEK provides the functions Task.primalsensitivity and Task.dualsensitivity for performing sensitivity analysis. The code in Listing 14.2 gives an example of its use.

Listing 14.2 Example of sensitivity analysis with the MOSEK Optimizer API for Python. Click here to download.
import sys
import mosek

# Since the actual value of Infinity is ignores, we define it solely
# for symbolic purposes:
inf = 0.0

# Define a stream printer to grab output from MOSEK
def streamprinter(text):
    sys.stdout.write(text)
    sys.stdout.flush()


def main():
    # Create a task
    with mosek.Task() as task:
        # Attach a printer to the task
        task.set_Stream(mosek.streamtype.log, streamprinter)

        # Set up data
        bkc = [mosek.boundkey.up, mosek.boundkey.up,
               mosek.boundkey.up, mosek.boundkey.fx,
               mosek.boundkey.fx, mosek.boundkey.fx,
               mosek.boundkey.fx]
        blc = [-inf, -inf, -inf, 800., 100., 500., 500.]
        buc = [400., 1200., 1000., 800., 100., 500., 500.]

        bkx = [mosek.boundkey.lo, mosek.boundkey.lo,
               mosek.boundkey.lo, mosek.boundkey.lo,
               mosek.boundkey.lo, mosek.boundkey.lo,
               mosek.boundkey.lo]
        c = [1.0, 2.0, 5.0, 2.0, 1.0, 2.0, 1.0]
        blx = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
        bux = [inf, inf, inf, inf, inf, inf, inf]

        ptrb = [0, 2, 4, 6, 8, 10, 12]
        ptre = [2, 4, 6, 8, 10, 12, 14]
        sub = [0, 3, 0, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6]

        val = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
               1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]

        numcon = len(bkc)
        numvar = len(bkx)
        numanz = len(val)

        # Input linear data
        task.inputdata(numcon, numvar,
                       c, 0.0,
                       ptrb, ptre, sub, val,
                       bkc, blc, buc,
                       bkx, blx, bux)
        # Set objective sense
        task.putobjsense(mosek.objsense.minimize)

        # Optimize
        task.optimize()

        # Analyze upper bound on c1 and the equality constraint on c4
        subi = [0, 3]
        marki = [mosek.mark.up, mosek.mark.up]

        # Analyze lower bound on the variables x12 and x31
        subj = [1, 4]
        markj = [mosek.mark.lo, mosek.mark.lo]

        (leftpricei, rightpricei, leftrangei, rightrangei, 
         leftpricej, rightpricej, leftrangej, rightrangej) = task.primalsensitivity(subi,
                                                                                    marki,
                                                                                    subj,
                                                                                    markj)

        print('Results from sensitivity analysis on bounds:')
        print('\tleftprice  | rightprice | leftrange  | rightrange ')
        print('For constraints:')

        for i in range(2):
            print('\t%10f   %10f   %10f   %10f' % (leftpricei[i],
                                                   rightpricei[i],
                                                   leftrangei[i],
                                                   rightrangei[i]))

        print('For variables:')
        for i in range(2):
            print('\t%10f   %10f   %10f   %10f' % (leftpricej[i],
                                                   rightpricej[i],
                                                   leftrangej[i],
                                                   rightrangej[i]))

        subc = [2, 5]

        leftprice, rightprice, leftrange, rightrange = task.dualsensitivity(subc)

        print('Results from sensitivity analysis on objective coefficients:')

        for i in range(2):
            print('\t%10f   %10f   %10f   %10f' % (leftprice[i],
                                                   rightprice[i],
                                                   leftrange[i],
                                                   rightrange[i]))

    return None

# call the main function
try:
    main()
except mosek.MosekException as e:
    print("ERROR: %s" % str(e.errno))
    if e.msg is not None:
        print("\t%s" % e.msg)
    sys.exit(1)
except:
    import traceback
    traceback.print_exc()
    sys.exit(1)