Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected results using Repast Simphony

I need to develop a Java version of the Iterated Prisoner Dilemma using Repast Simphony as simulator.

The ideas is that each Player is an agent, and we have a n x n grid of Player that can't be moved. Each Player has to play with 4 neighbours (northern, southern, western and eastern one), finding the best strategy based on the outcome of the 4 different games in each round.

Since there's not a built-in system to exchange messages between agents in Repast Simphony, I had to implement some kind of workaround to deal with agents' sync (A vs B and B vs A should count as the same round, that's why they need to be sync'ed).

This is done by seeing each round as:

  • Player i chooses the next move for each of the 4 enemies
  • Player i sends the correct move to each of the 4 enemies
  • Player i waits for each of the 4 enemies to reply

From my understanding of Repast Simphony, scheduled methods are sequential (no agent-level parallelism), meaning that I'm forced to do the waiting in a different method from the send one (scheduled with lower pritority to ensure that all the sends are completed before starting the waits).

The issue here is that, despite receiving all the 4 expected messages (at least this is what gets printed), once the waiting method starts it reports less than 4 received elements.

Here's the code taken from Player class:

// myPoint is the location inside the grid (unique, agents can't move and only one per cell is allowed)
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((myPoint == null) ? 0 : myPoint.hashCode());
    return result;
}

// Returns enemy's choice in the previous round
private byte getLastPlay(Player enemy) {
    return (neighbors.get(enemy)[1]) ? COOPERATE : DEFECT;
}

// Elements are saved as (player, choice)
private void receivePlay(Player enemy, byte play) {
    System.out.println(this + " receives (" + play + ") from " + enemy);
    while (!playSharedQueue.add(new Object[] { enemy, play })){
        // This doesn't get printed, meaning that the insertion is successful!
        System.out.println(this + " failed inserting");
    }
}

@ScheduledMethod(start = 1, interval = 1, priority = 10)
public void play() {
    System.out.println(this + " started playing");
    // Clear previous plays
    playSharedQueue.clear();
    for (Player enemy : neighbors.keySet()) {
        // properties[0] = true if we already played together
        // properties[1] = true if enemy choose to cooperate on the previous round
        Boolean[] properties = neighbors.get(enemy);
        // Choose which side we take this time
        byte myPlay;
        if (properties[0]) {
            // First time that we play, use memory-less strategy
            myPlay = (Math.random() <= strategy[0]) ? COOPERATE : DEFECT;
            // Report that we played
            properties[0] = false;
            neighbors.put(enemy, properties);
        } else {
            // We already had a round, use strategy with memory
            byte enemyLastPlay = enemy.getLastPlay(this);
            // Choose which side to take based on enemy's previous decision
            myPlay = (Math.random() <= strategy[(enemyLastPlay) == COOPERATE ? 1 : 2]) ? COOPERATE : DEFECT;
        }
        // Send my choice to the enemy
        System.out.println(this + " sent (" + myPlay + ") to " + enemy);
        enemy.receivePlay(this, myPlay);
    }
}

// Waits for the results and processes them
@ScheduledMethod(start = 1, interval = 1, priority = 5)
public void waitResults() {
    // Clear previous score
    lastPayoff = 0;
    System.out.println(this + " waits for results [" + playSharedQueue.size() + "]");
    if (playSharedQueue.size() != 4) {
        // Well, this happens on the first agent :(
        System.exit(1);
    }
    // ... process ...
}

Here's the console output, so that you can see that everything seems to be sent and received without issues (used a 3 x 3 grid):

