From 32ab1f4f286927131275697b1c8302441919ff50 Mon Sep 17 00:00:00 2001 From: Kristofer Munsterhjelm Date: Mon, 2 Sep 2024 21:16:05 +0200 Subject: [PATCH] Add a bunch of old multiwinner methods, quick and dirty port. They're not particularly good (either slow or disappointing performance or both), but one of the Webster methods may be interesting if I can figure out how it works, because it's not *that* bad. --- CMakeLists.txt | 1 + src/main/multiwinner.cc | 45 +- src/multiwinner/rusty/README.md | 4 + src/multiwinner/rusty/aux/assignment.h | 196 +++++ src/multiwinner/rusty/aux/assignment_double.h | 225 ++++++ src/multiwinner/rusty/aux/cassignment.h | 232 ++++++ src/multiwinner/rusty/aux/dsc.cc | 258 +++++++ src/multiwinner/rusty/aux/dsc.h | 31 + src/multiwinner/rusty/aux/greedy_cluster.h | 138 ++++ src/multiwinner/rusty/aux/qballot.h | 6 + src/multiwinner/rusty/choose_set.h | 64 ++ src/multiwinner/rusty/fc_kemeny.h | 417 ++++++++++ src/multiwinner/rusty/mono_webst_640.h | 529 +++++++++++++ src/multiwinner/rusty/mono_webst_c37.h | 725 ++++++++++++++++++ src/multiwinner/rusty/mono_webst_f03.h | 576 ++++++++++++++ src/multiwinner/rusty/mw_kemeny2_34e.h | 377 +++++++++ src/multiwinner/rusty/mw_kemeny_db0.h | 373 +++++++++ src/multiwinner/rusty/set_functs.h | 188 +++++ src/multiwinner/rusty/setwise/coalition.h | 164 ++++ src/multiwinner/set_webster.h | 4 + 20 files changed, 4547 insertions(+), 6 deletions(-) create mode 100644 src/multiwinner/rusty/README.md create mode 100644 src/multiwinner/rusty/aux/assignment.h create mode 100644 src/multiwinner/rusty/aux/assignment_double.h create mode 100644 src/multiwinner/rusty/aux/cassignment.h create mode 100644 src/multiwinner/rusty/aux/dsc.cc create mode 100644 src/multiwinner/rusty/aux/dsc.h create mode 100644 src/multiwinner/rusty/aux/greedy_cluster.h create mode 100644 src/multiwinner/rusty/aux/qballot.h create mode 100644 src/multiwinner/rusty/choose_set.h create mode 100644 src/multiwinner/rusty/fc_kemeny.h create mode 100644 src/multiwinner/rusty/mono_webst_640.h create mode 100644 src/multiwinner/rusty/mono_webst_c37.h create mode 100644 src/multiwinner/rusty/mono_webst_f03.h create mode 100644 src/multiwinner/rusty/mw_kemeny2_34e.h create mode 100644 src/multiwinner/rusty/mw_kemeny_db0.h create mode 100644 src/multiwinner/rusty/set_functs.h create mode 100644 src/multiwinner/rusty/setwise/coalition.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ecdeee4..206a560 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,7 @@ add_library(qe_multiwinner_methods src/multiwinner/qrange_stv.cc src/multiwinner/randballots.cc src/multiwinner/range_stv.cc + src/multiwinner/rusty/aux/dsc.cc src/multiwinner/shuntsstv.cc src/multiwinner/stv.cc src/stats/multiwinner/mwstats.cc) diff --git a/src/main/multiwinner.cc b/src/main/multiwinner.cc index 5bce054..79f5e89 100644 --- a/src/main/multiwinner.cc +++ b/src/main/multiwinner.cc @@ -24,6 +24,13 @@ #include "multiwinner/exhaustive/lpv.h" #include "multiwinner/exhaustive/psi.h" +#include "multiwinner/rusty/fc_kemeny.h" +#include "multiwinner/rusty/mono_webst_640.h" +#include "multiwinner/rusty/mono_webst_c37.h" +#include "multiwinner/rusty/mono_webst_f03.h" +#include "multiwinner/rusty/mw_kemeny2_34e.h" +#include "multiwinner/rusty/mw_kemeny_db0.h" + #include "multiwinner/auction.h" #include "multiwinner/compat_qbuck.h" #include "multiwinner/dhwl.h" @@ -653,6 +660,36 @@ std::vector get_multiwinner_methods() { e_methods.push_back(multiwinner_stats(std::make_shared( log_penalty_eval(1000)))); + // Test some rusty methods. + + // Nope + /*e_methods.push_back(multiwinner_stats( + std::make_shared(true))); + e_methods.push_back(multiwinner_stats( + std::make_shared(false)));*/ + + // Nope! (Times out and proportionality < 0.) + /*e_methods.push_back(multiwinner_stats( + std::make_shared()));*/ + + // Too slow, has worse proportionality than birational with + // about the same utility VSE. + /*e_methods.push_back(multiwinner_stats( + std::make_shared()));*/ + + // Slow but okay performance. May be something to investigate further, + // later, since it's much better than my recent attempt at + // reimplementing Set Webster. + + /* + e_methods.push_back(multiwinner_stats( + std::make_shared(MM_INVERTED, false, false))); + e_methods.push_back(multiwinner_stats( + std::make_shared(MM_PLUSHALF, false, false))); + e_methods.push_back(multiwinner_stats( + std::make_shared(false, MMC_PLUSONE, true, false))); + */ + // QPQ mass test // Disabled for now because QPQ needs a more extensive rework. /* @@ -678,11 +715,7 @@ std::vector get_multiwinner_methods() { } e_methods.push_back(multiwinner_stats(new QPQ(rc, true))); - } - - // QPQ hack - multiwinner_stats qpqmetam("Best of QPQ(Meta, multiround)"), qpqmetas( - "Best of QPQ(Meta, sequential)");*/ + }*/ return e_methods; } @@ -861,4 +894,4 @@ int main(int argc, char * * argv) { method_utility[e_methods[counter].get_name()].get()) << endl; } } -} +} \ No newline at end of file diff --git a/src/multiwinner/rusty/README.md b/src/multiwinner/rusty/README.md new file mode 100644 index 0000000..c600a93 --- /dev/null +++ b/src/multiwinner/rusty/README.md @@ -0,0 +1,4 @@ +This contains code directly copied from earlier simulators. The code is so +complex that I'm not going to try to clean it up. It's just for testing, so +that I can see which methods perform well, if any, and then focus my attention +on recreating them. diff --git a/src/multiwinner/rusty/aux/assignment.h b/src/multiwinner/rusty/aux/assignment.h new file mode 100644 index 0000000..eeee007 --- /dev/null +++ b/src/multiwinner/rusty/aux/assignment.h @@ -0,0 +1,196 @@ +#include +#include +#include +#include + +#include "tools/tools.h" +#include "common/ballots.h" + +#include "qballot.h" + +using namespace std; + +// Solve the forced clustering problem for a single instance. + +// BLUESKY: Points system MIP solver. +// Also should check overdetermined case, where clusters > voters + +class assignment { + + private: + glp_smcp params; + glp_prob * lp; + + double num_voters; + int num_ballots, num_clusters; + + void initialize(double n, int u, int k, const + vector & voters); + + public: + assignment(double n, int u, int k, const vector & + voters); + + ~assignment(); + + void set_constraint(int ballot_num, int cluster_num, + double value) const; + + double calc_minimum() const; + int get_status() const; + bool success() const; + + double get_unknown(int index) const; +}; + +// n: number of voters +// u: number of unique ballots +// k: number of clusters +void assignment::initialize(double n, int u, int k, + const vector & voters) { + assert(n > 0); + assert(u <= n && u > 0); + assert(k > 0); + + if (lp != NULL) { + glp_delete_prob(lp); + lp = NULL; + } + + // Set parameters so we don't get a lot of messages + glp_init_smcp(¶ms); + params.msg_lev = GLP_MSG_OFF; + + // Create problem. + lp = glp_create_prob(); + assert(lp != NULL); + glp_set_prob_name(lp, "clustering"); + glp_set_obj_dir(lp, GLP_MIN); // Minimum distance + + // Rows = alias variables (p = x1 + x3.. 0 < p < 100) + // Columns = terms (x1...xn) + glp_add_rows(lp, u + k); + + int counter = 0; + + // Set power constraints: sum of allocated ballot weights + // must equal number of voters who voted this way. + // Might be more resistant to vote management if we do + // 0 <= a <= U_0 (? seems to have no effect) + string name; + vector::const_iterator pos = voters.begin(); + for (counter = 0; counter < u; ++counter) { + assert(pos != voters.end()); + name = "pow_" + itos(counter); + glp_set_row_name(lp, counter + 1, name.c_str()); + glp_set_row_bnds(lp, counter + 1, GLP_FX, pos->strength, + pos->strength); + ++pos; + } + + // Set proportionality constraints: each cluster must have + // just as many allocated to it, so that no cluster is favored. + double fair_share = n/(double)k; + + for (counter = 0; counter < k; ++counter) { + name = "prop_" + itos(counter); + glp_set_row_name(lp, counter + u + 1, name.c_str()); + glp_set_row_bnds(lp, counter + u + 1, GLP_FX, fair_share, + fair_share); + } + + // Add columns (variables we want to find out) + glp_add_cols(lp, u * k); + + // Set "must be >= 0" constraints + for (counter = 0; counter < u * k; ++counter) { + name = "x_" + itos(counter); + glp_set_col_name(lp, counter + 1, name.c_str()); + glp_set_col_bnds(lp, counter + 1, GLP_LO, 0, 0); + } + + // Row (constraint #) indices in ia, column indices (var #) in ja, + // multipliers (always 1) in ar. + + //int ia[u*k], ja[u*k]; + //double ar[u*k]; + + int ia[u*k*2], ja[u*k*2]; + double ar[u*k*2]; + + // Now actually link the constraints to the right variables. + // First the ballot power constraints (includes a single row)... + int sec, lincount = 1, constraint_num = 1; + + for (counter = 0; counter < u * k; counter += k) { + for (sec = counter; sec < counter + k; ++sec) { + ia[lincount] = constraint_num; + ja[lincount] = sec + 1; + ar[lincount] = 1; // no multiples! + ++lincount; + } + ++constraint_num; + } + + // Then the proportionality constraints (includes a single column)... + for (counter = 0; counter < k; ++counter) { + for (sec = counter; sec < u * k; sec += k) { + ia[lincount] = constraint_num; + ja[lincount] = sec + 1; + ar[lincount] = 1; + ++lincount; + } + ++constraint_num; + } + + glp_load_matrix(lp, lincount-1, ia, ja, ar); + + // And we're all set to receive the distances. + return; +} + +assignment::assignment(double n, int u, int k, + const vector & voters) { + + num_voters = n; + num_clusters = k; + num_ballots = u; + lp = NULL; + + initialize(n, u, k, voters); +} + +assignment::~assignment() { + if (lp != NULL) { + glp_delete_prob(lp); + } +} + + +void assignment::set_constraint(int ballot_num, int cluster_num, + double value) const { + assert(ballot_num < num_ballots); + assert(cluster_num < num_clusters); + + glp_set_obj_coef(lp, (ballot_num * num_clusters) + cluster_num + 1, + value); +} + +double assignment::calc_minimum() const { + + glp_simplex(lp, ¶ms); + return (glp_get_obj_val(lp)); +} + +int assignment::get_status() const { + return (glp_get_status(lp)); +} + +bool assignment::success() const { + return (get_status() == GLP_OPT); +} + +double assignment::get_unknown(int index) const { + + return (glp_get_col_prim(lp, index+1)); +} \ No newline at end of file diff --git a/src/multiwinner/rusty/aux/assignment_double.h b/src/multiwinner/rusty/aux/assignment_double.h new file mode 100644 index 0000000..dbb07fc --- /dev/null +++ b/src/multiwinner/rusty/aux/assignment_double.h @@ -0,0 +1,225 @@ + +#include +#include +#include +#include + +#include "tools/tools.h" + +using namespace std; + +// Solve the forced clustering problem for a single instance. + +// BLUESKY: Points system MIP solver. +// Also should check overdetermined case, where clusters > voters + +// TODO: Add constraint < previous record (to quickly determine if it's +// infeasible) + +class assignment { + + private: + glp_smcp params; + glp_prob * lp; + + double num_voters; + int num_ballots, num_clusters; + + bool use_record; + int record_constraint; + + void initialize(double n, int u, int k, const vector & + voters); + + public: + assignment(double n, int u, int k, const vector & + voters, bool using_record); + + assignment(double n, int u, int k, const vector & + voters) : assignment(n, u, k, voters, false) {} + + ~assignment(); + + void set_constraint(int ballot_num, int cluster_num, + double value) const; + + double calc_minimum() const; + int get_status() const; + bool success() const; + + double get_unknown(int index) const; +}; + +// n: number of voters +// u: number of unique ballots +// k: number of clusters +void assignment::initialize(double n, int u, int k, + const vector & voters) { + assert(n > 0); + assert(u <= n && u > 0); + assert(k > 0); + + if (lp != NULL) { + glp_delete_prob(lp); + lp = NULL; + } + + // Set parameters so we don't get a lot of messages + glp_init_smcp(¶ms); + params.msg_lev = GLP_MSG_OFF; + + // Create problem. + lp = glp_create_prob(); + assert(lp != NULL); + glp_set_prob_name(lp, "clustering"); + glp_set_obj_dir(lp, GLP_MIN); // Minimum distance + + // Rows = alias variables (p = x1 + x3.. 0 < p < 100) + // Columns = terms (x1...xn) + if (use_record) { + glp_add_rows(lp, u + k + 1); + record_constraint = u + k + 1; + } else { + glp_add_rows(lp, u + k); + } + + int counter = 0; + + // Set power constraints: sum of allocated ballot weights + // must equal number of voters who voted this way. + // Might be more resistant to vote management if we do + // 0 <= a <= U_0 (?) + string name; + vector::const_iterator pos = voters.begin(); + for (counter = 0; counter < u; ++counter) { + assert(pos != voters.end()); + name = "pow_" + itos(counter); + glp_set_row_name(lp, counter + 1, name.c_str()); + glp_set_row_bnds(lp, counter + 1, GLP_FX, *pos, *pos); + ++pos; + } + + // Set proportionality constraints: each cluster must have + // just as many allocated to it, so that no cluster is favored. + double fair_share = n/(double)k; + + for (counter = 0; counter < k; ++counter) { + name = "prop_" + itos(counter); + glp_set_row_name(lp, counter + u + 1, name.c_str()); + glp_set_row_bnds(lp, counter + u + 1, GLP_FX, fair_share, + fair_share); + } + + // If desired, prepare the record constraint, used to bail out + // early if we know we can't exceed a past record (alpha-beta-esque?) + // Leave it free for now. + if (use_record) { + glp_set_row_name(lp, record_constraint, "record"); + glp_set_row_bnds(lp, record_constraint, GLP_FR, 0, 0); + } + + // Add columns (variables we want to find out) + glp_add_cols(lp, u * k); + + // Set "must be >= 0" constraints + for (counter = 0; counter < u * k; ++counter) { + name = "x_" + itos(counter); + glp_set_col_name(lp, counter + 1, name.c_str()); + glp_set_col_bnds(lp, counter + 1, GLP_LO, 0, 0); + } + + // Row (constraint #) indices in ia, column indices (var #) in ja, + // multipliers (always 1) in ar. + + //int ia[u*k], ja[u*k]; + //double ar[u*k]; + + int ia[u*k*2], ja[u*k*2]; + double ar[u*k*2]; + + // Now actually link the constraints to the right variables. + // First the ballot power constraints (includes a single row)... + int sec, lincount = 1, constraint_num = 1; + + for (counter = 0; counter < u * k; counter += k) { + for (sec = counter; sec < counter + k; ++sec) { + ia[lincount] = constraint_num; + ja[lincount] = sec + 1; + ar[lincount] = 1; // no multiples! + ++lincount; + } + ++constraint_num; + } + + // Then the proportionality constraints (includes a single column)... + for (counter = 0; counter < k; ++counter) { + for (sec = counter; sec < u * k; sec += k) { + ia[lincount] = constraint_num; + ja[lincount] = sec + 1; + ar[lincount] = 1; + ++lincount; + } + ++constraint_num; + } + + // If so desired, construct a record constraint to let the search + // abort early when we do have a record already. + + glp_load_matrix(lp, lincount-1, ia, ja, ar); + + // And we're all set to receive the distances. + return; +} + +assignment::assignment(double n, int u, int k, + const vector & voters, + bool using_record) { + + num_voters = n; + num_clusters = k; + num_ballots = u; + lp = NULL; + + use_record = using_record; + + initialize(n, u, k, voters); +} + +assignment::~assignment() { + if (lp != NULL) { + glp_delete_prob(lp); + } +} + + +void assignment::set_constraint(int ballot_num, int cluster_num, + double value) const { + assert(ballot_num < num_ballots); + assert(cluster_num < num_clusters); + + glp_set_obj_coef(lp, (ballot_num * num_clusters) + cluster_num + 1, + value); + + if (use_record) { + // oops. Needs a redesign! + } +} + +double assignment::calc_minimum() const { + + glp_simplex(lp, ¶ms); + return (glp_get_obj_val(lp)); +} + +int assignment::get_status() const { + return (glp_get_status(lp)); +} + +bool assignment::success() const { + return (get_status() == GLP_OPT); +} + +double assignment::get_unknown(int index) const { + + return (glp_get_col_prim(lp, index+1)); +} diff --git a/src/multiwinner/rusty/aux/cassignment.h b/src/multiwinner/rusty/aux/cassignment.h new file mode 100644 index 0000000..f4c33ea --- /dev/null +++ b/src/multiwinner/rusty/aux/cassignment.h @@ -0,0 +1,232 @@ +// KLUDGE to get both CFC-Range and FC-Kemeny. + +#pragma once + +#include +#include +#include +#include + +#include "tools/tools.h" +#include "common/ballots.h" + +using namespace std; + +// Solve the forced clustering problem for a single instance. + +// BLUESKY: Points system MIP solver. +// Also should check overdetermined case, where clusters > voters + +// Remember: must remove the other cassignment class or there will be a clash. + +class cassignment { + + private: + bool inited; + + glp_smcp params; + glp_prob * lp; + + double num_voters; + int num_ballots, num_clusters; + + public: + void initialize(double n, int u, int k, + const list & voters, + bool maximize); + + cassignment(double n, int u, int k, const list & + voters, bool maximize); + cassignment(); + + ~cassignment(); + + void set_constraint(int ballot_num, int cluster_num, + double value) const; + + double calc_optimum(bool debug) const; + double calc_optimum() const; + int get_status() const; + bool success() const; + + double get_unknown(int index) const; +}; + +// n: number of voters +// u: number of unique ballots +// k: number of clusters +void cassignment::initialize(double n, int u, int k, + const list & voters, bool maximize) { + assert(n > 0); + assert(u <= n && u > 0); + assert(k > 0); + + num_voters = n; + num_clusters = k; + num_ballots = u; + + if (lp != NULL) { + glp_delete_prob(lp); + lp = NULL; + } + + // Set parameters so we don't get a lot of messages + glp_init_smcp(¶ms); + params.msg_lev = GLP_MSG_OFF; + + // Create problem. + lp = glp_create_prob(); + assert(lp != NULL); + glp_set_prob_name(lp, "clustering"); + // If we're acting to maximize a score (a beneficial thing), such as + // with CFC-Range, then we should set this to GLP_MAX. If we're acting + // to minimize an error measure, such as with CFC-Kemeny, then we should + // set this to GLP_MIN. + if (maximize) { + glp_set_obj_dir(lp, GLP_MAX); // Maximum score + } else { + glp_set_obj_dir(lp, GLP_MIN); // Minimum distance + } + + // Rows = alias variables (p = x1 + x3.. 0 < p < 100) + // Columns = terms (x1...xn) + glp_add_rows(lp, u + k); + + int counter = 0; + + // Set power constraints: sum of allocated ballot weights + // must equal number of voters who voted this way. + // Might be more resistant to vote management if we do + // 0 <= a <= U_0 (? seems to have no effect) + string name; + list::const_iterator pos = voters.begin(); + for (counter = 0; counter < u; ++counter) { + assert(pos != voters.end()); + name = "pow_" + itos(counter); + glp_set_row_name(lp, counter + 1, name.c_str()); + glp_set_row_bnds(lp, counter + 1, GLP_FX, pos->get_weight(), + pos->get_weight()); + ++pos; + } + + // Set proportionality constraints: each cluster must have + // just as many allocated to it, so that no cluster is favored. + double fair_share = (n/(double)k); + + for (counter = 0; counter < k; ++counter) { + name = "prop_" + itos(counter); + glp_set_row_name(lp, counter + u + 1, name.c_str()); + glp_set_row_bnds(lp, counter + u + 1, GLP_FX, fair_share, + fair_share); + } + + // Add columns (variables we want to find out) + glp_add_cols(lp, u * k); + + // Set "must be >= 0" constraints + for (counter = 0; counter < u * k; ++counter) { + name = "x_" + itos(counter); + glp_set_col_name(lp, counter + 1, name.c_str()); + glp_set_col_bnds(lp, counter + 1, GLP_LO, 0, 0); + } + + // Row (constraint #) indices in ia, column indices (var #) in ja, + // multipliers (always 1) in ar. + + //int ia[u*k], ja[u*k]; + //double ar[u*k]; + + int ia[u*k*2], ja[u*k*2]; + double ar[u*k*2]; + + // Now actually link the constraints to the right variables. + // First the ballot power constraints (includes a single row)... + int sec, lincount = 1, constraint_num = 1; + + for (counter = 0; counter < u * k; counter += k) { + for (sec = counter; sec < counter + k; ++sec) { + ia[lincount] = constraint_num; + ja[lincount] = sec + 1; + ar[lincount] = 1; // no multiples! + ++lincount; + } + ++constraint_num; + } + + // Then the proportionality constraints (includes a single column)... + for (counter = 0; counter < k; ++counter) { + for (sec = counter; sec < u * k; sec += k) { + ia[lincount] = constraint_num; + ja[lincount] = sec + 1; + ar[lincount] = 1; + ++lincount; + } + ++constraint_num; + } + + glp_load_matrix(lp, lincount-1, ia, ja, ar); + + // And we're all set to receive the distances. + inited = true; + return; +} + +cassignment::cassignment(double n, int u, int k, + const list & voters, bool maximize) { + lp = NULL; + + initialize(n, u, k, voters, maximize); + inited = true; +} + +cassignment::cassignment() { + inited = false; + lp = NULL; +} + +cassignment::~cassignment() { + if (lp != NULL) { + glp_delete_prob(lp); + } +} + + +void cassignment::set_constraint(int ballot_num, int cluster_num, + double value) const { + assert(ballot_num < num_ballots); + assert(cluster_num < num_clusters); + assert(inited); + + glp_set_obj_coef(lp, (ballot_num * num_clusters) + cluster_num + 1, + value); +} + +double cassignment::calc_optimum(bool debug) const { + + assert(inited); + + glp_simplex(lp, ¶ms); + if (debug) { + glp_write_lp(lp, NULL, "debug.lp"); + } + return (glp_get_obj_val(lp)); +} + +double cassignment::calc_optimum() const { + return (calc_optimum(false)); +} + +int cassignment::get_status() const { + assert(inited); + return (glp_get_status(lp)); +} + +bool cassignment::success() const { + return (get_status() == GLP_OPT); +} + +double cassignment::get_unknown(int index) const { + assert(inited); + + return (glp_get_col_prim(lp, index+1)); +} \ No newline at end of file diff --git a/src/multiwinner/rusty/aux/dsc.cc b/src/multiwinner/rusty/aux/dsc.cc new file mode 100644 index 0000000..b124d3d --- /dev/null +++ b/src/multiwinner/rusty/aux/dsc.cc @@ -0,0 +1,258 @@ +#include "dsc.h" + +#include "tools/tools.h" + +// DSC stuff, copied from old code +// I have a better implementation elsewhere, but let's focus on reproducing +// the old code first. + +std::set get_candidates(ordering::const_iterator begin, + ordering::const_iterator ending, bool debug) { + + std::set toRet; + + for (ordering::const_iterator pos = begin; pos != ending; ++pos) { + toRet.insert(pos->get_candidate_num()); + } + + if (debug) { + std::cout << "{ "; + copy(toRet.begin(), toRet.end(), + std::ostream_iterator(std::cout, " ")); + std::cout << "}" << std::endl; + } + + return toRet; +} + +std::multimap > get_dsc( + const election_t & input) { + + // Descending solid coalitions: Treat a vote for A > B > C > D + // as one for {A, B, C} (which is the same as {B, C, A} and {C, B, A}) + // > D. Doing so, increment counts for each possible set that the voter + // is solidly committed to, where solid commitment means that the + // voter strictly ranks all the candidates above those outside the + // set. + + // Then go down the sets (in order where the one with the largest count, + // which is to say the most solidly supporting voters, go first), + // doing set intersections with the "hopeful candidates" set that + // starts off with all candidates. + + // Optimization: Don't increment the set if some excluded member is + // within start and end. This would ensure that no more than a certain + // amount of memory is used in each round. + + // Assumes complete ballots. We may have to put this into boolean + // so that it'll abort if the ballots aren't complete. + + // I haven't found a way to get a social ordering from this yet, + // especially in the case of ties. In the worst case, branches may + // be required to find the winners. + + std::map, double> set_counter; + + bool debug = false; + + for (int size = input.begin()->contents.size()-1; size > 0; --size) { + for (election_t::const_iterator ballot = input.begin(); + ballot != input.end(); ++ballot) { + ordering::const_iterator beginning, ending, + just_inside; + + beginning = ballot->contents.begin(); + ending = beginning; + + for (int counter = 0; counter < size; ++counter) { + ++ending; + } + + // Check that it's solidly committed - i.e that the + // next set member isn't equally ranked with the + // current one. If it's not... forget it. + + just_inside = ending; + --just_inside; + + bool solidly_committed = false; + + if (ending == ballot->contents.end()) { + solidly_committed = true; + } else solidly_committed = just_inside->get_score() > + ending->get_score(); + + if (solidly_committed) { + set_counter[get_candidates(beginning, ending, + false)] += ballot->get_weight(); + } + } + } + + // Debug + if (debug) { + for (std::map, double>::const_iterator p = + set_counter. begin(); p != set_counter.end(); + ++p) { + std::cout << p->second << "\t{"; + copy(p->first.begin(), p->first.end(), + std::ostream_iterator(std::cout, " ")); + std::cout << "}" << std::endl; + } + } + + std::multimap > out_coalitions; + + for (auto pos = set_counter.begin(); pos != set_counter.end(); ++pos) { + out_coalitions.insert({pos->second, pos->first}); + } + + return out_coalitions; +} + +// No regard for ties. The real way to do this is to branch on encountering +// a tie, then say all the candidates that the various (recursive) functions +// return as winners are tied for first place. +void simple_dsc(const election_t input, size_t num_candidates) { + + std::multimap > ranked_sets = get_dsc( + input); + + std::set hopefuls; + + for (size_t counter = 0; counter < num_candidates; ++counter) { + hopefuls.insert(counter); + } + + bool debug = false; + + for (std::map >::const_reverse_iterator + p = ranked_sets.rbegin(); p != ranked_sets.rend() && + hopefuls.size() > 1; ++p) { + if (debug) { + std::cout << "Now checking " << p->first << "\t{"; + copy(p->second.begin(), p->second.end(), + std::ostream_iterator(std::cout, " ")); + std::cout << "}" << " hopefuls: {"; + copy(hopefuls.begin(), hopefuls.end(), + std::ostream_iterator(std::cout, " ")); + std::cout << "}"; + } + + // Check if it's a set that would exclude all remaining + // hopefuls. If it is, ignore it, otherwise do a set + // intersection. + + if (!includes(p->second.begin(), p->second.end(), + hopefuls.begin(), hopefuls.end())) { + if (debug) { + std::cout << "\tIntersecting."; + } + + std::set intersect; + std::set_intersection(hopefuls.begin(), hopefuls.end(), + p->second.begin(), p->second.end(), + std::inserter(intersect, intersect.begin())); + + if (!intersect.empty()) { + hopefuls = intersect; + } + } else { + if (debug) { + std::cout << "\tIgnoring."; + } + } + if (debug) { + std::cout << std::endl; + } + } +} + +template std::multimap multi_invert( + const std::map input) { + + std::multimap inverted; + + for (typename std::map::const_iterator pos = input.begin(); + pos != input.end(); ++pos) { + inverted.insert(std::pair(pos->second, pos->first)); + } + + return (inverted); +} + + + +std::multimap > get_solid_coalitions( + const election_t & input, int num_candidates, + int tiebreaker) { + + // For each ballot_group, + // For each candidate, + // If the last one had a different score, + // Increase the map from current set by the ballot + // group's support. + // Add this one to the set + // Increase the map from current set by the ballot group's support. + + std::map, double > forwards; + + // Since it's very difficult to break ties in DAC/DSC, we hack it by + // adding a very small amount of votes to the first voter after + // "tiebreaker" number of voters. If this is -1, then that feature + // isn't added. TODO: Find a way of making sure this never alters + // a non-tie outcome. + double tie_count = 0; + + double numvoters_pow = 0; + + for (election_t::const_iterator outer_pos = input.begin(); + outer_pos != input.end(); ++outer_pos) { + std::set coalition; + double last_score = INFINITY; + + bool is_tiebreaker = false; + if (tiebreaker != -1 && tie_count != -1) { + if (tie_count >= tiebreaker) { + is_tiebreaker = true; + tie_count = -1; + } else { + tie_count += outer_pos->get_weight(); + } + } + + numvoters_pow += outer_pos->get_weight(); + + for (ordering::const_iterator inner_pos = outer_pos->contents. + begin(); inner_pos != outer_pos->contents.end(); + ++inner_pos) { + if (inner_pos->get_score() != last_score && + !coalition.empty()) { + forwards[coalition] += outer_pos->get_weight(); + // TODO: Proper epsilon here. + if (is_tiebreaker) forwards[coalition] += + 1e-5; + } + + coalition.insert(inner_pos->get_candidate_num()); + last_score = inner_pos->get_score(); + } + + if (!coalition.empty()) { + forwards[coalition] += outer_pos->get_weight(); + // Do right next time + if (is_tiebreaker) { + forwards[coalition] *= (1 + 1e-5); + } + } + } + + // Construct the set of all candidates. (HACK HACK) + std::set all; + for (int counter = 0; counter < num_candidates; ++counter) { + all.insert(counter); + } + forwards[all] = numvoters_pow; + + return (multi_invert(forwards)); +} diff --git a/src/multiwinner/rusty/aux/dsc.h b/src/multiwinner/rusty/aux/dsc.h new file mode 100644 index 0000000..f9919f2 --- /dev/null +++ b/src/multiwinner/rusty/aux/dsc.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include + +#include "common/ballots.h" +#include "tools/tools.h" + +// DSC stuff, copied from old code +// I have a better implementation elsewhere, but let's focus on reproducing +// the old code first. + +std::set get_candidates(ordering::const_iterator begin, + ordering::const_iterator ending, bool debug); + +std::multimap > get_dsc( + const election_t & input); + +std::multimap > get_solid_coalitions( + const election_t & input); + +// No regard for ties. The real way to do this is to branch on encountering +// a tie, then say all the candidates that the various (recursive) functions +// return as winners are tied for first place. +void simple_dsc(const election_t input, int num_candidates); + +std::multimap > get_solid_coalitions( + const election_t & input, int num_candidates, + int tiebreaker); \ No newline at end of file diff --git a/src/multiwinner/rusty/aux/greedy_cluster.h b/src/multiwinner/rusty/aux/greedy_cluster.h new file mode 100644 index 0000000..a4144b0 --- /dev/null +++ b/src/multiwinner/rusty/aux/greedy_cluster.h @@ -0,0 +1,138 @@ +#pragma once + +#include +#include +#include +#include +#include + +using namespace std; + +// Determine the best "equal size" clustering. This is a hack. It's also a +// greedy algorithm, and if this thing is NPC, that means we won't be optimal; +// but let's see how bad it is! + +class es_sorter { + + public: + vector full; + + int get_minimum(const vector::const_iterator & begin, + const vector::const_iterator & end) const; + + bool operator()(const vector >::const_iterator & a, + const vector >::const_iterator & b) + const; +}; + + +int es_sorter::get_minimum(const vector::const_iterator & begin, + const vector::const_iterator & end) const { + int record = *begin; + int count = 0; + + for (vector::const_iterator pos = begin; pos != end; ++pos) { + if (*pos < record && !full[count]) { + record = *pos; + } + ++count; + } + + return (record); +} + +bool es_sorter::operator()(const vector >::const_iterator & a, + const vector >::const_iterator & b) const { + + return (get_minimum(a->begin(), a->end()) < + get_minimum(b->begin(), b->end())); +} + +int get_clustering_error(const vector > & penalties, + int num_clusters) { + + //cout << "---" << endl; + + // The discrepancy between lower and upper can be arbitrarily + // assigned. (Oder? Spread as evenly as possible, perhaps?) + + int lower = floor(penalties.size()/(double)num_clusters); + //int upper = ceil(penalties.size()/(double)num_clusters); + int delta = penalties.size() - lower * + num_clusters; // these can be assigned anywhere. + + vector assigned(num_clusters, 0); + vector maxima(num_clusters, lower); + vector cands_assigned(penalties.size(), false); + es_sorter sorting; + sorting.full = vector(num_clusters, false); + int error = 0; + + // Sort by reference, by minimal error. + // Maybe do faster with ints. Later. + vector >::const_iterator> queue(penalties.size()); + + size_t counter = 0; + + for (counter = 0; counter < penalties.size(); ++counter) { + queue[counter] = penalties.begin() + counter; + } + + sort(queue.begin(), queue.end(), sorting); + + for (counter = penalties.size() - 1; counter >= 0; --counter) { + // If any are now above the threshold, mark them as full + // and redo sort based on this. + + int sec = 0; + bool altered = false; + for (sec = 0; sec < num_clusters; ++sec) { + if (sorting.full[sec]) { + continue; + } + // cout << "Maxima for " << sec << " is " << maxima[sec] << endl; + if (assigned[sec] >= maxima[sec] && delta <= 0) { + // cout << "!Set " << sec << " to done with delta " << delta << endl; + sorting.full[sec] = true; + altered = true; + } + } + + // Resort based only on those which are still free + if (altered) { + sort(queue.begin(), queue.begin() + counter, sorting); + } + + // Determine which cluster has the minimum. + // O(k), so sloow. Fix later! + int record = 0; + int recordholder = -1; + sec = 0; + for (vector::const_iterator pos = queue[counter]->begin(); + pos != queue[counter]->end(); ++pos) { + //cout << "Trying " << sec << endl; + // Warning: ties + if ((*pos < record || recordholder == -1) && !sorting.full[sec]) { + record = *pos; + recordholder = sec; + } + ++sec; + } + + assert(recordholder != -1); + + // Assign. (No need to do the actual assignment) + error += record; + assigned[recordholder]++; + + // If we would otherwise be full, withdraw from the delta + // account. + if (assigned[recordholder] > maxima[recordholder]) { + maxima[recordholder]++; + --delta; + assert(delta >= 0); + } + } + + return (error); +} \ No newline at end of file diff --git a/src/multiwinner/rusty/aux/qballot.h b/src/multiwinner/rusty/aux/qballot.h new file mode 100644 index 0000000..d748912 --- /dev/null +++ b/src/multiwinner/rusty/aux/qballot.h @@ -0,0 +1,6 @@ +#pragma once + +struct q_ballot { + int strength; + vector rank; +}; \ No newline at end of file diff --git a/src/multiwinner/rusty/choose_set.h b/src/multiwinner/rusty/choose_set.h new file mode 100644 index 0000000..546cbe4 --- /dev/null +++ b/src/multiwinner/rusty/choose_set.h @@ -0,0 +1,64 @@ +#include +#include +#include +#include + +using namespace std; + +void choose_set(int numcands, int curcand, int how_many, + int cur_chosen, std::set & current, + std::set > & choose_out) { + + cout << "[c: " << curcand << " of " << numcands << "] [h: " << cur_chosen + << " of " << how_many << "]" << endl; + + // Some sanity checks. Pick k out of n with k > n doesn't make sense. + if (curcand == 0) { + assert(how_many <= numcands); + } + + // If cur_chosen = how_many, insert set into output. + // If numcands = curcand, return. Else... + + // Add a candidate to the set. As we do this in ascending order, + // we know this candidate has to be at the end. Recurse with + // cur_chosen +1, curcand+1. Then remove and recurse with + // cur_chosen, curcand + 1. + + // Recursion, dudes. You dig it? + + // Perhaps if (how_many - cur_chosen > numcands - curcand) return. + // Something like that. Experiment another time. + if (cur_chosen == how_many) { + choose_out.insert(current); + } + + // If we have exhausted all candidates or there's no way of making + // up the difference, bail outta here. + if (how_many - cur_chosen > numcands - curcand || numcands == curcand) { + cout << "Breaking." << endl; + return; + } + + // Hint that the next candidate will be added at the end (because it + // will). Removed. + current.insert(curcand); + choose_set(numcands, curcand+1, how_many, cur_chosen+1, current, + choose_out); + // Remove the one we added. + current.erase(--current.end()); + choose_set(numcands, curcand+1, how_many, cur_chosen, current, + choose_out); +} + +main() { + std::set foo; + std::set > choose_set_out; + + int numcands = 4; + int how_many = 2; + + choose_set(numcands, 0, how_many, 0, foo, choose_set_out); + + cout << *(choose_set_out.rbegin()->begin()) << endl; +} diff --git a/src/multiwinner/rusty/fc_kemeny.h b/src/multiwinner/rusty/fc_kemeny.h new file mode 100644 index 0000000..89c3961 --- /dev/null +++ b/src/multiwinner/rusty/fc_kemeny.h @@ -0,0 +1,417 @@ +#pragma once + +// Not centroid. Geometric median. + +// Brute force solution to the multiwinner Kemeny idea + +// Multiwinner Kemeny: find the ranks that constitute the centroid ranks, +// subject to that each candidate can be mentioned in each rank position +// (column) no more than once. + +// Ranks are struct vectors of ints, easier that way. + +#include "aux/greedy_cluster.h" +#include "aux/assignment.h" +#include "multiwinner/methods.h" +#include "tools/ballot_tools.h" + +#include "aux/qballot.h" + +#include +#include +#include +#include + +using namespace std; + +class fc_kemeny : public multiwinner_method { + + private: + // QnD + std::vector > tournament_matrix(const + std::vector & rank, int maxcand) const; + // Kemeny distance + int p_distance(const std::vector > & cmat_a, + const std::vector > & cmat_b, + int maxcand) const; + int p_distance(const std::vector & a, + const std::vector & b, int maxcand) const; + + int tiebreak(const std::vector > & a, int maxcand) const; + void permutation(int input, std::vector & rank) const; + void print_rank(const std::vector & rank) const; + pair distance_to_closest(const std::vector & + our_rank, const std::vector > & + centroids, int numcands) const; + int get_continuous_score(const std::vector & ballots, + const std::vector > & centroids, + int numcands, assignment & solver, + bool brute_calc) const; + int get_score(const std::vector & ballots, + const std::vector > & centroids, + int numcands) const; + bool verify(const std::vector > & centroids, + int numcands) const; + q_ballot build_ballot(int strength, string order) const; + int factorial(int n) const; + void recurse_ranking(const std::vector & ballots, + const std::vector & arch, + std::vector > & recordholder, + double & record, + std::vector > & centroids, + int pos, int offset, + int numcands, int fact, + assignment & solver, + const std::vector > > & + voter_matrices, + std::vector > > & + medoid_matrices) const; + + bool use_continuous; + + public: + std::list get_council(size_t council_size, size_t num_candidates, + const election_t & ballots) const; + + fc_kemeny(bool continuous_in) { + use_continuous = continuous_in; + } + + string name() const { + if (use_continuous) { + return ("CFC-Kemeny (EXP)"); + } else { + return ("FC-Kemeny (EXP)"); + } + } +}; + +std::vector > fc_kemeny::tournament_matrix( + const std::vector & rank, int maxcand) const { + + std::vector > toRet(maxcand,std::vector(maxcand, + false)); + + for (size_t counter = 0; counter < rank.size(); ++counter) + for (size_t sec = counter+1; sec < rank.size(); ++sec) { + toRet[rank[counter]][rank[sec]] = true; + } + + return (toRet); +} + +int fc_kemeny::p_distance(const std::vector > & cmat_a, + const std::vector > & cmat_b, + int maxcand) const { + + int mismatch_count = 0; + + for (int counter = 0; counter < maxcand; ++counter) + for (int sec = counter+1; sec < maxcand; ++sec) + if (cmat_a[counter][sec] ^ cmat_b[counter][sec]) { + ++mismatch_count; + } + + return (mismatch_count); +} + +int fc_kemeny::p_distance(const std::vector & a, + const std::vector & b, + int maxcand) const { + + std::vector > cmat_a = tournament_matrix(a, maxcand), + cmat_b = tournament_matrix(b, maxcand); + + return (p_distance(cmat_a, cmat_b, maxcand)); +} + + +// Improvised tiebreak: returns sum distance between centroids (all pairs). +// Is greater better, or less better? We'll try less for now. +int fc_kemeny::tiebreak(const std::vector > & a, + int maxcand) const { + + int sum = 0; + + for (size_t counter = 0; counter < a.size(); ++counter) + for (size_t sec = counter+1; sec < a.size(); ++sec) { + sum += p_distance(a[counter], a[sec], maxcand); + } + + return (sum); +} + +void fc_kemeny::permutation(int input, std::vector & rank) const { + for (size_t counter = 0; counter < rank.size(); ++counter) { + size_t radix = rank.size() - counter; + size_t s = input % radix; + input = (input - s)/radix; + + swap(rank[counter], rank[counter + s]); + } +} + +void fc_kemeny::print_rank(const std::vector & rank) const { + + for (size_t counter = 0; counter < rank.size(); ++counter) { + if (counter > 0) { + cout << ">"; + } + cout << (char)(rank[counter] + 'A'); + } + //cout << endl; +} + +// Returns pair where the first is the record distance and the second is +// the recordholder. +pair fc_kemeny::distance_to_closest(const std::vector & + our_rank, + const std::vector > & centroids, int numcands) const { + + pair record(-1, -1); + + for (size_t counter = 0; counter < centroids.size(); ++counter) { + int candidate = p_distance(our_rank, centroids[counter], + numcands); + + if (record.first == -1 || record.first > candidate) { + record.first = candidate; + record.second = counter; + } + } + + return (record); +} + +// Behold the joy of linear programming! +int fc_kemeny::get_continuous_score(const std::vector & ballots, + const std::vector > & medoids, int numcands, + assignment & solver, bool brute_calc) const { + + // Do gradually later. + + for (size_t counter = 0; counter < ballots.size() && brute_calc; ++counter) + for (size_t sec = 0; sec < medoids.size(); ++sec) + solver.set_constraint(counter, sec, + p_distance(ballots[counter].rank, + medoids[sec], numcands)); + + // TODO: Implement last record so that if infeasible, it can abort + // quickly. + + double score = solver.calc_minimum(); + + assert(solver.success()); + + return (score); +} + +int fc_kemeny::get_score(const std::vector & ballots, + const std::vector > & medoids, int numcands) const { + + // Force a clustering of equal size. For ideas, see the original + // fc_kemeny.cc - linearization might be possible! + + std::vector > scores; + + size_t counter, sec; + + for (counter = 0; counter < ballots.size(); ++counter) { + std::vector cur(medoids.size(), 0); + + for (sec = 0; sec < medoids.size(); ++sec) + cur[sec] = p_distance(ballots[counter].rank, + medoids[sec], numcands); + + for (sec = 0; sec < (size_t)ballots[counter].strength; ++sec) { + scores.push_back(cur); + } + } + + return (get_clustering_error(scores, medoids.size())); +} + +bool fc_kemeny::verify(const std::vector > & centroids, + int numcands) const { + // No candidate can be mentioned more than once in a single rank. + // Maybe this is unneccessary and the best centroids will have this + // property anyway. + + for (int column = 0; column < 1; + ++column) { //centroids[0].size(); ++column) { + std::vector seen(numcands, false); + for (size_t sec = 0; sec < centroids.size(); ++sec) + if (seen[centroids[sec][column]]) { + return (false); + } else { + seen[centroids[sec][column]] = true; + } + } + + return (true); // for now +} + +q_ballot fc_kemeny::build_ballot(int strength, string order) const { + + q_ballot toret; + toret.strength = strength; + + for (size_t counter = 0; counter < order.size(); ++counter) { + toret.rank.push_back(order[counter] - 'A'); + } + + return (toret); +} + +int fc_kemeny::factorial(int n) const { + if (n <= 1) { + return (n); + } + return (n * factorial(n-1)); +} + +// This can be sped up by having the permutation function return in +// sorted order. Then we can do something like "if first is A, start next at +// B first". PROOF OF CONCEPT! + +// Centroids is the vector of ranks that constitute our centroid (really) set. +// Pos is the position (determining first member, second member), numerical +// ranking is used so we don't check "B, A" if we've already checked "A, B" +void fc_kemeny::recurse_ranking(const std::vector & ballots, + const std::vector & arch, + std::vector > & recordholder, double & record, + std::vector > & centroids, int pos, + int offset, int numcands, int fact, assignment & solver, + const std::vector > > & voter_matrices, + std::vector > > & medoid_matrices) const { + + int counter; + int maximum = centroids.size(); + + if (pos == maximum) { + // REQUIRED for number ten! + // Why? Find out. + if (!verify(centroids, numcands)) { + return; + } + + double cand; + if (use_continuous) + cand = get_continuous_score(ballots, centroids, + numcands, solver, false); + else { + cand = get_score(ballots, centroids, numcands); + } + + bool replace_with_new = cand < record || record == -1; + // Run improvised tiebreak. Whoever has the least distance + // between centroids wins (or should this be greatest?) + // (Must be > to pass the DPC?) + // We might have to have a tiebreak inside LP too. Or maybe + // not? + if (!replace_with_new && cand == record) + replace_with_new = (tiebreak(centroids, numcands) > + tiebreak(recordholder, numcands)); + + // Beware ties + if (replace_with_new) { + record = cand; + recordholder = centroids; + } + + return; + } + + if (offset == numcands) { + return; + } + if (numcands - offset < maximum - pos) { + return; // not enough space + } + + // Recurse! + // See other example for why we do it like this + recurse_ranking(ballots, arch, recordholder, record, centroids, pos, + offset+1, numcands, fact, solver, voter_matrices, + medoid_matrices); + + for (counter = offset; counter < fact; ++counter) { + centroids[pos] = arch; + permutation(counter, centroids[pos]); + + medoid_matrices[pos] = tournament_matrix(centroids[pos], + numcands); + + if (use_continuous) + for (size_t ball = 0; ball < ballots.size(); ++ball) + solver.set_constraint(ball, pos, + p_distance(voter_matrices[ball], + medoid_matrices[pos], + numcands)); + + recurse_ranking(ballots, arch, recordholder, record, + centroids, pos+1, offset+1, numcands, fact, + solver, voter_matrices, medoid_matrices); + } +} + + +// Make properly recursive later + +std::list fc_kemeny::get_council(size_t council_size, + size_t num_candidates, + const election_t & vballots) const { + + election_t compressed_ballots = ballot_tools().compress(vballots); + + std::vector rank(num_candidates, 0); + iota(rank.begin(), rank.end(), 0); + + assert(num_candidates < 12); + + std::vector xlat_ballots; + std::vector > > ballot_matrices; + // translate here + // Doesn't handle equal rank, etc. + double num_voters = 0; + for (election_t::const_iterator pos = compressed_ballots.begin(); + pos != compressed_ballots.end(); ++pos) { + q_ballot next_one; + num_voters += pos->get_weight(); + next_one.strength = pos->get_weight(); + for (ordering::const_iterator spos = pos->contents.begin(); + spos != pos->contents.end(); ++spos) { + next_one.rank.push_back(spos->get_candidate_num()); + } + xlat_ballots.push_back(next_one); + ballot_matrices.push_back(tournament_matrix(next_one.rank, + num_candidates)); + } + + double record = -1; + std::vector > recordholder(council_size, + std::vector(num_candidates, -1)); + std::vector > centroids(council_size); + + assignment check(num_voters, xlat_ballots.size(), council_size, + xlat_ballots); + + std::vector > > medoid_matrices( + council_size); + + recurse_ranking(xlat_ballots, rank, recordholder, record, centroids, 0, + 0, num_candidates, factorial(num_candidates), check, + ballot_matrices, medoid_matrices); + + std::list toRet; + + std::vector already(num_candidates, false); + + for (size_t counter = 0; counter < recordholder.size(); ++counter) { + toRet.push_back(recordholder[counter][0]); + assert(!already[recordholder[counter][0]]); + already[recordholder[counter][0]] = true; + } + + return (toRet); +} \ No newline at end of file diff --git a/src/multiwinner/rusty/mono_webst_640.h b/src/multiwinner/rusty/mono_webst_640.h new file mode 100644 index 0000000..bb09b51 --- /dev/null +++ b/src/multiwinner/rusty/mono_webst_640.h @@ -0,0 +1,529 @@ +#pragma once + +// A possible monotone version of Set Webster. It's a huge mess, but that will +// change if it's actually monotone. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aux/dsc.h" +#include "multiwinner/methods.h" +#include "tools/ballot_tools.h" +#include "setwise/coalition.h" +#include "tools/tools.h" + +using namespace std; + +enum margin_type { MM_PLUSONE = 0, MM_PLUSHALF = 1, MM_FABS = 2, MM_INVERTED = 3 }; + +class mono_webster_640 : public multiwinner_method { + + private: + solid_coalition construct_sc(string memberset, + double support) const; + string show_membership(const std::set & x) const; + string show_membership(const solid_coalition & x) const; + void power_set(int numcands, int curcand, std::set current, + std::set remaining, + std::set > & power_set_out) const; + double get_retained(const solid_coalition & check_against, + const std::set & proposed) const; + bool verify(const std::vector & constraints, + const std::set & proposed, + double divisor) const; + void explain(const std::vector & constraints, + double divisor) const; + std::set > get_passed_sets(const std::vector + & + constraints, const std::set > & in, + double divisor) const; + std::set > get_passed_lowest(const + std::vector & constraints, + const std::set > & in, double high, + double low, double stepsize, + double & divisor) const; + std::vector alt_voter_support(const + std::vector & constraints, + const std::set & current, + const std::vector & tiebreak_array, + double divisor) const; + std::set > get_highest_scoring_tiebreak( + const std::vector & constraints, + const std::set > & input, + const std::vector & tiebreak_array, + double divisor) const; + std::set naive_sweb(const std::vector & constraints, + int num_seats, double stepsize) const; + + margin_type margin_management; + bool simple_membership; + bool debug; + + public: + // Debug functions. + std::set get_brute_force_pure_ballot(int num_seats, + double stepsize, int a, int b, int c, int d, + int e, int f) const; + std::set check_one_brute() const; + + // You know why these are here... + + // TODO: typedef enum + mono_webster_640(margin_type marg_man, bool simple_member, bool debug_in) { + margin_management = marg_man; + simple_membership = simple_member; + debug = debug_in; + } + + + std::list get_council(size_t council_size, size_t num_candidates, + const election_t & ballots) const; + + string name() const { + // Should mention debug here? + string name_out = "M-Set Webster (640, EXP, " + + itos((int)margin_management); + if (simple_membership) { + name_out += ", simple)"; + } else { + name_out += ", complx)"; + } + return (name_out); + } +}; + + +// Perhaps these should be in coalition.cc + +solid_coalition mono_webster_640::construct_sc(string memberset, + double support) const { + solid_coalition output; + + for (size_t counter = 0; counter < memberset.size(); ++counter) { + output.candidates.insert(memberset[counter]-'A'); + } + + output.support = support; + return (output); +} + +string mono_webster_640::show_membership(const std::set & x) const { + string out; + + for (std::set::const_iterator p = x.begin(); p != x.end(); ++p) { + out += 'A' + *p; + } + + return (out); +} + +string mono_webster_640::show_membership(const solid_coalition & x) const { + return (show_membership(x.candidates)); +} + +void mono_webster_640::power_set(int numcands, int curcand, + std::set current, + std::set remaining, std::set > & power_set_out) const { + + if (curcand == numcands) { + power_set_out.insert(current); + return; + } else { + std::set::iterator pos = remaining.begin(), bakpos; + while (pos != remaining.end()) { + std::set newcurr = current, newrema = remaining; + newcurr.insert(*pos); + newrema.erase(newrema.find(*pos)); + power_set(numcands, curcand+1, newcurr, newrema, + power_set_out); + ++pos; + } + } +} + +double mono_webster_640::get_retained(const solid_coalition & + check_against, + const std::set & proposed) const { + + // ... intersect + std::set intersect; + + set_intersection(check_against.candidates.begin(), + check_against.candidates.end(), + proposed.begin(), proposed.end(), inserter( + intersect, intersect.begin())); + + // ... get size + return (intersect.size()); +} + +bool mono_webster_640::verify(const std::vector & + constraints, + const std::set & proposed, double divisor) const { + + for (size_t counter = 0; counter < constraints.size(); ++counter) { + int seats_deserved = round(constraints[counter].support / + divisor); + + int actual = get_retained(constraints[counter], proposed); + + if (actual < seats_deserved) { + return (false); + } + } + return (true); +} + +void mono_webster_640::explain(const std::vector & + constraints, + double divisor) const { + + cout << "Showing constraints for this divisor:" << endl; + cout << "\tSet:\t#v:\tunround:\tat least:" << endl; + + for (size_t counter = 0; counter < constraints.size(); ++counter) { + double unround = constraints[counter].support / divisor; + + cout << "\t" << show_membership(constraints[counter].candidates) << "\t" << + constraints[counter].support << "\t" << unround << "\t" + << round(unround) << "\t" << (floor(unround) + 0.5) - unround << endl; + } +} + +std::set > mono_webster_640::get_passed_sets( + const std::vector & constraints, + const std::set > & in, double divisor) const { + std::set > toRet; + + for (std::set >::const_iterator spos = in.begin(); + spos != in.end(); + ++spos) { + if (verify(constraints, *spos, divisor)) { + toRet.insert(*spos); + } + } + + return (toRet); +} + +std::set > mono_webster_640::get_passed_lowest( + const std::vector & constraints, + const std::set > & in, double high, double low, + double stepsize, double & divisor) const { + // Find the lowest divisor possible where at least one set passes. + // Uses binary search because I can't be bothered. + double last_true = INFINITY; + + while (fabs(high - low) > stepsize) { + double mid = low + ((high - low) / 2); + int num_passed_sets = get_passed_sets(constraints, in, + mid).size(); + //cout << "Trying <" << high << ", ["<< mid << "], " << low << ">: "<< num_passed_sets << endl; + + if (num_passed_sets > 0) { + high = mid; + last_true = mid; + } else { + low = mid; + } + } + + assert(finite(last_true)); + + std::set > passed_sets = get_passed_sets(constraints, in, + last_true); + + // DEBUG + if (debug) { + cout << "Binary search found divisor " << last_true << " which gives " << + passed_sets.size() << " solutions. " << endl; + cout << "Solutions:"; + + + for (std::set >::const_iterator pos = passed_sets.begin(); + pos != passed_sets.end(); ++pos) { + cout << " {" << show_membership(*pos) << "}"; + } + cout << endl; + explain(constraints, last_true); + } + divisor = last_true; + + if (finite(last_true)) { + return (passed_sets); + } else { + return (std::set >()); // Shouldn't happen + } +} + +// DONE: Somehow make this leximax. Just dump in all the margins where we do +// pass the requirement (includes), then do a "straightforward" leximax compare +// later. +// I don't know if it's proper leximax, but "v[0] < w[0]" then as +// tiebreaker, v[1] < w[1], then as tiebreaker... etc. + +std::vector mono_webster_640::alt_voter_support( + const std::vector & constraints, + const std::set & current, const std::vector & tiebreak_array, + double divisor) const { + + //List is better, but we're not overly concerned with speed at the + //moment. + std::vector retval; + + for (size_t counter = 0; counter < constraints.size(); ++counter) { + double unround = constraints[counter].support / divisor; + double margin = (floor(unround) + 0.5) - unround; + + // You can put what you want here and it doesn't break + // monotonicity, it seems. Setting different forms of + // adjustment here does alter the outcome in some cases. + // Other adjustments that have also been tried: + // margin = fabs(margin) + //while (margin < 0) margin ++; + while (margin < 0) { + switch (margin_management) { + default: + case MM_PLUSONE: ++margin; break; + case MM_PLUSHALF: margin += 0.5; break; + case MM_FABS: margin = fabs(margin); break; + case MM_INVERTED: margin = 1 + fabs(margin); + break; + }; + } + + //cout << "MARGIN: " << margin << "\t" << divisor << endl; + + bool permitted_this_score; + // If simple membership, we can add the margin if we share + // a candidate with the constraint. If not, the decision is + // somewhat more complex. The former seems to avoid some + // monotonicity errors, but we want to see how it works, first. + // TODO: check council_size=1 cases. + if (simple_membership) + permitted_this_score = (get_retained(constraints + [counter], current) > 0); + else + permitted_this_score = includes(constraints[counter]. + candidates.begin(), + constraints[counter].candidates.end(), + current.begin(), current.end()) + || + includes(current.begin(), current.end(), + constraints[counter].candidates.begin(), + constraints[counter].candidates.end()); + + // Idea: tiebreak also with number of candidates in common. + // Either that as a leximax after the leximax, or as the pair, + // e.g (-0.1,20) beats (-0.1,10), rather than tying it. + if (permitted_this_score) { + retval.push_back(margin); + } + } + + sort(retval.begin(), retval.end()); // Least is now first. + + return (retval); +} + +std::set > mono_webster_640::get_highest_scoring_tiebreak( + const std::vector & constraints, + const std::set > & input, + const std::vector & tiebreak_array, double divisor) const { + + // First dump into a set to get sorted data. + std::vector, std::set > > first_sort; + + for (std::set > ::const_iterator pos = input.begin(); pos != + input.end(); ++pos) { + if (debug) { + cout << "Tiebreak: Considering " << show_membership(*pos) << "\t" << + flush; + } + std::vector comp = alt_voter_support(constraints, *pos, + tiebreak_array, divisor); + if (debug) { + copy(comp.begin(), comp.end(), ostream_iterator(cout, " ")); + cout << endl; + } + + first_sort.push_back(pair, std::set >(comp, + *pos)); + } + + // Sort. Since the individual doubles were sorted, this works as a kind + // of leximax comparison: v[0] vs q[0], if they're equal, v[1] vs q[1], + // etc. + sort(first_sort.begin(), first_sort.end()); + //reverse(first_sort.begin(), first_sort.end()); + + // Now get all the sets who are equal to the first as far as the record + // goes (because those are best). + //cout << "Verify: " << first_sort[0].first[0] << "\t" << first_sort[0].first[1] << endl; + + std::set > toRet; + + bool eligible = true; + for (std::vector, std::set > >::const_iterator + spos = + first_sort.begin(); spos != first_sort.end() && + eligible; ++spos) { + cout << "Eligible: " << show_membership(spos->second) << endl; + toRet.insert(spos->second); + + // Check if the next is also eligible. + std::vector, std::set > >::const_iterator + next; + next = spos; + ++next; + if (next != first_sort.end()) { + eligible = (spos->first == next->first); + } + } + + return (toRet); +} + +// Generate all possible councils of this size, then run tiebreak to find which +// we prefer. +std::set mono_webster_640::naive_sweb(const + std::vector & constraints, + int num_seats, double stepsize) const { + + std::set > psout; + power_set(num_seats, 0, std::set(), constraints[0].candidates, psout); + cout << "Engaging divisor engine." << endl; + double divisor; + std::vector tiebreak_array(0); // NOT TO BE ACCESSED + + std::set > accepted_limiters = get_passed_lowest(constraints, + psout, constraints[0].support * 4, 1, stepsize, + divisor); + + // Now we have a bunch of sets. + //cout << "Sets after divisor stuff: " << accepted_limiters.size() << endl; + if (accepted_limiters.size() == 1) { + return (*accepted_limiters.begin()); + } else { + cout << "Time for breaking some ties, methinks." << endl; + accepted_limiters = get_highest_scoring_tiebreak(constraints, + accepted_limiters, tiebreak_array, divisor); + } + + cout << "Now there are " << accepted_limiters.size() << " sets. "; + if (accepted_limiters.size() > 1) { + cout << "Breaking randomly."; + } + cout << endl; + + return (*accepted_limiters.begin()); + +} + +std::set mono_webster_640::get_brute_force_pure_ballot(int num_seats, + double stepsize, int a, int b, int c, int d, int e, + int f) const { + + std::vector scs; + scs.push_back(construct_sc("ABC", a+b+c+d+e+f)); + scs.push_back(construct_sc("AB", a+c)); + scs.push_back(construct_sc("AC", b+e)); + scs.push_back(construct_sc("BC", d+f)); + scs.push_back(construct_sc("A", a+b)); + scs.push_back(construct_sc("B", b+c)); + scs.push_back(construct_sc("C", d+e)); + + return (naive_sweb(scs, num_seats, stepsize)); +} + +std::set mono_webster_640::check_one_brute() const { + int mxv = 1 + random() % 19; + + // Must be at least one c, or we can't very well lower, can we? + int a = random() % mxv, b = random() % mxv, c = (random() % mxv) + 1, + d = random() % mxv, e = random() % mxv, f = random() % mxv; + + cout << "----- Generating original council ---" << endl; + + int num_seats = 1 + random()%2; + double stepsize = 0.005; + + std::set origc = get_brute_force_pure_ballot(num_seats, stepsize, a, + b, c, d, e, f); + + // Adjust for monotonicity. + --c; + ++a; + + cout << "----- Generating second (A raised) council ---" << endl; + + std::set newc = get_brute_force_pure_ballot(num_seats, stepsize, a, + b, c, d, e, f); + + std::set aonly; aonly.insert(0); + + cout << "All done. First council was: " << show_membership(origc); + cout << " and the new one is " << show_membership(newc) << endl; + + bool a_in_first = includes(origc.begin(), origc.end(), aonly.begin(), + aonly.end()); + bool a_in_second = includes(newc.begin(), newc.end(), aonly.begin(), + aonly.end()); + + if (origc != newc) { + cout << "Something is happening." << endl; + } + + if (a_in_first && !a_in_second) { + cout << "Oh dear, A vanished after being raised." << endl; + cout << "Where's my kaishakunin?" << endl; + assert(1 != 1); + } + + return (origc); +} + +std::list mono_webster_640::get_council(size_t council_size, + size_t num_candidates, + const election_t & ballots) const { + + // Turn the input into a list of solid coalitions. (Put this code + // elsewhere?) + + multimap > init_coalitions = + get_solid_coalitions(ballots, num_candidates, rand()%num_candidates); + + std::vector scs; + + for (multimap >::const_reverse_iterator + pos= + init_coalitions.rbegin(); pos != init_coalitions.rend(); + ++pos) { + solid_coalition q; + copy(pos->second.begin(), pos->second.end(), inserter( + q.candidates, q.candidates.begin())); + q.support = pos->first; + scs.push_back(q); + } + + // Turn it into the standardized format. + sort(scs.begin(), scs.end(), greater()); + + // Stepsize should probably at most be 1/numvoters. If the granularity + // is 0.1, 0.1/numvoters, etc.. Later. + + double stepsize = min(0.01, 1.0/scs.begin()->support); + std::set council = naive_sweb(scs, council_size, stepsize); + + // But the output wants it in another format. + std::list toRet; + copy(council.begin(), council.end(), inserter(toRet, toRet.begin())); + + return (toRet); +} \ No newline at end of file diff --git a/src/multiwinner/rusty/mono_webst_c37.h b/src/multiwinner/rusty/mono_webst_c37.h new file mode 100644 index 0000000..08a140f --- /dev/null +++ b/src/multiwinner/rusty/mono_webst_c37.h @@ -0,0 +1,725 @@ +#pragma once + +// A possible monotone version of Set Webster. It's a huge mess, but that will +// change if it's actually monotone. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aux/dsc.h" +#include "multiwinner/methods.h" +#include "tools/ballot_tools.h" +#include "setwise/coalition.h" +#include "tools/tools.h" + +using namespace std; + +enum margin_type_c37 { MMC_PLUSONE = 0, MMC_PLUSHALF = 1, MMC_FABS = 2, + MMC_INVERTED = 3, MMC_UNCERTAIN = 4, MMC_UNCERTAIN_INF = 5 +}; + +class mono_webster_c37 : public multiwinner_method { + + private: + solid_coalition construct_sc(string memberset, + double support) const; + string show_membership(const std::set & x) const; + string show_membership(const solid_coalition & x) const; + void power_set(int numcands, int curcand, std::set current, + std::set remaining, + std::set > & power_set_out) const; + void choose_set(int numcands, int curcand, int how_many, + int cur_chosen, std::set & current, + std::set > & choose_out) const; + double get_retained(const solid_coalition & check_against, + const std::set & proposed) const; + int share_candidates(const std::set & smaller, + const std::set & larger, + int early_break) const; + int share_candidates(const solid_coalition & check_against, + const std::set & proposed, + int early_break) const; + bool verify(const std::vector & constraints, + const std::set & proposed, + double divisor) const; + void explain(const std::vector & constraints, + double divisor) const; + std::set > get_passed_sets(const std::vector + & + constraints, const std::set > & in, + double divisor) const; + std::set > get_passed_lowest(const + std::vector & constraints, + const std::set > & in, double high, + double low, double stepsize, + double & divisor) const; + std::vector alt_voter_support(const + std::vector & constraints, + const std::set & current, + const std::vector & tiebreak_array, + double divisor) const; + std::set > get_highest_scoring_tiebreak( + const std::vector & constraints, + const std::set > & input, + const std::vector & tiebreak_array, + double divisor) const; + std::set naive_sweb(const std::vector & constraints, + int num_seats, double stepsize) const; + + margin_type_c37 margin_management; + bool simple_membership; + bool debug; + bool limiter; + + public: + // Debug functions. + std::set get_brute_force_pure_ballot(int num_seats, + double stepsize, int a, int b, int c, int d, + int e, int f) const; + std::set check_one_brute() const; + + // You know why these are here... + + // TODO: typedef enum + mono_webster_c37(bool limit_in, margin_type_c37 marg_man, + bool simple_member, bool debug_in) { + limiter = limit_in; + margin_management = marg_man; + simple_membership = simple_member; + debug = debug_in; + } + + + std::list get_council(size_t council_size, size_t num_candidates, + const election_t & ballots) const; + + string name() const { + // Should mention debug here? + string name_out = "M-Set Webster (c37, EXP, " + + itos((int)margin_management); + if (simple_membership) { + name_out += ", simple, "; + } else { + name_out += ", complx, "; + } + if (limiter) { + name_out += "limiter)"; + } else { + name_out += "unlimit)"; + } + + return (name_out); + } +}; + + +// Perhaps these should be in coalition.cc + +solid_coalition mono_webster_c37::construct_sc(string memberset, + double support) const { + solid_coalition output; + + for (size_t counter = 0; counter < memberset.size(); ++counter) { + output.candidates.insert(memberset[counter]-'A'); + } + + output.support = support; + return (output); +} + +string mono_webster_c37::show_membership(const std::set & x) const { + string out; + + for (std::set::const_iterator p = x.begin(); p != x.end(); ++p) { + out += 'A' + *p; + } + + return (out); +} + +string mono_webster_c37::show_membership(const solid_coalition & x) const { + return (show_membership(x.candidates)); +} + +void mono_webster_c37::power_set(int numcands, int curcand, + std::set current, + std::set remaining, std::set > & power_set_out) const { + + if (curcand == numcands) { + power_set_out.insert(current); + return; + } else { + std::set::iterator pos = remaining.begin(), bakpos; + while (pos != remaining.end()) { + std::set newcurr = current, newrema = remaining; + newcurr.insert(*pos); + newrema.erase(newrema.find(*pos)); + power_set(numcands, curcand+1, newcurr, newrema, + power_set_out); + ++pos; + } + } +} + +void mono_webster_c37::choose_set(int numcands, int curcand, int how_many, + int cur_chosen, std::set & current, + std::set > & choose_out) const { + + // Some sanity checks. Pick k out of n with k > n doesn't make sense. + if (curcand == 0) { + assert(how_many <= numcands); + } + + // If cur_chosen = how_many, insert set into output. + // If numcands = curcand, return. Else... + + // Add a candidate to the set. As we do this in ascending order, + // we know this candidate has to be at the end. Recurse with + // cur_chosen +1, curcand+1. Then remove and recurse with + // cur_chosen, curcand + 1. + + // Recursion, dudes. You dig it? + + // Perhaps if (how_many - cur_chosen > numcands - curcand) return. + // Something like that. Experiment another time. + if (cur_chosen == how_many) { + choose_out.insert(current); + } + + // If we have exhausted all candidates or there's no way of making + // up the difference, bail outta here. + if (how_many - cur_chosen > numcands - curcand || numcands == curcand) { + return; + } + + // Hint that the next candidate will be added at the end (because it + // will). Removed. + current.insert(curcand); + choose_set(numcands, curcand+1, how_many, cur_chosen+1, current, + choose_out); + // Remove the one we added. + current.erase(--current.end()); + choose_set(numcands, curcand+1, how_many, cur_chosen, current, + choose_out); +} + +double mono_webster_c37::get_retained(const solid_coalition & + check_against, + const std::set & proposed) const { + + // ... intersect + std::set intersect; + + set_intersection(check_against.candidates.begin(), + check_against.candidates.end(), + proposed.begin(), proposed.end(), inserter( + intersect, intersect.begin())); + + // ... get size + return (intersect.size()); +} + +// Checks whether smaller shares any candidates with larger. The runtime should +// be (smaller.size() * ln(larger.size())) worst case. +// The early_break parameter means "stop once we've found this many matches". +// If -1, it doesn't stop until it's done. +int mono_webster_c37::share_candidates(const std::set & smaller, + const std::set & larger, int early_break) const { + + // Make use of the fact that both are sorted to produce an algorithm + // of this kind: + // For each position in the smaller set + // Advance pointer of the larger set until it's equal or + // above, or we hit the end. + // If equal, increment shares and check if we're done. + // If we hit the end, return how many matches we got. + // Next + + // If we just want a pass-fail, there are all sorts of inventive tricks + // we could use to break if there's no way we can reach early_break. + // However, that would ruin the elegance here. + + std::set::const_iterator spos, lpos = larger.begin(); + int matches = 0; + + for (spos = smaller.begin(); spos != smaller.end(); ++spos) { + // This is first so we return early for a match like >= 0. + if (matches == early_break && early_break > -1) { + return (matches); + } + + while (lpos != larger.end() && *lpos < *spos) { + ++lpos; + } + + if (lpos == larger.end()) { + return (matches); + } + + if (*spos == *lpos) { + ++matches; + } + } + + return (matches); +} + +int mono_webster_c37::share_candidates(const solid_coalition & + check_against, + const std::set & proposed, int early_break) const { + + if (check_against.candidates.size() < proposed.size()) + return (share_candidates(check_against.candidates, proposed, + early_break)); + else return (share_candidates(proposed, check_against.candidates, + early_break)); +} + +bool mono_webster_c37::verify(const std::vector & + constraints, + const std::set & proposed, double divisor) const { + + for (size_t counter = 0; counter < constraints.size(); ++counter) { + int seats_deserved = round(constraints[counter].support / + divisor); + if (limiter) { + seats_deserved = min(seats_deserved, + (int)constraints[counter].candidates.size()); + } + + //int actual = get_retained(constraints[counter], proposed); + + // I can improve that even more by having it abort as soon + // as it's above or equal to seats_deserved. + int actual = share_candidates(constraints[counter], proposed, + seats_deserved); + + if (actual < seats_deserved) { + return (false); + } + } + return (true); +} + +void mono_webster_c37::explain(const std::vector & + constraints, + double divisor) const { + + cout << "Showing constraints for this divisor:" << endl; + cout << "\tSet:\t#v:\tunround:\tat least:" << endl; + + for (size_t counter = 0; counter < constraints.size(); ++counter) { + double unround = constraints[counter].support / divisor; + + cout << "\t" << show_membership(constraints[counter].candidates) << "\t" << + constraints[counter].support << "\t" << unround << "\t" + << round(unround) << "\t" << (floor(unround) + 0.5) - unround << endl; + } +} + +std::set > mono_webster_c37::get_passed_sets( + const std::vector & constraints, + const std::set > & in, double divisor) const { + std::set > toRet; + + for (std::set >::const_iterator spos = in.begin(); + spos != in.end(); + ++spos) { + if (verify(constraints, *spos, divisor)) { + toRet.insert(*spos); + } + } + + return (toRet); +} + +std::set > mono_webster_c37::get_passed_lowest( + const std::vector & constraints, + const std::set > & in, double high, double low, + double stepsize, double & divisor) const { + // Find the lowest divisor possible where at least one set passes. + // Uses binary search because I can't be bothered. + double last_true = INFINITY; + + while (fabs(high - low) > stepsize) { + double mid = low + ((high - low) / 2); + int num_passed_sets = get_passed_sets(constraints, in, + mid).size(); + if (debug) { + cout << "Binary: Trying <" << high << ", ["<< mid << "], " << low << + ">: "<< num_passed_sets << endl; + } + + if (num_passed_sets > 0) { + high = mid; + last_true = mid; + } else { + low = mid; + } + } + + assert(finite(last_true)); + + std::set > passed_sets = get_passed_sets(constraints, in, + last_true); + + // DEBUG + if (debug) { + cout << "Binary search found divisor " << last_true << " which gives " << + passed_sets.size() << " solutions. " << endl; + cout << "Solutions:"; + + + for (std::set >::const_iterator pos = passed_sets.begin(); + pos != passed_sets.end(); ++pos) { + cout << " {" << show_membership(*pos) << "}"; + } + cout << endl; + explain(constraints, last_true); + } + divisor = last_true; + + if (finite(last_true)) { + return (passed_sets); + } else { + return (std::set >()); // Shouldn't happen + } +} + +// DONE: Somehow make this leximax. Just dump in all the margins where we do +// pass the requirement (includes), then do a "straightforward" leximax compare +// later. +// I don't know if it's proper leximax, but "v[0] < w[0]" then as +// tiebreaker, v[1] < w[1], then as tiebreaker... etc. + +std::vector mono_webster_c37::alt_voter_support( + const std::vector & constraints, + const std::set & current, const std::vector & tiebreak_array, + double divisor) const { + + //List is better, but we're not overly concerned with speed at the + //moment. + std::vector retval; + + for (size_t counter = 0; counter < constraints.size(); ++counter) { + double unround = constraints[counter].support / divisor; + double margin = (floor(unround) + 0.5) - unround; + + // You can put what you want here and it doesn't break + // monotonicity, it seems. Setting different forms of + // adjustment here does alter the outcome in some cases. + // Other adjustments that have also been tried: + // margin = fabs(margin) + //while (margin < 0) margin ++; + + while (margin < 0) { + switch (margin_management) { + default: + case MMC_PLUSONE: ++margin; break; + case MMC_PLUSHALF: margin += 0.5; break; + case MMC_FABS: margin = fabs(margin); break; + case MMC_INVERTED: margin = 1 + fabs(margin); + break; + // Two models of the maximal leeway we have. + case MMC_UNCERTAIN: margin = 1; break; + case MMC_UNCERTAIN_INF: margin = INFINITY; break; + }; + } + + //cout << "MARGIN: " << margin << "\t" << divisor << endl; + + // TODO: if limiter is on and some council is limited by its + // membership, don't factor it in when dealing with margins - + // it can never impose an additional constraint. + + bool permitted_this_score; + // If simple membership, we can add the margin if we share + // a candidate with the constraint. If not, the decision is + // somewhat more complex. The former seems to avoid some + // monotonicity errors, but we want to see how it works, first. + // TODO: check council_size=1 cases. + if (simple_membership) + /*permitted_this_score = (get_retained(constraints + [counter], current) > 0);*/ + permitted_this_score = (share_candidates(constraints + [counter], current, 1) > 0); + else + permitted_this_score = includes(constraints[counter]. + candidates.begin(), + constraints[counter].candidates.end(), + current.begin(), current.end()) + || + includes(current.begin(), current.end(), + constraints[counter].candidates.begin(), + constraints[counter].candidates.end()); + + if (limiter && round(unround) >= constraints[counter].candidates.size()) { + permitted_this_score = false; + } + + // Idea: tiebreak also with number of candidates in common. + // Either that as a leximax after the leximax, or as the pair, + // e.g (-0.1,20) beats (-0.1,10), rather than tying it. + // The infinity is because any matching is better than none at + // all. + if (permitted_this_score) { + retval.push_back(margin); + } else { + retval.push_back(INFINITY); + } + } + + sort(retval.begin(), retval.end()); // Least is now first. + + return (retval); +} + +std::set > mono_webster_c37::get_highest_scoring_tiebreak( + const std::vector & constraints, + const std::set > & input, + const std::vector & tiebreak_array, double divisor) const { + + // First dump into a set to get sorted data. + std::vector, std::set > > first_sort; + + for (std::set > ::const_iterator pos = input.begin(); pos != + input.end(); ++pos) { + if (debug) { + cout << "Tiebreak: Considering " << show_membership(*pos) << "\t" << + flush; + } + std::vector comp = alt_voter_support(constraints, *pos, + tiebreak_array, divisor); + if (debug) { + copy(comp.begin(), comp.end(), ostream_iterator(cout, " ")); + cout << endl; + } + + first_sort.push_back(pair, std::set >(comp, + *pos)); + } + + // Sort. Since the individual doubles were sorted, this works as a kind + // of leximax comparison: v[0] vs q[0], if they're equal, v[1] vs q[1], + // etc. + sort(first_sort.begin(), first_sort.end()); + //reverse(first_sort.begin(), first_sort.end()); + + // Now get all the sets who are equal to the first as far as the record + // goes (because those are best). + //cout << "Verify: " << first_sort[0].first[0] << "\t" << first_sort[0].first[1] << endl; + + std::set > toRet; + + bool eligible = true; + for (std::vector, std::set > >::const_iterator + spos = + first_sort.begin(); spos != first_sort.end() && + eligible; ++spos) { + cout << "Eligible: " << show_membership(spos->second) << endl; + toRet.insert(spos->second); + + // Check if the next is also eligible. + std::vector, std::set > >::const_iterator + next; + next = spos; + ++next; + if (next != first_sort.end()) { + eligible = (spos->first == next->first); + } + } + + return (toRet); +} + +// Generate all possible councils of this size, then run tiebreak to find which +// we prefer. +std::set mono_webster_c37::naive_sweb(const + std::vector & constraints, + int num_seats, double stepsize) const { + + std::set > psout; + std::set current; + //power_set(num_seats, 0, std::set(), constraints[0].candidates, psout); + choose_set(constraints[0].candidates.size(), 0, num_seats, 0, + current, psout); + + if (debug) { + cout << "Engaging divisor engine." << endl; + } + double divisor; + std::vector tiebreak_array(0); // NOT TO BE ACCESSED + + if (debug) { + cout << "Membership: " << show_membership(constraints[0].candidates) << + endl; + cout << "The combined set for " << num_seats << " seat/s consists of:"; + for (std::set >::const_iterator scpos = psout.begin(); + scpos != psout.end(); ++scpos) { + cout << " {" << show_membership(*scpos) << "}"; + } + cout << ", in all " << psout.size() << endl; + + cout << "Worst case " << endl; + explain(constraints, constraints[0].support * 4); + cout << "--" << endl; + } + + std::set > accepted_limiters = get_passed_lowest(constraints, + psout, constraints[0].support * 4, 1, stepsize, + divisor); + + // Now we have a bunch of sets. + //cout << "Sets after divisor stuff: " << accepted_limiters.size() << endl; + if (accepted_limiters.size() == 1) { + if (debug) { + cout << "Early break." << endl; + } + return (*accepted_limiters.begin()); + } else { + if (debug) { + cout << "Time for breaking some ties, methinks." << endl; + } + accepted_limiters = get_highest_scoring_tiebreak(constraints, + accepted_limiters, tiebreak_array, divisor); + } + + cout << "Now there are " << accepted_limiters.size() << " sets. "; + if (accepted_limiters.size() > 1) { + cout << "Breaking randomly."; + } + cout << "Our sets are:"; + for (std::set >::const_iterator vpos = + accepted_limiters.begin(); vpos != accepted_limiters.end(); ++vpos) { + cout << " {" << show_membership(*vpos) << "}"; + } + cout << endl; + + return (*accepted_limiters.begin()); + +} + +std::set mono_webster_c37::get_brute_force_pure_ballot(int num_seats, + double stepsize, int a, int b, int c, int d, int e, + int f) const { + + std::vector scs; + scs.push_back(construct_sc("ABC", a+b+c+d+e+f)); + scs.push_back(construct_sc("AB", a+c)); + scs.push_back(construct_sc("AC", b+e)); + scs.push_back(construct_sc("BC", d+f)); + scs.push_back(construct_sc("A", a+b)); + scs.push_back(construct_sc("B", b+c)); + scs.push_back(construct_sc("C", d+e)); + + return (naive_sweb(scs, num_seats, stepsize)); +} + +std::set mono_webster_c37::check_one_brute() const { + int mxv = 1 + random() % 10; + + // Must be at least one c, or we can't very well lower, can we? + int a = random() % mxv, b = random() % mxv, c = (random() % mxv) + 1, + d = random() % mxv, e = random() % mxv, f = random() % mxv; + + cout << a << ": ABC" << endl; + cout << b << ": ACB" << endl; + cout << c << ": BAC" << endl; + cout << d << ": BCA" << endl; + cout << e << ": CAB" << endl; + cout << f << ": CBA" << endl; + + cout << "----- Generating original council ---" << endl; + + int num_seats = 1 + random()%2; + //int num_seats = 2; + double stepsize = 0.005; + + std::set origc = get_brute_force_pure_ballot(num_seats, stepsize, a, + b, c, d, e, f); + + // Adjust for monotonicity. + --c; + ++a; + + cout << "----- Generating second (A raised) council ---" << endl; + + std::set newc = get_brute_force_pure_ballot(num_seats, stepsize, a, + b, c, d, e, f); + + std::set aonly; aonly.insert(0); + + cout << "All done. First council was: " << show_membership(origc); + cout << " and the new one is " << show_membership(newc) << endl; + + bool a_in_first = includes(origc.begin(), origc.end(), aonly.begin(), + aonly.end()); + bool a_in_second = includes(newc.begin(), newc.end(), aonly.begin(), + aonly.end()); + + if (origc != newc) { + cout << "Something is happening." << endl; + } + + if (a_in_first && !a_in_second) { + cout << "Oh dear, A vanished after being raised." << endl; + cout << "Where's my kaishakunin?" << endl; + assert(1 != 1); + } + + return (origc); +} + +std::list mono_webster_c37::get_council(size_t council_size, + size_t num_candidates, const election_t & ballots) const { + + assert(council_size <= num_candidates); + assert(council_size <= num_candidates); + assert(num_candidates > 0); + + // Turn the input into a list of solid coalitions. (Put this code + // elsewhere?) + + // Disable the "automatic tiebreak" feature, because we will be doing + // our own tiebreaking, TYVM. + // (Really symptomatic of a greater problem - that the mono-raise check + // can't distinguish chance from bias, and therefore thinks anything + // resolved by a roll of the dice violates mono-raise.) + multimap > init_coalitions = + get_solid_coalitions(ballots, num_candidates, + -1/*rand()%num_candidates*/); + + std::vector scs; + + for (multimap >::const_reverse_iterator + pos= + init_coalitions.rbegin(); pos != init_coalitions.rend(); + ++pos) { + solid_coalition q; + copy(pos->second.begin(), pos->second.end(), inserter( + q.candidates, q.candidates.begin())); + q.support = pos->first; + scs.push_back(q); + } + + // Turn it into the standardized format. + sort(scs.begin(), scs.end(), greater()); + + // Stepsize should probably at most be 1/numvoters. If the granularity + // is 0.1, 0.1/numvoters, etc.. Later. + + double stepsize = min(0.01, 1.0/scs.begin()->support); + std::set council = naive_sweb(scs, council_size, stepsize); + + // But the output wants it in another format. + std::list toRet; + copy(council.begin(), council.end(), inserter(toRet, toRet.begin())); + + return (toRet); +} \ No newline at end of file diff --git a/src/multiwinner/rusty/mono_webst_f03.h b/src/multiwinner/rusty/mono_webst_f03.h new file mode 100644 index 0000000..b5ff350 --- /dev/null +++ b/src/multiwinner/rusty/mono_webst_f03.h @@ -0,0 +1,576 @@ +#pragma once + +// A possible monotone version of Set Webster. It's a huge mess, but that will +// change if it's actually monotone. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aux/dsc.h" +#include "multiwinner/methods.h" +#include "tools/ballot_tools.h" +#include "setwise/coalition.h" +#include "tools/tools.h" + +#include "set_functs.h" + +using namespace std; + +enum margin_type_f03 { MMF_PLUSONE = 0, MMF_PLUSHALF = 1, MMF_FABS = 2, + MMF_INVERTED = 3, MMF_UNCERTAIN = 4, MMF_UNCERTAIN_INF = 5 +}; + +class mono_webster_f03 : public multiwinner_method { + + private: + set_functions sf; + bool verify(const std::vector & constraints, + const std::set & proposed, + double divisor) const; + void explain(const std::vector & constraints, + double divisor) const; + std::set > get_passed_sets(const std::vector + & + constraints, const std::set > & in, + double divisor) const; + std::set > get_passed_lowest(const + std::vector & constraints, + const std::set > & in, double high, + double low, double stepsize, + double & divisor) const; + std::vector alt_voter_support(const + std::vector & constraints, + const std::set & current, + const std::vector & tiebreak_array, + double divisor) const; + std::set > get_highest_scoring_tiebreak( + const std::vector & constraints, + const std::set > & input, + const std::vector & tiebreak_array, + double divisor) const; + std::set naive_sweb(const std::vector & constraints, + int num_seats, double stepsize) const; + + margin_type_f03 margin_management; + bool simple_membership; + bool debug; + bool limiter; + + public: + // Debug functions. + std::set get_brute_force_pure_ballot(int num_seats, + double stepsize, int a, int b, int c, int d, + int e, int f) const; + std::set check_one_brute() const; + + // You know why these are here... + + // TODO: typedef enum + mono_webster_f03(bool limit_in, margin_type_f03 marg_man, + bool simple_member, bool debug_in) { + limiter = limit_in; + margin_management = marg_man; + simple_membership = simple_member; + debug = debug_in; + } + + std::list get_council(size_t council_size, size_t num_candidates, + const election_t & ballots) const; + + string name() const { + // Should mention debug here? + string name_out = "M-Set Webster (f03, EXP, " + + itos((int)margin_management); + if (simple_membership) { + name_out += ", simple, "; + } else { + name_out += ", complx, "; + } + if (limiter) { + name_out += "limiter)"; + } else { + name_out += "unlimit)"; + } + + return (name_out); + } +}; + +bool mono_webster_f03::verify(const std::vector & + constraints, + const std::set & proposed, double divisor) const { + + for (size_t counter = 0; counter < constraints.size(); ++counter) { + int seats_deserved = round(constraints[counter].support / + divisor); + if (limiter) + seats_deserved = min(seats_deserved, + (int)constraints[counter].candidates. + size()); + + // I can improve that even more by having it abort as soon + // as it's above or equal to seats_deserved. DONE. + // I can improve that even more by having it abort as soon as + // it's impossible to reach the limit. + int actual = sf.share_candidates(constraints[counter], proposed, + seats_deserved); + + if (actual < seats_deserved) { + return (false); + } + } + return (true); +} + +void mono_webster_f03::explain(const std::vector & + constraints, + double divisor) const { + + cout << "Showing constraints for this divisor:" << endl; + cout << "\tSet:\t#v:\tunround:\tat least:" << endl; + + for (size_t counter = 0; counter < constraints.size(); ++counter) { + double unround = constraints[counter].support / divisor; + + cout << "\t" << sf.show_membership(constraints[counter]. + candidates) << "\t" << constraints[counter]. + support << "\t" << unround << "\t" << round(unround) + << "\t" << (floor(unround) + 0.5) - unround << endl; + } +} + +std::set > mono_webster_f03::get_passed_sets( + const std::vector & constraints, + const std::set > & in, double divisor) const { + std::set > toRet; + + for (std::set >::const_iterator spos = in.begin(); + spos != in.end(); + ++spos) { + // Memoize somehow? Worth it? + // More like, only check constraints on those that actually + // changed since the last check. + if (verify(constraints, *spos, divisor)) { + toRet.insert(*spos); + } + } + + return (toRet); +} + +std::set > mono_webster_f03::get_passed_lowest( + const std::vector & constraints, + const std::set > & in, double high, double low, + double stepsize, double & divisor) const { + // Find the lowest divisor possible where at least one set passes. + // Uses binary search because I can't be bothered. + double last_true = INFINITY; + + // We can speed up the process by making the observation that the + // number of permitted sets decrease monotonically with an increase + // in divisor. Therefore, as soon as we see a number of passed sets > 0, + // we know that the number of sets in the final outcome must be this + // many or less, so we don't have to test against all of them, just + // that subset. + std::set > limited, limited_possible; + // Hard to tell whether it helped - the effect's probably + // more easily observed with large sets. + + while (fabs(high - low) > stepsize) { + double mid = low + ((high - low) / 2); + if (limited.empty()) + limited_possible = get_passed_sets(constraints, in, + mid); + else limited_possible = get_passed_sets(constraints, + limited, mid); + + int num_passed_sets = limited_possible.size(); + if (num_passed_sets > 0) { + limited = limited_possible; + } + + if (debug) { + cout << "Binary: Trying <" << high << ", ["<< mid << "], " << low << + ">: "<< num_passed_sets << endl; + } + + if (num_passed_sets > 0) { + high = mid; + last_true = mid; + } else { + low = mid; + } + } + + assert(finite(last_true)); + + std::set > passed_sets = get_passed_sets(constraints, in, + last_true); + + // DEBUG + if (debug) { + cout << "Binary search found divisor " << last_true << " which gives " << + passed_sets.size() << " solutions. " << endl; + cout << "Solutions:"; + + + for (std::set >::const_iterator pos = passed_sets.begin(); + pos != passed_sets.end(); ++pos) { + cout << " {" << sf.show_membership(*pos) << "}"; + } + cout << endl; + explain(constraints, last_true); + } + divisor = last_true; + + if (finite(last_true)) { + return (passed_sets); + } else { + return (std::set >()); // Shouldn't happen + } +} + +// DONE: Somehow make this leximax. Just dump in all the margins where we do +// pass the requirement (includes), then do a "straightforward" leximax compare +// later. +// I don't know if it's proper leximax, but "v[0] < w[0]" then as +// tiebreaker, v[1] < w[1], then as tiebreaker... etc. + +std::vector mono_webster_f03::alt_voter_support( + const std::vector & constraints, + const std::set & current, const std::vector & tiebreak_array, + double divisor) const { + + //List is better, but we're not overly concerned with speed at the + //moment. + std::vector retval; + + for (size_t counter = 0; counter < constraints.size(); ++counter) { + double unround = constraints[counter].support / divisor; + double margin = (floor(unround) + 0.5) - unround; + + // You can put what you want here and it doesn't break + // monotonicity, it seems. Setting different forms of + // adjustment here does alter the outcome in some cases. + // Other adjustments that have also been tried: + // margin = fabs(margin) + //while (margin < 0) margin ++; + + while (margin < 0) { + switch (margin_management) { + default: + case MMF_PLUSONE: ++margin; break; + case MMF_PLUSHALF: margin += 0.5; break; + case MMF_FABS: margin = fabs(margin); break; + case MMF_INVERTED: margin = 1 + fabs(margin); + break; + // Two models of the maximal leeway we have. + case MMF_UNCERTAIN: margin = 1; break; + case MMF_UNCERTAIN_INF: margin = INFINITY; break; + }; + } + + //cout << "MARGIN: " << margin << "\t" << divisor << endl; + + // TODO: if limiter is on and some council is limited by its + // membership, don't factor it in when dealing with margins - + // it can never impose an additional constraint. + + bool permitted_this_score; + // If simple membership, we can add the margin if we share + // a candidate with the constraint. If not, the decision is + // somewhat more complex. The former seems to avoid some + // monotonicity errors, but we want to see how it works, first. + // TODO: check council_size=1 cases. + if (simple_membership) + /*permitted_this_score = (get_retained(constraints + [counter], current) > 0);*/ + permitted_this_score = (sf.share_candidates(constraints + [counter], current, 1) > 0); + else + permitted_this_score = includes(constraints[counter]. + candidates.begin(), + constraints[counter].candidates.end(), + current.begin(), current.end()) + || + includes(current.begin(), current.end(), + constraints[counter].candidates.begin(), + constraints[counter].candidates.end()); + + if (limiter && round(unround) >= constraints[counter].candidates.size()) { + permitted_this_score = false; + } + + // Idea: tiebreak also with number of candidates in common. + // Either that as a leximax after the leximax, or as the pair, + // e.g (-0.1,20) beats (-0.1,10), rather than tying it. + // The infinity is because any matching is better than none at + // all. + if (permitted_this_score) { + retval.push_back(margin); + } else { + retval.push_back(INFINITY); + } + } + + sort(retval.begin(), retval.end()); // Least is now first. + + return (retval); +} + +std::set > mono_webster_f03::get_highest_scoring_tiebreak( + const std::vector & constraints, + const std::set > & input, + const std::vector & tiebreak_array, double divisor) const { + + // First dump into a set to get sorted data. + std::vector, std::set > > first_sort; + + for (std::set > ::const_iterator pos = input.begin(); pos != + input.end(); ++pos) { + if (debug) { + cout << "Tiebreak: Considering " << sf.show_membership( + *pos) << "\t" << flush; + } + std::vector comp = alt_voter_support(constraints, *pos, + tiebreak_array, divisor); + if (debug) { + copy(comp.begin(), comp.end(), ostream_iterator(cout, " ")); + cout << endl; + } + + first_sort.push_back(pair, std::set >(comp, + *pos)); + } + + // Sort. Since the individual doubles were sorted, this works as a kind + // of leximax comparison: v[0] vs q[0], if they're equal, v[1] vs q[1], + // etc. + sort(first_sort.begin(), first_sort.end()); + //reverse(first_sort.begin(), first_sort.end()); + + // Now get all the sets who are equal to the first as far as the record + // goes (because those are best). + //cout << "Verify: " << first_sort[0].first[0] << "\t" << first_sort[0].first[1] << endl; + + std::set > toRet; + + bool eligible = true; + for (std::vector, std::set > >::const_iterator + spos = + first_sort.begin(); spos != first_sort.end() && + eligible; ++spos) { + cout << "Eligible: " << sf.show_membership(spos->second) << endl; + toRet.insert(spos->second); + + // Check if the next is also eligible. + std::vector, std::set > >::const_iterator + next; + next = spos; + ++next; + if (next != first_sort.end()) { + eligible = (spos->first == next->first); + } + } + + return (toRet); +} + +// Generate all possible councils of this size, then run tiebreak to find which +// we prefer. +std::set mono_webster_f03::naive_sweb(const + std::vector & constraints, + int num_seats, double stepsize) const { + + std::set > psout; + std::set current; + //power_set(num_seats, 0, std::set(), constraints[0].candidates, psout); + sf.choose_set(constraints[0].candidates.size(), 0, num_seats, 0, + current, psout); + + if (debug) { + cout << "Engaging divisor engine." << endl; + } + double divisor; + std::vector tiebreak_array(0); // NOT TO BE ACCESSED + + if (debug) { + cout << "Membership: " << sf.show_membership(constraints[0].candidates) << + endl; + cout << "The combined set for " << num_seats << " seat/s consists of:"; + for (std::set >::const_iterator scpos = psout.begin(); + scpos != psout.end(); ++scpos) { + cout << " {" << sf.show_membership(*scpos) << "}"; + } + cout << ", in all " << psout.size() << endl; + + cout << "Worst case " << endl; + explain(constraints, constraints[0].support * 4); + cout << "--" << endl; + } + + // Highest divisor: 2 * numvoters, because then numvoters will round + // down to 0 and no other coalition can be greater than that one. + // Least divisor used to be / (numseats+0.5), but now that we have the + // minimum rule, might not be so. + std::set > accepted_limiters = get_passed_lowest(constraints, + psout, (constraints[0].support * 2), 1, stepsize, + divisor); + + // Now we have a bunch of sets. + //cout << "Sets after divisor stuff: " << accepted_limiters.size() << endl; + if (accepted_limiters.size() == 1) { + if (debug) { + cout << "Early break." << endl; + } + return (*accepted_limiters.begin()); + } else { + if (debug) { + cout << "Time for breaking some ties, methinks." << endl; + } + accepted_limiters = get_highest_scoring_tiebreak(constraints, + accepted_limiters, tiebreak_array, divisor); + } + + cout << "Now there are " << accepted_limiters.size() << " sets. "; + if (accepted_limiters.size() > 1) { + cout << "Breaking randomly."; + } + cout << "Our sets are:"; + for (std::set >::const_iterator vpos = + accepted_limiters.begin(); vpos != accepted_limiters.end(); ++vpos) { + cout << " {" << sf.show_membership(*vpos) << "}"; + } + cout << endl; + + return (*accepted_limiters.begin()); + +} + +std::set mono_webster_f03::get_brute_force_pure_ballot(int num_seats, + double stepsize, int a, int b, int c, int d, int e, + int f) const { + + std::vector scs; + scs.push_back(sf.construct_sc("ABC", a+b+c+d+e+f)); + scs.push_back(sf.construct_sc("AB", a+c)); + scs.push_back(sf.construct_sc("AC", b+e)); + scs.push_back(sf.construct_sc("BC", d+f)); + scs.push_back(sf.construct_sc("A", a+b)); + scs.push_back(sf.construct_sc("B", b+c)); + scs.push_back(sf.construct_sc("C", d+e)); + + return (naive_sweb(scs, num_seats, stepsize)); +} + +std::set mono_webster_f03::check_one_brute() const { + int mxv = 1 + random() % 10; + + // Must be at least one c, or we can't very well lower, can we? + int a = random() % mxv, b = random() % mxv, c = (random() % mxv) + 1, + d = random() % mxv, e = random() % mxv, f = random() % mxv; + + cout << a << ": ABC" << endl; + cout << b << ": ACB" << endl; + cout << c << ": BAC" << endl; + cout << d << ": BCA" << endl; + cout << e << ": CAB" << endl; + cout << f << ": CBA" << endl; + + cout << "----- Generating original council ---" << endl; + + int num_seats = 1 + random()%2; + //int num_seats = 2; + double stepsize = 0.005; + + std::set origc = get_brute_force_pure_ballot(num_seats, stepsize, a, + b, c, d, e, f); + + // Adjust for monotonicity. + --c; + ++a; + + cout << "----- Generating second (A raised) council ---" << endl; + + std::set newc = get_brute_force_pure_ballot(num_seats, stepsize, a, + b, c, d, e, f); + + std::set aonly; aonly.insert(0); + + cout << "All done. First council was: " << sf.show_membership(origc); + cout << " and the new one is " << sf.show_membership(newc) << endl; + + bool a_in_first = includes(origc.begin(), origc.end(), aonly.begin(), + aonly.end()); + bool a_in_second = includes(newc.begin(), newc.end(), aonly.begin(), + aonly.end()); + + if (origc != newc) { + cout << "Something is happening." << endl; + } + + if (a_in_first && !a_in_second) { + cout << "Oh dear, A vanished after being raised." << endl; + cout << "Where's my kaishakunin?" << endl; + assert(1 != 1); + } + + return (origc); +} + +std::list mono_webster_f03::get_council(size_t council_size, + size_t num_candidates, + const election_t & ballots) const { + + assert(council_size > 0 && council_size <= num_candidates); + assert(council_size <= num_candidates); + assert(num_candidates > 0); + + // Turn the input into a list of solid coalitions. (Put this code + // elsewhere?) + + // Disable the "automatic tiebreak" feature, because we will be doing + // our own tiebreaking, TYVM. + // (Really symptomatic of a greater problem - that the mono-raise check + // can't distinguish chance from bias, and therefore thinks anything + // resolved by a roll of the dice violates mono-raise.) + /*multimap > init_coalitions = + get_solid_coalitions(ballots, num_candidates, + -1); + + std::vector scs; + + for (multimap >::const_reverse_iterator pos= + init_coalitions.rbegin(); pos != init_coalitions.rend(); + ++pos) { + solid_coalition q; + copy(pos->second.begin(), pos->second.end(), inserter( + q.candidates, q.candidates.begin())); + q.support = pos->first; + scs.push_back(q); + } + + // Turn it into the standardized format. + sort(scs.begin(), scs.end(), greater());*/ + + std::vector scs = get_acquiescing_coalitions(ballots, + num_candidates); + + // Stepsize should probably at most be 1/numvoters. If the granularity + // is 0.1, 0.1/numvoters, etc.. Later. + + double stepsize = min(0.01, 1.0/scs.begin()->support); + std::set council = naive_sweb(scs, council_size, stepsize); + + // But the output wants it in another format. + std::list toRet; + copy(council.begin(), council.end(), inserter(toRet, toRet.begin())); + + return (toRet); +} \ No newline at end of file diff --git a/src/multiwinner/rusty/mw_kemeny2_34e.h b/src/multiwinner/rusty/mw_kemeny2_34e.h new file mode 100644 index 0000000..93c06bb --- /dev/null +++ b/src/multiwinner/rusty/mw_kemeny2_34e.h @@ -0,0 +1,377 @@ +#pragma once + +// Not centroid. Geometric median. + +// Brute force solution to the multiwinner Kemeny idea + +// Multiwinner Kemeny: find the ranks that constitute the centroid ranks, +// subject to that each candidate can be mentioned in each rank position +// (column) no more than once. + +// Ranks are struct vectors of ints, easier that way. + +#include "multiwinner/methods.h" +#include "tools/ballot_tools.h" + +#include +#include +#include +#include + +#include "aux/qballot.h" + +using namespace std; + +class mw_kemeny2_34e : public multiwinner_method { + + private: + ballot_tools btools; + + // QnD + std::vector > tournament_matrix(const + std::vector & rank, int maxcand) const; + int p_distance(const std::vector & a, + const std::vector & b, int maxcand) const; + int tiebreak(const std::vector > & a, int maxcand) const; + void permutation(int input, std::vector & rank) const; + void print_rank(const std::vector & rank) const; + pair distance_to_closest(const std::vector & + our_rank, const std::vector > & + centroids, int numcands) const; + int get_score(const std::vector & ballots, + const std::vector > & centroids, + std::vector & support, + int numcands) const; + pair > verify( + const std::vector > & centroids, + const std::vector & support, int numcands, + bool fill_list) const; + q_ballot build_ballot(int strength, string order) const; + int factorial(int n) const; + void recurse_ranking(const std::vector & ballots, + const std::vector & arch, + std::vector > & recordholder, + int & record, std::vector > & centroids, + int pos, int lastnum, + int numcands, int fact) const; + + public: + std::list get_council(size_t council_size, size_t num_candidates, + const election_t & ballots) const; + + string name() const { + return ("SL-Kemeny (EXP, 34e)"); + } +}; + +std::vector > mw_kemeny2_34e::tournament_matrix( + const std::vector & rank, + int maxcand) const { + + std::vector > toRet(maxcand,std::vector(maxcand, + false)); + + for (size_t counter = 0; counter < rank.size(); ++counter) + for (size_t sec = counter+1; sec < rank.size(); ++sec) { + toRet[rank[counter]][rank[sec]] = true; + } + + return (toRet); +} + +int mw_kemeny2_34e::p_distance(const std::vector & a, + const std::vector & b, + int maxcand) const { + + std::vector > cmat_a = tournament_matrix(a, maxcand), + cmat_b = tournament_matrix(b, maxcand); + + int mismatch_count = 0; + + for (int counter = 0; counter < maxcand; ++counter) + for (int sec = counter+1; sec < maxcand; ++sec) + if (cmat_a[counter][sec] ^ cmat_b[counter][sec]) { + ++mismatch_count; + } + + return (mismatch_count); +} + +// Improvised tiebreak: returns sum distance between centroids (all pairs). +// Is greater better, or less better? We'll try less for now. +int mw_kemeny2_34e::tiebreak(const std::vector > & a, + int maxcand) const { + + int sum = 0; + + for (size_t counter = 0; counter < a.size(); ++counter) + for (size_t sec = counter+1; sec < a.size(); ++sec) { + sum += p_distance(a[counter], a[sec], maxcand); + } + + return (sum); +} + +void mw_kemeny2_34e::permutation(int input, + std::vector & rank) const { + for (size_t counter = 0; counter < rank.size(); ++counter) { + int radix = rank.size() - counter; + int s = input % radix; + input = (input - s)/radix; + + swap(rank[counter], rank[counter + s]); + } +} + +void mw_kemeny2_34e::print_rank(const std::vector & rank) const { + + for (size_t counter = 0; counter < rank.size(); ++counter) { + if (counter > 0) { + cout << ">"; + } + cout << (char)(rank[counter] + 'A'); + } + //cout << endl; +} + +// Returns pair where the first is the record distance and the second is +// the recordholder. +pair mw_kemeny2_34e::distance_to_closest(const std::vector & + our_rank, + const std::vector > & centroids, int numcands) const { + + pair record(-1, -1); + + for (size_t counter = 0; counter < centroids.size(); ++counter) { + int candidate = p_distance(our_rank, centroids[counter], + numcands); + + if (record.first == -1 || record.first > candidate) { + record.first = candidate; + record.second = counter; + } + } + + return (record); +} + +int mw_kemeny2_34e::get_score(const std::vector & ballots, + const std::vector > & centroids, + std::vector & support, int numcands) const { + + int sum = 0; + + for (size_t counter = 0; counter < ballots.size(); ++counter) { + pair component = distance_to_closest(ballots[counter]. + rank, centroids, numcands); + + sum += ballots[counter].strength * component.first; + support[component.second]++; + + // cout << counter << "("; print(ballots[counter].rank); cout << ") is closest to " << component.second << " with " << ballots[counter].strength << " * " << component.first << " = " << ballots[counter].strength * component.first << endl; + } + //cout << endl; + + return (sum); +} + +pair > mw_kemeny2_34e::verify( + const std::vector > & centroids, + const std::vector & support, int numcands, + bool fill_list) const { + // Proportionality hack. This is more like divisor proportionality + // than Droop proportionality: we take the fractions closest to each + // ordering and adjust the divisor so the sum is equal to the number + // of seats. Then we take the appropriate number from each ranking and + // see if any single member appears twice. If so, verification fails, + // otherwise succeeds. + // SLOW, but PoC. + + // No. We can just use Sainte-LaguĂ«. + std::vector iters(centroids.size(), 0); + std::vector seen(numcands, false); + int total_iter; + + int numseats = centroids.size(); + std::list positions; + + //cout << "CALL!" << endl; + + for (total_iter = 0; total_iter < numseats; ++total_iter) { + //if (fill_list) + // cout << "Support[" << total_iter << "] = " << support[total_iter] << endl; + int recordholder = 0; + for (size_t counter = 1; counter < centroids.size(); ++counter) { + if (support[counter]/(2.0*iters[counter]+1) > + support[recordholder]/ + (2.0*iters[recordholder]+1)) { + recordholder = counter; + } + } + + /*if (fill_list) + cout << "Picked " << iters[recordholder] << "th from " << + recordholder << " with adj. support " << + support[recordholder] / (2.0 * iters[recordholder]+1) + << endl;*/ + + + int cand = centroids[recordholder][iters[recordholder]++]; + + if (fill_list) { + positions.push_back(cand); + } + if (seen[cand]) { + return (pair >(false, positions)); + } else { + seen[cand] = true; + } + } + + return (pair >(true, positions)); // for now +} + +q_ballot mw_kemeny2_34e::build_ballot(int strength, string order) const { + + q_ballot toret; + toret.strength = strength; + + for (size_t counter = 0; counter < order.size(); ++counter) { + toret.rank.push_back(order[counter] - 'A'); + } + + return (toret); +} + +int mw_kemeny2_34e::factorial(int n) const { + if (n <= 1) { + return (n); + } + return (n * factorial(n-1)); +} + +// This can be sped up by having the permutation function return in +// sorted order. Then we can do something like "if first is A, start next at +// B first". PROOF OF CONCEPT! + +// Centroids is the vector of ranks that constitute our centroid (really) set. +// Pos is the position (determining first member, second member), numerical +// ranking is used so we don't check "B, A" if we've already checked "A, B" +void mw_kemeny2_34e::recurse_ranking(const std::vector & ballots, + const std::vector & arch, + std::vector > & recordholder, int & record, + std::vector > & centroids, int pos, + int lastnum, int numcands, int fact) const { + + int counter; + int maximum = centroids.size(); + + /*if (pos != maximum) { + std::cout << "MW-Kemeny: Recursing on " << pos << " of " << maximum << std::endl; + }*/ + + if (pos == maximum) { + // REQUIRED for number ten! + // Why? Find out. + std::vector support(centroids.size(), 0); + int cand = get_score(ballots, centroids, support, numcands); + + if (cand > record && record != -1) { + return; // no point verifying + } + + if (!verify(centroids, support, numcands, false).first) { + return; + } + + //cout << "Passed and stuff" << endl; + + bool replace_with_new = cand < record || record == -1; + // Run improvised tiebreak. Whoever has the least distance + // between centroids wins (or should this be greatest?) + // (Must be > to pass the DPC?) + if (!replace_with_new && cand == record) + replace_with_new = (tiebreak(centroids, numcands) > + tiebreak(recordholder, numcands)); + + // Beware ties + if (replace_with_new) { + // cout << "Replace" << endl; + record = cand; + recordholder = centroids; + } + + return; + } + + //if (offset == numcands) return; + //if (numcands - offset < maximum - pos) return; // not enough space + + // Recurse! + // See other example for why we do it like this + //recurse_ranking(ballots, arch, recordholder, record, centroids, pos, + // offset+1, numcands, fact); + + for (counter = lastnum; counter < fact; ++counter) { + //if (pos == 0) cout << counter << " of " << fact << endl; + centroids[pos] = arch; + permutation(counter, centroids[pos]); + + recurse_ranking(ballots, arch, recordholder, record, + centroids, pos+1, lastnum+1, numcands, fact); + } +} + + +// Make properly recursive later + +std::list mw_kemeny2_34e::get_council(size_t council_size, + size_t num_candidates, + const election_t & vballots) const { + + election_t compressed_ballots = btools.compress(vballots); + + std::vector rank(num_candidates, 0); + iota(rank.begin(), rank.end(), 0); + + assert(num_candidates < 12); + + std::vector xlat_ballots; + // translate here + // Doesn't handle equal rank, etc. + for (election_t::const_iterator pos = compressed_ballots.begin(); + pos != compressed_ballots.end(); ++pos) { + q_ballot next_one; + next_one.strength = pos->get_weight(); + for (ordering::const_iterator spos = pos->contents.begin(); + spos != pos->contents.end(); ++spos) { + next_one.rank.push_back(spos->get_candidate_num()); + } + xlat_ballots.push_back(next_one); + } + + int record = -1; + std::vector > recordholder(council_size, + std::vector(num_candidates, -1)); + std::vector > centroids(council_size); + + recurse_ranking(xlat_ballots, rank, recordholder, record, centroids, 0, + 0, num_candidates, factorial(num_candidates)); + + std::vector support(centroids.size(), 0); + /*int cand = get_score(xlat_ballots, recordholder, support, + num_candidates);*/ + return (verify(recordholder, support, num_candidates, true).second); + + std::list toRet; + + std::vector already(num_candidates, false); + + for (size_t counter = 0; counter < recordholder.size(); ++counter) { + toRet.push_back(recordholder[counter][0]); + assert(!already[recordholder[counter][0]]); + already[recordholder[counter][0]] = true; + } + + return (toRet); +} \ No newline at end of file diff --git a/src/multiwinner/rusty/mw_kemeny_db0.h b/src/multiwinner/rusty/mw_kemeny_db0.h new file mode 100644 index 0000000..6fe45f0 --- /dev/null +++ b/src/multiwinner/rusty/mw_kemeny_db0.h @@ -0,0 +1,373 @@ +#pragma once + +// Not centroid. Geometric median. + +// Brute force solution to the multiwinner Kemeny idea + +// Multiwinner Kemeny: find the ranks that constitute the centroid ranks, +// subject to that each candidate can be mentioned in each rank position +// (column) no more than once. + +// Ranks are struct vectors of ints, easier that way. + +#include "multiwinner/methods.h" +#include "tools/ballot_tools.h" +#include "aux/qballot.h" + +#include +#include +#include +#include + +using namespace std; + +/*bool same_rank(const ordering & a, const ordering & b) { + + ordering::const_iterator apos = a.begin(), bpos = b.begin(), + alast = a.begin(), blast = b.begin(); + + ++apos; ++bpos; + + while (apos != a.end() && bpos != b.end()) { + // Hm, but what about equal rank? Should work, since set orders + // those the same way + if (alast->get_candidate_num() != blast->get_candidate_num()) + return(false); + + if ((alast->get_score() <= apos->get_score()) != + (blast->get_score() <= bpos->get_score())) + return(false); + + if ((alast->get_score() == apos->get_score()) != + (blast->get_score() == bpos->get_score())) + return(false); + + alast = apos++; + blast = bpos++; + + } + + return(true); +} + + +election_t compress(const election_t & uncompressed) { + + // Simple n^2 + + election_t compressed = uncompressed; + + for (election_t::iterator check = compressed.begin(); + check != compressed.end(); ++check) { + election_t::iterator check_against = check; + ++check_against; + while (check_against != compressed.end()) { + if (same_rank(check->contents, + check_against->contents)) { + check->weight += check_against->weight; + check_against = compressed.erase( + check_against); + } else + ++check_against; + } + } + + return(compressed); +}*/ + +class mw_kemeny : public multiwinner_method { + + private: + // QnD + ballot_tools btools; + + std::vector > tournament_matrix(const + std::vector & rank, size_t maxcand) const; + int p_distance(const std::vector & a, + const std::vector & b, size_t maxcand) const; + int tiebreak(const std::vector > & a, + size_t maxcand) const; + void permutation(int input, std::vector & rank) const; + void print_rank(const std::vector & rank) const; + pair distance_to_closest(const std::vector & + our_rank, const std::vector > & + centroids, int numcands) const; + int get_score(const std::vector & ballots, + const std::vector > & centroids, + int numcands) const; + bool verify(const std::vector > & centroids, + int numcands) const; + q_ballot build_ballot(int strength, string order) const; + int factorial(int n) const; + void recurse_ranking(const std::vector & ballots, + const std::vector & arch, + std::vector > & recordholder, + int & record, std::vector > & centroids, + int pos, int offset, + int numcands, int fact) const; + + public: + std::list get_council(size_t council_size, size_t num_candidates, + const election_t & ballots) const; + + string name() const { + return ("MW-Kemeny (EXP)"); + } +}; + +std::vector > mw_kemeny::tournament_matrix( + const std::vector & rank, + size_t maxcand) const { + + std::vector > toRet(maxcand,std::vector(maxcand, + false)); + + for (size_t counter = 0; counter < rank.size(); ++counter) + for (size_t sec = counter+1; sec < rank.size(); ++sec) { + toRet[rank[counter]][rank[sec]] = true; + } + + return (toRet); +} + +int mw_kemeny::p_distance(const std::vector & a, + const std::vector & b, + size_t maxcand) const { + + std::vector > cmat_a = tournament_matrix(a, maxcand), + cmat_b = tournament_matrix(b, maxcand); + + int mismatch_count = 0; + + for (size_t counter = 0; counter < maxcand; ++counter) + for (size_t sec = counter+1; sec < maxcand; ++sec) + if (cmat_a[counter][sec] ^ cmat_b[counter][sec]) { + ++mismatch_count; + } + + return (mismatch_count); +} + +// Improvised tiebreak: returns sum distance between centroids (all pairs). +// Is greater better, or less better? We'll try less for now. +int mw_kemeny::tiebreak(const std::vector > & a, + size_t maxcand) const { + + int sum = 0; + + for (size_t counter = 0; counter < a.size(); ++counter) + for (size_t sec = counter+1; sec < a.size(); ++sec) { + sum += p_distance(a[counter], a[sec], maxcand); + } + + return (sum); +} + +void mw_kemeny::permutation(int input, std::vector & rank) const { + for (size_t counter = 0; counter < rank.size(); ++counter) { + int radix = rank.size() - counter; + int s = input % radix; + input = (input - s)/radix; + + swap(rank[counter], rank[counter + s]); + } +} + +void mw_kemeny::print_rank(const std::vector & rank) const { + + for (size_t counter = 0; counter < rank.size(); ++counter) { + if (counter > 0) { + cout << ">"; + } + cout << (char)(rank[counter] + 'A'); + } + //cout << endl; +} + +// Returns pair where the first is the record distance and the second is +// the recordholder. +pair mw_kemeny::distance_to_closest(const std::vector & + our_rank, + const std::vector > & centroids, int numcands) const { + + pair record(-1, -1); + + for (size_t counter = 0; counter < centroids.size(); ++counter) { + int candidate = p_distance(our_rank, centroids[counter], + numcands); + + if (record.first == -1 || record.first > candidate) { + record.first = candidate; + record.second = counter; + } + } + + return (record); +} + +int mw_kemeny::get_score(const std::vector & ballots, + const std::vector > & centroids, int numcands) const { + + int sum = 0; + + for (size_t counter = 0; counter < ballots.size(); ++counter) { + pair component = distance_to_closest(ballots[counter]. + rank, centroids, numcands); + + sum += ballots[counter].strength * component.first; + + // cout << counter << "("; print(ballots[counter].rank); cout << ") is closest to " << component.second << " with " << ballots[counter].strength << " * " << component.first << " = " << ballots[counter].strength * component.first << endl; + } + //cout << endl; + + return (sum); +} + +bool mw_kemeny::verify(const std::vector > & centroids, + int numcands) const { + // No candidate can be mentioned more than once in a single rank. + // Maybe this is unneccessary and the best centroids will have this + // property anyway. + + for (int column = 0; column < 1; + ++column) { //centroids[0].size(); ++column) { + std::vector seen(numcands, false); + for (size_t sec = 0; sec < centroids.size(); ++sec) + if (seen[centroids[sec][column]]) { + return (false); + } else { + seen[centroids[sec][column]] = true; + } + } + + return (true); // for now +} + +q_ballot mw_kemeny::build_ballot(int strength, string order) const { + + q_ballot toret; + toret.strength = strength; + + for (size_t counter = 0; counter < order.size(); ++counter) { + toret.rank.push_back(order[counter] - 'A'); + } + + return (toret); +} + +int mw_kemeny::factorial(int n) const { + if (n <= 1) { + return (n); + } + return (n * factorial(n-1)); +} + +// This can be sped up by having the permutation function return in +// sorted order. Then we can do something like "if first is A, start next at +// B first". PROOF OF CONCEPT! + +// Centroids is the vector of ranks that constitute our centroid (really) set. +// Pos is the position (determining first member, second member), numerical +// ranking is used so we don't check "B, A" if we've already checked "A, B" +void mw_kemeny::recurse_ranking(const std::vector & ballots, + const std::vector & arch, + std::vector > & recordholder, int & record, + std::vector > & centroids, int pos, + int offset, int numcands, int fact) const { + + int maximum = centroids.size(); + + if (pos == maximum) { + // REQUIRED for number ten! + // Why? Find out. + if (!verify(centroids, numcands)) { + return; + } + + int cand = get_score(ballots, centroids, numcands); + + bool replace_with_new = cand < record || record == -1; + // Run improvised tiebreak. Whoever has the least distance + // between centroids wins (or should this be greatest?) + // (Must be > to pass the DPC?) + if (!replace_with_new && cand == record) + replace_with_new = (tiebreak(centroids, numcands) > + tiebreak(recordholder, numcands)); + + // Beware ties + if (replace_with_new) { + record = cand; + recordholder = centroids; + } + + return; + } + + if (offset == numcands) { + return; + } + if (numcands - offset < maximum - pos) { + return; // not enough space + } + + // Recurse! + // See other example for why we do it like this + recurse_ranking(ballots, arch, recordholder, record, centroids, pos, + offset+1, numcands, fact); + + for (int counter = offset; counter < fact; ++counter) { + centroids[pos] = arch; + permutation(counter, centroids[pos]); + + recurse_ranking(ballots, arch, recordholder, record, + centroids, pos+1, offset+1, numcands, fact); + } +} + + +// Make properly recursive later + +std::list mw_kemeny::get_council(size_t council_size, + size_t num_candidates, const election_t & vballots) const { + + election_t compressed_ballots = btools.compress(vballots); + + std::vector rank(num_candidates, 0); + iota(rank.begin(), rank.end(), 0); + + assert(num_candidates < 12); + + std::vector xlat_ballots; + // translate here + // Doesn't handle equal rank, etc. + for (election_t::const_iterator pos = compressed_ballots.begin(); + pos != compressed_ballots.end(); ++pos) { + q_ballot next_one; + next_one.strength = pos->get_weight(); + for (ordering::const_iterator spos = pos->contents.begin(); + spos != pos->contents.end(); ++spos) { + next_one.rank.push_back(spos->get_candidate_num()); + } + xlat_ballots.push_back(next_one); + } + + int record = -1; + std::vector > recordholder(council_size, + std::vector(num_candidates, -1)); + std::vector > centroids(council_size); + + recurse_ranking(xlat_ballots, rank, recordholder, record, centroids, 0, + 0, num_candidates, factorial(num_candidates)); + + std::list toRet; + + std::vector already(num_candidates, false); + + for (size_t counter = 0; counter < recordholder.size(); ++counter) { + toRet.push_back(recordholder[counter][0]); + assert(!already[recordholder[counter][0]]); + already[recordholder[counter][0]] = true; + } + + return (toRet); +} \ No newline at end of file diff --git a/src/multiwinner/rusty/set_functs.h b/src/multiwinner/rusty/set_functs.h new file mode 100644 index 0000000..05725a2 --- /dev/null +++ b/src/multiwinner/rusty/set_functs.h @@ -0,0 +1,188 @@ +#pragma once + +// Set generation functions common to "whittle down by constraints" divisor-type +// methods. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aux/dsc.h" +#include "multiwinner/methods.h" +#include "tools/ballot_tools.h" +#include "multiwinner/rusty/setwise/coalition.h" +#include "tools/tools.h" + +using namespace std; + +class set_functions { + + public: + solid_coalition construct_sc(string memberset, + double support) const; + string show_membership(const std::set & x) const; + string show_membership(const solid_coalition & x) const; + void power_set(int numcands, int curcand, std::set current, + std::set remaining, + std::set > & power_set_out) const; + void choose_set(int numcands, int curcand, int how_many, + int cur_chosen, std::set & current, + std::set > & choose_out) const; + int share_candidates(const std::set & smaller, + const std::set & larger, + int early_break) const; + int share_candidates(const solid_coalition & check_against, + const std::set & proposed, + int early_break) const; +}; + +// Perhaps these should be in coalition.cc + +solid_coalition set_functions::construct_sc(string memberset, + double support) const { + solid_coalition output; + + for (size_t counter = 0; counter < memberset.size(); ++counter) { + output.candidates.insert(memberset[counter]-'A'); + } + + output.support = support; + return (output); +} + +string set_functions::show_membership(const std::set & x) const { + string out; + + for (std::set::const_iterator p = x.begin(); p != x.end(); ++p) { + out += 'A' + *p; + } + + return (out); +} + +string set_functions::show_membership(const solid_coalition & x) const { + return (show_membership(x.candidates)); +} + +void set_functions::power_set(int numcands, int curcand, + std::set current, + std::set remaining, std::set > & power_set_out) const { + + if (curcand == numcands) { + power_set_out.insert(current); + return; + } else { + std::set::iterator pos = remaining.begin(), bakpos; + while (pos != remaining.end()) { + std::set newcurr = current, newrema = remaining; + newcurr.insert(*pos); + newrema.erase(newrema.find(*pos)); + power_set(numcands, curcand+1, newcurr, newrema, + power_set_out); + ++pos; + } + } +} + +void set_functions::choose_set(int numcands, int curcand, int how_many, + int cur_chosen, std::set & current, + std::set > & choose_out) const { + + // Some sanity checks. Pick k out of n with k > n doesn't make sense. + if (curcand == 0) { + assert(how_many <= numcands); + } + + // If cur_chosen = how_many, insert set into output. + // If numcands = curcand, return. Else... + + // Add a candidate to the set. As we do this in ascending order, + // we know this candidate has to be at the end. Recurse with + // cur_chosen +1, curcand+1. Then remove and recurse with + // cur_chosen, curcand + 1. + + // Recursion, dudes. You dig it? Might be possible to speed this up + // even more by early lookahead stuff. + + // Perhaps if (how_many - cur_chosen > numcands - curcand) return. + // Something like that. Experiment another time. + if (cur_chosen == how_many) { + choose_out.insert(current); + } + + // If we have exhausted all candidates or there's no way of making + // up the difference, bail outta here. + if (how_many - cur_chosen > numcands - curcand || numcands == curcand) { + return; + } + + // Hint that the next candidate will be added at the end (because it + // will). Removed. + current.insert(curcand); + choose_set(numcands, curcand+1, how_many, cur_chosen+1, current, + choose_out); + // Remove the one we added. + current.erase(--current.end()); + choose_set(numcands, curcand+1, how_many, cur_chosen, current, + choose_out); +} + +// Checks whether smaller shares any candidates with larger. The runtime should +// be (smaller.size() * ln(larger.size())) worst case. +// The early_break parameter means "stop once we've found this many matches". +// If -1, it doesn't stop until it's done. +int set_functions::share_candidates(const std::set & smaller, + const std::set & larger, int early_break) const { + + // Make use of the fact that both are sorted to produce an algorithm + // of this kind: + // For each position in the smaller set + // Advance pointer of the larger set until it's equal or + // above, or we hit the end. + // If equal, increment shares and check if we're done. + // If we hit the end, return how many matches we got. + // Next + + // If we just want a pass-fail, there are all sorts of inventive tricks + // we could use to break if there's no way we can reach early_break. + // However, that would ruin the elegance here. + + std::set::const_iterator spos, lpos = larger.begin(); + int matches = 0; + + for (spos = smaller.begin(); spos != smaller.end(); ++spos) { + // This is first so we return early for a match like >= 0. + if (matches == early_break && early_break > -1) { + return (matches); + } + + while (lpos != larger.end() && *lpos < *spos) { + ++lpos; + } + + if (lpos == larger.end()) { + return (matches); + } + + if (*spos == *lpos) { + ++matches; + } + } + + return (matches); +} + +int set_functions::share_candidates(const solid_coalition & check_against, + const std::set & proposed, int early_break) const { + + if (check_against.candidates.size() < proposed.size()) + return (share_candidates(check_against.candidates, proposed, + early_break)); + else return (share_candidates(proposed, check_against.candidates, + early_break)); +} \ No newline at end of file diff --git a/src/multiwinner/rusty/setwise/coalition.h b/src/multiwinner/rusty/setwise/coalition.h new file mode 100644 index 0000000..5a65f6e --- /dev/null +++ b/src/multiwinner/rusty/setwise/coalition.h @@ -0,0 +1,164 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "common/ballots.cc" + +using namespace std; + +class solid_coalition { + public: + set candidates; + double support; + bool less(const solid_coalition & rhs) const { + if (support != rhs.support) { + return (support < rhs.support); + } else return (candidates.size() < rhs.candidates. + size()); + } + bool equal(const solid_coalition & rhs) const { + return (support == rhs.support && candidates.size() == + rhs.candidates.size()); + } + bool operator< (const solid_coalition & rhs) const { + return (less(rhs)); + } + + bool operator> (const solid_coalition & rhs) const { + return (!less(rhs) && !equal(rhs)); + } +}; + +class sorted_reference { + public: + const solid_coalition * check; + double overriding_support; + + bool less(const sorted_reference & rhs) const { + if (overriding_support != rhs.overriding_support) + return (overriding_support < + rhs.overriding_support); + else return (check->candidates.size() < + rhs.check->candidates.size()); + } + + bool equal(const sorted_reference & rhs) const { + return (overriding_support == rhs.overriding_support && + check->candidates.size() == + rhs.check->candidates.size()); + } + + bool operator<(const sorted_reference & rhs) const { + return (less(rhs)); + } + + bool operator>(const sorted_reference & rhs) const { + return (!less(rhs) && !equal(rhs)); + } + + sorted_reference(const solid_coalition * check_in) { + check = check_in; + } +}; + +// Hopefully quicker! DAC style for now, do DSC later. +// This is slow if there are lots of candidates and truncation going on, e.g. +// 65000 candidates and everybody bullet-votes. In that case, set will generate +// 1+2+..+65000 ints per, this will generate 65000^2 bits per. +// It would be better to post-generate the "done" ballots, but is that worth it? +// I.e. count AB|, C|, AC|, then later complete these in one swoop each... Hm. +// DSC doesn't have to deal with that. + +struct simple_hash : unary_function, size_t> { + + size_t operator()(const vector & a) const { + + size_t ct = 2166136261;//1337; + + // bad hash. Just to check! + int counter = 1; + for (vector::const_iterator p = a.begin(); p != a.end(); + ++p) { + if (*p) { + ct ^= counter; + } + ct *= 16777619; + ++counter; + } + + return (ct); + } +}; + +vector get_acquiescing_coalitions( + const list & input, int num_candidates) { + + // For each ballot group, + // For each candidate, + // Add that candidate to the set + // Increment the map to the set by the group's power. + // Next + // Clear the set + // Next + + // Might try sorting by candidate, later, to see if it helps. + + std::unordered_map, double, simple_hash> forwards; + vector current_coal(num_candidates, false); + vector seen_candidates(num_candidates, false); // Used for DAC + + for (list::const_iterator pos = input.begin(); pos != + input.end(); ++pos) { + + fill(current_coal.begin(), current_coal.end(), false); + + for (ordering::const_iterator inner_pos = pos->contents.begin(); + inner_pos != pos->contents.end(); ++inner_pos) { + current_coal[inner_pos->get_candidate_num()] = true; + forwards[current_coal] += pos->get_weight(); + } + + // Now add all remaining candidates, one at a time. + for (vector::iterator cpos = current_coal.begin(); cpos + != current_coal.end(); ++cpos) { + if (*cpos) { + continue; + } + *cpos = true; + forwards[current_coal] += pos->get_weight(); + } + } + + // Dump to a vector. + vector output; + output.reserve(forwards.size()); + solid_coalition to_add; + + for (std::unordered_map, double, simple_hash>:: + const_iterator mpos = forwards.begin(); + mpos != forwards.end(); ++mpos) { + to_add.candidates.clear(); + int rictr = 0; + for (vector::const_iterator cipos = mpos->first.begin(); + cipos != mpos->first.end(); ++cipos) { + if (*cipos) { + to_add.candidates.insert(rictr); + } + ++rictr; + } + to_add.support = mpos->second; + + output.push_back(to_add); + } + + // Finally, sort. + sort(output.begin(), output.end(), greater()); + + return (output); +} diff --git a/src/multiwinner/set_webster.h b/src/multiwinner/set_webster.h index 752474b..d012bce 100644 --- a/src/multiwinner/set_webster.h +++ b/src/multiwinner/set_webster.h @@ -9,6 +9,10 @@ // try every possible set inside a bisection search that finds the greatest value // of x. +// Note, this may be different from the earlier (thirdelect etc) versions of +// Set Webster, where I seem to have been trying to make somethind that reduces +// to DAC or DSC in the single winner case. + #include "coalitions/coalitions.h" #include "multiwinner/exhaustive/set_webster_helper.h"