Giter VIP home page Giter VIP logo

voting's Introduction

Voting system simulator

This is a voting system simulator intended to simulate various methods used in proportional voting systems, in particular those that use a biproportional apportionment method for allocation of adjustment seats based on national outcomes. Such systems are common, such as in Iceland, Sweden, and Norway.

M.L. Balinski and G. Demange have shown that only one method, the Alternating-Scaling method, exists that upholds the five axioms for proportionality in matrices. All other methods are heuristic simplifications that approach the optimal solution to varying degrees. This software implements the Alternating-Scaling method, and various other methods for comparison, and provides mechanisms to compare them.

Biproportional allocation on matrices is a common issue that arises when you have multiple parties in multiple constituencies vying for a set number of seats which are pinned to different constituencies. The goal is to determine which parties get which seats in which constituencies.

More generally this approach could be used to allocate limited resources to factories depending on their relative importance or needs, or to solve a number of other biproportional optimization problems.

Installation

It is highly recommended that you use a Virtualenv for voting. Python 3 is recommended.

  1. Create a new Python3 Virtualenv, e.g. 'voting', and enter it.
  2. Make sure you have npm available on your system.
  3. Clone this repository (e.g. from [email protected]:smari/voting)
  4. (cd backend && pip install -r requirements.txt)
  5. (cd vue-frontend && npm install)
  6. (cd vue-frontend && npm run-script build)

That should be enough to start using the simulator.

Command Line Interface

The basic interaction mode. You feed it some data files, it feeds you some results.

For help, try:

python cli.py --help

A few usage examples follow below.

Apportionment

Basic apportioning is done through the apportion command. For help with that command, do:

python cli.py apportion --help

The apportion command takes several flags, including:

  • constituencies: path to a CSV or XLSX file describing constituencies.
  • votes: path to a CSV or XLSX file containing votes.
  • divider: the name of a supported divider method.
  • adjustment-divider: a supported divider method to use for adjustment seats; defaults to the same as the selected divider method.
  • adjustment-method: a supported adjustment method to use for resolving adjustment seat apportionment. Use multiple times to compare outputs.
  • output: the output format to use.
  • show-entropy: show the calculated entropy of each method.

Example using the 2013 elections in Iceland and d'Hondt method:

python cli.py apportion \
	--constituencies=../data/constituencies/constituencies_iceland_2013.csv \
	--votes=../data/elections/iceland_landskjorstjorn_2013.csv \
	--divider=dhondt \
	--adjustment-method=alternating-scaling \
	--show-entropy

You can get HTML, LaTeX, MediaWiki or various other types of table output and swap out the divider methods as you please:

python cli.py apportion \
	--constituencies=../data/constituencies/constituencies_iceland_2013.csv \
	--votes=../data/elections/iceland_landskjorstjorn_2013.csv \
	--divider=sainte-lague \
    --adjustment-method=monge \
	--output=html

Simulation

Simulation is done through the simulate command. For help with that command, do:

python cli.py simulate --help

The simulate command takes several flags, including:

  • constituencies: path to a CSV or XLSX file describing constituencies.
  • votes: path to a CSV or XLSX file containing votes to use as reference for the simulation.
  • test_method: the method to be tested.
  • num_sim: number of simulations to run.
  • gen_method: a supported method to generate votes.

Example using the 2013 elections in Iceland:

python cli.py simulate \
	--constituencies=../data/constituencies/constituencies_iceland_2013.csv \
	--votes=../data/elections/iceland_landskjorstjorn_2013.csv \
	--test_method=ice_law_dhondt

Script mode

Because all the parameters can be confusing and sometimes you just want to be able to work with a particular set of settings again and again, there is a "script mode" (for lack of a better term) which allows you to specify a set of rules which then execute:

python cli.py script ../data/presets/iceland2013.json

A script or preset is simply a JSON file that specifies what should happen, see examples in data/presets/.

Web Interface

The web interface can be started by:

python web.py

Then direct a browser to http://localhost:5000/ and start having fun. This is the recommended mode to use the simulator in.

Design

The web interface involves a Javascript Single Page App (SPA) which acts as a visual editor for data that is then passed to the backend for calculations. As such, the SPA is the source of truth, and the backend is "dumb", merely reacting to the frontend. The backend is made with Flask.

The SPA's data model should be the same as the backend's script-mode input model.