Player[2, 0] started playing
Player[2, 0] sent (0) to Player[2, 1]
Player[2, 1] receives (0) from Player[2, 0]
Player[2, 0] sent (0) to Player[2, 2]
Player[2, 2] receives (0) from Player[2, 0]
Player[2, 0] sent (0) to Player[0, 0]
Player[0, 0] receives (0) from Player[2, 0]
Player[2, 0] sent (0) to Player[1, 0]
Player[1, 0] receives (0) from Player[2, 0]
Player[1, 2] started playing
Player[1, 2] sent (1) to Player[2, 2]
Player[2, 2] receives (1) from Player[1, 2]
Player[1, 2] sent (1) to Player[0, 2]
Player[0, 2] receives (1) from Player[1, 2]
Player[1, 2] sent (1) to Player[1, 0]
Player[1, 0] receives (1) from Player[1, 2]
Player[1, 2] sent (1) to Player[1, 1]
Player[1, 1] receives (1) from Player[1, 2]
Player[0, 2] started playing
Player[0, 2] sent (1) to Player[2, 2]
Player[2, 2] receives (1) from Player[0, 2]
Player[0, 2] sent (1) to Player[0, 0]
Player[0, 0] receives (1) from Player[0, 2]
Player[0, 2] sent (1) to Player[0, 1]
Player[0, 1] receives (1) from Player[0, 2]
Player[0, 2] sent (1) to Player[1, 2]
Player[1, 2] receives (1) from Player[0, 2]
Player[0, 1] started playing
Player[0, 1] sent (1) to Player[2, 1]
Player[2, 1] receives (1) from Player[0, 1]
Player[0, 1] sent (1) to Player[0, 0]
Player[0, 0] receives (1) from Player[0, 1]
Player[0, 1] sent (1) to Player[0, 2]
Player[0, 2] receives (1) from Player[0, 1]
Player[0, 1] sent (1) to Player[1, 1]
Player[1, 1] receives (1) from Player[0, 1]
Player[1, 0] started playing
Player[1, 0] sent (0) to Player[2, 0]
Player[2, 0] receives (0) from Player[1, 0]
Player[1, 0] sent (0) to Player[0, 0]
Player[0, 0] receives (0) from Player[1, 0]
Player[1, 0] sent (0) to Player[1, 1]
Player[1, 1] receives (0) from Player[1, 0]
Player[1, 0] sent (0) to Player[1, 2]
Player[1, 2] receives (0) from Player[1, 0]
Player[1, 1] started playing
Player[1, 1] sent (0) to Player[2, 1]
Player[2, 1] receives (0) from Player[1, 1]
Player[1, 1] sent (0) to Player[0, 1]
Player[0, 1] receives (0) from Player[1, 1]
Player[1, 1] sent (0) to Player[1, 0]
Player[1, 0] receives (0) from Player[1, 1]
Player[1, 1] sent (0) to Player[1, 2]
Player[1, 2] receives (0) from Player[1, 1]
Player[2, 2] started playing
Player[2, 2] sent (0) to Player[2, 0]
Player[2, 0] receives (0) from Player[2, 2]
Player[2, 2] sent (0) to Player[2, 1]
Player[2, 1] receives (0) from Player[2, 2]
Player[2, 2] sent (0) to Player[0, 2]
Player[0, 2] receives (0) from Player[2, 2]
Player[2, 2] sent (0) to Player[1, 2]
Player[1, 2] receives (0) from Player[2, 2]
Player[0, 0] started playing
Player[0, 0] sent (1) to Player[2, 0]
Player[2, 0] receives (1) from Player[0, 0]
Player[0, 0] sent (1) to Player[0, 1]
Player[0, 1] receives (1) from Player[0, 0]
Player[0, 0] sent (1) to Player[0, 2]
Player[0, 2] receives (1) from Player[0, 0]
Player[0, 0] sent (1) to Player[1, 0]
Player[1, 0] receives (1) from Player[0, 0]
Player[2, 1] started playing
Player[2, 1] sent (1) to Player[2, 0]
Player[2, 0] receives (1) from Player[2, 1]
Player[2, 1] sent (1) to Player[2, 2]
Player[2, 2] receives (1) from Player[2, 1]
Player[2, 1] sent (1) to Player[0, 1]
Player[0, 1] receives (1) from Player[2, 1]
Player[2, 1] sent (1) to Player[1, 1]
Player[1, 1] receives (1) from Player[2, 1]
Player[2, 2] waits for results [1]

As you can see in the last line, playSharedQueue.size() is 1 and I really don't understand why.

If methods invocation are sequential the waitResults()methos is invoked after the 9play()` executions, and given that each of the correctly sends 4 messages, I can't find a reason why that size is still 1.

Of course being everything sequential means that there are no synchronization issues, even if I had the same problem using LinkedBlockingQueue instead of HashSet.

Do you guys have any hint on this?

like image 524
StepTNT Avatar asked Jan 20 '16 17:01

StepTNT


1 Answers

After some time I opened the code again and I found out that I was doing a simple, yet serious mistake:

@ScheduledMethod(start = 1, interval = 1, priority = 10)
public void play() {
    System.out.println(this + " started playing");
    // Clear previous plays
    playSharedQueue.clear();

The playSharedQueue.clear(); is executed to clear out the previous results, but since invocations are sequential the second player will call it after the first one sent him his play, so that play is discarded.

Moving that line at the end of waitResults solved it.

like image 135
StepTNT Avatar answered Nov 16 '22 21:11

StepTNT