Skip to content

Commit

Permalink
Merge pull request #104 from lzear/fix-lp-solver
Browse files Browse the repository at this point in the history
Fix the LP solver used in Randomized Condorcet and Maximal Lotteries
  • Loading branch information
lzear authored Apr 16, 2023
2 parents a5cc7ac + e6dc697 commit 57aff09
Show file tree
Hide file tree
Showing 138 changed files with 1,810 additions and 1,401 deletions.
8 changes: 8 additions & 0 deletions .changeset/eight-islands-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'votes': minor
---

**Fix the LP solver used in Randomized Condorcet and Maximal Lotteries.**

The methods should still be tested more deeply, but at least they should be less
buggy now. Their warnings and deprecation notices are removed with this release.
64 changes: 58 additions & 6 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ module.exports = {
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:markdown/recommended',
'plugin:import/recommended',
'plugin:import/errors',
'plugin:import/typescript',
'plugin:unicorn/recommended',
'prettier',
],
Expand All @@ -30,19 +32,69 @@ module.exports = {
plugins: [
'@typescript-eslint',
'eslint-plugin-tsdoc',
'react',
'unicorn',
'prettier',
'simple-import-sort',
],
rules: {
// '@typescript-eslint/no-floating-promises': 2,

'@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/consistent-type-imports': 2,
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-unused-vars': [
2,
{
ignoreRestSiblings: true,
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: `^_`,
},
],
'import/first': 2,
'import/newline-after-import': 2,
'import/no-duplicates': 2,
'import/order': 0,
'jsx-quotes': 2,
'object-shorthand': 2,
'prettier/prettier': 2,
'react/jsx-curly-brace-presence': [2, 'never'],
'simple-import-sort/exports': 2,
'simple-import-sort/imports': [
2,
{
groups: [
// Node.js builtins. You could also generate this regex if you use a `.js` config.
// For example: `^(${require("module").builtinModules.join("|")})(/|$)`
// Note that if you use the `node:` prefix for Node.js builtins,
// you can avoid this complexity: You can simply use "^node:".
['^node:'],
[
'^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)',
],
// Packages. `react` related packages come first.
['^react', '^@?\\w'],
// Internal packages.
['^(votes)(\\/.*|$)'],
// Side effect imports.
['^\\u0000'],
// Parent imports. Put `..` last.
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
// Other relative imports. Put same-folder imports and `.` last.
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
// Style imports.
['^.+\\.s?css$'],
],
},
],
'tsdoc/syntax': 1,
'unicorn/prevent-abbreviations': 0,
'unicorn/prefer-object-from-entries': 0,
'unicorn/no-array-reduce': 0,
'unicorn/no-new-array': 0,
'unicorn/no-null': 0,
'unicorn/prefer-number-properties': 0,
'unicorn/no-new-array': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'prettier/prettier': 2,
'unicorn/prefer-object-from-entries': 0,
'unicorn/prevent-abbreviations': 0,
},
overrides: [
{
Expand Down
3 changes: 3 additions & 0 deletions .ncurc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
reject: ['antd', 'javascript-lp-solver']
}
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,11 @@ preference order aiming to minimize dissatisfaction of the voters. Also known as
Kemeny rule, VoteFair popularity ranking, the maximum likelihood method, and the
median relation.

**⚠️ Maximal lotteries & Randomized Condorcet ⚠️** (Contain big implementation
errors!): Returns probabilities for each candidate that should be used for a
lottery between the Candidates. If a candidate is the Condorcet winner, its
probability will be 1. Despite being non-deterministic, those methods are the
fairest. Currently, these methods give incorrect results in many cases because
of mistakes in the codes!
**Maximal lotteries & Randomized Condorcet**: Returns probabilities for each
candidate that should be used for a lottery between the Candidates. If a
candidate is the Condorcet winner, its probability will be 1. Despite being
non-deterministic, those methods are the fairest. Currently, these methods give
incorrect results in many cases because of mistakes in the codes!

**Majority Judgement**: (**⚠️ cardinal voting system**). Voters can rank
candidates in an array corresponding to 6 categories ("Excellent", "Very good",
Expand Down
7 changes: 6 additions & 1 deletion demo/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ const { getThemeVariables } = require('antd/dist/theme')
// compact: true, // Enable compact mode
// })

module.exports = withPlugins(
/**
* @type {import('next').NextConfig}
*/
const nextConfig = withPlugins(
[
[withBundleAnalyzer],
[
Expand Down Expand Up @@ -79,3 +82,5 @@ module.exports = withPlugins(
},
},
)

module.exports = nextConfig
31 changes: 15 additions & 16 deletions demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,51 +10,50 @@
},
"dependencies": {
"@ant-design/icons": "^5.0.1",
"@fortawesome/fontawesome-free": "^6.3.0",
"@fortawesome/fontawesome-svg-core": "^6.3.0",
"@fortawesome/free-regular-svg-icons": "^6.3.0",
"@fortawesome/free-solid-svg-icons": "^6.3.0",
"@fortawesome/fontawesome-free": "^6.4.0",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@next/bundle-analyzer": "^13.2.4",
"@next/eslint-plugin-next": "^13.2.4",
"@react-spring/web": "^9.7.1",
"@next/bundle-analyzer": "^13.3.0",
"@next/eslint-plugin-next": "^13.3.0",
"@react-spring/web": "^9.7.2",
"@types/d3": "^7.4.0",
"@types/d3-dispatch": "^3.0.2",
"@types/d3-force": "^3.0.4",
"@types/d3-hierarchy": "^3.1.2",
"@types/d3-scale": "^4.0.3",
"@types/d3-selection": "^3.0.5",
"@types/d3-timer": "^3.0.0",
"@types/node": "18.15.9",
"@types/node": "18.15.11",
"@types/numeral": "^2.0.2",
"@types/react": "18.0.29",
"@use-gesture/react": "^10.2.24",
"@types/react": "18.0.35",
"@use-gesture/react": "^10.2.26",
"antd": "^4.24.1",
"babel-preset-next": "^1.4.0",
"d3": "^7.8.3",
"d3": "^7.8.4",
"d3-dispatch": "^3.0.1",
"d3-force": "^3.0.0",
"d3-hierarchy": "^3.1.2",
"d3-scale": "^4.0.2",
"d3-selection": "^3.0.0",
"d3-timer": "^3.0.1",
"eslint-config-next": "^13.2.4",
"eslint-config-next": "^13.3.0",
"hsluv": "^1.0.0",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"next": "13.2.4",
"next": "13.3.0",
"next-compose-plugins": "^2.2.1",
"next-plausible": "^3.7.2",
"next-with-less": "^2.0.5",
"numeral": "^2.0.6",
"react": "^18",
"react-dom": "^18",
"react-spring": "^9.7.1",
"react-with-gesture": "^4.0.8",
"tsconfig": "*",
"typescript": "5.0.2",
"typescript": "5.0.4",
"votes": "*",
"zustand": "^4.3.6"
"zustand": "^4.3.7"
},
"devDependencies": {},
"nextBundleAnalysis": {
Expand Down
3 changes: 2 additions & 1 deletion demo/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { config } from '@fortawesome/fontawesome-svg-core'
import type { AppProps } from 'next/app'
import PlausibleProvider from 'next-plausible'

import '../assets/antd-custom.less'
import 'antd/dist/antd.less'
import { config } from '@fortawesome/fontawesome-svg-core'

import '@fortawesome/fontawesome-svg-core/styles.css'

config.autoAddCss = false
Expand Down
3 changes: 2 additions & 1 deletion demo/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { NextPage } from 'next'
import Head from 'next/head'
import dynamic from 'next/dynamic'
import Head from 'next/head'

import { MyLayout } from '../src/layout'

const Sandbox = dynamic(() => import('../src/sandbox'), { ssr: false })
Expand Down
5 changes: 3 additions & 2 deletions demo/src/axis.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import _ from 'lodash'
import React from 'react'
import { StoreBallots } from './ballot-with-id'
import _ from 'lodash'

import type { StoreBallots } from './ballot-with-id'
import { numberToLetters } from './list-votes-group'

export const AxisBallot: React.FC<{
Expand Down
4 changes: 3 additions & 1 deletion demo/src/ballot-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import _ from 'lodash'
import type { Ballot } from 'votes'
import { totalWeight, WithWeights } from './generate-ballots'

import type { WithWeights } from './generate-ballots'
import { totalWeight } from './generate-ballots'

export const serializeRank = (rank: string[]): string =>
JSON.stringify(rank.sort((a, b) => a.localeCompare(b)))
Expand Down
5 changes: 3 additions & 2 deletions demo/src/ballots-ui.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Ballot, Round } from 'votes'

import type { BallotWithId } from './ballot-with-id'
import { totalWeight } from './generate-ballots'
import { BallotWithId } from './ballot-with-id'
import { Scale } from './scale'
import type { Scale } from './scale'

export type WithOffset = Ballot & {
offset: number
Expand Down
17 changes: 9 additions & 8 deletions demo/src/ballots.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { useStore } from './store'
import {
selectAddRandomBallot,
selectBallots,
selectNormalizeWeights100,
} from './store/selectors'
import { totalWeight } from './generate-ballots'
import { Button, Typography } from 'antd'
import {
ArrowRightOutlined,
DeleteOutlined,
EditOutlined,
MinusOutlined,
PlusOutlined,
} from '@ant-design/icons'
import { Button, Typography } from 'antd'

import {
selectAddRandomBallot,
selectBallots,
selectNormalizeWeights100,
} from './store/selectors'
import { totalWeight } from './generate-ballots'
import { useStore } from './store'

export const BallotsComp = () => {
const groupedBallots = useStore(selectBallots)
Expand Down
9 changes: 5 additions & 4 deletions demo/src/boxes-transition.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { a, useTransition } from '@react-spring/web'
import type { TransitionFn } from '@react-spring/core'
import { useHover } from '@use-gesture/react'
import { a, useTransition } from '@react-spring/web'
import type { useHover } from '@use-gesture/react'
import _ from 'lodash'
import { BoxMeta, BoxPosition, WithColor } from './ballots-ui'

import type { BoxMeta, BoxPosition, WithColor } from './ballots-ui'

type TrState = {
x: number
Expand Down Expand Up @@ -79,7 +80,7 @@ export const renderBoxes = (
y={y}
// transform={`translate(${offset[0]}, ${offset[1]})`}
style={{
opacity: opacity, //.to({ range: [1, 0], output: [1, 0] }),
opacity, //.to({ range: [1, 0], output: [1, 0] }),
x,
filter,
y,
Expand Down
7 changes: 4 additions & 3 deletions demo/src/candidates.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React, { useState } from 'react'
import { PlusOutlined } from '@ant-design/icons'
import { Button, Input, Tag, type TagProps, Typography } from 'antd'
import { useStore } from './store'

import {
selectAddCandidate,
selectRemoveCandidate,
selectUpdateCandidateCount,
useCandidates,
} from './store/selectors'
import { PlusOutlined } from '@ant-design/icons'
import { Candidate } from './generate-ballots'
import type { Candidate } from './generate-ballots'
import { useStore } from './store'

export const Candidates: React.FC = () => {
const candidates = useCandidates()
Expand Down
18 changes: 6 additions & 12 deletions demo/src/display-ballots.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import React, { useMemo } from 'react'
import type { Ballot, Round } from 'votes'

import { useCandidatesColors } from './store/selectors'
import { StoreBallots } from './ballot-with-id'
import {
BoxBase,
BoxPosition,
toBoxes,
withBallotX,
withColor,
WithColor,
withQualified,
} from './ballots-ui'
import { renderBoxes, useBoxesTransition } from './boxes-transition'
import { SvgBallots } from './svg-ballots'
import { AxisBallot } from './axis'
import type { StoreBallots } from './ballot-with-id'
import type { BoxBase, BoxPosition, WithColor } from './ballots-ui'
import { toBoxes, withBallotX, withColor, withQualified } from './ballots-ui'
import { renderBoxes, useBoxesTransition } from './boxes-transition'
import { scaling } from './scale'
import { SvgBallots } from './svg-ballots'

const getHighestBallot = (ballots: Ballot[]) =>
Math.max(...ballots.map((b) => b.ranking.length))
Expand Down
9 changes: 5 additions & 4 deletions demo/src/edit-ballot-rank.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React from 'react'
import { useStore } from './store'
import { Button } from 'antd'

import Ranker from './react-ranker/ranker'
import { WithWidth } from './react-ranker/use-width'
import {
selectChangeBallotRanking,
selectSelectBallot,
selectSelectedBallot,
selectWidth,
useCandidates,
} from './store/selectors'
import { Button } from 'antd'
import { WithWidth } from './react-ranker/use-width'
import Ranker from './react-ranker/ranker'
import { useStore } from './store'

export const RankedWithWidth = WithWidth(Ranker)

Expand Down
11 changes: 6 additions & 5 deletions demo/src/force-graph-spring.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import React, { useEffect, useRef, useState } from 'react'
import { a, useSprings } from '@react-spring/web'
import { dispatch } from 'd3-dispatch'
import {
forceCenter,
forceManyBody,
forceSimulation,
type Simulation,
} from 'd3-force'
import React, { useEffect, useRef, useState } from 'react'
import { dispatch } from 'd3-dispatch'
import { interval } from 'd3-timer'
import { forceLink } from './force-link'
import { a, useSprings } from '@react-spring/web'

import { selectSkewMatrix } from './store/selectors'
import { curvedPath } from './curved-path'
import { forceLink } from './force-link'
import { randomString } from './random-string'
import { useStore } from './store'
import { selectSkewMatrix } from './store/selectors'

const width = 600
const height = 600
Expand Down
Loading

1 comment on commit 57aff09

@vercel
Copy link

@vercel vercel bot commented on 57aff09 Apr 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

votes – ./

rank-votes.vercel.app
votes-lzear.vercel.app
votes-git-master-lzear.vercel.app

Please sign in to comment.