The SPA is built using vue.js.

Features

Basic functionality

  • Read constituency data files
  • Read vote data files
  • Basic click UI
  • Per ruleset click UI options
  • Simulation click UI options
  • Web server

Apportionment methods

  • One dimensional greedy apportionment
    • d'Hondt method
    • Sainte-Lague method
    • Nordic Sainte-Lague method
  • Constituency seat allocation
  • Threshold elimination (on matrices and vectors)
  • Optimization and heuristic methods
    • Linear programming
    • Greedy Alternating-Scaling algorithm (AS)
    • Alternating-Scaling algorithm (AS)
    • Relative Superiority algorithm (RS)
    • Relative Inferiority algorithm (RI)
    • Nearest neighbor algorithm
    • Monge algorithm
    • Icelandic voting law algorithm
    • Swedish voting law algorithm
    • Norwegian voting law algorithm
    • Norwegian voting law algorithm adjusted for Icelandic conditions
    • Kristinn Lund method
    • Pure vote ratios method

Simulation

  • Generate random initial votes
    • Draw percentages for each party in each district from a beta distribution with historical mean and variance. Then normalize the percentages to add up to 100%.
  • Fuzz votes
    • Add votes, one-by-one, in support of a party list in a district
      • If a new vote doesn't gain that party list a seat, report any change in results.
      • Even if a new vote does gain that party list a seat, do report if the result change is greater than just moving a seat in the relevant constituency between parties and moving one seat in the opposite direction in another constituency.
  • Compare different apportionment methods

Evaluation of methods

  • Apportionment entropy
  • Entropy relative to optimal entropy
  • Seat deviation from optimal solution
  • Seat deviation from Icelandic law
  • Seat deviation from independent constituencies
  • Seat deviation from single constituency country
  • Seat deviation from all seats apportioned with adjustment method
  • Loosemore-Hanby index
  • Sainte-Lague minsum index
  • d'Hondt maxmin index
  • d'Hondt minsum index
  • Monotonicity violation: A party list losing a seat by receiving an additional vote
  • Significant irrelevant alternative: A new vote having side effects without without affecting the number of seats won by the party list voted for.

Output formats

  • Simple text result table (Total seats) output
  • HTML, LaTeX, CSV, result table (Total seats) output
  • Excel file detailed result output
    • Votes
    • Vote shares
    • Constituency seats
    • Adjustment seats
    • Total seats
    • Evaluation metrics

Web interface

  • Simple web server
  • Javascript SPA
    • Configurable running of single elections
    • Configurable running of simulations
    • Display results
      • Votes
      • Vote shares
      • Constituency seats
      • Adjustment seats
      • Total seats
      • Evaluation metrics
    • Visualizations
      • "Election TV" result animations
      • Simulation errors
      • 3D bar chart of cumulative violations in the constituency/party matrix
  • Configurable host/port/etc

Tickets

See our issue tracker on Github.

Authors

  • Smári McCarthy
  • Þorkell Helgason
  • Martha Guðrún Bjarnadóttir
  • Pétur Ólafur Aðalgeirsson
  • Helgi Hrafn Gunnarsson
  • Bjartur Thorlacius.

Licence

Released under the terms of the Affero GNU General Public License version 3.

voting's People

Contributors

bjartur avatar busla avatar dependabot[bot] avatar gislibg avatar helgihg avatar marthagb avatar marthagudrun avatar osk avatar pallih avatar peturoa avatar smari avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

voting's Issues

Add support for candidate lists

Candidate lists should include name, age, party, seat in party-list, photo URL, gender, etc.

This helps with showing results in a fun and interactive way.

ValueError: math domain error

I got this error in one of my simulations but couldn't grab the values from the traceback. I'll start logging the output and report back.

