import%20marimo%0A%0A__generated_with%20%3D%20%220.23.16%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Stackelberg%20Security%20Allocation%0A%0A%20%20%20%20A%20defender%20allocates%20limited%20security%20resources%20across%20a%20set%20of%20targets.%20An%0A%20%20%20%20attacker%20observes%20the%20protection%20policy%20and%20selects%20the%20most%20attractive%0A%20%20%20%20target.%20The%20defender%20therefore%20acts%20as%20a%20Stackelberg%20leader%20and%20anticipates%0A%20%20%20%20the%20attacker's%20best%20response%20when%20allocating%20protection.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20from%20pathlib%20import%20Path%0A%0A%20%20%20%20import%20cvxpy%20as%20cp%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20from%20matplotlib.patches%20import%20Patch%0A%0A%20%20%20%20from%20blvpy%20import%20BilevelProblem%2C%20LowerProblem%0A%0A%20%20%20%20plt.style.use(Path(__file__).resolve().parents%5B1%5D%20%2F%20%22_shared%22%20%2F%20%22zhlatex.mplstyle%22)%0A%20%20%20%20return%20BilevelProblem%2C%20LowerProblem%2C%20Patch%2C%20Path%2C%20cp%2C%20mo%2C%20np%2C%20plt%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Bilevel%20formulation%0A%0A%20%20%20%20Suppose%20there%20are%20%24n%24%20targets.%20Let%20%24p%5Cin%5Cmathbf%7BR%7D%5En%24%2C%20with%0A%20%20%20%20%240%5Cpreceq%20p%5Cpreceq%5Cmathbf%7B1%7D%24%2C%20denote%20their%20protection%20probabilities%2C%20and%0A%20%20%20%20let%20%24q%5Cin%5Cmathbf%7BR%7D%5En%24%2C%20with%20%240%5Cpreceq%20q%5Cpreceq%5Cmathbf%7B1%7D%24%2C%20denote%20their%0A%20%20%20%20attack%20probabilities.%20For%20a%20fixed%20defender%20allocation%20%24p%24%2C%20the%20attacker%0A%20%20%20%20solves%0A%0A%20%20%20%20%5C%5B%0A%20%20%20%20%5Cbegin%7Barray%7D%7Brl%7D%0A%20%20%20%20%20%20%20%20S(p)%20%3D%20%5Cmathop%7B%5Cmathrm%7Bargmin%7D%7D_%7Bq%7D%20%26%0A%20%20%20%20%20%20%20%20%20%20-%5Cdisplaystyle%5Csum_%7Bi%3D1%7D%5En(r_i-c_ip_i)q_i%5C%5C%0A%20%20%20%20%20%20%20%20%5Ctext%7Bsubject%20to%7D%20%26%0A%20%20%20%20%20%20%20%20%20%20q%5Csucceq0%2C%5Cquad%5Cmathbf%7B1%7D%5ETq%3D1%2C%0A%20%20%20%20%5Cend%7Barray%7D%0A%20%20%20%20%5C%5D%0A%0A%20%20%20%20where%20%24r%2Cc%5Cin%5Cmathbf%7BR%7D%5En%24%20are%20the%20attacker%20reward%20and%20defender%20coverage%0A%20%20%20%20effectiveness%20vectors.%20Thus%20%24r_i-c_ip_i%24%20is%20the%20attacker's%20payoff%20for%0A%20%20%20%20target%20%24i%24%20after%20observing%20defender%20coverage.%0A%0A%20%20%20%20The%20defender%20adapts%20its%20protection%20strategy%20by%20solving%0A%0A%20%20%20%20%5C%5B%0A%20%20%20%20%5Cbegin%7Barray%7D%7Bll%7D%0A%20%20%20%20%5Cmathop%7B%5Cmathrm%7Bminimize%7D%7D_%7Bp%2Cq%7D%20%26%0A%20%20%20%20%20%20%5Cdisplaystyle%5Csum_%7Bi%3D1%7D%5En%20L_i(1-p_i)q_i%0A%20%20%20%20%20%20%2B%5Cgamma%5ClVert%20p%5CrVert_2%5E2%5C%5C%0A%20%20%20%20%5Cmathop%7B%5Cmathrm%7Bsubject%5C%20to%7D%7D%20%26%0A%20%20%20%20%20%200%5Cpreceq%20p%5Cpreceq%5Cmathbf%7B1%7D%2C%5Cquad%5Cmathbf%7B1%7D%5ETp%5Cleq%20B%2C%5C%5C%0A%20%20%20%20%20%20%26q%5Cin%20S(p).%0A%20%20%20%20%5Cend%7Barray%7D%0A%20%20%20%20%5C%5D%0A%0A%20%20%20%20The%20first%20upper-level%20term%20is%20the%20expected%20defender%20loss%3A%20%24L_i%24%20is%20the%0A%20%20%20%20uncovered%20loss%20at%20target%20%24i%24%2C%20and%20%24(1-p_i)q_i%24%20is%20its%20probability%20of%20being%0A%20%20%20%20attacked%20without%20protection.%20The%20quadratic%20term%20discourages%20concentrated%0A%20%20%20%20policies.%20The%20vectors%20%24r%2Cc%2CL%24%2C%20penalty%20%24%5Cgamma%3E0%24%2C%20and%20resource%20budget%0A%20%20%20%20%24B%3E0%24%20are%20given.%20This%20instance%20uses%20%24B%3D2.5%24%20and%20%24%5Cgamma%3D0.03%24.%0A%0A%20%20%20%20The%20lower%20linear%20program%20can%20have%20multiple%20optimal%20attack%20targets.%20BLVPY's%0A%20%20%20%20optimistic%20semantics%20implement%20the%20strong-Stackelberg%20convention%3A%20among%0A%20%20%20%20tied%20responses%20in%20%24S(p)%24%2C%20the%20one%20most%20favorable%20to%20the%20defender%20may%20be%0A%20%20%20%20selected.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(B%2C%20L%2C%20mo%2C%20n%2C%20r)%3A%0A%20%20%20%20mo.md(rf%22%22%22%0A%20%20%20%20%23%23%20Synthetic%20security%20instance%0A%0A%20%20%20%20The%20instance%20contains%20%7Bn%7D%20synthetic%20targets.%0A%20%20%20%20Uncovered%20attacker%20rewards%20range%20from%0A%20%20%20%20%60%7Br.min()%3A.1f%7D%60%20to%20%60%7Br.max()%3A.1f%7D%60%2C%20while%20defender%20losses%20range%20from%0A%20%20%20%20%60%7BL.min()%3A.1f%7D%60%20to%20%60%7BL.max()%3A.1f%7D%60.%20The%20security%20budget%20of%20%60%7BB%3A.1f%7D%60%20is%0A%20%20%20%20insufficient%20to%20cover%20every%20target%20fully.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(np)%3A%0A%20%20%20%20r%20%3D%20np.array(%5B4.0%2C%203.2%2C%205.0%2C%202.6%2C%204.4%2C%203.8%2C%205.5%2C%203.0%5D)%0A%20%20%20%20c%20%3D%20np.array(%5B3.0%2C%203.5%2C%202.2%2C%202.0%2C%204.4%2C%202.8%2C%203.2%2C%204.0%5D)%0A%20%20%20%20L%20%3D%20np.array(%5B8.0%2C%205.0%2C%2010.0%2C%204.0%2C%207.0%2C%206.0%2C%2012.0%2C%205.0%5D)%0A%20%20%20%20B%20%3D%202.5%0A%20%20%20%20gamma%20%3D%200.03%0A%20%20%20%20n%20%3D%20r.size%0A%20%20%20%20target_indices%20%3D%20np.arange(1%2C%20n%20%2B%201)%0A%20%20%20%20return%20B%2C%20L%2C%20c%2C%20gamma%2C%20n%2C%20r%2C%20target_indices%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(L%2C%20Path%2C%20c%2C%20np%2C%20plt%2C%20r%2C%20target_indices)%3A%0A%20%20%20%20payoff_figure_dir%20%3D%20Path(__file__).resolve().parent%20%2F%20%22figures%22%0A%20%20%20%20payoff_figure_dir.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20payoff_figure%2C%20payoff_axes%20%3D%20plt.subplots(%0A%20%20%20%20%20%20%20%203%2C%0A%20%20%20%20%20%20%20%201%2C%0A%20%20%20%20%20%20%20%20figsize%3D(5.5%2C%204.5)%2C%0A%20%20%20%20%20%20%20%20sharex%3DTrue%2C%0A%20%20%20%20%20%20%20%20sharey%3DTrue%2C%0A%20%20%20%20%20%20%20%20layout%3D%22constrained%22%2C%0A%20%20%20%20)%0A%20%20%20%20payoff_values%20%3D%20(r%2C%20c%2C%20L)%0A%20%20%20%20payoff_labels%20%3D%20(r%22%24r%24%22%2C%20r%22%24c%24%22%2C%20r%22%24L%24%22)%0A%20%20%20%20maximum_payoff%20%3D%20max(np.max(values)%20for%20values%20in%20payoff_values)%0A%20%20%20%20for%20payoff_axis%2C%20values%2C%20label%20in%20zip(%0A%20%20%20%20%20%20%20%20payoff_axes%2C%0A%20%20%20%20%20%20%20%20payoff_values%2C%0A%20%20%20%20%20%20%20%20payoff_labels%2C%0A%20%20%20%20%20%20%20%20strict%3DTrue%2C%0A%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20payoff_axis.bar(target_indices%2C%20values%2C%20width%3D0.72%2C%20facecolor%3D%22w%22%2C%20edgecolor%3D%22k%22%2C%20linewidth%3D1.0%2C%20hatch%3D%22....%22)%0A%20%20%20%20%20%20%20%20payoff_axis.set(ylabel%3Dlabel%2C%20ylim%3D(0.0%2C%201.08%20*%20maximum_payoff))%0A%20%20%20%20payoff_axes%5B-1%5D.set_xticks(target_indices)%0A%20%20%20%20payoff_axes%5B-1%5D.set_xlabel(%22%24i%24%22)%0A%0A%20%20%20%20payoff_figure_path%20%3D%20payoff_figure_dir%20%2F%20%22stackelberg_port_security_payoffs.pdf%22%0A%20%20%20%20payoff_figure.savefig(payoff_figure_path%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20plt.show()%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(B%2C%20BilevelProblem%2C%20L%2C%20LowerProblem%2C%20c%2C%20cp%2C%20gamma%2C%20n%2C%20np%2C%20r)%3A%0A%20%20%20%20p%20%3D%20cp.Variable(n%2C%20bounds%3D%5B0.0%2C%201.0%5D%2C%20name%3D%22p%22)%0A%20%20%20%20p.value%20%3D%20np.full(n%2C%20B%20%2F%20n)%0A%20%20%20%20q%20%3D%20cp.Variable(n%2C%20nonneg%3DTrue%2C%20name%3D%22q%22)%0A%0A%20%20%20%20attacker_payoff%20%3D%20r%20-%20cp.multiply(c%2C%20p)%0A%20%20%20%20attacker_problem%20%3D%20LowerProblem(%0A%20%20%20%20%20%20%20%20cp.Minimize(-attacker_payoff%20%40%20q)%2C%0A%20%20%20%20%20%20%20%20%5Bcp.sum(q)%20%3D%3D%201.0%5D%2C%0A%20%20%20%20%20%20%20%20parameters%3D%5Bp%5D%2C%0A%20%20%20%20)%0A%20%20%20%20uncovered_loss%20%3D%20cp.multiply(L%2C%201.0%20-%20p)%0A%20%20%20%20expected_defender_loss%20%3D%20uncovered_loss%20%40%20q%0A%20%20%20%20problem%20%3D%20BilevelProblem(%0A%20%20%20%20%20%20%20%20cp.Minimize(expected_defender_loss%20%2B%20gamma%20*%20cp.sum_squares(p))%2C%0A%20%20%20%20%20%20%20%20attacker_problem%2C%0A%20%20%20%20%20%20%20%20upper_constraints%3D%5Bcp.sum(p)%20%3C%3D%20B%5D%2C%0A%20%20%20%20)%0A%20%20%20%20assert%20problem.is_dblp()%0A%20%20%20%20problem.validate()%0A%20%20%20%20return%20p%2C%20problem%2C%20q%0A%0A%0A%40app.cell%0Adef%20_(problem)%3A%0A%20%20%20%20epsilon_target%20%3D%201e-5%0A%20%20%20%20result%20%3D%20problem.solve(%0A%20%20%20%20%20%20%20%20epsilon_initial%3D1e-2%2C%0A%20%20%20%20%20%20%20%20epsilon_target%3Depsilon_target%2C%0A%20%20%20%20%20%20%20%20solver_options%3D%7B%22max_iter%22%3A%201000%7D%2C%0A%20%20%20%20%20%20%20%20verbose%3DFalse%2C%0A%20%20%20%20)%0A%20%20%20%20diagnostics%20%3D%20problem.gap_diagnostics(result)%0A%20%20%20%20return%20diagnostics%2C%20epsilon_target%2C%20result%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Baseline%20protection%20policies%0A%0A%20%20%20%20We%20compare%20the%20optimized%20policy%20with%20two%20transparent%20baselines.%20The%0A%20%20%20%20no-protection%20policy%20leaves%20every%20target%20uncovered.%20The%20uniform%20policy%0A%20%20%20%20divides%20the%20complete%20resource%20budget%20equally.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(B%2C%20L%2C%20c%2C%20n%2C%20np%2C%20r)%3A%0A%20%20%20%20def%20_reference_response(p_value)%3A%0A%20%20%20%20%20%20%20%20payoff_value%20%3D%20r%20-%20c%20*%20p_value%0A%20%20%20%20%20%20%20%20best_payoff%20%3D%20float(np.max(payoff_value))%0A%20%20%20%20%20%20%20%20tied_targets%20%3D%20np.flatnonzero(np.isclose(payoff_value%2C%20best_payoff%2C%20atol%3D1e-10%2C%20rtol%3D0.0))%0A%20%20%20%20%20%20%20%20tied_losses%20%3D%20L%5Btied_targets%5D%20*%20(1.0%20-%20p_value%5Btied_targets%5D)%0A%20%20%20%20%20%20%20%20selected_target%20%3D%20int(tied_targets%5Bnp.argmin(tied_losses)%5D)%0A%20%20%20%20%20%20%20%20q_value%20%3D%20np.zeros(n)%0A%20%20%20%20%20%20%20%20q_value%5Bselected_target%5D%20%3D%201.0%0A%20%20%20%20%20%20%20%20defender_outcome%20%3D%20float((L%20*%20(1.0%20-%20p_value))%20%40%20q_value)%0A%20%20%20%20%20%20%20%20attacker_outcome%20%3D%20float(payoff_value%20%40%20q_value)%0A%20%20%20%20%20%20%20%20return%20q_value%2C%20attacker_outcome%2C%20defender_outcome%0A%0A%20%20%20%20baseline_p%20%3D%20np.vstack(%0A%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20np.zeros(n)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20np.full(n%2C%20B%20%2F%20n)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20)%0A%20%20%20%20baseline_q%20%3D%20np.empty_like(baseline_p)%0A%20%20%20%20baseline_attacker_utility%20%3D%20np.empty(baseline_p.shape%5B0%5D)%0A%20%20%20%20baseline_defender_loss%20%3D%20np.empty(baseline_p.shape%5B0%5D)%0A%20%20%20%20for%20baseline_index%2C%20fixed_p%20in%20enumerate(baseline_p)%3A%0A%20%20%20%20%20%20%20%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20baseline_q%5Bbaseline_index%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20baseline_attacker_utility%5Bbaseline_index%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20baseline_defender_loss%5Bbaseline_index%5D%2C%0A%20%20%20%20%20%20%20%20)%20%3D%20_reference_response(fixed_p)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20baseline_attacker_utility%2C%0A%20%20%20%20%20%20%20%20baseline_defender_loss%2C%0A%20%20%20%20%20%20%20%20baseline_p%2C%0A%20%20%20%20%20%20%20%20baseline_q%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(%0A%20%20%20%20B%2C%0A%20%20%20%20L%2C%0A%20%20%20%20baseline_defender_loss%2C%0A%20%20%20%20c%2C%0A%20%20%20%20diagnostics%2C%0A%20%20%20%20epsilon_target%2C%0A%20%20%20%20gamma%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20np%2C%0A%20%20%20%20p%2C%0A%20%20%20%20q%2C%0A%20%20%20%20r%2C%0A%20%20%20%20result%2C%0A%20%20%20%20target_indices%2C%0A)%3A%0A%20%20%20%20optimized_p%20%3D%20np.array(p.value%2C%20copy%3DTrue)%0A%20%20%20%20optimized_q%20%3D%20np.array(q.value%2C%20copy%3DTrue)%0A%20%20%20%20attacker_payoff_by_target%20%3D%20r%20-%20c%20*%20optimized_p%0A%20%20%20%20optimized_defender_loss%20%3D%20float(np.sum(L%20*%20(1.0%20-%20optimized_p)%20*%20optimized_q))%0A%20%20%20%20protection_regularizer%20%3D%20float(gamma%20*%20np.sum(np.square(optimized_p)))%0A%20%20%20%20attacked_target_index%20%3D%20int(np.argmax(optimized_q))%0A%20%20%20%20attacked_target_number%20%3D%20int(target_indices%5Battacked_target_index%5D)%0A%20%20%20%20maximum_attacker_utility%20%3D%20float(np.max(attacker_payoff_by_target))%0A%20%20%20%20optimized_attacker_utility%20%3D%20float(attacker_payoff_by_target%20%40%20optimized_q)%0A%20%20%20%20active_attack_targets%20%3D%20np.flatnonzero(optimized_q%20%3E%201e-6)%0A%20%20%20%20near_optimal_target_count%20%3D%20int(%0A%20%20%20%20%20%20%20%20np.count_nonzero(attacker_payoff_by_target%20%3E%3D%20maximum_attacker_utility%20-%201.1%20*%20epsilon_target)%0A%20%20%20%20)%0A%20%20%20%20used_budget%20%3D%20float(np.sum(optimized_p))%0A%20%20%20%20uniform_loss_reduction%20%3D%20100.0%20*%20(baseline_defender_loss%5B1%5D%20-%20optimized_defender_loss)%20%2F%20baseline_defender_loss%5B1%5D%0A%0A%20%20%20%20assert%20result.succeeded%2C%20result.message%0A%20%20%20%20assert%20np.isfinite(result.objective)%0A%20%20%20%20assert%20np.isclose(result.final_epsilon%2C%20epsilon_target)%0A%20%20%20%20assert%20result.residuals.max_violation%20%3C%3D%201e-7%0A%20%20%20%20assert%20abs(diagnostics.source_gap)%20%3C%3D%201.1%20*%20epsilon_target%0A%20%20%20%20assert%20np.all((optimized_p%20%3E%3D%20-1e-8)%20%26%20(optimized_p%20%3C%3D%201.0%20%2B%201e-8))%0A%20%20%20%20assert%20used_budget%20%3C%3D%20B%20%2B%201e-7%0A%20%20%20%20assert%20np.isclose(np.sum(optimized_q)%2C%201.0%2C%20atol%3D1e-7)%0A%20%20%20%20assert%20np.all(optimized_q%20%3E%3D%20-1e-8)%0A%20%20%20%20assert%20np.all(attacker_payoff_by_target%5Bactive_attack_targets%5D%20%3E%3D%20maximum_attacker_utility%20-%201.1%20*%20epsilon_target)%0A%20%20%20%20assert%20optimized_defender_loss%20%3C%20np.min(baseline_defender_loss)%20-%201.0%0A%0A%20%20%20%20mo.md(rf%22%22%22%0A%20%20%20%20%23%23%20Result%0A%0A%20%20%20%20%7C%20quantity%20%7C%20value%20%7C%0A%20%20%20%20%7C%20---%20%7C%20---%3A%20%7C%0A%20%20%20%20%7C%20status%20%7C%20%60%7Bresult.status%7D%60%20%7C%0A%20%20%20%20%7C%20most%20likely%20attack%20target%20%7C%20%24i%3D%7Battacked_target_number%7D%24%20%7C%0A%20%20%20%20%7C%20maximum%20attacker%20utility%20%7C%20%7Bmaximum_attacker_utility%3A.6f%7D%20%7C%0A%20%20%20%20%7C%20optimized%20expected%20attacker%20utility%20%7C%20%7Boptimized_attacker_utility%3A.6f%7D%20%7C%0A%20%20%20%20%7C%20no-protection%20defender%20loss%20%7C%20%7Bbaseline_defender_loss%5B0%5D%3A.6f%7D%20%7C%0A%20%20%20%20%7C%20uniform-protection%20defender%20loss%20%7C%20%7Bbaseline_defender_loss%5B1%5D%3A.6f%7D%20%7C%0A%20%20%20%20%7C%20optimized%20expected%20defender%20loss%20%7C%20%7Boptimized_defender_loss%3A.6f%7D%20%7C%0A%20%20%20%20%7C%20%24%5Cgamma%5ClVert%20p%5E%5Cstar%5CrVert_2%5E2%24%20%7C%20%7Bprotection_regularizer%3A.6f%7D%20%7C%0A%20%20%20%20%7C%20%24%5Cmathbf%7B%7B1%7D%7D%5ETp%5E%5Cstar%24%20%7C%20%7Bused_budget%3A.6f%7D%20%2F%20%7BB%3A.1f%7D%20%7C%0A%20%20%20%20%7C%20final%20epsilon%20%7C%20%7Bresult.final_epsilon%3A.3e%7D%20%7C%0A%20%20%20%20%7C%20maximum%20lifted%20residual%20%7C%20%7Bresult.residuals.max_violation%3A.3e%7D%20%7C%0A%20%20%20%20%7C%20signed%20source%20gap%20%7C%20%7Bdiagnostics.source_gap%3A.3e%7D%20%7C%0A%0A%20%20%20%20The%20policy%20makes%20%7Bnear_optimal_target_count%7D%20targets%20approximate%20best%0A%20%20%20%20responses.%20Under%20optimistic%20strong-Stackelberg%20semantics%2C%20the%20most%20likely%0A%20%20%20%20attack%20is%20target%20%7Battacked_target_number%7D%2C%20the%20leader-favorable%20choice%20among%0A%20%20%20%20those%20responses.%20The%20optimized%20policy%20reduces%20defender%20loss%20by%0A%20%20%20%20%60%7Buniform_loss_reduction%3A.1f%7D%25%60%20relative%20to%20uniform%20protection.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20optimized_attacker_utility%2C%0A%20%20%20%20%20%20%20%20optimized_defender_loss%2C%0A%20%20%20%20%20%20%20%20optimized_p%2C%0A%20%20%20%20%20%20%20%20optimized_q%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Policy%20and%20outcome%20comparisons%0A%0A%20%20%20%20The%20mirrored%20policy%20chart%20compares%20both%20sides%20of%20the%20game%20under%20no%0A%20%20%20%20protection%2C%20uniform%20protection%2C%20and%20optimized%20protection.%20The%20defender%0A%20%20%20%20probabilities%20%24p%24%20extend%20right%2C%20while%20the%20attacker%20probabilities%20%24q%24%20extend%0A%20%20%20%20left%20on%20the%20same%20scale.%20The%20outcome%20panel%20compares%20expected%20defender%20loss%0A%20%20%20%20and%20expected%20attacker%20utility%20across%20the%20same%20three%20setups.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(%0A%20%20%20%20Patch%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20baseline_attacker_utility%2C%0A%20%20%20%20baseline_defender_loss%2C%0A%20%20%20%20baseline_p%2C%0A%20%20%20%20baseline_q%2C%0A%20%20%20%20np%2C%0A%20%20%20%20optimized_attacker_utility%2C%0A%20%20%20%20optimized_defender_loss%2C%0A%20%20%20%20optimized_p%2C%0A%20%20%20%20optimized_q%2C%0A%20%20%20%20plt%2C%0A%20%20%20%20target_indices%2C%0A)%3A%0A%20%20%20%20figure_dir%20%3D%20Path(__file__).resolve().parent%20%2F%20%22figures%22%0A%20%20%20%20figure_dir.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20target_axis%20%3D%20np.arange(target_indices.size)%0A%20%20%20%20setup_names%20%3D%20(%22None%22%2C%20%22Uniform%22%2C%20%22Optimized%22)%0A%20%20%20%20p_policies%20%3D%20np.vstack((baseline_p%2C%20optimized_p))%0A%20%20%20%20q_policies%20%3D%20np.vstack((baseline_q%2C%20optimized_q))%0A%0A%20%20%20%20figure%20%3D%20plt.figure(figsize%3D(7.5%2C%204)%2C%20layout%3D%22constrained%22)%0A%20%20%20%20figure_grid%20%3D%20figure.add_gridspec(1%2C%202%2C%20width_ratios%3D(1.25%2C%200.75))%0A%20%20%20%20policy_axis%20%3D%20figure.add_subplot(figure_grid%5B0%5D)%0A%20%20%20%20outcome_axis%20%3D%20figure.add_subplot(figure_grid%5B1%5D)%0A%0A%20%20%20%20policy_height%20%3D%200.25%0A%20%20%20%20setup_colors%20%3D%20(%22gray%22%2C%20%22C0%22%2C%20%22C3%22)%0A%20%20%20%20for%20setup_index%2C%20setup_color%20in%20enumerate(setup_colors)%3A%0A%20%20%20%20%20%20%20%20policy_offset%20%3D%20(setup_index%20-%201)%20*%20policy_height%0A%20%20%20%20%20%20%20%20policy_axis.barh(%0A%20%20%20%20%20%20%20%20%20%20%20%20target_axis%20%2B%20policy_offset%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20p_policies%5Bsetup_index%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3Dpolicy_height%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3Dsetup_color%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20hatch%3D%22......%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20policy_axis.barh(%0A%20%20%20%20%20%20%20%20%20%20%20%20target_axis%20%2B%20policy_offset%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20-q_policies%5Bsetup_index%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3Dpolicy_height%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3Dsetup_color%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alpha%3D0.72%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20hatch%3D%22%2F%2F%2F%2F%2F%2F%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20policy_axis.axvline(0.0%2C%20color%3D%22k%22%2C%20linewidth%3D0.8)%0A%20%20%20%20policy_axis.set_yticks(target_axis%2C%20target_indices)%0A%20%20%20%20policy_axis.set_xticks((-1.0%2C%20-0.5%2C%200.0%2C%200.5%2C%201.0)%2C%20(%221.0%22%2C%20%220.5%22%2C%20%220%22%2C%20%220.5%22%2C%20%221.0%22))%0A%20%20%20%20policy_axis.set(%0A%20%20%20%20%20%20%20%20xlabel%3D%22Probability%22%2C%0A%20%20%20%20%20%20%20%20ylabel%3D%22%24i%24%22%2C%0A%20%20%20%20%20%20%20%20xlim%3D(-1.08%2C%201.08)%2C%0A%20%20%20%20)%0A%20%20%20%20policy_axis.invert_yaxis()%0A%20%20%20%20policy_legend_handles%20%3D%20%5B%0A%20%20%20%20%20%20%20%20Patch(facecolor%3Dcolor%2C%20edgecolor%3D%22none%22%2C%20label%3Dname)%0A%20%20%20%20%20%20%20%20for%20name%2C%20color%20in%20zip(setup_names%2C%20setup_colors%2C%20strict%3DTrue)%0A%20%20%20%20%5D%0A%20%20%20%20policy_axis.legend(%0A%20%20%20%20%20%20%20%20handles%3Dpolicy_legend_handles%2C%0A%20%20%20%20%20%20%20%20frameon%3DFalse%2C%0A%20%20%20%20%20%20%20%20fontsize%3D13%2C%0A%20%20%20%20%20%20%20%20ncols%3D1%2C%0A%20%20%20%20%20%20%20%20loc%3D%22upper%20left%22%2C%0A%20%20%20%20)%0A%20%20%20%20policy_axis.text(%0A%20%20%20%20%20%20%20%200.95%2C%0A%20%20%20%20%20%20%20%200.05%2C%0A%20%20%20%20%20%20%20%20r%22%24p%24%22%2C%0A%20%20%20%20%20%20%20%20transform%3Dpolicy_axis.transAxes%2C%0A%20%20%20%20%20%20%20%20ha%3D%22right%22%2C%0A%20%20%20%20%20%20%20%20va%3D%22bottom%22%2C%0A%20%20%20%20)%0A%20%20%20%20policy_axis.text(%0A%20%20%20%20%20%20%20%200.05%2C%0A%20%20%20%20%20%20%20%200.05%2C%0A%20%20%20%20%20%20%20%20r%22%24q%24%22%2C%0A%20%20%20%20%20%20%20%20transform%3Dpolicy_axis.transAxes%2C%0A%20%20%20%20%20%20%20%20ha%3D%22left%22%2C%0A%20%20%20%20%20%20%20%20va%3D%22bottom%22%2C%0A%20%20%20%20)%0A%0A%20%20%20%20outcome_positions%20%3D%20np.arange(len(setup_names))%0A%20%20%20%20outcome_width%20%3D%200.36%0A%20%20%20%20defender_outcomes%20%3D%20np.append(baseline_defender_loss%2C%20optimized_defender_loss)%0A%20%20%20%20attacker_outcomes%20%3D%20np.append(baseline_attacker_utility%2C%20optimized_attacker_utility)%0A%20%20%20%20defender_bars%20%3D%20outcome_axis.bar(%0A%20%20%20%20%20%20%20%20outcome_positions%20-%20outcome_width%20%2F%202%2C%0A%20%20%20%20%20%20%20%20defender_outcomes%2C%0A%20%20%20%20%20%20%20%20width%3Doutcome_width%2C%0A%20%20%20%20%20%20%20%20color%3Dsetup_colors%2C%0A%20%20%20%20%20%20%20%20edgecolor%3D%22k%22%2C%0A%20%20%20%20%20%20%20%20linewidth%3D1.1%2C%0A%20%20%20%20%20%20%20%20hatch%3D%22....%22%2C%0A%20%20%20%20)%0A%20%20%20%20attacker_bars%20%3D%20outcome_axis.bar(%0A%20%20%20%20%20%20%20%20outcome_positions%20%2B%20outcome_width%20%2F%202%2C%0A%20%20%20%20%20%20%20%20attacker_outcomes%2C%0A%20%20%20%20%20%20%20%20width%3Doutcome_width%2C%0A%20%20%20%20%20%20%20%20color%3Dsetup_colors%2C%0A%20%20%20%20%20%20%20%20edgecolor%3D%22k%22%2C%0A%20%20%20%20%20%20%20%20linewidth%3D1.1%2C%0A%20%20%20%20%20%20%20%20hatch%3D%22%2F%2F%2F%2F%22%2C%0A%20%20%20%20)%0A%20%20%20%20outcome_axis.bar_label(defender_bars%2C%20fmt%3D%22%25.2f%22%2C%20padding%3D3%2C%20fontsize%3D11)%0A%20%20%20%20outcome_axis.bar_label(attacker_bars%2C%20fmt%3D%22%25.2f%22%2C%20padding%3D3%2C%20fontsize%3D11)%0A%20%20%20%20outcome_axis.set_xticks(outcome_positions%2C%20setup_names%2C%20fontsize%3D13)%0A%20%20%20%20outcome_axis.set(ylim%3D(0.0%2C%201.16%20*%20np.max(defender_outcomes))%2C%20ylabel%3D%22Loss%2FUtility%22)%0A%20%20%20%20outcome_legend_handles%20%3D%20(%0A%20%20%20%20%20%20%20%20Patch(%0A%20%20%20%20%20%20%20%20%20%20%20%20facecolor%3D%22white%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20edgecolor%3D%22black%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20hatch%3D%22....%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20label%3Dr%22%24%5Csum_%7Bi%3D1%7D%5En%20L_i(1-p_i)q_i%24%22%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20Patch(%0A%20%20%20%20%20%20%20%20%20%20%20%20facecolor%3D%22white%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20edgecolor%3D%22black%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20hatch%3D%22%2F%2F%2F%2F%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20label%3Dr%22%24%5Csum_%7Bi%3D1%7D%5En(r_i-c_i%20p_i)q_i%24%22%2C%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20)%0A%20%20%20%20outcome_axis.legend(%0A%20%20%20%20%20%20%20%20handles%3Doutcome_legend_handles%2C%0A%20%20%20%20%20%20%20%20frameon%3DFalse%2C%0A%20%20%20%20%20%20%20%20fontsize%3D12%2C%0A%20%20%20%20%20%20%20%20ncols%3D1%2C%0A%20%20%20%20)%0A%0A%20%20%20%20figure_path%20%3D%20figure_dir%20%2F%20%22stackelberg_port_security.pdf%22%0A%20%20%20%20figure.savefig(figure_path%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20plt.show()%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
cd6a23f03e3496468f2b7d7e2ca99f85