/**
* Name: vote
* Author: MAPS TEAM (Frederic Amblard, Thomas Louail, Romain Reulier, Paul Salze et Patrick Taillandier)
* Description: Modeling of an election
* Tags: gui
*/
model vote
global {
//Shape of the environment
geometry shape <- rectangle({200, 200});
//Number of electors
int nb_electors <- 1500;
//Number of candidates
int nb_candidates <- 7;
//Weight of each candidates
int weight_candidates <- 50;
//Threshold for the attraction candidates
int threshold_attraction_candidates <- 80;
//Threshold for the repulsion candidates
int threshold_repulsion_candidates <- 200;
//Threshold for the attraction electors
int threshold_attraction_electors <- 20;
//Distance traveled
float distance_traveled <- 7.0;
//Distribution of the electors
string distribution_electors <- "Uniform" among: ["Uniform", "Normal"];
//Distribution of candidates
string distribution_candidates <- "Polygon" among: ["Random", "Polygon", "Line", "Diagonal"];
//Strategy of the candidates
string strategy_candidates <- "No strategy" among: ["No strategy", "Search electors", "Distinction", "Group", "Go closer to the best","Random" ];
//Count of max group
int cpt_Group_max <- 5;
//Count of group
int cpt_Group <- cpt_Group_max;
float entropy;
//List of all the active candidates
list active_candidates ;
init {
//Creation of the elector
create elector number: nb_electors;
do creation_candidates;
}
//Action to create the candidates according to the distribution of candidates
action creation_candidates {
switch distribution_candidates {
match "Polygon" {
list liste_points <- list(nb_candidates points_at 50.0);
int cpt <- 0;
create candidate number: nb_candidates{
color <- rgb (rnd(255), rnd(255), rnd(255));
location <- liste_points at cpt;
cpt <- cpt + 1;
}
}
match "Line" {
int cpt <- 0;
create candidate number: nb_candidates{
color <- rgb ([rnd(255), rnd(255), rnd(255)]);
float x_cord <- 200 * cpt / nb_candidates;
float y_cord <- 100.0;
location <- {x_cord, y_cord};
cpt <- cpt + 1;
}
}
match "Diagonal" {
int cpt <- 0;
create candidate number: nb_candidates{
color <- rgb ([rnd(255), rnd(255), rnd(255)]);
float x_cord <- 200 * cpt / nb_candidates;
float y_cord <- x_cord;
location <- {x_cord, y_cord};
cpt <- cpt + 1;
}
}
}
//Initialization of all the active candidates as the list of candidates
active_candidates <- list(copy(candidate));
}
//Reflex representing the dynamics of the models
reflex dynamique {
//For each elector, ask to move
ask elector {
do moving;
}
//For each candidate, ask to move
ask active_candidates{
do moving;
my_electors <- list([]);
}
//For each elector, do its definition
ask elector {
do definition_candidate;
}
int nb_electors_max <- 0;
candidate candidat_elected <- nil;
//Ask to all the active candidates to compute their percentage of vote and set the number of maximum electors to know which candidate is elected
ask active_candidates{
int nb_el <- length(my_electors) ;
percentage_vote <- (nb_el/nb_electors * 100) with_precision 2;
if (nb_el > nb_electors_max) {
nb_electors_max <- nb_el;
candidat_elected <- self;
}
}
//update of the state of the candidate
ask candidate {
is_elected <- false;
}
ask candidat_elected {
is_elected <- true;
}
}
//Reflex to show the final results
reflex resultats_finaux when: time = 72 {
candidate elected <- active_candidates with_max_of (each.percentage_vote);
//Display a window telling who is the winner and halt the model
do tell msg: "The winner is " + elected.name;
do pause;
}
//Reflex to compute the creation of group when one candidate chooses this strategy
reflex creation_Group when: (strategy_candidates in ["Group", "Random"]) {
if (cpt_Group = cpt_Group_max) {
//Kill all the group of electors
ask Group_electors as list {
do die;
}
//Compute the list of elector according to their distance
list> Groups;
geometry geoms <- union(elector collect (each.shape + (threshold_attraction_electors, 4, #round)));
loop geom over: geoms.geometries {
if (geom != nil and !empty(geom.points)) {
list els <- (elector inside geom);
add els to: Groups;
}
}
//Create new groups of electors according to the list of electors
loop gp over: Groups {
create Group_electors {
effectif <- length(gp);
electors_dans_Group <- gp;
location <- mean(electors_dans_Group collect (each.location)) ;
}
}
}
cpt_Group <- cpt_Group - 1;
if (cpt_Group = 0) { cpt_Group <- cpt_Group_max;}
}
//Reflex to compute the entropy
reflex calcule_entropy {
entropy <- 0.0;
//Compute the abstinence rate
float abst <- (nb_electors - sum (active_candidates collect (length(each.my_electors)))) / nb_electors;
if (abst > 0) {
entropy <- entropy - (abst * ln(abst));
}
//Ask to all the active candidates their number of electors to compute the entropy
ask active_candidates {
float p <- length(my_electors) / nb_electors;
if (p > 0) {
entropy <- entropy - (p * ln(p));
}
}
entropy <- entropy / ln (length(active_candidates) + 1);
}
}
//Species representing a group of electors
species Group_electors {
int effectif <- 0;
//List of all the elector agents in the group
list electors_dans_Group ;
aspect default {
draw square(2) color: #orange;
}
}
//Species representing the elector moving
species elector skills: [moving]{
init {
//At initialization, place the elector in a certain place according to the distribution of electors
if (distribution_electors = "Normal") {
float x_cord <- max([0.0, min([200.0, gauss ({100, 35})])]);
float y_cord <- max([0.0, min([200.0, gauss ({100, 35})])]);
location <- {x_cord, y_cord};
}
}
rgb color <- #white;
//Candidate chosen by the elector
candidate my_candidate;
aspect base {
draw pyramid(2) color: color ;
}
//Action to define the candidate
action definition_candidate {
//The candidate chosen is the one closest to the elector in the attraction range
my_candidate <- active_candidates with_min_of (self distance_to each);
my_candidate <- (self distance_to my_candidate < threshold_attraction_candidates) ? my_candidate : nil;
if (my_candidate != nil) {
add self to: my_candidate.my_electors;
color <- my_candidate.color;
}
}
//Action to move the elector
action moving {
//Make the agent move closer to another elector, representing the influence of this one
if ( rnd(100) > (weight_candidates)) {
elector my_elector <- shuffle(elector) first_with ((self distance_to each) < threshold_attraction_electors);
if (my_elector != nil) {
do goto target:my_elector speed: distance_traveled;
}
} else {
//Move the elector closer to one of the candidate to represent its repulsion or attraction
candidate the_candidate <- one_of(candidate) ;
if (the_candidate != nil) {
float dist <- self distance_to the_candidate;
if dist < threshold_attraction_candidates {
do goto target: the_candidate speed: distance_traveled;
} else if dist > threshold_repulsion_candidates {
do goto target: location + location - the_candidate.location speed: distance_traveled;
}
}
}
}
}
//Species candidate using the skill moving
species candidate skills:[moving]{
rgb color <- rgb([100 + rnd(155),100 + rnd(155),100 + rnd(155)]);
//Boolean to know if the candidate is active
bool active <- true;
//Float representing the percentage of vote for the candidate
float percentage_vote;
//List of all the electors of the candidate
list my_electors of: elector;
//Boolean to know if the candidate is elected
bool is_elected <- false;
aspect default {
draw sphere(3) color: color;
}
aspect dynamic {
if (active) {
float radius <- 1 + (percentage_vote / 4.0);
if (is_elected) {
draw cube( radius * 2) color: color.brighter.brighter;
}
draw sphere(radius) color: color;
draw string(percentage_vote) size: 5 color: #white anchor: #center;
}
}
//Action to move the candidate according to its strategy
action moving {
switch strategy_candidates {
match "No strategy" {}
match "Search electors" {do strategy_1;}
match "Distinction" {do strategy_2;}
match "Group" {do strategy_3;}
match "Go closer to the best" {do strategy_4;}
match "Random" {
switch (rnd(4)) {
match 0 {}
match 1 {do strategy_1;}
match 2 {do strategy_2;}
match 3 {do strategy_3;}
match 4 {do strategy_4;}
}
}
}
}
action strategy_1 {
//go closer to electors
elector my_elector <- shuffle(elector) first_with ((self distance_to each) < threshold_attraction_electors);
if (my_elector != nil) {
do goto target:my_elector speed: distance_traveled;
}
}
action strategy_2 {
//go in opposite directions to other candidates
list cands <- list(copy(candidate));
remove self from: cands;
candidate the_candidate <- one_of(cands) ;
if (the_candidate != nil) {
do goto target: (location + location - the_candidate.location) speed: distance_traveled;
}
}
action strategy_3 {
//go closer to a group of electors
Group_electors mon_Group <- (Group_electors where ((self distance_to each) < threshold_attraction_electors)) with_max_of (each.effectif);
if (mon_Group != nil) {
do goto target:mon_Group speed: distance_traveled;
}
}
action strategy_4 {
//go toward the candidate with max of votes
candidate the_candidate <- candidate with_max_of (percentage_vote) ;
if (the_candidate != nil) {
do goto target:the_candidate speed: distance_traveled;
}
}
}
experiment vote type: gui {
/** Insert here the definition of the input and output of the model */
parameter "Number of electors : " var: nb_electors category: "elector";
parameter "Moving speed of electors toward another electors : " var: distance_traveled category: "elector";
parameter "Attraction distance between electors : " var: threshold_attraction_electors category: "elector";
parameter "Number of candidates : " var: nb_candidates category: "Candidate";
parameter "Attraction distance between candidates and electors : " var: threshold_attraction_candidates category: "elector";
parameter "Repulsion distance between candidates : " var: threshold_repulsion_candidates category: "elector";
parameter "weight of candidates : " var: weight_candidates category: "Candidate";
parameter "Distribution type of electors : " var: distribution_electors category: "elector";
parameter "Distribution type of candidates : " var: distribution_candidates category: "Candidate";
parameter "Strategy of candidates : " var: strategy_candidates category: "Candidate";
output {
layout #split;
display main background: #black {
species elector aspect: base;
species candidate aspect: dynamic;
species Group_electors;
}
display votants {
chart "Distribution of electors" type: pie background: #white {
loop cand over: candidate {
data cand.name value:cand.percentage_vote color: cand.color ;
}
}
}
display indicateurs {
chart "Shannon Entropy" type: series background: #white size: {1,0.5} position: {0, 0} {
data "entropy" value: entropy color: #blue ;
}
chart "Opinion distribution" type: series background: #white size: {1,0.5} position: {0, 0.5} {
data "Space area covered" value: (union(candidate collect (each.shape buffer threshold_attraction_candidates))).area / 40000 color: #blue ;
}
}
}
}