Commit 4125ed4a authored by Jason Rhinelander's avatar Jason Rhinelander

Added Election base class and FPTP instance

FPTP = First Past The Post
parent 8528a724
#pragma once
#include <functional>
#include <eris/Agent.hpp>
#include <eris/InterOptimizer.hpp>
namespace voting {
using eris::eris_id_t;
/** A election agent for the voting model. This class isn't really an agent that makes an
* optimization decision, it simply sets an election state every x periods. In this special
* election state, regular polling and party movement stops and an election takes place, with this
* class determining the winner.
*
* This is an abstract class in that it doesn't actually implement calculation of the winner;
* subclasses must provide the election() method to do that.
*/
class Election : public eris::Agent {
public:
/** Creates an election that occurs every `period` simulation periods.
*/
Election(int period);
/** Creates an election that occurs every time the given function/lambda returns true. The
* function will be called exactly once per inter-optimization, and, when it returns true,
* will cause the following period to be an election round.
*/
Election(std::function<bool()> callElection);
/** Returns true if the current period is an election period.
*/
bool electionPeriod();
/** Conducts an election at the current positions, returns the winner. Abstract method.
*/
virtual eris_id_t election() = 0;
/** Advances, checking whether an election should be called. If so, electionPeriod() will
* return true until the following advance.
*/
virtual void advance() override;
private:
bool election_period_ = false;
std::function<bool()> election_;
};
}
// vim:tw=100
#pragma once
#include <eris/Random.hpp>
#include "voting/Election.hpp"
#include "voting/Party.hpp"
#include "voting/Voter.hpp"
#include <unordered_map>
#include <stdexcept>
namespace voting {
/** First-past-the-post election mechanism. The party with the largest number of votes wins. If
* two or more parties are tied for the highest number of votes, the winner is determined randomly.
* Each voter votes for the nearest party, randomizing to resolve ties.
*/
class FPTP : public Election {
public:
using Election::Election;
/** Conducts and election (if one hasn't been performed already) and returns the winner.
*/
virtual eris_id_t election() override;
/** Resets the winner and vote tallies when advancing.
*/
virtual void advance() override;
/** Returns the map of parties to votes each party received in the election. Performs the
* election first, if it hasn't yet been performed.
*/
virtual const std::unordered_map<eris_id_t, int>& votes();
private:
eris_id_t winner_ = 0;
std::unordered_map<eris_id_t, int> votes_;
};
}
// vim:tw=100
#include "voting/Election.hpp"
namespace voting {
Election::Election(int period) {
long counter;
election_ = [period,&counter]() -> bool {
if (++counter >= period) {
counter = 0;
return true;
}
return false;
};
}
Election::Election(std::function<bool()> callElection) : election_(callElection) {}
}
#include "voting/election/FPTP.hpp"
namespace voting {
eris_id_t FPTP::election() {
if (!electionPeriod()) throw std::runtime_error("election() called when not in an election period");
// Already had an election
if (winner_ > 0) return winner_;
auto parties = simulation()->agentFilter<Party>();
if (parties.empty()) throw std::runtime_error("Cannot conduct an election without any parties");
auto voters = simulation()->agentFilter<Voter>();
if (voters.empty()) throw std::runtime_error("Cannot conduct an election without any voters");
votes_.clear();
for (auto &p : parties)
votes_[p.first] = 0;
for (auto &v : voters) {
eris_id_t closest = 0;
double min_dist = 0.0;
int identical = 0;
for (auto &p : parties) {
double dist = v.second->distance(p.second);
if (closest == 0 or dist < min_dist) {
closest = p.first;
min_dist = dist;
identical = 1;
}
else if (dist == min_dist) {
// Found a party exactly the same distance away; we need to change randomly, such
// that each equal-distance party has an equal chance of being chosen
if (++identical > 1) {
if (std::bernoulli_distribution(1.0 / identical)(eris::Random::rng())) {
closest = p.first;
}
}
}
}
votes_[closest]++;
}
int most = -1, most_count = 0;
for (auto &p : votes_) {
if (p.second > most) {
winner_ = p.first;
most = p.second;
most_count = 1;
}
else if (p.second == most) {
most_count++;
// Decide a tie with randomization. Change to this party with probability 1/T, where T
// is the number of ties found so far. (Thus each party will have an equal probability
// of winning).
if (std::bernoulli_distribution(1.0 / most_count)(eris::Random::rng())) {
winner_ = p.first;
}
}
}
return winner_;
}
const std::unordered_map<eris_id_t, int>& FPTP::votes() {
if (not winner_ > 0) election();
return votes_;
}
void FPTP::advance() {
winner_ = 0;
votes_.clear();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment