Commit e2972cac authored by Jason Rhinelander's avatar Jason Rhinelander

Updated for recent eris changes; minor changes

- Optimizer overhaul (gets rid of PartyMover in favour of having parties
  register themselves as interoptimizers and handle movement themselves).
- Eris interface change, getting rid of seperate createThis, createThat
  in favour of a single create<...> method.
- Got rid of short-lived (now removed) simulation->threadModel
- Locate eris via a symlink rather than looking at ../eris
- Updated for PositionalAgent interface change (no longer accepting
  double arguments, instead requiring Position-convertible arguments
  such as initializer_list<double>s).
- Added --skip-file option to skip creating a results file
parent a605a3ba
......@@ -30,9 +30,9 @@ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/results)
# If we're building in "foo", look for ../eris/foo for eris build dir, then
# fall back to ../eris/build if that doesn't work.
get_filename_component(BUILD_BASENAME "${CMAKE_BINARY_DIR}" NAME)
set(ERIS_INCLUDE ../eris/include)
set(ERIS_BUILD_SAME "../eris/${BUILD_BASENAME}")
set(ERIS_BUILD_BUILD ../eris/build)
set(ERIS_INCLUDE eris/include)
set(ERIS_BUILD_SAME "eris/${BUILD_BASENAME}")
set(ERIS_BUILD_BUILD eris/build)
include_directories(${ERIS_INCLUDE})
find_library(
ERIS_LIB
......
../eris
\ No newline at end of file
#pragma once
#include <functional>
#include <eris/Agent.hpp>
#include <eris/InterOptimizer.hpp>
#include <eris/Optimize.hpp>
namespace voting {
......@@ -15,7 +15,7 @@ using eris::eris_id_t;
* 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 {
class Election : public eris::Agent, public virtual eris::interopt::Advance {
public:
/** Creates an election that occurs every `period` simulation periods.
*/
......@@ -38,7 +38,7 @@ class Election : public eris::Agent {
/** Advances, checking whether an election should be called. If so, electionPeriod() will
* return true until the following advance.
*/
virtual void advance() override;
virtual void interAdvance() override;
private:
bool election_period_ = false;
......
#pragma once
#include <eris/InterOptimizer.hpp>
#include <eris/Optimize.hpp>
#include "voting/Voter.hpp"
#include <list>
......@@ -10,7 +10,7 @@ using eris::eris_id_t;
/** This class handles the inter-period influence adjustments of voters on other voters.
*/
class Influence : public InterOptimizer {
class Influence : public eris::Member, public virtual eris::interopt::Apply {
public:
/** Creates a new Influence inter-period optimizer attached to the given voter. After each
* period, with probability `probability`, the voter will become an activist. An activist
......@@ -41,7 +41,7 @@ class Influence : public InterOptimizer {
Influence(eris_id_t voter, double probability, double drift = 0.0, bool influence_all = true);
/** Applies influence. */
virtual void apply();
virtual void interApply() override;
protected:
eris_id_t voter_;
......
#pragma once
#include <eris/Position.hpp>
#include <eris/agent/PositionalAgent.hpp>
#include <eris/Optimize.hpp>
#include <unordered_map>
#include <random>
namespace voting {
......@@ -10,25 +12,42 @@ using eris::agent::PositionalAgent;
/** A "Party" eris agent. A party has a Position and moves around.
*/
class Party : public PositionalAgent {
class Party : public PositionalAgent, public virtual eris::interopt::OptApply {
public:
// Inherited constructors is ideal here, but support is lacking, so just replicate the one
// we used:
//using PositionalAgent::PositionalAgent;
Party(double p, double b1, double b2) : PositionalAgent(p, b1, b2) {}
Party(double p, double b1, double b2) : PositionalAgent({p}, {b1}, {b2}) {}
/** Overridden to make movements bind to the position boundaries.
*/
virtual bool moveToBoundary() const noexcept override { return true; }
protected:
/// Adds a PartyMover inter-optimizer when added
virtual void added() override;
/** Figures out whether the party should move left (down) or right (up) for the next
* iteration by calling should_move().
*/
virtual void interOptimize() override;
/** Figures out whether the party should increase or decrease for the next iteration, and
* returns the movement. This happens by conducting a poll for one step upward and one step
* downward and a poll for the current position, choosing whichever direction yields the
* best response (or not moving at all if that seems best). In the case of a tie (between
* two or all three options) an option is chosen randomly.
*
* If the party is at a boundary it only considers moving away or staying.
*
* Returns the amount to move by.
*/
virtual double should_move();
/** Takes whatever step was calculated in optimize(). */
virtual void interApply() override;
private:
// The distribution from which we pull step sizes for this Party's PartyMover
std::uniform_real_distribution<double> step_dist_ = std::uniform_real_distribution<double>(1.0e-10, 0.1);
std::uniform_real_distribution<double> step_dist_{1.0e-10, 0.1};
double move_;
};
......
#pragma once
#include <eris/algorithms.hpp>
#include <eris/InterOptimizer.hpp>
#include <functional>
namespace voting {
using eris::InterOptimizer;
using eris::eris_id_t;
class PartyMover : public InterOptimizer {
public:
/** Constructs a party movement optimizer that takes a constant step size each iteration. */
PartyMover(eris_id_t party, double step_size);
/** Constructs a party movement optimizer that calls step_generator to obtain a new step
* size for each iteration. The step generator should always return a strictly positive
* value. */
PartyMover(eris_id_t party, std::function<double()> step_generator);
protected:
/** Figures out whether the party should move left (down) or right (up) for the next
* iteration by calling should_move().
*/
virtual void optimize() const override;
/** Figures out whether the party should increase or decrease for the next iteration, and
* returns the movement. This happens by conducting a poll for one step upward and one step
* downward and a poll for the current position, choosing whichever direction yields the
* best response (or not moving at all if that seems best). In the case of a tie (between
* two or all three options) an option is chosen randomly.
*
* If the party is at a boundary it only considers moving away or staying.
*
* Returns the amount to move by.
*/
virtual double should_move() const;
/** Takes whatever step was calculated in optimize(). */
virtual void apply() override;
/** Declares a dependency on the controlled party when added. */
virtual void added() override;
private:
eris_id_t party_;
std::function<double()> step_gen_;
mutable double move_;
};
}
// vim:tw=100
#pragma once
#include <eris/Position.hpp>
#include <eris/agent/PositionalAgent.hpp>
#include <eris/Optimize.hpp>
#include <eris/Random.hpp>
#include <unordered_map>
......@@ -11,12 +12,12 @@ using eris::agent::PositionalAgent;
/** A "Voter" eris agent. A voter has a Position and a set of friends.
*/
class Voter : public PositionalAgent {
class Voter : public PositionalAgent, public virtual interopt::Advance {
public:
// Inherited constructors is ideal here, but support is lacking, so just replicate the one
// we used:
//using PositionalAgent::PositionalAgent;
Voter(double p, double b1, double b2) : PositionalAgent(p, b1, b2) {}
Voter(double p, double b1, double b2) : PositionalAgent({p}, {b1}, {b2}) {}
/** Returns true if the given voter is a friend of this voter. */
bool isFriend(eris_id_t voter) const;
......@@ -106,7 +107,7 @@ class Voter : public PositionalAgent {
* in the past), and drift is positive, the agent takes a step of length `drift` (or less,
* if already within distance `drift`) towards their natural position.
*/
virtual void advance() override;
virtual void interAdvance() override;
protected:
/** The map of friends of this voter. */
......
......@@ -12,7 +12,7 @@ namespace voting {
* 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 {
class FPTP : public Election, public virtual eris::interopt::Advance {
public:
//using Election::Election;
......@@ -32,7 +32,7 @@ class FPTP : public Election {
/** Resets the winner and vote tallies when advancing.
*/
virtual void advance() override;
virtual void interAdvance() 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.
......
......@@ -4,7 +4,6 @@
#include <eris/Random.hpp>
#include "voting/Voter.hpp"
#include "voting/Party.hpp"
#include "voting/PartyMover.hpp"
#include "voting/Poll.hpp"
#include "voting/Influence.hpp"
#include "voting/election/FPTP.hpp"
......@@ -32,8 +31,8 @@ void voterHist(const Eris<Simulation> &sim) {
const double bin_end = 1;
const double bin_width = (bin_end - bin_start) / bin_count;
std::vector<int> bins(bin_count+1, 0);
for (auto v: sim->agentFilter<Voter>()) {
double p = v.second->position()[0];
for (auto v: sim->agents<Voter>()) {
double p = v->position()[0];
unsigned int bin = p < bin_start ? 0 : (p - bin_start) / bin_width;
if (bin >= bins.size()) bin = bins.size()-1;
bins[bin]++;
......@@ -68,8 +67,8 @@ void voterHist(const Eris<Simulation> &sim) {
std::cout << ">\n";
std::vector<int> party_bins(bin_count+1, 0);
for (auto pp: sim->agentFilter<Party>()) {
double p = pp.second->position()[0];
for (auto pp: sim->agents<Party>()) {
double p = pp->position()[0];
unsigned int bin = p < bin_start ? 0 : (p - bin_start) / bin_width;
if (bin >= party_bins.size()) bin = party_bins.size()-1;
party_bins[bin]++;
......@@ -98,6 +97,7 @@ struct prog_params {
bool influence_all = false;
bool constr_parties = false;
bool show_hist = false;
bool skip_file = false;
double constr_left = 2; // Anything > 1 is considered unset.
dist vdist = dist::beta55;
election etype = election::periodic;
......@@ -143,6 +143,9 @@ prog_params parseCmdArgs(int argc, char **argv) {
TCLAP::SwitchArg constrArg("c", "constrained", "Constrain parties to +/- 0.5 of initial location.",
cmd, p.constr_parties);
TCLAP::SwitchArg skipArg("", "skip-file", "Don't create the output results file.",
cmd, p.skip_file);
RangeConstraint<double> constrLeftConstr(-1, 1);
TCLAP::ValueArg<double> constrLeftArg("L", "constrain-left-below", "The upper constraint for the left-most party. Defaults to 1 (unconstrained) if parties are not constrained, "
"otherwise defaults to the initial party location.",
......@@ -204,6 +207,7 @@ prog_params parseCmdArgs(int argc, char **argv) {
p.parties = partiesArg.getValue();
p.constr_parties = constrArg.getValue();
p.skip_file = skipArg.getValue();
p.voters = votersArg.getValue();
p.vdist = distArg.getValue() == "even" ? dist::even : distArg.getValue() == "uniform" ? dist::uniform :
distArg.getValue() == "beta22" ? dist::beta22 : dist::beta55;
......@@ -251,7 +255,7 @@ int main(int argc, char **argv) {
if (i == 1 and params.constr_left <= 1) {
right_con = params.constr_left;
}
auto p = sim->createAgent<Party>(pos, left_con, right_con);
auto p = sim->create<Party>(pos, left_con, right_con);
parties[p] = i;
}
......@@ -296,24 +300,24 @@ int main(int argc, char **argv) {
for (unsigned int i = 0; i < params.voters; i++) {
auto voter = sim->createAgent<Voter>(positioner(i), -1, 1);
sim->createInterOpt<Influence>(voter, params.influence_prob, params.drift, params.influence_all);
auto voter = sim->create<Voter>(positioner(i), -1, 1);
sim->create<Influence>(voter, params.influence_prob, params.drift, params.influence_all);
}
std::bernoulli_distribution friend_flip(params.friend_prob);
// Create some friendships
for (auto &v1 : sim->agentFilter<Voter>()) {
for (auto &v2 : sim->agentFilter<Voter>()) {
if (v1.first != v2.first and friend_flip(rng))
v1.second->addFriend(v2.first);
for (auto &v1 : sim->agents<Voter>()) {
for (auto &v2 : sim->agents<Voter>()) {
if (v1 != v2 and friend_flip(rng))
v1->addFriend(v2);
}
}
auto pollster = sim->createAgent<Poll>();
auto pollster = sim->create<Poll>();
SharedMember<FPTP> election = (params.etype == ::election::periodic)
? sim->createAgent<FPTP>(params.eperiod)
: sim->createAgent<FPTP>([&params,&rng]() -> bool { return std::bernoulli_distribution(1.0 / params.eperiod)(rng); });
? sim->create<FPTP>(params.eperiod)
: sim->create<FPTP>([&params,&rng]() -> bool { return std::bernoulli_distribution(1.0 / params.eperiod)(rng); });
std::ostringstream filename;
filename << "results/elections:p" << params.parties << (params.constr_parties ? ",c" : ",!c");
......@@ -325,13 +329,15 @@ int main(int argc, char **argv) {
filename << ",E" << params.eperiod << ",seed=" << eris::Random::seed() << ".data";
std::ofstream outfile;
outfile.open(filename.str(), std::ofstream::out | std::ofstream::trunc);
if (!outfile) {
std::perror(("Unable to write to " + filename.str() + ": ").c_str());
exit(1);
if (not params.skip_file) {
outfile.open(filename.str(), std::ofstream::out | std::ofstream::trunc);
if (!outfile) {
std::perror(("Unable to write to " + filename.str() + ": ").c_str());
exit(1);
}
outfile.precision(18);
outfile << "t,winner,position\n";
}
outfile.precision(18);
outfile << "t,winner,position\n";
std::cout << "\e[?25l"; // Hide cursor (from `tput civis`)
std::cout << "\e[2J"; // clear screen
......@@ -341,7 +347,8 @@ int main(int argc, char **argv) {
std::cout << "\e[1;0H"; // Position at line 0, col 0
std::cout << "Running (" << run_details << ")\n";
std::cout << "Results data: " << filename.str() << "\n";
if (not params.skip_file)
std::cout << "Results data: " << filename.str() << "\n";
std::list<double> winner_pos, winnerNL_pos;
double winner_mean = std::numeric_limits<double>::quiet_NaN();
......@@ -350,7 +357,6 @@ int main(int argc, char **argv) {
double winnerNL_sd = std::numeric_limits<double>::quiet_NaN();
sim->maxThreads(4);
sim->threadModel(Simulation::ThreadModel::Hybrid);
auto begin = steady_clock::now();
auto last = steady_clock::now();
while (sim->t() < params.iterations) {
......@@ -387,7 +393,8 @@ int main(int argc, char **argv) {
}) / (winnerNL_pos.size() - 1));
}
outfile << sim->t() << "," << parties[winner] << "," << pos << "\n";
if (not params.skip_file)
outfile << sim->t() << "," << parties[winner] << "," << pos << "\n";
}
printf("Winning party positions: last: %8.6f, mean: %8.6f, sd: %8.6f\n",
......@@ -400,6 +407,7 @@ int main(int argc, char **argv) {
std::cout << "\e[?12;25h"; // Restore cursor (from `tput cvvis`)
outfile.close();
if (not params.skip_file)
outfile.close();
}
......@@ -18,7 +18,7 @@ bool Election::electionPeriod() const {
return election_period_;
}
void Election::advance() {
void Election::interAdvance() {
election_period_ = election_();
}
......
......@@ -8,7 +8,7 @@ namespace voting {
Influence::Influence(eris_id_t voter, double probability, double drift, bool influence_all)
: voter_(voter), prob_(probability), drift_(drift), infl_all_(influence_all) {}
void Influence::apply() {
void Influence::interApply() {
auto voter = simAgent<Voter>(voter_);
auto friends = voter->friends();
auto infls = influencees(friends);
......
#include "voting/Party.hpp"
#include "voting/PartyMover.hpp"
#include "voting/Election.hpp"
#include "voting/Poll.hpp"
#include <eris/Random.hpp>
#include <random>
#include <iostream>
namespace voting {
void Party::added() {
auto &step_dist = step_dist_;
simulation()->createInterOpt<PartyMover>(*this, [&step_dist]() -> double { return step_dist(eris::Random::rng()); });
void Party::interOptimize() {
move_ = should_move();
}
double Party::should_move() {
for (auto &election : simulation()->agents<Election>()) {
if (election->electionPeriod()) {
// No movement during an election period.
return 0.0;
}
}
auto pollsters = simulation()->agents<Poll>();
if (pollsters.empty())
throw std::runtime_error("Party optimization failure: simulation has no Poll agent\n");
auto pollster = pollsters[0];
std::unordered_map<double, int> polls;
double step_size = step_dist_(eris::Random::rng());
// Conduct three polls: a "don't move", one step left, and one step right.
polls[0.0] = pollster->conductPoll().closest_party[*this];
if (!bindingLower())
polls[-step_size] = pollster->conductPollIf(
*this,
toBoundary(position() - Position({ step_size }))).closest_party[*this];
if (!bindingUpper())
polls[step_size] = pollster->conductPollIf(
*this,
toBoundary(position() + Position({ step_size }))).closest_party[*this];
double highest_step = 0;
int highest = -1;
int num_highest = 0;
for (auto &poll : polls) {
if (poll.second > highest) {
highest = poll.second;
highest_step = poll.first;
num_highest = 1;
}
else if (poll.second == highest) {
num_highest++;
if (std::uniform_int_distribution<int>(1, num_highest)(eris::Random::rng()) == 1) {
highest_step = poll.first;
}
}
}
return highest_step;
}
void Party::interApply() {
if (move_ != 0.0)
moveBy({move_});
}
}
#include "voting/PartyMover.hpp"
#include "voting/Election.hpp"
#include <random>
#include <eris/Random.hpp>
#include "voting/Party.hpp"
#include "voting/Poll.hpp"
#include <iostream>
namespace voting {
using eris::Stepper;
PartyMover::PartyMover(eris_id_t party, double step) : party_(party), step_gen_([step]() { return step; }) {}
PartyMover::PartyMover(eris_id_t party, std::function<double()> step_gen) : party_(party), step_gen_(step_gen) {}
void PartyMover::added() {
dependsOn(party_);
}
void PartyMover::optimize() const {
move_ = should_move();
}
double PartyMover::should_move() const {
auto party = simAgent<Party>(party_);
for (auto &election : simulation()->agentFilter<Election>()) {
if (election.second->electionPeriod()) {
// No movement during an election period.
return 0.0;
}
}
auto pollsters = simulation()->agentFilter<Poll>();
if (pollsters.empty())
throw std::runtime_error("PartyMover optimizer failure: simulation has no Poll agent\n");
auto pollster = pollsters.begin()->second;
std::unordered_map<double, int> polls;
double step_size = step_gen_();
// Conduct three polls: a "don't move", one step left, and one step right.
polls[0.0] = pollster->conductPoll().closest_party[party];
if (!party->bindingLower())
polls[-step_size] = pollster->conductPollIf(
party,
party->toBoundary(party->position() - Position({ step_size }))).closest_party[party];
if (!party->bindingUpper())
polls[step_size] = pollster->conductPollIf(
party,
party->toBoundary(party->position() + Position({ step_size }))).closest_party[party];
double highest_step = 0;
int highest = -1;
int num_highest = 0;
for (auto &poll : polls) {
if (poll.second > highest) {
highest = poll.second;
highest_step = poll.first;
num_highest = 1;
}
else if (poll.second == highest) {
num_highest++;
if (std::uniform_int_distribution<int>(1, num_highest)(eris::Random::rng()) == 1) {
highest_step = poll.first;
}
}
}
return highest_step;
}
void PartyMover::apply() {
if (move_ != 0.0) {
auto party = simAgent<Party>(party_);
party->moveBy(move_);
}
}
}
......@@ -11,26 +11,26 @@ namespace voting {
Poll::poll Poll::conductPollIf(eris_id_t party_id, Position try_pos) {
// First check to make sure this isn't an election period:
for (auto &election : simulation()->agentFilter<Election>()) {
if (election.second->electionPeriod())
for (auto &election : simulation()->agents<Election>()) {
if (election->electionPeriod())
throw std::runtime_error("Cannot poll during an election period");
}
auto parties = simulation()->agentFilter<Party>();
auto voters = simulation()->agentFilter<Voter>();
auto parties = simulation()->agents<Party>();
auto voters = simulation()->agents<Voter>();
Poll::poll new_poll;
for (auto &p : parties)
new_poll.closest_party[p.first] = 0;
new_poll.closest_party[p] = 0;
for (auto &v : voters) {
eris_id_t closest = 0;
double min_dist = 0.0;
int identical = 0;
for (auto &p : parties) {
const Position &pos = p.first == party_id ? try_pos : p.second->position();
double dist = v.second->position().distance(pos);
const Position &pos = p == party_id ? try_pos : p->position();
double dist = v->position().distance(pos);
if (closest == 0 or dist < min_dist) {
closest = p.first;
closest = p;
min_dist = dist;
identical = 1;
}
......@@ -39,7 +39,7 @@ Poll::poll Poll::conductPollIf(eris_id_t party_id, Position try_pos) {
// that each equal-distance party has an equal chance of being chosen
if (++identical > 1) {
if (std::uniform_int_distribution<int>(1, identical)(eris::Random::rng()) == 1) {
closest = p.first;
closest = p;
}
}
}
......@@ -52,7 +52,7 @@ Poll::poll Poll::conductPollIf(eris_id_t party_id, Position try_pos) {
}
Poll::poll Poll::conductPoll() {
return conductPollIf(0, Position(1));
return conductPollIf(0, Position({0}));
}
}
......@@ -30,8 +30,8 @@ double Voter::friendDistance(eris_id_t voter) const {
double Voter::conviction() const {
double min_dist = 2.0;
for (auto &p : simulation()->agentFilter<Party>()) {
double pdist = position().distance(p.second->position());
for (auto &p : simulation()->agents<Party>()) {
double pdist = position().distance(p->position());
if (pdist < min_dist) min_dist = pdist;
}
return std::max(1.0 - min_dist, 0.0);
......@@ -97,9 +97,7 @@ double Voter::drift() const {
: 0.0;
}
void Voter::advance() {
PositionalAgent::advance();
void Voter::interAdvance() {
if (just_moved_) {
just_moved_ = false;
}
......
......@@ -8,24 +8,24 @@ eris_id_t FPTP::election() {
// Already had an election
if (winner_ > 0) return winner_;
auto parties = simulation()->agentFilter<Party>();
auto parties = simulation()->agents<Party>();
if (parties.empty()) throw std::runtime_error("Cannot conduct an election without any parties");
auto voters = simulation()->agentFilter<Voter>();
auto voters = simulation()->agents<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;
votes_[p] = 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);
double dist = v->distance(p);
if (closest == 0 or dist < min_dist) {
closest = p.first;
closest = p;
min_dist = dist;
identical = 1;
}
......@@ -34,7 +34,7 @@ eris_id_t FPTP::election() {
// 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;
closest = p;
}
}
}
......@@ -71,8 +71,8 @@ const std::unordered_map<eris_id_t, int>& FPTP::votes() {
return votes_;
}
void FPTP::advance() {
Election::advance();
void FPTP::interAdvance() {
Election::interAdvance();
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