# A sketch LP: four constraint lines, each x2 = slope * x1 + intercept.
# We minimize x1 + 2 x2 and relax the third constraint by lowering its intercept.
sketch_slope = [4.5, -1, -1.5, 0.5]
sketch_intercept = [0, 16, 12, 0]
relaxed_by = [0, 4, 14]
relaxed_style = [:solid, :dashdot, :dot]
relaxed_name = ["1: now", "2: relaxed a little", "3: relaxed a lot"]
# Where constraint lines i and k cross
function sketch_crossing(i, k)
x1 = (sketch_intercept[k] - sketch_intercept[i]) / (sketch_slope[i] - sketch_slope[k])
return (x1, sketch_slope[i] * x1 + sketch_intercept[i])
end
# The optimum after relaxing the third constraint: it slides down the fourth
# line until the first and fourth lines meet, and can go no further
function sketch_optimum(relaxation)
corner = sketch_crossing(1, 4)
x1 = (sketch_intercept[3] - relaxation) / (sketch_slope[4] - sketch_slope[3])
if x1 <= corner[1]
return corner
end
return (x1, sketch_slope[4] * x1)
end
sketch_objective(point) = point[1] + 2 * point[2]
# Left: the feasible region now (darker) and after a large relaxation (lighter)
now_region = [sketch_crossing(1, 3), sketch_crossing(1, 2), sketch_crossing(2, 4),
sketch_crossing(4, 3)]
largest_region = [sketch_crossing(1, 4), sketch_crossing(1, 2), sketch_crossing(2, 4)]
sketch_x1 = -3:0.5:14
p_move = plot(Shape(first.(largest_region), last.(largest_region)), color=cb_blue, alpha=0.1,
linewidth=0, xlims=(-3, 14), ylims=(-3, 16), ticks=false, xlabel=L"x_1", ylabel=L"x_2",
legend=:topright, legendfontsize=13)
plot!(p_move, Shape(first.(now_region), last.(now_region)), color=cb_blue, alpha=0.25,
linewidth=0)
for i in [1, 2, 4]
plot!(p_move, sketch_x1, sketch_slope[i] .* sketch_x1 .+ sketch_intercept[i],
color=:gray30, linewidth=2.5)
end
for k in 1:length(relaxed_by)
plot!(p_move, sketch_x1, sketch_slope[3] .* sketch_x1 .+ sketch_intercept[3] .- relaxed_by[k],
color=cb_vermillion, linestyle=relaxed_style[k], linewidth=2.5, label=relaxed_name[k])
best = sketch_optimum(relaxed_by[k])
scatter!(p_move, [best[1]], [best[2]], color=cb_vermillion, markersize=9,
markerstrokecolor=:black)
annotate!(p_move, best[1] + 0.9, best[2] - 1.1, text(string(k), 18, :black, plot_font))
end
# Right: the optimal objective against the relaxation, and what the shadow price predicts
relaxation = 0:0.5:16
actual = [sketch_objective(sketch_optimum(r)) for r in relaxation]
predicted = sketch_objective(sketch_optimum(0)) .- relaxation
p_value = plot(relaxation, predicted, color=cb_vermillion, linestyle=:dash, linewidth=2.5,
ticks=false, xlims=(-1, 17), ylims=(-5.5, 13.5),
xlabel="How far the constraint is relaxed", ylabel="Optimal cost")
plot!(p_value, relaxation, actual, color=:black, linewidth=3)
for k in 1:length(relaxed_by)
value_k = sketch_objective(sketch_optimum(relaxed_by[k]))
scatter!(p_value, [relaxed_by[k]], [value_k], color=cb_vermillion, markersize=9,
markerstrokecolor=:black)
annotate!(p_value, relaxed_by[k] + 0.5, value_k + 1.2, text(string(k), 18, :black, plot_font))
end
annotate!(p_value, 12.6, -3, text("the shadow price predicts", 15, :right, :black, plot_font))
annotate!(p_value, 16.6, 4, text("re-solving gives", 15, :right, :black, plot_font))
plot(p_move, p_value, layout=(1, 2), size=(1150, 310), left_margin=12mm, bottom_margin=8mm)