Skip to content

Bug(s) in weak_nanson, possibly all Nanson/Baldwin methods #178

Description

@mjbaldwin

Hi Wes and Eric, Michael here.

I've run into a bug in weak_nanson:

File .../pref_voting/pref_voting/iterative_methods.py:1613, in weak_nanson(profile, curr_cands)
   1611 else: 
   1612     num_cands = len(candidates)
-> 1613     updated_rankings = _find_updated_profile(rankings, cands_to_ignore, num_cands)
   1615 while len(winners) == 0: 
   1617     borda_scores = {c: _borda_score(updated_rankings, rcounts, num_cands - cands_to_ignore.shape[0], c) 
   1618                     for c in candidates if not isin(cands_to_ignore, c)}

ValueError: negative dimensions not allowed

Looking through the code, the problem seems to be that on line 1613 (and 1633), rankings has curr_cands candidates, cands_to_ignore is the combination of candidates excluded by curr_cands as well as candidates excluded in previous Nanson iterations, but num_cands only goes up to curr_cands -- so cands_to_ignore can actually be a list that has more elements num_cands, producing the negative number error. (In my case, I was using a profile with 10 candidates, while curr_cands had 6 candidates.)

In the process, I also discovered that the Borda scores (line 1617) seem to be similarly incorrect, calculating the number of candidates as num_cands - cands_to_ignore.shape[0], I think where num_cands ought to be all_num_cands in that case.

I went ahead and forked the project to submit a pull request just for weak_nanson, but then saw that similar but not identical logic is repeated across the _with_explanation() versions, with strict_nanson, and with baldwin. For example, while weak_nanson() initially defines at the start:

cands_to_ignore = np.empty(0) if curr_cands is None else np.array([c for c in profile.candidates if c not in curr_cands])

On the other hand, strict_nanson() defines at the start:

cands_to_ignore = np.empty(0)

And then that seems to introduce its own bug on line 1459, because it's not accounting for curr_cands at all.

So instead of submitting the fix for just weak_nanson or trying to check and fix them all, I'm realizing you may want to standarize on a single logic pattern across all these functions, and I'm not sure which approach you'll prefer for tracking candidates excluded via curr_cands vs. candidates excluded via iteration, and ensuring that candidate removal and Borda score calculations are done with the right numbers of candidates.

(Just for context, I ran into the bug because I'm calculating a ranked list of winners for a profile by calculating the (first-place) winner, applying a tiebreaker as needed, removing them from curr_cands to calculate the (second-place) winner, and so forth.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions