You are on page 1of 11

ConvexOpt Assessment 2

March 29, 2023

1 Prisoners’ Dilemma
The Prisoners’ Dilemma is a famous model for illustrating the conflict between social cooperation
and self-interested behavior. Consider a two player Prisoners’ Dilemma in which the cooperative
payoff possibilities are mathematically described by a polytope which is defined as the convex hull
of the payoff vectors (4,4), (6,0), (0,6) and (0,0). Suppose we think of the Prisoners’ Dilemma as
a bargaining situation where the disagreement point is d = (𝑑1 , 𝑑2 ). The notion of a disagreement
point introduces a constraint that player 𝑖 cannot get a payoff below her disagreement point payoff
𝑑𝑖 .
Whether or not you want to invest in understanding this model, you can always start your work
taking as given that the feasible set ℱ of payoff vectors (𝑢1 , 𝑢2 ) for the bargaining problem is
mathematically described as

𝑢1 + 2𝑢2 ≤ 12
2𝑢1 + 𝑢2 ≤ 12
𝑢1 ≥ 𝑑1 , 𝑢2 ≥ 𝑑2

2 Problem 1 (5 points). (Linear optimization using CVXPY).


Suppose the disagreement point is given by d = (3.5, 2). A weighted utilitarian criterion is defined
as
𝑊 (𝑢1 , 𝑢2 ) = 𝜃𝑢1 + (1 − 𝜃)𝑢2 where 𝜃 ∈ [0, 1]
The weighted utilitarian solution of the bargaining problem, defined as

max 𝑊 (𝑢1 , 𝑢2 ) such that (𝑢1 , 𝑢2 ) ∈ ℱ


𝑢1 ,𝑢2

Plot player 1’s utilitarian optimum 𝑢1 (𝜃) as the weight 𝜃 varies in [0, 1].

[1]: #Compute the weighted utilitarian optimum of Prisoners Dilemma as a solution to␣
↪a linear optimization problem

import numpy as np
import cvxpy as cp
import matplotlib.pyplot as plt
from matplotlib import rc
np.set_printoptions(precision=4, suppress=True)
font_properties = {'family': 'monospace', 'size': 10}
rc('font', **font_properties)

1
#Vector variable with shape (2,).
x = cp.Variable(2)

#Positive vector parameter with shape (2,).


d = cp.Parameter(2, pos=True)

#Positive weight parameter


theta = cp.Parameter(1, nonneg=True)

#Create two constraints.


constraints = [x[0] >= d[0],
x[1] >= d[1],
x[0] + 2*x[1] <= 12,
2*x[0] + x[1] <= 12]

#Form objective.
obj = cp.Maximize(theta * (x[0] - d[0]) + (1-theta) * (x[1] - d[1]))

#Form problem.
prob = cp.Problem(obj, constraints)

#Set the parameter values


d.value = [3.5, 2]

#create a 10-point grid


grid = np.linspace(0, 1, 10)

#create empty list to store repeated solves of the problem


u_1 = []

#Form a loop to solve problem repeatedly as weight parameter theta varies.


for val in grid:
theta.value = [val]
prob.solve()
u_1.append(x[0].value)

########################generate comparative statics␣


↪plots##########################

#create a figure object and an axes object


fig, ax = plt.subplots(1, 1, figsize = (6,6))

#specify plot legend, color, linestyle, linewidth


ax.plot(grid, u_1, label = 'Payoff of player 1', color = 'k', linestyle = '-',␣
↪linewidth = 3)

#set lower and upper limits of axes for display

2
ax.set_xlim(0,1)
ax.set_ylim(3,5.5)

#set the grid display on or off for each axis


ax.xaxis.grid(True, linestyle = '-', linewidth = 0.5)
ax.yaxis.grid(True, linestyle = '-', linewidth = 0.5)

#set axis labels


ax.set_xlabel('$theta$', fontsize = 10)
ax.set_ylabel('')

#specify plot title


ax.set_title('Comparative statics of utilitarian optimum in $theta$')

#set axis tickmarks for display


ax.set_xticks([0,0.2,0.4,0.6,0.8,1])
ax.set_yticks([3,4,5])

ax.legend()

#activate when a pdf is needed; needs to be called before plt.show() lest it␣
↪give a blank output

