Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI and config updates for Stop Tabulation Early #636

Merged
merged 5 commits into from
Mar 19, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/main/java/network/brightspots/rcv/ContestConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ class ContestConfig {
private static final int MAX_ROW_INDEX = 100000;
private static final int MIN_MAX_RANKINGS_ALLOWED = 1;
private static final int MIN_MAX_SKIPPED_RANKS_ALLOWED = 0;
private static final int MIN_NUMBER_OF_ROUNDS = 0;
private static final int MAX_NUMBER_OF_ROUNDS = Integer.MAX_VALUE;
artoonie marked this conversation as resolved.
Show resolved Hide resolved
private static final int MIN_NUMBER_OF_WINNERS = 0;
private static final int MIN_DECIMAL_PLACES_FOR_VOTE_ARITHMETIC = 1;
private static final int MAX_DECIMAL_PLACES_FOR_VOTE_ARITHMETIC = 20;
Expand Down Expand Up @@ -701,6 +703,15 @@ && getMaxRankingsAllowed() < MIN_MAX_RANKINGS_ALLOWED)) {
ValidationError.RULES_MULTI_SEAT_BOTTOMS_UP_PERCENTAGE_THRESHOLD_INVALID);
}

if (fieldOutOfRangeOrNotInteger(
getStopEarlyOnRoundRaw(),
"stopEarlyOnRound",
MIN_NUMBER_OF_ROUNDS,
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be 1? If they say to stop after 0 rounds, that's the same as just not running the tabulation at all, isn't it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, I figured that might be useful in some cases -- you'd still get the output files with zero rounds (similar to running a Zero Report?). I can change it to 1 though if you prefer.

MAX_NUMBER_OF_ROUNDS,
false)) {
validationErrors.add(ValidationError.RULES_STOP_EARLY_ON_ROUND_INVALID);
}

