{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "b90bff58",
   "metadata": {},
   "outputs": [],
   "source": [
    "import scipy.stats\n",
    "from scipy.stats import norm\n",
    "from scipy.stats import beta\n",
    "from scipy.stats import uniform\n",
    "from matplotlib import pyplot as plt\n",
    "plt.style.use('seaborn-paper')\n",
    "import numpy as np\n",
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c607e467",
   "metadata": {},
   "source": [
    "### Utility Functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "3a1dea55",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Pre-determined parameters\n",
    "eps = 0.5\n",
    "u = 1\n",
    "q = 0.5\n",
    "alpha_a = 0.2\n",
    "alpha_b = 0.25\n",
    "mu_0 = 0\n",
    "mu_1 = 0.5\n",
    "mu_2 = 1\n",
    "sd = 1\n",
    "mu_c = 0\n",
    "std_c = 0.25"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "2318a527",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Function to get non-strategic optimal threshold\n",
    "def get_opt_theta_no_strategic(alpha):\n",
    "    \"\"\"\n",
    "    get optimal threshold under non-strategic situation\n",
    "    \"\"\"\n",
    "    val = u*(1-alpha)/(u*alpha)\n",
    "    from scipy.optimize import fsolve\n",
    "    func = lambda theta: norm.pdf(theta,mu_2,sd)/norm.pdf(theta,mu_0,sd) - val\n",
    "    root = fsolve(func,0.2)\n",
    "    return root[0]\n",
    "\n",
    "# Function to get non-strategic utility\n",
    "def get_nonstrat_utility(alpha, theta):\n",
    "    return u*alpha*(1-norm.cdf(theta,mu_2,sd))-u*(1-alpha)*(1-norm.cdf(theta,mu_0,sd))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "528f97a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Function to calculate manipulation probability\n",
    "def get_manip_prob(theta, q, eps):\n",
    "    \"\"\"\n",
    "    function to get manip prob under q and eps\n",
    "    \"\"\"\n",
    "    value = (1-q)*(norm.cdf(theta, mu_1, sd) - norm.cdf(theta, mu_2, sd)) - eps*(1 - norm.cdf(theta, mu_2, sd))\n",
    "    return norm.cdf(value, mu_c, std_c)\n",
    "\n",
    "# Function to get Phi under different k1, k2, k3\n",
    "def get_Phi(alpha, theta, q, eps, k1 = 1, k2 = 1, k3 = 1):\n",
    "    \"\"\"\n",
    "    Get Phi, which is strategic - non-strategic\n",
    "    k1, k2, k3 measure our preferences for 3 kinds of behaviors\n",
    "    larger k1 means we prefer improvement\n",
    "    larger k2 means we do not prefer failure of improvement\n",
    "    larger k3 means we do not prefer manipulation\n",
    "    k1=k2=k3=1 is the naive difference\n",
    "    \"\"\"\n",
    "    # manipulation probability\n",
    "    pm = get_manip_prob(theta, q, eps)\n",
    "    # Reverse Benefit\n",
    "    b1 = k1*(1-pm)*q*(2 - norm.cdf(theta, mu_0, sd) - norm.cdf(theta, mu_2, sd))\n",
    "    # Loss of Failure of Improvement \n",
    "    l1 = k2*(1-pm)*(1-q)*(norm.cdf(theta, mu_0, sd) - norm.cdf(theta, mu_1, sd))\n",
    "    # Loss of manipulation\n",
    "    l2 = k3*pm*((1-eps)*(1 - norm.cdf(theta,mu_2,sd))-(1-norm.cdf(theta,mu_0,sd)))\n",
    "    return u*(1-alpha)*(b1-l1-l2)\n",
    "\n",
    "# Function to get strategic optimal theta under different k1, k2, k3\n",
    "def get_strat_optimal(alpha, q, eps, k1=1, k2=1, k3=1):\n",
    "    def get_strat_utility(theta, alpha0 = alpha, k10 = k1, k20 = k2, k30 = k3, q0 = q, eps0 = eps):\n",
    "        phi = get_Phi(alpha0,theta,q0,eps0,k10,k20,k30)\n",
    "        U = get_nonstrat_utility(alpha0, theta) + phi\n",
    "        return -U\n",
    "\n",
    "    from scipy.optimize import minimize_scalar\n",
    "    res = minimize_scalar(get_strat_utility)\n",
    "    return res.x, -get_strat_utility(res.x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "8b168300",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0.6507295209022637, 0.04583100798268797)"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "get_strat_optimal(0.3,0.25,0.5)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4acc221",
   "metadata": {},
   "source": [
    "### Experiments\n",
    "\n",
    "Under specific combinations of pre-determined parameters, we could first verify whether any case of Theorem 6.1 holds, and then vary $k_1$ or $k_2$ to see the change of optimal threshold, optimal utility, and fairness."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62f62855",
   "metadata": {},
   "source": [
    "#### Verify process\n",
    "\n",
    "Firstly we should work out the non-strategic optimal threshold and manipulation probability curve"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "d4d6df5c",
   "metadata": {},
   "outputs": [],
   "source": [
    "uhat_a = get_opt_theta_no_strategic(alpha_a)\n",
    "u_a = get_strat_optimal(alpha_a, q, eps)[0]\n",
    "nonstrat_a = get_nonstrat_utility(alpha_a, uhat_a)\n",
    "uhat_b = get_opt_theta_no_strategic(alpha_b)\n",
    "u_b = get_strat_optimal(alpha_b, q, eps)[0]\n",
    "nonstrat_b = get_nonstrat_utility(alpha_b, uhat_b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "8daac404",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 26936 characters]\n",
      "text/plain": [
       "<Figure size 400x300 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# plot manipulation probability\n",
    "x = np.linspace(-4,4,100)\n",
    "plt.figure(figsize = (4,3))\n",
    "plt.xticks(fontsize = 14)\n",
    "plt.yticks(fontsize = 14)\n",
    "manip_func = np.vectorize(get_manip_prob, excluded=['q','eps'])\n",
    "prob = manip_func(x,q,eps)\n",
    "plt.plot(x,prob,label = r'$P_M$ curve')\n",
    "prob_a = get_manip_prob(u_a,q,eps)\n",
    "prob_b = get_manip_prob(u_b,q,eps)\n",
    "plt.plot(u_a, prob_a,  marker=\"o\", markersize=10, label = r'$P_M(\\theta_a^*)$')\n",
    "plt.plot(u_b, prob_b, marker=\"s\", markersize=10, label = r'$P_M(\\theta_b^*)$')\n",
    "plt.xlabel(r'$\\theta$',fontsize = 14)\n",
    "plt.ylabel(r'$P_M(\\theta^*)$',fontsize = 14)\n",
    "plt.legend(fontsize = 12)\n",
    "plt.savefig('manipcurve2.eps',bbox_inches='tight')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "184c4c54",
   "metadata": {},
   "source": [
    "The figure above demonstrates that condition 1 in 6.1 is satisfied. Thus, we can increase $k_1$ for both groups to disincentivize manipulation, incentivize improvement and promote fairness. The effects are depicted in following plots\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "d17d188a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# k1_list = np.linspace(1,1000,100)\n",
    "# k1_list = [1,2,5,10,20,50,100,500,10000]\n",
    "k1_list = np.linspace(1,2,5)\n",
    "strat_func = np.vectorize(get_strat_optimal, excluded=['alpha','q','eps, k1, k3'])\n",
    "theta_a_list = strat_func(alpha_a,q,eps, k1_list, 1,1)[0]\n",
    "phi_func = np.vectorize(get_Phi, excluded = ['alpha','q','eps','k1','k2','k3'])\n",
    "nonstrat_func = np.vectorize(get_nonstrat_utility, excluded = ['alpha'])\n",
    "utility_a_list = phi_func(alpha_a,theta_a_list,q,eps) + nonstrat_func(alpha_a,theta_a_list)\n",
    "utility_a_non = [get_Phi(alpha_a, uhat_a, q, eps) + get_nonstrat_utility(alpha_a, uhat_a)]*5\n",
    "manip_list_a = manip_func(theta_a_list,q,eps)\n",
    "cdf_a = np.array([norm.cdf(theta,mu_2,sd) for theta in theta_a_list])\n",
    "manip_hat_a = get_manip_prob(uhat_a,q,eps)\n",
    "cdf_a = np.array([norm.cdf(theta,mu_2,sd) for theta in theta_a_list])\n",
    "manip_hat_a = get_manip_prob(uhat_a,q,eps)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "793d1003",
   "metadata": {},
   "outputs": [],
   "source": [
    "k1_list = np.linspace(1,2,5)\n",
    "strat_func = np.vectorize(get_strat_optimal, excluded=['alpha','q','eps, k1, k3'])\n",
    "theta_b_list = strat_func(alpha_b,q,eps,k1_list, 1,1)[0]\n",
    "phi_func = np.vectorize(get_Phi, excluded = ['alpha','q','eps','k1','k2','k3'])\n",
    "nonstrat_func = np.vectorize(get_nonstrat_utility, excluded = ['alpha'])\n",
    "utility_b_list = phi_func(alpha_b,theta_b_list,q,eps) + nonstrat_func(alpha_b,theta_b_list)\n",
    "utility_b_non = [get_Phi(alpha_b, uhat_b, q, eps) + get_nonstrat_utility(alpha_b, uhat_b)]*5\n",
    "manip_list_b = manip_func(theta_b_list,q,eps)\n",
    "cdf_b = np.array([norm.cdf(theta,mu_2,sd) for theta in theta_b_list])\n",
    "manip_hat_b = get_manip_prob(uhat_b,q,eps)\n",
    "cdf_b = np.array([norm.cdf(theta,mu_2,sd) for theta in theta_b_list])\n",
    "manip_hat_b = get_manip_prob(uhat_b,q,eps)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "3d5a55da",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.08698626629890871"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "non_strat_unfairness = norm.cdf(uhat_a,mu_2,sd) - norm.cdf(uhat_b,mu_2,sd)\n",
    "non_strat_unfairness"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "d0879a32",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.2505647, 0.2414687, 0.2414687, 0.2414687, 0.2414687])"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "utility_b_list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "e6257832",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 94380 characters]\n",
      "text/plain": [
       "<Figure size 600x1200 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# plot optimal threshold, (non)-strategic utility, and manipulation probability\n",
    "x = k1_list\n",
    "fig, axes = plt.subplots(2,1)\n",
    "fig.set_size_inches(6, 12)\n",
    "axes[0].tick_params(axis='both', which='major', labelsize=14)\n",
    "axes[1].tick_params(axis='both', which='major', labelsize=14)\n",
    "axes[0].plot(x,manip_list_a, marker = 'o',label = r'$P_M(\\theta^*(k_1))$',linestyle = 'dashed',color = 'blue')\n",
    "axes[0].plot(x,utility_a_list, marker = 'o',label = r'strategic utility',linestyle = 'solid',color = 'blue')\n",
    "axes[0].plot(x,cdf_a, marker = 'o',label = r'$F_{X|Y,S}(\\theta|1,s)$',linestyle = 'dotted',color = 'green')\n",
    "axes[0].plot(x, utility_a_non, marker = 'o', label = r'non-strategic utility',linestyle = 'solid',color = 'red')\n",
    "axes[0].plot(x, [manip_hat_a]*5, marker = 'o',label = r'$P_M(\\widehat{\\theta}^*)$',linestyle = 'dashed',color = 'red')\n",
    "axes[0].set_xlabel(r'$k_1$ normalized by $u(1-\\alpha)$', fontsize = 12)\n",
    "axes[0].set_ylabel(r'value', fontsize = 12)\n",
    "axes[0].legend(fontsize = 12, bbox_to_anchor=(0.2, 0.5))\n",
    "axes[0].set_title('Group a',fontsize = 12)\n",
    "\n",
    "axes[1].plot(x,manip_list_b, marker = 'o',label = r'$P_M(\\theta^*(k_1))$',linestyle = 'dashed',color = 'blue')\n",
    "axes[1].plot(x,utility_b_list,marker = 'o',label = r'strategic utility',linestyle = 'solid',color = 'blue')\n",
    "axes[1].plot(x,cdf_b, marker = 'o',label = r'$F_{X|Y,S}(\\theta|1,s)$',linestyle = 'dotted',color = 'green')\n",
    "axes[1].plot(x, utility_b_non ,marker = 'o', label = r'non-strategic utility',linestyle = 'solid',color = 'red')\n",
    "axes[1].plot(x, [manip_hat_b]*5, marker = 'o',label = r'$P_M(\\widehat{\\theta}^*)$',linestyle = 'dashed',color = 'red')\n",
    "axes[1].set_xlabel(r'$k_1$ normalized by $u(1-\\alpha)$', fontsize = 12)\n",
    "axes[1].set_ylabel(r'value', fontsize = 12)\n",
    "axes[1].legend(fontsize = 12,bbox_to_anchor=(0.2, 0.2))\n",
    "axes[1].set_title('Group b',fontsize = 12)\n",
    "\n",
    "fig.savefig('fairness2.eps')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6fa6427a",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