plt.savefig('comparative_statics_utilopt.png')

#show the plot on screen


plt.show()

/opt/anaconda3/lib/python3.8/site-
packages/cvxpy/reductions/solvers/solving_chain.py:163: UserWarning: You are
solving a parameterized problem that is not DPP. Because the problem is not DPP,
subsequent solves will not be faster than the first one. For more information,
see the documentation on Discplined Parametrized Programming, at
https://www.cvxpy.org/tutorial/advanced/index.html#disciplined-
parametrized-programming
warnings.warn(dpp_error_msg)

3
3 Problem 2. (8 points). (Convex optimization using CVXPY).
Suppose the disagreement point is given by d = (3.5, 2). A Nash welfare criterion is defined as

𝑁 (𝑢1 , 𝑢2 ) = log(𝑢1 − 𝑑1 ) + log(𝑢2 − 𝑑2 )

Find the Nash bargaining solution of the bargaining problem, defined as

max 𝑁 (𝑢1 , 𝑢2 ) such that (𝑢1 , 𝑢2 ) ∈ ℱ


𝑢1 ,𝑢2

(a) (4 points). In addition to displaying the Nash bargaining solution, display the primal optimal
value and the optimal dual variables for the constraints as well.

[2]: #Compute the Nash bargaining solution of Prisoners Dilemma as a solution to a␣


↪convex optimization problem

4
import numpy as np
import cvxpy as cp

# Vector variable with shape (2,).


x = cp.Variable(2)

# Positive vector parameter with shape (2,).


d = cp.Parameter(2, pos=True)

# Create two constraints.


constraints = [x[0] >= d[0],
x[1] >= d[1],
x[0] + 2*x[1] <= 12,
2*x[0] + x[1] <= 12]

# Form objective.
obj = cp.Maximize(cp.sum(cp.log(x - d)))

# Form problem.
prob = cp.Problem(obj, constraints)

#Set the parameter values


d.value = [3.5, 2]

#Solve problem.
prob.solve()

#print solver status, primal optimal value and primal optimal variables.
print(f'status: {prob.status}')
print(f'primal optimal value: {prob.value:.4f}')
print(f'The Nash solution is the primal optimal solution: {x.value}')

#optimal dual variable (Lagrange multiplier) for a constraint is stored in␣


↪constraint.dual_value.

print(f'optimal (x >= 2) dual variable is: {constraints[0].dual_value:.4f}')


print(f'optimal (y >= 1) dual variable is: {constraints[1].dual_value:.4f}')
print(f'optimal (x + 2y <= 12) dual variable is: {constraints[2].dual_value:.
↪4f}')

print(f'optimal (2x + y <= 12) dual variable is: {constraints[3].dual_value:.


↪4f}')

status: optimal
primal optimal value: 0.1178
The Nash solution is the primal optimal solution: [4.25 3.5 ]
optimal (x >= 2) dual variable is: 0.0000
optimal (y >= 1) dual variable is: 0.0000
optimal (x + 2y <= 12) dual variable is: 0.0000

5
optimal (2x + y <= 12) dual variable is: 0.6667

4 Comparative statics
(b) (4 points). Now fix the disagreement payoff of player 2 at 𝑑2 = 2. In the same figure, plot
how both players’ payoffs in the Nash bargaining solution vary as the disagreement payoff 𝑑1
of player 1 varies over the interval [2, 5].

[3]: #Comparative statics of Nash bargaining solution in disagreement point


import numpy as np
import cvxpy as cp
import matplotlib.pyplot as plt
from matplotlib import rc
np.set_printoptions(precision=4, suppress=True)
font_properties = {'family': 'monospace', 'size': 10}
rc('font', **font_properties)

# Vector variable with shape (2,).


x = cp.Variable(2)
d = cp.Parameter(2, pos=True)

# Create two constraints.


constraints = [x[0] >= d[0],
x[1] >= d[1],
x[0] + 2*x[1] <= 12,
2*x[0] + x[1] <= 12]

# Form objective.
obj = cp.Maximize(cp.sum(cp.log(x - d)))

# Form and solve problem.


prob = cp.Problem(obj, constraints)

#create an n-point grid for the real interval [d0_min, d0_max]