Traceback (most recent call last):
  File "/home/levy/code/voting/venv/lib/python3.6/site-packages/flask/app.py", line 1997, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/levy/code/voting/venv/lib/python3.6/site-packages/flask/app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "/home/levy/code/voting/venv/lib/python3.6/site-packages/flask_cors/extension.py", line 161, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "/home/levy/code/voting/venv/lib/python3.6/site-packages/flask/app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/home/levy/code/voting/venv/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/home/levy/code/voting/venv/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/levy/code/voting/venv/lib/python3.6/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/levy/code/voting/venv/lib/python3.6/site-packages/flask_cors/extension.py", line 161, in wrapped_function
    return cors_after_request(app.make_response(f(*args, **kwargs)))
  File "/home/levy/code/voting/venv/lib/python3.6/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/home/levy/code/voting/venv/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/home/levy/code/voting/venv/lib/python3.6/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/levy/code/voting/venv/lib/python3.6/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/levy/code/voting/backend/web.py", line 194, in check_simulation
    "results": simulation.get_results_dict()
  File "/home/levy/code/voting/backend/simulate.py", line 526, in get_results_dict
    self.analysis()
  File "/home/levy/code/voting/backend/simulate.py", line 505, in analysis
    self.analyze_list(ruleset, measure, c, p)
  File "/home/levy/code/voting/backend/simulate.py", line 280, in analyze_list
    std = sqrt(var)
ValueError: math domain error

CORS config

Since we decoupled the frontend from the backend we need to consider CORS headers configs. Running web servers is not my expertise so it would be great to hear peoples general opinions on this when dealing with frontend applications making XMLHttpRequests (or Fetch) directly to a service.

I guess this is also somewhat related to authentication of the app in general but another topic.

So instead of same-origin we could:

  • Whitelist IPs?
  • Allow all?

`constituencies` not part of /api/capabilities/ response

Hey all,

Fixed a few issues to get the frontend working, see #10

The bakend doesn´t seem to return the constituency array required for the frontend to render VotesTable.

The setPreset func asks for a constituency property which is not part of the response:

https://github.com/smari/voting/blob/master/voting/static/js/ui.js#L584

Output from /api/capabilities/

{
  "key": 1,
  "rules": {},
  "election_rules": {
    "primary_divider": "dhondt",
    "adjustment_divider": "dhondt",
    "adjustment_threshold": 0.05,
    "adjustment_method": "icelandic-law",
    "constituency_seats": [
      7,
      9,
      9,
      11,
      9,
      9
    ],
    "constituency_adjustment_seats": [
      1,
      1,
      1,
      2,
      2,
      2
    ],
    "constituency_names": [
      "Norðvestur",
      "Norðaustur",
      "Suður",
      "Suðvestur",
      "Reykjavík suður",
      "Reykjavík norður"
    ],
    "parties": [
      "A",
      "B",
      "D",
      "G",
      "H",
      "I",
      "J",
      "K",
      "L",
      "M",
      "R",
      "S",
      "T",
      "V",
      "Þ"
    ],
    "votes": [
      [
        792,
        6104,
        4282,
        208,
        0,
        161,
        774,
        0,
        251,
        326,
        0,
        2122,
        328,
        1470,
        537
      ],
      [
        1537,
        8173,
        5327,
        296,
        0,
        241,
        306,
        0,
        313,
        0,
        0,
        2505,
        460,
        3733,
        716
      ],
      [
        1202,
        9265,
        7596,
        703,
        0,
        786,
        412,
        0,
        431,
        0,
        0,
        2734,
        904,
        1582,
        1269
      ],
      [
        4687,
        10944,
        15608,
        925,
        0,
        1838,
        188,
        0,
        1241,
        0,
        0,
        6932,
        1927,
        3995,
        2541
      ],
      [
        3790,
        5931,
        9466,
        575,
        55,
        1394,
        161,
        222,
        1025,
        0,
        54,
        5007,
        1163,
        4279,
        2179
      ],
      [
        3576,
        5759,
        8187,
        556,
        71,
        1289,
        181,
        0,
        1398,
        0,
        64,
        4996,
        1073,
        5493,
        2407
      ]
    ],
    "simulate": false,
    "debug": false,
    "show_entropy": false,
    "output": "simple"
  },
  "simulation_rules": {
    "simulate": false,
    "simulation_count": 100,
    "simulation_variate": "beta"
  },
  "presets": [
    {
      "name": "Icelandic Parliament election 2013",
      "action": "election",
      "election_rules": {
        "primary_divider": "dhondt",
        "adjustment_divider": "dhondt",
        "adjustment_threshold": 0.05,
        "adjustment_method": "icelandic-law",
        "constituency_seats": [
          7,
          9,
          9,
          11,
          9,
          9
        ],
        "constituency_adjustment_seats": [
          1,
          1,
          1,
          2,
          2,
          2
        ],
        "constituency_names": [
          "Norðvestur",
          "Norðaustur",
          "Suður",
          "Suðvestur",
          "Reykjavík suður",
          "Reykjavík norður"
        ],
        "parties": [
          "A",
          "B",
          "D",
          "G",
          "H",
          "I",
          "J",
          "K",
          "L",
          "M",
          "R",
          "S",
          "T",
          "V",
          "Þ"
        ],
        "votes": [
          [
            792,
            6104,
            4282,
            208,
            0,
            161,
            774,
            0,
            251,
            326,
            0,
            2122,
            328,
            1470,
            537
          ],
          [
            1537,
            8173,
            5327,
            296,
            0,
            241,
            306,
            0,
            313,
            0,
            0,
            2505,
            460,
            3733,
            716
          ],
          [
            1202,
            9265,
            7596,
            703,
            0,
            786,
            412,
            0,
            431,
            0,
            0,
            2734,
            904,
            1582,
            1269
          ],
          [
            4687,
            10944,
            15608,
            925,
            0,
            1838,
            188,
            0,
            1241,
            0,
            0,
            6932,
            1927,
            3995,
            2541
          ],
          [
            3790,
            5931,
            9466,
            575,
            55,
            1394,
            161,
            222,
            1025,
            0,
            54,
            5007,
            1163,
            4279,
            2179
          ],
          [
            3576,
            5759,
            8187,
            556,
            71,
            1289,
            181,
            0,
            1398,
            0,
            64,
            4996,
            1073,
            5493,
            2407
          ]
        ],
        "simulate": false,
        "debug": false,
        "show_entropy": false,
        "output": "simple"
      }
    }
  ],
  "capabilities": {
    "adjustment_methods": {
      "alternating-scaling": "Alternating-Scaling Method",
      "icelandic-law": "Icelandic law 24/2000 (Kosningar til Alþingis)",
      "monge": "Monge algorithm",
      "relative-inferiority": "Relative Inferiority Method",
      "relative-superiority": "Relative Superiority Method"
    },
    "divider_rules": {
      "dhondt": "D'Hondt's method",
      "sainte-lague": "Sainte-Laguë method",
      "swedish": "Nordic Sainte-Laguë variant"
    }
  },
  "capabilities_loaded": true,
  "votes": []
}

