Source: compute/voteCasters/pairwise/castPairwise.js

/** @module */

import * as types from '@paretoman/votekit-types'
import castPairwisePlanes2D from './castPairwisePlanes2D.js'
import castPairwiseIntervals1D from './castPairwiseIntervals1D.js'
import castPairwiseGrid from './castPairwiseGrid.js'
/**
 * Voters cast votes for candidates.
 * @param {types.typesGeometry.geometry} geometry - geometry for casting votes
 * @param {types.typesCast.castOptions} castOptions - options for how to cast votes.
 * @returns {types.typesVotes.votes} votes
 */
export default function castPairwise(geometry, castOptions) {
    const { canPoints, voterGeoms, dimensions, parties } = geometry
    const { verbosity } = castOptions

    const someGaussian2D = voterGeoms.some((v) => v.densityProfile === 'gaussian') && dimensions === 2

    const castRegions = (dimensions === 1)
        ? castPairwiseIntervals1D
        : castPairwisePlanes2D
    const cast = (someGaussian2D) ? castPairwiseGrid : castRegions

    // get fraction of votes for each candidate so we can summarize results
    let totalVotes = 0

    // should ideally make a set of polygons for each ranking so that we avoid repeating rankings.

    const n = canPoints.length
    const winsPairwise = Array(n).fill(0)
    for (let i = 0; i < n; i++) {
        winsPairwise[i] = Array(n).fill(0)
    }
    const votesByGeom = []
    voterGeoms.forEach((voterGeom, g) => {
        const votesForGeom = cast(voterGeom, geometry, castOptions)
        const { winsPairwise: winsPairwiseForGeom,
            totalVotes: totalVotesForGeom } = votesForGeom

        for (let i = 0; i < n; i++) {
            for (let k = 0; k < n; k++) {
                winsPairwise[i][k] += winsPairwiseForGeom[i][k]
            }
        }
        totalVotes += totalVotesForGeom

        if (verbosity < 2) return

        votesByGeom[g] = votesForGeom
    })
    const invTotalCount = 1 / totalVotes
    const winFractionPairwise = winsPairwise.map((x) => x.map((a) => a * invTotalCount))

    const pairwiseTallies = { winFractionPairwise }
    const numCans = canPoints.length
    const votes = { pairwiseTallies, parties, numCans }
    if (verbosity < 1) return votes

    // borda scores
    const bordaScoreSumByCan = Array(n).fill(0)
    const bordaFractionAverageByCan = Array(n)
    const invTotalCountTimesNMinus1 = invTotalCount / (n - 1)
    for (let i = 0; i < n; i++) {
        for (let k = 0; k < n; k++) {
            bordaScoreSumByCan[i] += winsPairwise[i][k]
        }
        bordaFractionAverageByCan[i] = bordaScoreSumByCan[i] * invTotalCountTimesNMinus1
    }
    const candidateTallies = { bordaFractionAverageByCan }
    votes.candidateTallies = candidateTallies
    if (verbosity < 2) return votes

    votes.votesByGeom = votesByGeom
    return votes
}