d0_min = 2
d0_max = 5
n = 31
grid = np.linspace(d0_min, d0_max, n)

#create empty lists to store repeated solves of the problem


nbs_1 = []
nbs_2 = []
nash_prod = []

#loop to repeatedly solve the problem on a grid of parameter values


for val in grid:
d.value = [val, 2]

6
prob.solve()
nbs_1.append(x[0].value)
nbs_2.append(x[1].value)
nash_prod.append(prob.value)

########################generate comparative statics␣


↪plots##########################

#create a figure object and an axes object


fig, ax = plt.subplots(1, 1, figsize = (6,6))

#set the opacity/alpha value of the plot to capture overlapping lines


overlapping = 0.7

#specify plot legend, color, linestyle, linewidth


ax.plot(grid, nbs_1, label = 'Nash bargain for player 1', color = 'r',␣
↪linestyle = '-', linewidth = 3, alpha = overlapping)

ax.plot(grid, nbs_2, label = 'Nash bargain for player 2', color = 'b',␣
↪linestyle = '--', linewidth = 3, alpha = overlapping)

#set lower and upper limits of axes for display


ax.set_xlim(2,5)
ax.set_ylim(2,5)

#set the grid display on or off for each axis


ax.xaxis.grid(True, linestyle = '-', linewidth = 0.5)
ax.yaxis.grid(True, linestyle = '-', linewidth = 0.5)

#set axis labels


ax.set_xlabel('disagreement point $d_1$ for player 1', fontsize = 10)
ax.set_ylabel('')

#specify plot title


ax.set_title('Comparative statics of players Nash bargains when $d_2 = 2$')

#set axis tickmarks for display


ax.set_xticks([2,3,4,5])
ax.set_yticks([2,3,4,5])

ax.legend()

#activate when a pdf is needed; needs to be called before plt.show() lest it␣
↪give a blank output

plt.savefig('comparative_statics_nbs.png')

#show the plot on screen


plt.show()

7
5 Problem 3. (7 points). (Convex optimization using interior
point algorithm).
You cannot use the CVXPY package here. Please implement the algorithm.
Suppose the disagreement point is given by d = (2, 1). Solve Problem 2(a) by implementing the
interior point algorithm that uses log barrier for inequality constraints. Display the following
(i) initial t and final t (ii) Nash bargaining solution (iii) primal optimal value (Maximized Nash
welfare) (iv) dual optimal variables for constraints (v) inequality constraint function values
at the optimum

[1]: #######################implement the barrier␣


↪method####################################

import numpy as np

8
import numdifftools as nd
from scipy.optimize import minimize_scalar
from scipy.linalg import cholesky
#option precision tells NumPy the number of digits after decimal point
#for printing; option suppress tells NumPy not to use exponential
#notation for very small numbers. This only affects printing not
#calculations
np.set_printoptions(precision=4, suppress=True)
######################################################################################
######################parameters of the optimization␣
↪problem###########################

#diagreement point
d = np.array([2, 1])

#number of inequality constraints


m = 4

######################parameters of the␣
↪algorithm######################################

#set the values of acceptable descent parameter alpha in (0,0.5) and reduction␣
↪factor beta in (0,1)

alpha, beta = 0.2, 0.5

#set the tolerance level for stopping criterion


eta = 1e-6

#set the scale factor mu


mu = 25
##########################################################################################
def log_Nash(x):
"""returns the log Nash welfare"""
return np.sum(np.log(x - d))

def f(x):
"""return inequality constraint function values as a flat 1D array"""
return np.array([- x[0] + d[0], - x[1] + d[1], x[0] + 2 * x[1] - 12, 2 *␣
↪x[0] + x[1] - 12])

def log_barrier(x):
"""logarithmic barrier for inequality constraints"""
return - np.sum(np.log(-f(x)))

def obj(x, t):


"""return the objective fn value for the barrier method"""
return - t * log_Nash(x) + log_barrier(x)

def grad(x, t):

9
"""return the gradient vector of objective fn as a flat 1D array"""
epsilon = nd.MaxStepGenerator(base_step=2.0, num_steps=10, offset=0)
return nd.Gradient(obj, method = 'forward', step = epsilon)(x, t)

def hess(x, t):