WinnerElectionMode winnerMode = getWinnerElectionMode();
if (Utils.isInt(getNumberOfWinnersRaw())) {
if (getNumberOfWinners() > 0) {
Expand Down Expand Up @@ -789,6 +800,10 @@ private String getNumberOfWinnersRaw() {
return rawConfig.rules.numberOfWinners;
}

private String getStopEarlyOnRoundRaw() {
HEdingfield marked this conversation as resolved.
Show resolved Hide resolved
return rawConfig.rules.stopTabulationEarlyOnRound;
}

Integer getNumberOfWinners() {
return Integer.parseInt(getNumberOfWinnersRaw());
}
Expand Down Expand Up @@ -947,6 +962,12 @@ boolean isContinueUntilTwoCandidatesRemainEnabled() {
return rawConfig.rules.continueUntilTwoCandidatesRemain;
}

Integer getStopTabulationAtRound() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: getStopTabulationEarlyOnRound

Copy link
Contributor

Choose a reason for hiding this comment

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

This still says "at" instead of "on", so recommend changing it to be consistent with everything else.

return isNullOrBlank(getStopEarlyOnRoundRaw())
? Integer.MAX_VALUE
: Integer.parseInt(getStopEarlyOnRoundRaw());
}

int getNumDeclaredCandidates() {
int size = getCandidateCodeList().size();
if (undeclaredWriteInsEnabled()) {
Expand Down Expand Up @@ -1125,6 +1146,7 @@ enum ValidationError {
RULES_MIN_DECIMAL_PLACES_FOR_VOTE_ARITHMETIC_INVALID,
RULES_MIN_VOTE_THRESHOLD_INVALID,
RULES_MULTI_SEAT_BOTTOMS_UP_PERCENTAGE_THRESHOLD_INVALID,
RULES_STOP_EARLY_ON_ROUND_INVALID,
artoonie marked this conversation as resolved.
Show resolved Hide resolved
RULES_NUMBER_OF_WINNERS_INVALID_FOR_WINNER_ELECTION_MODE,
RULES_CONTINUE_UNTIL_TWO_CANDIDATES_REMAIN_TRUE_FOR_MULTI_SEAT,
RULES_BATCH_ELIMINATION_TRUE_FOR_MULTI_SEAT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ static void migrateConfigVersion(ContestConfig config)
}
}

// New rule to stop tabulation early
HEdingfield marked this conversation as resolved.
Show resolved Hide resolved
if (rules.stopTabulationEarlyOnRound == null) {
rules.stopTabulationEarlyOnRound = "";
}

Logger.info(
"Migrated tabulator config version from %s to %s.",
config.rawConfig.tabulatorVersion != null ? config.rawConfig.tabulatorVersion : "unknown",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ public class GuiConfigController implements Initializable {
@FXML
private CheckBox checkBoxContinueUntilTwoCandidatesRemain;
@FXML
private TextField textFieldStopTabulationEarly;
artoonie marked this conversation as resolved.
Show resolved Hide resolved
@FXML
private CheckBox checkBoxExhaustOnDuplicateCandidate;
@FXML
private MenuBar menuBar;
Expand Down Expand Up @@ -795,6 +797,7 @@ private void clearAndDisableWinningRuleFields() {
checkBoxMaxRankingsAllowedMax.setDisable(true);
textFieldMinimumVoteThreshold.clear();
textFieldMinimumVoteThreshold.setDisable(true);
textFieldStopTabulationEarly.clear();
artoonie marked this conversation as resolved.
Show resolved Hide resolved
checkBoxBatchElimination.setSelected(false);
checkBoxBatchElimination.setDisable(true);
checkBoxContinueUntilTwoCandidatesRemain.setSelected(false);
Expand Down Expand Up @@ -1250,6 +1253,7 @@ private void loadConfig(ContestConfig config) throws ConfigVersionIsNewerThanApp
setThresholdCalculationMethodRadioButton(rules.nonIntegerWinningThreshold, rules.hareQuota);
checkBoxBatchElimination.setSelected(rules.batchElimination);
checkBoxContinueUntilTwoCandidatesRemain.setSelected(rules.continueUntilTwoCandidatesRemain);
textFieldStopTabulationEarly.setText(rules.stopTabulationEarlyOnRound);
checkBoxExhaustOnDuplicateCandidate.setSelected(rules.exhaustOnDuplicateCandidate);
}

Expand Down Expand Up @@ -1340,6 +1344,7 @@ private RawContestConfig createRawContestConfig() {
rules.hareQuota = radioThresholdHareQuota.isSelected();
rules.batchElimination = checkBoxBatchElimination.isSelected();
rules.continueUntilTwoCandidatesRemain = checkBoxContinueUntilTwoCandidatesRemain.isSelected();
rules.stopTabulationEarlyOnRound = getTextOrEmptyString(textFieldStopTabulationEarly);
rules.exhaustOnDuplicateCandidate = checkBoxExhaustOnDuplicateCandidate.isSelected();
rules.rulesDescription = getTextOrEmptyString(textFieldRulesDescription);
config.rules = rules;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/network/brightspots/rcv/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
public class Main extends GuiApplication {

public static final String APP_NAME = "RCTab";
public static final String APP_VERSION = "1.3.0";
public static final String APP_VERSION = "1.4.0";
artoonie marked this conversation as resolved.
Show resolved Hide resolved

/**
* Main entry point to RCTab.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ public static class ContestRules {
public boolean hareQuota;
public boolean batchElimination;
public boolean continueUntilTwoCandidatesRemain;
public String stopTabulationEarlyOnRound;
public boolean exhaustOnDuplicateCandidate;
public String rulesDescription;

Expand Down
13 changes: 13 additions & 0 deletions src/main/resources/network/brightspots/rcv/GuiConfigLayout.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,19 @@
<Insets bottom="4.0" left="4.0" right="4.0" top="4.0"/>
</padding>
</HBox>
<HBox spacing="4.0">
<children>
<Label prefWidth="220.0" text="Stop Tabulation Early on Round">
<HBox.margin>
<Insets />
</HBox.margin>
</Label>
<TextField fx:id="textFieldStopTabulationEarly" maxWidth="80.0" />
</children>
HEdingfield marked this conversation as resolved.
Show resolved Hide resolved
<padding>
<Insets bottom="4.0" left="4.0" right="4.0" top="4.0" />
</padding>
</HBox>
<HBox alignment="CENTER_LEFT" spacing="4.0">
<CheckBox fx:id="checkBoxBatchElimination" mnemonicParsing="false"
text="Use Batch Elimination">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Use Batch Elimination: Batch elimination, or simultaneous elimination of all can

Continue until Two Candidates Remain: Single-winner ranked-choice voting elections can stop as soon as a candidate receives a majority of votes, even though 3 or more candidates may still be in the race. Selecting this option will run the round-by-round count until only two candidates remain, regardless of when a candidate wins a majority of votes. Available only when Winner Election Mode is "Single-winner majority determines winner" or "Multi-pass IRV."

Stop Tabulation Early: If a winner is not found by the given round, tabulation stops early.
artoonie marked this conversation as resolved.
Show resolved Hide resolved

Tiebreak Mode (required): Ties in ranked-choice voting contests can occur when eliminating candidates or when electing candidates. Multi-winner contests can have ties between candidates who have both crossed the threshold of election; in that case ties are broken to determine whose surplus vote value transfers first. Tiebreak procedures are set in law, either in the ranked-choice voting law used in your jurisdiction or in the elections code more generally. Select the option from this list that complies with law and procedure in your jurisdiction.

* Random: Randomly select a tied candidate to eliminate or, in multi-winner contests only, elect. Requires a random seed.
Expand Down