Implement --quota, --quota-criterion
This commit is contained in:
parent
f6fba85049
commit
e88d2674d6
15
src/main.rs
15
src/main.rs
|
@ -77,11 +77,22 @@ struct STV {
|
|||
#[clap(help_heading=Some("ROUNDING"), long, value_name="dps")]
|
||||
round_quota: Option<usize>,
|
||||
|
||||
// -----------
|
||||
// -- Quota --
|
||||
|
||||
/// Quota type
|
||||
#[clap(help_heading=Some("QUOTA"), short, long, possible_values=&["droop", "hare", "droop_exact", "hare_exact"], default_value="droop_exact")]
|
||||
quota: String,
|
||||
|
||||
/// Whether to elect candidates on meeting (geq) or strictly exceeding (gt) the quota
|
||||
#[clap(help_heading=Some("QUOTA"), short='c', long, possible_values=&["geq", "gt"], default_value="gt", value_name="criterion")]
|
||||
quota_criterion: String,
|
||||
|
||||
// ------------------
|
||||
// -- STV variants --
|
||||
|
||||
/// Method of surplus transfers
|
||||
#[clap(help_heading=Some("STV VARIANTS"), long, possible_values=&["wig", "uig", "eg", "meek"], default_value="wig", value_name="method")]
|
||||
#[clap(help_heading=Some("STV VARIANTS"), short='s', long, possible_values=&["wig", "uig", "eg", "meek"], default_value="wig", value_name="method")]
|
||||
surplus: String,
|
||||
|
||||
#[clap(help_heading=Some("STV VARIANTS"), long, possible_values=&["by_size", "by_order"], default_value="by_size", value_name="order")]
|
||||
|
@ -142,6 +153,8 @@ where
|
|||
cmd_opts.round_weights,
|
||||
cmd_opts.round_votes,
|
||||
cmd_opts.round_quota,
|
||||
&cmd_opts.quota,
|
||||
&cmd_opts.quota_criterion,
|
||||
&cmd_opts.surplus,
|
||||
&cmd_opts.surplus_order,
|
||||
cmd_opts.transferable_only,
|
||||
|
|
|
@ -47,6 +47,7 @@ where
|
|||
|
||||
fn pow_assign(&mut self, exponent: i32);
|
||||
fn floor_mut(&mut self, dps: usize);
|
||||
fn ceil_mut(&mut self, dps: usize);
|
||||
|
||||
fn parse(s: &str) -> Self {
|
||||
if let Ok(value) = Self::from_str_radix(s, 10) {
|
||||
|
|
|
@ -39,6 +39,11 @@ impl Number for NativeFloat64 {
|
|||
let factor = 10.0_f64.powi(dps as i32);
|
||||
self.0 = (self.0 * factor).floor() / factor;
|
||||
}
|
||||
|
||||
fn ceil_mut(&mut self, dps: usize) {
|
||||
let factor = 10.0_f64.powi(dps as i32);
|
||||
self.0 = (self.0 * factor).ceil() / factor;
|
||||
}
|
||||
}
|
||||
|
||||
impl Num for NativeFloat64 {
|
||||
|
|
|
@ -44,6 +44,17 @@ impl Number for Rational {
|
|||
self.0 /= factor;
|
||||
}
|
||||
}
|
||||
|
||||
fn ceil_mut(&mut self, dps: usize) {
|
||||
if dps == 0 {
|
||||
self.0 = self.0.ceil();
|
||||
} else {
|
||||
let factor = BigRational::from_integer(BigInt::from(10)).pow(dps as i32);
|
||||
self.0 *= &factor;
|
||||
self.0 = self.0.ceil();
|
||||
self.0 /= factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Num for Rational {
|
||||
|
|
|
@ -44,6 +44,17 @@ impl Number for Rational {
|
|||
self.0 /= factor;
|
||||
}
|
||||
}
|
||||
|
||||
fn ceil_mut(&mut self, dps: usize) {
|
||||
if dps == 0 {
|
||||
self.0.ceil_mut();
|
||||
} else {
|
||||
let factor = rug::Rational::from(10).pow(dps as u32);
|
||||
self.0 *= &factor;
|
||||
self.0.ceil_mut();
|
||||
self.0 /= factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Num for Rational {
|
||||
|
|
|
@ -34,6 +34,8 @@ pub struct STVOptions {
|
|||
pub round_weights: Option<usize>,
|
||||
pub round_votes: Option<usize>,
|
||||
pub round_quota: Option<usize>,
|
||||
pub quota: QuotaType,
|
||||
pub quota_criterion: QuotaCriterion,
|
||||
pub surplus: SurplusMethod,
|
||||
pub surplus_order: SurplusOrder,
|
||||
pub transferable_only: bool,
|
||||
|
@ -48,6 +50,8 @@ impl STVOptions {
|
|||
round_weights: Option<usize>,
|
||||
round_votes: Option<usize>,
|
||||
round_quota: Option<usize>,
|
||||
quota: &str,
|
||||
quota_criterion: &str,
|
||||
surplus: &str,
|
||||
surplus_order: &str,
|
||||
transferable_only: bool,
|
||||
|
@ -59,6 +63,18 @@ impl STVOptions {
|
|||
round_weights,
|
||||
round_votes,
|
||||
round_quota,
|
||||
quota: match quota {
|
||||
"droop" => QuotaType::Droop,
|
||||
"hare" => QuotaType::Hare,
|
||||
"droop_exact" => QuotaType::DroopExact,
|
||||
"hare_exact" => QuotaType::HareExact,
|
||||
_ => panic!("Invalid --quota"),
|
||||
},
|
||||
quota_criterion: match quota_criterion {
|
||||
"geq" => QuotaCriterion::GreaterOrEqual,
|
||||
"gt" => QuotaCriterion::Greater,
|
||||
_ => panic!("Invalid --quota-criterion"),
|
||||
},
|
||||
surplus: match surplus {
|
||||
"wig" => SurplusMethod::WIG,
|
||||
"uig" => SurplusMethod::UIG,
|
||||
|
@ -83,6 +99,22 @@ impl STVOptions {
|
|||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum QuotaType {
|
||||
Droop,
|
||||
Hare,
|
||||
DroopExact,
|
||||
HareExact,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum QuotaCriterion {
|
||||
GreaterOrEqual,
|
||||
Greater,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum SurplusMethod {
|
||||
|
@ -110,7 +142,7 @@ pub enum ExclusionMethod {
|
|||
pub fn count_init<N: Number>(mut state: &mut CountState<'_, N>, opts: &STVOptions) {
|
||||
distribute_first_preferences(&mut state);
|
||||
calculate_quota(&mut state, opts);
|
||||
elect_meeting_quota(&mut state);
|
||||
elect_meeting_quota(&mut state, opts);
|
||||
}
|
||||
|
||||
pub fn count_one_stage<N: Number>(mut state: &mut CountState<'_, N>, opts: &STVOptions) -> bool
|
||||
|
@ -129,25 +161,25 @@ where
|
|||
|
||||
// Continue exclusions
|
||||
if continue_exclusion(&mut state, &opts) {
|
||||
elect_meeting_quota(&mut state);
|
||||
elect_meeting_quota(&mut state, opts);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Distribute surpluses
|
||||
if distribute_surpluses(&mut state, &opts) {
|
||||
elect_meeting_quota(&mut state);
|
||||
elect_meeting_quota(&mut state, opts);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt bulk election
|
||||
if bulk_elect(&mut state) {
|
||||
elect_meeting_quota(&mut state);
|
||||
elect_meeting_quota(&mut state, opts);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exclude lowest hopeful
|
||||
if exclude_hopefuls(&mut state, &opts) {
|
||||
elect_meeting_quota(&mut state);
|
||||
elect_meeting_quota(&mut state, opts);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -256,11 +288,19 @@ fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
|
|||
state.quota = state.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes });
|
||||
log.push_str(format!("{:.dps$} usable votes, so the quota is ", state.quota, dps=opts.pp_decimals).as_str());
|
||||
|
||||
// TODO: Different quotas
|
||||
match opts.quota {
|
||||
QuotaType::Droop | QuotaType::DroopExact => {
|
||||
state.quota /= N::from(state.election.seats + 1);
|
||||
}
|
||||
QuotaType::Hare | QuotaType::HareExact => {
|
||||
state.quota /= N::from(state.election.seats);
|
||||
}
|
||||
}
|
||||
|
||||
// Increment to next available increment
|
||||
if let Some(dps) = opts.round_quota {
|
||||
match opts.quota {
|
||||
QuotaType::Droop | QuotaType::Hare => {
|
||||
// Increment to next available increment
|
||||
let mut factor = N::from(10);
|
||||
factor.pow_assign(dps as i32);
|
||||
state.quota *= &factor;
|
||||
|
@ -268,21 +308,37 @@ fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
|
|||
state.quota += N::one();
|
||||
state.quota /= factor;
|
||||
}
|
||||
QuotaType::DroopExact | QuotaType::HareExact => {
|
||||
// Round up to next available increment if necessary
|
||||
let mut factor = N::from(10);
|
||||
factor.pow_assign(dps as i32);
|
||||
state.quota *= &factor;
|
||||
state.quota.ceil_mut(0);
|
||||
state.quota /= factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.push_str(format!("{:.dps$}.", state.quota, dps=opts.pp_decimals).as_str());
|
||||
|
||||
state.logger.log_literal(log);
|
||||
}
|
||||
|
||||
fn meets_quota<N: Number>(quota: &N, count_card: &CountCard<N>) -> bool {
|
||||
// TODO: Different quota rules
|
||||
fn meets_quota<N: Number>(quota: &N, count_card: &CountCard<N>, opts: &STVOptions) -> bool {
|
||||
match opts.quota_criterion {
|
||||
QuotaCriterion::GreaterOrEqual => {
|
||||
return count_card.votes >= *quota;
|
||||
}
|
||||
QuotaCriterion::Greater => {
|
||||
return count_card.votes > *quota;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn elect_meeting_quota<N: Number>(state: &mut CountState<N>) {
|
||||
fn elect_meeting_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
|
||||
let quota = &state.quota; // Have to do this or else the borrow checker gets confused
|
||||
let mut cands_meeting_quota: Vec<(&&Candidate, &mut CountCard<N>)> = state.candidates.iter_mut()
|
||||
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL && meets_quota(quota, cc))
|
||||
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL && meets_quota(quota, cc, opts))
|
||||
.collect();
|
||||
|
||||
if cands_meeting_quota.len() > 0 {
|
||||
|
|
|
@ -59,6 +59,8 @@ fn aec_tas19_rational() {
|
|||
round_weights: None,
|
||||
round_votes: Some(0),
|
||||
round_quota: Some(0),
|
||||
quota: stv::QuotaType::Droop,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
surplus: stv::SurplusMethod::UIG,
|
||||
surplus_order: stv::SurplusOrder::ByOrder,
|
||||
transferable_only: false,
|
||||
|
|
|
@ -27,6 +27,8 @@ fn prsa1_rational() {
|
|||
round_weights: Some(3),
|
||||
round_votes: Some(3),
|
||||
round_quota: Some(3),
|
||||
quota: stv::QuotaType::Droop,
|
||||
quota_criterion: stv::QuotaCriterion::GreaterOrEqual,
|
||||
surplus: stv::SurplusMethod::EG,
|
||||
surplus_order: stv::SurplusOrder::ByOrder,
|
||||
transferable_only: true,
|
||||
|
|
Loading…
Reference in New Issue