"""return the diagonal Hessian matrix of objective fn"""
epsilon = nd.MaxStepGenerator(base_step=2.0, num_steps=10, offset=0)
return nd.Hessian(obj, method = 'forward', step = epsilon)(x, t)

def line_search(x, d, t):


"""given a point x and a direction d from x, returns the stepsize in that␣
↪direction

that satisfies a sufficient descent criterion"""


stepsize=1
while np.isnan(obj(x + stepsize*d, t)) == True or obj(x, t) - obj(x +␣
↪stepsize*d, t) < alpha * ((- grad(x, t))@ (stepsize*d)):

stepsize = beta * stepsize


return stepsize

def newton(x, t):


"""given an initial point x, returns the unconstrained minimum of f. For a␣
↪formula

for descent direction in terms of Cholesky factorization, see p510 of␣


↪Boyd-Vanderberghe"""

while np.linalg.norm(grad(x, t)) > eta:


descent_dir = np.linalg.inv(hess(x, t)) @ (- grad(x, t))
stepsize = line_search(x, descent_dir, t)
x = x + stepsize * descent_dir
return x

def barrier(x, t):


"""barrier method to return constrained minimum"""
while m/t >= eta:
t = mu * t
x = newton(x, t)
return x, t
###################################################################################

#specify an initial strictly feasible point x_0 as a flat 1D array


x_0 = np.array([3, 2.5])

#initialize the centering accuracy parameter to be scaled up during the␣


↪algorithm

t_0 = 10

#call the barrier method with a strictly feasible point


nbs, t = barrier(x_0, t_0)

10
dual = - np.reciprocal(t*f(nbs))

print(f'The initial t is {t_0}')


print(f'The final t is {t}')
print(f'Nash bargaining solution with respect to disagreement point {d} is␣
↪{nbs}')

print(f'Maximized Nash product is {log_Nash(nbs):.4f}')


print(f'The dual optimal variables are {dual}')
print(f'Inequality constraint function values are {f(nbs)}')

The initial t is 10
The final t is 97656250
Nash bargaining solution with respect to disagreement point [2 1] is [3.9931
3.9859]
Maximized Nash product is 1.7836
The dual optimal variables are [0. 0. 0. 0.]
Inequality constraint function values are [-1.9931 -2.9859 -0.0351 -0.0278]
/opt/anaconda3/lib/python3.8/site-packages/numpy/lib/nanfunctions.py:1395:
RuntimeWarning: All-NaN slice encountered
result = np.apply_along_axis(_nanquantile_1d, axis, a, q,
/opt/anaconda3/lib/python3.8/site-packages/numdifftools/limits.py:150:
UserWarning: All-NaN slice encountered
warnings.warn(str(msg))
/opt/anaconda3/lib/python3.8/site-packages/numpy/lib/nanfunctions.py:1395:
RuntimeWarning: All-NaN slice encountered
result = np.apply_along_axis(_nanquantile_1d, axis, a, q,
/opt/anaconda3/lib/python3.8/site-packages/numdifftools/limits.py:150:
UserWarning: All-NaN slice encountered
warnings.warn(str(msg))
/opt/anaconda3/lib/python3.8/site-packages/numpy/lib/nanfunctions.py:1395:
RuntimeWarning: All-NaN slice encountered
result = np.apply_along_axis(_nanquantile_1d, axis, a, q,
/opt/anaconda3/lib/python3.8/site-packages/numdifftools/limits.py:150:
UserWarning: All-NaN slice encountered
warnings.warn(str(msg))
/opt/anaconda3/lib/python3.8/site-packages/numpy/lib/nanfunctions.py:1395:
RuntimeWarning: All-NaN slice encountered
result = np.apply_along_axis(_nanquantile_1d, axis, a, q,
/opt/anaconda3/lib/python3.8/site-packages/numdifftools/limits.py:150:
UserWarning: All-NaN slice encountered
warnings.warn(str(msg))
/opt/anaconda3/lib/python3.8/site-packages/numpy/lib/nanfunctions.py:1395:
RuntimeWarning: All-NaN slice encountered
result = np.apply_along_axis(_nanquantile_1d, axis, a, q,
/opt/anaconda3/lib/python3.8/site-packages/numdifftools/limits.py:150:
UserWarning: All-NaN slice encountered
warnings.warn(str(msg))

11

You might also like