12.4 Decisions and games in software

Software 12.4.1 (Decision criteria). The criteria of the subsection on decisions under uncertainty, and the EMV, applied to Example 12.1.1, with the action each selects; then the EVPI:

>>> import numpy as np
>>> from scipy.optimize import linprog
>>> acts = ['large', 'medium', 'small']
>>> P = np.array([[200, 50, -120],         # payoff (Rs. lakh) by demand:
...               [120, 80, -30],          # high, medium, low
...               [60, 40, 20]])
>>> prob = np.array([0.3, 0.5, 0.2])
>>> regret = P.max(axis=0) - P
>>> crit = {'maximin': P.min(axis=1), 'maximax': P.max(axis=1),
...         'Hurwicz 0.6': 0.6 * P.max(axis=1) + 0.4 * P.min(axis=1),
...         'Laplace': P.mean(axis=1), 'minimax regret': -regret.max(axis=1),
...         'EMV': P @ prob}
>>> for name, score in crit.items():
...     print(f"{name:15s}", np.round(score, 2), acts[int(np.argmax(score))])
...
maximin         [-120  -30   20] small
maximax         [200 120  60] large
Hurwicz 0.6     [72. 60. 44.] large
Laplace         [43.33 56.67 40.  ] medium
minimax regret  [-140  -80 -140] medium
EMV             [61. 70. 42.] medium
>>> evpi = prob @ P.max(axis=0) - (P @ prob).max(); evpi
34.0

Reading the output. Maximin chooses the small plant, maximax and Hurwicz (\(\alpha = 0.6\)) the large, Laplace, minimax regret (printed with a minus sign, so that the largest is best) and EMV the medium. The EVPI is \(34\).

MATLAB/Octave: min(P, [], 2), max(P, [], 2) and P*prob’ give the same columns.

Software 12.4.2 (The value of a survey). Example 12.2.1: the probability of a favourable report, the posterior probabilities after each report, the best action and its expected payoff after each, and the expected payoff with the survey, the EVSI and the efficiency:

>>> like = np.array([0.8, 0.5, 0.1])       # P(favourable survey | demand)
>>> pf = prob @ like                       # P(favourable)
>>> post_f = prob * like / pf; post_u = prob * (1 - like) / (1 - pf)
>>> round(pf, 3), np.round(post_f, 4), np.round(post_u, 4)
(0.51, array([0.4706, 0.4902, 0.0392]), array([0.1224, 0.5102, 0.3673]))
>>> best_f, best_u = (P @ post_f).max(), (P @ post_u).max()
>>> choose = lambda post: acts[int(np.argmax(P @ post))]
>>> (choose(post_f), round(best_f, 2)), (choose(post_u), round(best_u, 2))
(('large', 113.92), ('medium', 44.49))
>>> with_survey = pf * best_f + (1 - pf) * best_u
>>> evsi = with_survey - (P @ prob).max()
>>> round(with_survey, 2), round(evsi, 2), round(evsi / evpi, 3)  # efficiency
(79.9, 9.9, 0.291)

Reading the output. A favourable report (probability \(0.51\)) makes high demand much more likely (\(0.47\)) and leads to the large plant, worth \(113.9\); an unfavourable one leads to the medium plant, worth \(44.5\). With the survey the expected payoff is \(79.9\); the EVSI is \(9.9\), \(29\%\) of the EVPI.

MATLAB/Octave: the same vector arithmetic, prob.*like/(prob*like’).

Software 12.4.3 (Games by linear programming). A function that solves any zero-sum game by the two LPs of the proof of Theorem 12.3.1 (after adding a constant to make all entries positive, so that the value is positive and the variables \(y = p/v\) can be used); a check for a saddle point; and three games:

>>> def solve_game(A):               # value and optimal mixed strategies
...     A = np.asarray(A, float); m, n = A.shape
...     k = 1 - A.min(); B = A + k         # shift so that every entry is > 0
...     # row player: minimise sum y with B^T y >= 1; then p = y / sum y
...     r = linprog(np.ones(m), A_ub=-B.T, b_ub=-np.ones(n), method='highs')
...     p = r.x / r.x.sum(); v = 1 / r.x.sum() - k
...     s = linprog(-np.ones(n), A_ub=B, b_ub=np.ones(m), method='highs')
...     q = s.x / s.x.sum()
...     return round(v, 6), np.round(p, 4), np.round(q, 4)
...
>>> A1 = [[4, 2, 3], [5, 3, 4], [1, 0, 6]]  # has a saddle point
>>> np.max(np.min(A1, axis=1)), np.min(np.max(A1, axis=0))
(3, 3)
>>> solve_game([[3, -1], [-2, 4]])         # 2 x 2 without a saddle point
(1.0, array([0.6, 0.4]), array([0.5, 0.5]))
>>> solve_game([[0, -1, 1], [1, 0, -1], [-1, 1, 0]])   # rock, paper, scissors
(0.0, array([0.3333, 0.3333, 0.3333]), array([0.3333, 0.3333, 0.3333]))
>>> solve_game([[2, -1, 3], [-1, 3, -2], [1, 0, 1]])
(0.714286, array([0.5714, 0.4286, 0.    ]), array([0.5714, 0.4286, 0.    ]))

Reading the output. The first matrix has maximin \(=\) minimax \(= 3\), a saddle point. The \(2 \times 2\) game has value \(1\) with \(p = (0.6, 0.4)\) and \(q = (0.5, 0.5)\), as the formula gives. Rock–paper–scissors has value \(0\) and the uniform strategies. The last game has value \(0.714 = \frac 57\), both players mixing their first two strategies in the ratio \(4 : 3\).

MATLAB/Octave: the same two linprog calls.

Questions on this section

Stuck on something here? Ask below and it stays attached to this topic.