diff --git a/docs/options.md b/docs/options.md
index 8ea4b98..8a6398f 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -104,10 +104,11 @@ Random sample methods are also supported, but also not recommended:
The use of a random sample method requires *Normalise ballots* to be enabled, and will usually be used with a *Quota criterion* set to *>=*.
-### Papers to examine in surplus transfer (--transferable-only)
+### Papers to examine in surplus transfer (--transferable-only/--subtract-nontransferable)
* *Include non-transferable papers* (default): When this option is selected, all ballot papers of the transferring candidate are examined. Non-transferable papers are always exhausted at the relevant surplus fractions. This is the method typically used with the weighted inclusive Gregory or Meek methods.
-* *Use transferable papers only*: When this option is selected, only transferable papers of the transferring candidate are examined. Non-transferable papers are exhausted only if the value of the transferable papers is less than the surplus. This is the method typically used with other surplus distribution methods.
+* *Use transferable papers only* (--transferable-only): When this option is selected, only transferable papers of the transferring candidate are examined. Non-transferable papers are exhausted only if the value of the transferable papers is less than the surplus. This is the method typically used with other surplus distribution methods.
+* *Subtract non-transferables* (--transferable-only --subtract-nontransferable): Same as *Use transferable papers only*, but the value of the transferable papers is calculated by subtracting the value of non-transferable papers from the progress total. This has effect only as far as concerns rounding.
### (Gregory) Exclusion method (--exclusion)
diff --git a/html/index.html b/html/index.html
index 74cc4ef..149a195 100644
--- a/html/index.html
+++ b/html/index.html
@@ -123,6 +123,7 @@
diff --git a/html/index.js b/html/index.js
index ad49e62..2c3c7ec 100644
--- a/html/index.js
+++ b/html/index.js
@@ -154,7 +154,7 @@ async function clickCount() {
document.getElementById('txtSeed').value,
document.getElementById('selMethod').value,
document.getElementById('selSurplus').value,
- document.getElementById('selPapers').value == 'transferable',
+ document.getElementById('selPapers').value,
document.getElementById('selExclusion').value,
document.getElementById('chkMeekNZExclusion').checked,
document.getElementById('selSample').value,
diff --git a/html/presets.js b/html/presets.js
index 9e5c505..a8416ad 100644
--- a/html/presets.js
+++ b/html/presets.js
@@ -239,7 +239,7 @@ function changePreset() {
document.getElementById('selSumTransfers').value = 'by_value_and_source';
document.getElementById('selSurplus').value = 'by_order';
document.getElementById('selMethod').value = 'wig';
- document.getElementById('selPapers').value = 'transferable';
+ document.getElementById('selPapers').value = 'subtract_nontransferable';
document.getElementById('selExclusion').value = 'single_stage';
document.getElementById('selTies').value = 'backwards,random';
} else if (document.getElementById('selPreset').value === 'minneapolis') {
diff --git a/src/cli/stv.rs b/src/cli/stv.rs
index 788497e..5f7c5b1 100644
--- a/src/cli/stv.rs
+++ b/src/cli/stv.rs
@@ -125,6 +125,10 @@ pub struct SubcmdOptions {
#[clap(help_heading=Some("STV VARIANTS"), long)]
transferable_only: bool,
+ /// (Gregory STV) If --transferable-only, calculate value of transferable papers by subtracting value of non-transferable papers
+ #[clap(help_heading=Some("STV VARIANTS"), long)]
+ subtract_nontransferable: bool,
+
/// (Gregory STV) Method of exclusions
#[clap(help_heading=Some("STV VARIANTS"), long, possible_values=&["single_stage", "by_value", "by_source", "parcels_by_order", "wright"], default_value="single_stage", value_name="method")]
exclusion: String,
@@ -294,6 +298,7 @@ where
cmd_opts.surplus.into(),
cmd_opts.surplus_order.into(),
cmd_opts.transferable_only,
+ cmd_opts.subtract_nontransferable,
cmd_opts.exclusion.into(),
cmd_opts.meek_nz_exclusion,
cmd_opts.sample.into(),
diff --git a/src/stv/gregory/mod.rs b/src/stv/gregory/mod.rs
index b626f44..22a5ad1 100644
--- a/src/stv/gregory/mod.rs
+++ b/src/stv/gregory/mod.rs
@@ -265,6 +265,11 @@ where
let count_card = state.candidates.get_mut(elected_candidate).unwrap();
count_card.ballot_transfers = -&total_ballots;
+ if opts.transferable_only && opts.subtract_nontransferable {
+ // Override transferable_votes
+ transferable_votes = count_card.votes.clone() - exhausted_votes;
+ }
+
let mut surplus_denom = calculate_surplus_denom(&surplus, &transferable_ballots, &transferable_votes, &total_ballots, &total_votes, opts);
let surplus_numer;
let mut surplus_fraction;
diff --git a/src/stv/mod.rs b/src/stv/mod.rs
index 1bb912d..31f65e4 100644
--- a/src/stv/mod.rs
+++ b/src/stv/mod.rs
@@ -104,6 +104,10 @@ pub struct STVOptions {
#[builder(default="false")]
pub transferable_only: bool,
+ /// (Gregory STV) If --transferable-only, calculate value of transferable papers by subtracting value of non-transferable papers
+ #[builder(default="false")]
+ pub subtract_nontransferable: bool,
+
/// (Gregory STV) Method of exclusions
#[builder(default="ExclusionMethod::SingleStage")]
pub exclusion: ExclusionMethod,
@@ -189,6 +193,7 @@ impl STVOptions {
if self.surplus != SurplusMethod::Meek {
if self.surplus_order != SurplusOrder::BySize { flags.push(self.surplus_order.describe()); }
if self.transferable_only { flags.push("--transferable-only".to_string()); }
+ if self.subtract_nontransferable { flags.push("--subtract-nontransferable".to_string()); }
if self.exclusion != ExclusionMethod::SingleStage { flags.push(self.exclusion.describe()); }
}
if self.surplus == SurplusMethod::Meek && self.meek_nz_exclusion { flags.push("--meek-nz-exclusion".to_string()); }
@@ -224,6 +229,7 @@ impl STVOptions {
//if self.sample == SampleMethod::StratifyFloor && self.sample_per_ballot { return Err(STVError::InvalidOptions("--sample stratify_floor is incompatible with --sample-per-ballot")); }
if self.sample_per_ballot && !self.immediate_elect { return Err(STVError::InvalidOptions("--sample-per-ballot is incompatible with --no-immediate-elect")); }
}
+ if self.subtract_nontransferable && !self.transferable_only { return Err(STVError::InvalidOptions("--subtract-nontransferable requires --transferable-only")) }
if self.min_threshold != "0" && self.defer_surpluses { return Err(STVError::InvalidOptions("--min-threshold is incompatible with --defer-surpluses (not yet implemented)")); } // TODO: NYI
if self.round_subtransfers == RoundSubtransfersMode::ByValueAndSource && self.bulk_exclude { return Err(STVError::InvalidOptions("--round-subtransfers by_value_and_source is incompatible with --bulk-exclude (not yet implemented)")); } // TODO: NYI
return Ok(());
diff --git a/src/stv/wasm.rs b/src/stv/wasm.rs
index 7ece0cc..d470389 100644
--- a/src/stv/wasm.rs
+++ b/src/stv/wasm.rs
@@ -249,7 +249,7 @@ impl STVOptions {
random_seed: String,
surplus: &str,
surplus_order: &str,
- transferable_only: bool,
+ papers: &str,
exclusion: &str,
meek_nz_exclusion: bool,
sample: &str,
@@ -277,7 +277,8 @@ impl STVOptions {
ties::from_strs(ties.iter().map(|v| v.as_string().unwrap()).collect(), Some(random_seed)),
surplus.into(),
surplus_order.into(),
- transferable_only,
+ if papers == "transferable" || papers == "subtract_nontransferable" { true } else { false },
+ if papers == "subtract_nontransferable" { true } else { false },
exclusion.into(),
meek_nz_exclusion,
sample.into(),
diff --git a/tests/tests_impl/nswlg.rs b/tests/tests_impl/nswlg.rs
index 3b1ca6e..8d8abed 100644
--- a/tests/tests_impl/nswlg.rs
+++ b/tests/tests_impl/nswlg.rs
@@ -33,9 +33,10 @@ fn nswlg_albury21_rational() {
.ties(vec![TieStrategy::Backwards, TieStrategy::Random(String::from("20220322"))])
.surplus_order(stv::SurplusOrder::ByOrder)
.transferable_only(true)
+ .subtract_nontransferable(true)
.build().unwrap();
- assert_eq!(stv_opts.describe::(), "--round-votes 0 --round-quota 0 --round-subtransfers by_value_and_source --quota-criterion geq --ties backwards random --random-seed 20220322 --surplus-order by_order --transferable-only");
+ assert_eq!(stv_opts.describe::(), "--round-votes 0 --round-quota 0 --round-subtransfers by_value_and_source --quota-criterion geq --ties backwards random --random-seed 20220322 --surplus-order by_order --transferable-only --subtract-nontransferable");
utils::read_validate_election::("tests/data/City_of_Albury-finalpreferencedatafile.csv", "tests/data/City_of_Albury-finalpreferencedatafile.blt", stv_opts, None, &[]);
}