{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ProfileNoisyDiscrete" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Welcome to this series of tutorials! The objective here is to get you up and running with the package *Poisson Approval*, but not to present all its features in detail. For more exhaustive information, refer to the *Reference* section of the documentation." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.473803Z", "start_time": "2021-02-15T09:50:07.238998Z" } }, "outputs": [], "source": [ "from fractions import Fraction\n", "import poisson_approval as pa" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A Profile and its Basic Properties" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To get familiar with profiles, we will consider the example of the class *ProfileNoisyDiscrete*. Let's create a profile:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.490111Z", "start_time": "2021-02-15T09:50:12.476098Z" } }, "outputs": [ { "data": { "text/plain": [ " (Condorcet winner: b)" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile = pa.ProfileNoisyDiscrete({\n", " ('abc', 0.5, 0.01): Fraction(1, 10),\n", " ('bac', 0.25, 0.01): Fraction(3, 10),\n", " ('bac', 0.75, 0.01): Fraction(3, 10),\n", " ('cab', 0.4, 0.01): Fraction(3, 10)\n", "})\n", "profile" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the example above, let us examine the first group of voters. They have the preference ranking $abc$. By convention, their utility for their top candidate $a$ is 1 and their utility for their bottom candidate $c$ is 0. In our example, their utility for their middle candidate $b$ is 0.5 with a *noise* of 0.01, which means that it is uniformly distributed in the interval [0.49, 0.51]. This group represents a share 1/10 of the voters.\n", "\n", "Similarly, there is a second group of voters $bac$, with a utility for $a$ that is $0.25 \\pm 0.01$, representing 3/10 of the voters; etc.\n", "\n", "You can define the same profile with an alternate syntax, specifying the noise parameter once and for all:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.516224Z", "start_time": "2021-02-15T09:50:12.495305Z" } }, "outputs": [ { "data": { "text/plain": [ " (Condorcet winner: b)" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile = pa.ProfileNoisyDiscrete({\n", " ('abc', 0.5): Fraction(1, 10),\n", " ('bac', 0.25): Fraction(3, 10),\n", " ('bac', 0.75): Fraction(3, 10),\n", " ('cab', 0.4): Fraction(3, 10)\n", "}, noise=0.01)\n", "profile" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Share of voters $abc$ (i.e. who prefer candidate $a$, then $b$, then $c$):" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.539233Z", "start_time": "2021-02-15T09:50:12.519217Z" } }, "outputs": [ { "data": { "text/plain": [ "Fraction(1, 10)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.abc" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Which rankings are in the profile?" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.577551Z", "start_time": "2021-02-15T09:50:12.542224Z" } }, "outputs": [ { "data": { "text/plain": [ "{abc, bac, cab}" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.support_in_rankings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Are all possible rankings in the profile?" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.593623Z", "start_time": "2021-02-15T09:50:12.579546Z" } }, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.is_generic_in_rankings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Is one ranking shared by a majority of voters?" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.603619Z", "start_time": "2021-02-15T09:50:12.595585Z" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.has_majority_ranking" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Is one candidate prefered by a majority of voters?" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.623720Z", "start_time": "2021-02-15T09:50:12.607608Z" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.has_majority_favorite" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Is the profile single-peaked?" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.635096Z", "start_time": "2021-02-15T09:50:12.627109Z" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.is_single_peaked" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Weighted majority graph:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.647793Z", "start_time": "2021-02-15T09:50:12.637764Z" } }, "outputs": [ { "data": { "text/plain": [ "array([[0, Fraction(-1, 5), Fraction(2, 5)],\n", " [Fraction(1, 5), 0, Fraction(2, 5)],\n", " [Fraction(-2, 5), Fraction(-2, 5), 0]], dtype=object)" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.weighted_maj_graph" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Condorcet winner(s):" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.662840Z", "start_time": "2021-02-15T09:50:12.651837Z" } }, "outputs": [ { "data": { "text/plain": [ "{'b'}" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.condorcet_winners" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Does the profile have a Condorcet winner?" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.685650Z", "start_time": "2021-02-15T09:50:12.664834Z" } }, "outputs": [ { "data": { "text/plain": [ "1.0" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.is_profile_condorcet" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For ``is_profile_condorcet``, the output 1.0 conventionally means that there is a strict Condorcet winner; 0.5 that there are one or several weak Condorcet winners, and 0.0 means that there is no Condorcet winner at all." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Ordinal Strategy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Define an ordinal strategy:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.698451Z", "start_time": "2021-02-15T09:50:12.689017Z" } }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "strategy = pa.StrategyOrdinal({'abc': 'a', 'bac': 'ab', 'cab': 'c'})\n", "strategy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ballot of the voters with ranking $abc$:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.741468Z", "start_time": "2021-02-15T09:50:12.701443Z" } }, "outputs": [ { "data": { "text/plain": [ "'a'" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "strategy.abc" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Tau-vector (ballot shares) associated to the strategy in the given profile:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.784943Z", "start_time": "2021-02-15T09:50:12.744555Z" } }, "outputs": [ { "data": { "text/plain": [ " ==> a" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.tau(strategy)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Is the strategy an equilibrium for the given profile?" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.854921Z", "start_time": "2021-02-15T09:50:12.789065Z" } }, "outputs": [ { "data": { "text/plain": [ "EquilibriumStatus.EQUILIBRIUM" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.is_equilibrium(strategy)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, as soon as you define a strategy, you can attach a profile to it. In that case, the strategy is considered from the point of view of its usage in the given profile. Thus you can write:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.892244Z", "start_time": "2021-02-15T09:50:12.857898Z" } }, "outputs": [ { "data": { "text/plain": [ " ==> a" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "strategy = pa.StrategyOrdinal({'abc': 'a', 'bac': 'ab', 'cab': 'c'}, profile=profile)\n", "strategy" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.912256Z", "start_time": "2021-02-15T09:50:12.895236Z" } }, "outputs": [ { "data": { "text/plain": [ " ==> a" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "strategy.tau" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.942174Z", "start_time": "2021-02-15T09:50:12.914251Z" } }, "outputs": [ { "data": { "text/plain": [ "EquilibriumStatus.EQUILIBRIUM" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "strategy.is_equilibrium" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Threshold Strategy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Not all strategies are ordinal. In the following strategy, voters $bac$ vote for their preferred candidate $b$ if their utility for $a$ is lower than 0.5 but vote for their two first candidates, $b$ and $a$, if their utility for $a$ is greater than 0.5:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:12.973480Z", "start_time": "2021-02-15T09:50:12.944169Z" } }, "outputs": [ { "data": { "text/plain": [ " ==> b" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "strategy = pa.StrategyThreshold({'abc': 1, 'bac': 0.5, 'cab': 1}, profile=profile)\n", "strategy" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:13.008386Z", "start_time": "2021-02-15T09:50:12.977535Z" } }, "outputs": [ { "data": { "text/plain": [ " ==> b" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "strategy.tau" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:13.043835Z", "start_time": "2021-02-15T09:50:13.011378Z" } }, "outputs": [ { "data": { "text/plain": [ "EquilibriumStatus.NOT_EQUILIBRIUM" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "strategy.is_equilibrium" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Analyze Several Strategies" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Analyze all ordinal strategies:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:13.137809Z", "start_time": "2021-02-15T09:50:13.049812Z" } }, "outputs": [ { "data": { "text/plain": [ "Equilibria:\n", " ==> b (FF)\n", " ==> a (D)\n", "\n", "Non-equilibria:\n", " ==> b (FF)\n", " ==> a (D)\n", " ==> b (FF)\n", " ==> b (FF)\n", " ==> a, b (FF)\n", " ==> a (D)" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.analyzed_strategies_ordinal" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To access one of these strategies in particular:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:13.148830Z", "start_time": "2021-02-15T09:50:13.140800Z" } }, "outputs": [ { "data": { "text/plain": [ " ==> b" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.analyzed_strategies_ordinal.equilibria[0]" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:13.174037Z", "start_time": "2021-02-15T09:50:13.151822Z" } }, "outputs": [ { "data": { "text/plain": [ " ==> b" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.analyzed_strategies_ordinal.non_equilibria[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Group strategies* do not correspond to a well-defined theoretical notion, but they are convenient in practice for *ProfileNoisyDiscrete*. In these strategies, roughly speaking, each group has the same ballot. In our profile, it means for example that all the voters of the group $(bac, 0.25, 0.01)$ have the same ballot; and all the voters of the group $(bac, 0.75, 0.01)$ have the same ballot (but not necessarily the same as the previous group). In this example, it is a threshold strategy where the threshold utility for voters $bac$ can be:\n", "\n", "* 1: they all vote for $b$,\n", "* 0.5: voters $(bac, 0.25, 0.01)$ vote for $b$, voters $(bac, 0.75, 0.01)$ vote for $b$ and $a$,\n", "* 0: they all vote for $b$ and $a$." ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:13.498263Z", "start_time": "2021-02-15T09:50:13.176031Z" } }, "outputs": [ { "data": { "text/plain": [ "Equilibria:\n", " ==> a (D)\n", " ==> b (FF)\n", "\n", "Non-equilibria:\n", " ==> a (D)\n", " ==> a, b (FF)\n", " ==> a, b (FF)\n", " ==> b (D)\n", " ==> b (FF)\n", " ==> b (FF)\n", " ==> a (D)\n", " ==> a (FF)\n", " ==> b (D)\n", " ==> b (FF)" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.analyzed_strategies_group" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "More generally, there is a method *analyzed_strategies* where you can specify an iterable of strategies that you want to study:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:13.537874Z", "start_time": "2021-02-15T09:50:13.502322Z" } }, "outputs": [ { "data": { "text/plain": [ "Equilibrium:\n", " ==> a (D)\n", "\n", "Non-equilibrium:\n", " ==> b (D)" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.analyzed_strategies(strategies=[\n", " pa.StrategyOrdinal({'abc': 'a', 'bac': 'ab', 'cab': 'c'}),\n", " pa.StrategyThreshold({'abc': 1, 'bac': 0.5, 'cab': 1})\n", "])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Iterated Voting and Fictitious Play" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Seek an equilibrium by iterated voting:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:13.601006Z", "start_time": "2021-02-15T09:50:13.541844Z" } }, "outputs": [ { "data": { "text/plain": [ "{'converges': True,\n", " 'cycle_taus_perceived': [ ==> b],\n", " 'cycle_strategies': [ ==> b],\n", " 'cycle_taus_actual': [ ==> b],\n", " 'tau_init': ==> b,\n", " 'n_episodes': 4,\n", " 'd_candidate_winning_frequency': {b: 1}}" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.iterated_voting(init='sincere', n_max_episodes=100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Seek an equilibrium by fictitious play:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:13.923502Z", "start_time": "2021-02-15T09:50:13.609007Z" } }, "outputs": [ { "data": { "text/plain": [ "{'converges': False,\n", " 'tau': None,\n", " 'strategy': None,\n", " 'tau_init': ==> b,\n", " 'n_episodes': 100,\n", " 'd_candidate_winning_frequency': {b: 1}}" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.fictitious_play(init='sincere', n_max_episodes=100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For the precise differences between *iterated voting* and *fictitious play*, cf. the *Reference* section." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In practice, iterated voting and fictitious play converge more often and faster with *update ratios* of the form $\\frac{1}{\\log(t) + 1}$, where $t$ is the index of the episode:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "ExecuteTime": { "end_time": "2021-02-15T09:50:14.120423Z", "start_time": "2021-02-15T09:50:13.927601Z" } }, "outputs": [ { "data": { "text/plain": [ "{'converges': True,\n", " 'tau': ==> b,\n", " 'strategy': ==> b,\n", " 'tau_init': ==> b,\n", " 'n_episodes': 47,\n", " 'd_candidate_winning_frequency': {b: 1}}" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "profile.fictitious_play(\n", " init='sincere',\n", " n_max_episodes=100,\n", " perception_update_ratio=pa.one_over_log_t_plus_one,\n", " ballot_update_ratio=pa.one_over_log_t_plus_one,\n", " winning_frequency_update_ratio=pa.one_over_log_t_plus_one\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are several ways to initialize these processes:\n", "\n", "* ``'sincere'`` (like above): voters vote for their preferred candidate if their utility for their second candidate is lower than 0.5, and vote for their two first candidates if it is greater than 0.5.\n", "* ``'fanatic'``: voters vote for their preferred candidate only.\n", "* ``'random_tau'``: a tau-vector (ballot shares) drawn uniformly at random.\n", "* ``'random_tau_undominated'``: a random tau-vector where each voter uses an undominated ballot.\n", "* A given tau-vector,\n", "* Or a given strategy." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.7.3" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": false, "sideBar": true, "skip_h1_title": true, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": false } }, "nbformat": 4, "nbformat_minor": 2 }