Samanburðarmæling

Að bera saman óreiðu mismunandi aðferða í mismunandi kosningum, sem hlutfall af bestu lausn (Balinski).

Samanburður á óreiðu eftir aðferðum

Fuzz votes

Add votes, one-by-one, in support of a party list in a district.

  • If a new vote doesn't gain that party list a seat, report any change in results.
  • Even if a new vote does gain that party list a seat, do report if the result change is greater than just moving a seat in the relevant constituency between parties and moving one seat in the opposite direction in another constituency.

Generate random initial votes

Draw percentages for each party in each district from a beta distribution with historical mean and variance. Then normalize the percentages to add up to 100%.

Uploading vote table from xlsx file - AttributeError: 'SpooledTemporaryFile' object has no attribute 'seekable'

I was running some simulations recently, and discovered that the file upload had broken.
I do not remember when I last tried to upload a vote table xlsx file, so I do not know exactly when this became a problem,
but at least it was most definitely not a problem when I was working on the file upload feature last year.

The issue is when uploading votes from an xlsx file, and loading the filestream into a workbook using openpyxl.
The filestream is of type SpooledTemporaryFile, but openpyxl seems to expect it to implement BufferedIOBase or something like that (I'm no expert in these IO implementations)

Here's the relevant code:

@app.route('/api/votes/upload/', methods=['POST'])
def upload_votes():
    ...
    f = request.files['file']
    res = util.load_votes_from_stream(f.stream, f.filename)
    ...
def load_votes_from_stream(stream, filename):
    rd = []
    if filename.endswith(".csv"):
        ...
    elif filename.endswith(".xlsx"):
        book = openpyxl.load_workbook(stream)
        ...
    else:
        return None, None, None

I found this related werkzeug issue from two years ago.
pallets/werkzeug#1344

It seems to have been resolved, so I tried just updating werkzeug and flask, as well as openpyxl.
But that did not solve this, so I added a couple of lines to fix this, although I'm not satisfied with that solution:

if not hasattr(stream, "seekable") and hasattr(stream, "_file"):
    stream.seekable = stream._file.seekable

See PR: #94

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.