To be useful, there must be a way to get and set values in the value layer, and, typically, for agents to use them. To demonstrate this, we will add a simple method to our agents that allows them to look in a value layer and make changes to it.

Agents will be able to inspect the value layer at points within the local boundaries of the space, but also within the 'buffer zones' exactly as with spatial projections. Importantly, the values in the boundary areas are considered non-local and should be treated just like non-local agents- that is, changes made to the non-local areas of the value layer are transitory. (Note that the Repast HPC code could prevent such changes, but in rare cases if handled very carefully such changes might be reasonably used, so the possibility of making them was left open.)

First we add the method signature in the Agent.h file (along with the appropriate include directive):

/* Demo_04_Agent.h */

#ifndef DEMO_04_AGENT
#define DEMO_04_AGENT

#include "repast_hpc/AgentId.h"
#include "repast_hpc/SharedContext.h"
#include "repast_hpc/SharedDiscreteSpace.h"
#include "repast_hpc/ValueLayerND.h"
    /* Actions */
    bool cooperate();                                                 // Will indicate whether the agent cooperates or not; probability determined by = c / total
    void play(repast::SharedContext<RepastHPCDemoAgent>* context);    // Choose three other agents from the given context and see if they cooperate or not
    void move(repast::SharedDiscreteSpace<RepastHPCDemoAgent, repast::WrapAroundBorders, repast::SimpleAdder<RepastHPCDemoAgent> >* space);
    void processValues(repast::ValueLayerND<double>* valueLayer, 
               repast::SharedDiscreteSpace<RepastHPCDemoAgent, repast::WrapAroundBorders, repast::SimpleAdder<RepastHPCDemoAgent> >* space););

Note that we need the space pointer because we need to get the agent's location in that space.

The content of the new method is:

void RepastHPCDemoAgent::processValues(repast::ValueLayerND<double>* valueLayer, 
       repast::SharedDiscreteSpace<RepastHPCDemoAgent, repast::WrapAroundBorders, repast::SimpleAdder<RepastHPCDemoAgent> >* space){
    std::vector<int> agentLoc;
    space->getLocation(id_, agentLoc);
    std::vector<int> agentNewLoc;
    agentNewLoc.push_back(agentLoc[0] + (id_.id() < 7 ? -1 : 1));
    agentNewLoc.push_back(agentLoc[1] + (id_.id() > 3 ? -1 : 1));
    agentNewLoc.push_back(agentLoc[2] + (id_.id() > 5 ? -1 : 1));

    bool errorFlag1 = false;
    bool errorFlag2 = false;
    double originalValue = valueLayer->getValueAt(agentLoc, errorFlag1);
    double nextValue = valueLayer->getValueAt(agentNewLoc, errorFlag2);
    if(errorFlag1 || errorFlag2){
      std::cout << "An error occurred for Agent " << id_ << " in RepastHPCDemoAgent::processValues()" << std::endl;
      return;
    }
    if(originalValue < nextValue && total > 0){                // If the likely next value is better
      valueLayer->addValueAt(total-c, agentLoc, errorFlag1);   // Drop part of what you're carrying
      std::cout << "Agent " << id_ << " dropping " << (total - c) << " at " << 
          agentLoc[0] << "," << agentLoc[1] << "," << agentLoc[2] << " for new value " << valueLayer->getValueAt(agentLoc, errorFlag1) << std::endl;
      total = c;
    }
    else{                                                // Otherwise
      total += originalValue;                            // Pick up whatever was in the value layer
      valueLayer->setValueAt(0, agentLoc, errorFlag2);  // Value Layer is now at zero
      std::cout << "Agent " << id_ << " picking up " << originalValue << " at " << 
          agentLoc[0] << "," << agentLoc[1] << "," << agentLoc[2] << " for new value " << valueLayer->getValueAt(agentLoc, errorFlag2) << std::endl;
    }
}

The first part of this code looks exactly like the code from the 'move' method, and does the same thing: it establishes the agent's current location and then finds another, new location.

The variables originalValue and nextValue are then set based on what is in the ValueLayerND at those two locations. The use of the 'errorFlag' variables should be noted: if the attempt to get a value out of the ValueLayerND encounters an error, the return value will be invalid. However, this return value could be anything, so there is no way to test whether the return value is 'invalid' or not simply by looking at the return value itself. Instead, a flag is set: if there is an error after the first call, the value of errorFlag1 will be set to 'true', and similarly for errorFlag2 in the second call. The most common error is that the coordinates passed will be out of the boundaries of the ValueLayer object (local boundaries plus buffer zones).

The three calls made are the three things that you can do with a ValueLayer:

Note that here we have used a vector<int> to specify location, but one can also use a Point<int>.

Finally, the most important point: the example given above inspects the cell that the agent is on and another, adjacent cell. We will only call this routine on local agents, so we know that the agent will be on a local cell, but the adjacent cell might not be. Therefore we do not make changes to the adjacent cell; doing so might violate the basic rule of making changes to non-local data in Repast HPC.

Next we have to schedule the method so that it occurs for all of the agents. In the Model.cpp code, we add:

	std::vector<RepastHPCDemoAgent*> agents;
	context.selectAgents(repast::SharedContext<RepastHPCDemoAgent>::LOCAL, countOfAgents, agents);
	std::vector<RepastHPCDemoAgent*>::iterator it = agents.begin();
	while(it != agents.end()){
        (*it)->play(&context);
		it++;
    }

    
    it = agents.begin();
    while(it != agents.end()){
		(*it)->processValues(valueLayer, discreteSpace);
		it++;
    }
    
    valueLayer->synchronize();


    it = agents.begin();
    while(it != agents.end()){
		(*it)->move(discreteSpace);
		it++;
    }

    discreteSpace->balance();
    repast::RepastProcess::instance()->synchronizeAgentStatus<RepastHPCDemoAgent, RepastHPCDemoAgentPackage, RepastHPCDemoAgentPackageProvider, RepastHPCDemoAgentPackageReceiver>(context, *provider, *receiver, *receiver);
    
    repast::RepastProcess::instance()->synchronizeProjectionInfo<RepastHPCDemoAgent, RepastHPCDemoAgentPackage, RepastHPCDemoAgentPackageProvider, RepastHPCDemoAgentPackageReceiver>(context, *provider, *receiver, *receiver);

    repast::RepastProcess::instance()->synchronizeAgentStates<RepastHPCDemoAgentPackage, RepastHPCDemoAgentPackageProvider, RepastHPCDemoAgentPackageReceiver>(*provider, *receiver);

The last line in this is significant: Value Layers are not synchronized with projection information or agent information. They must be synchronized via explicit calls to the 'synchronize()' method.

To test this, we will add some temporary outputting code. In the model constructor, we add:

    valueLayer = new repast::ValueLayerND<double>(processDims, gd, 2, true, repast::RepastProcess::instance()->rank(), 1);
    valueLayer->write("./","AFTER_INIT",true);

And around the synchronize method that we just added, we place:

    bool doWrite =(repast::RepastProcess::instance()->getScheduleRunner().currentTick() == 2.0);
    if(doWrite)  valueLayer->write("./","TEST_BEFORE",true);
    
    std::cout << " VALUE LAYER SYNCHRONIZING " << std::endl;
    valueLayer->synchronize();
    std::cout << " VALUE LAYER DONE SYNCHRONIZING " << std::endl;
    if(doWrite)  valueLayer->write("./","TEST_AFTER",true);
    

The result of this will be three sets of files, one from just after the Value Layer is created and two from just before and after the synchronization routine is called. You can inspect any specific location within the grid within these files, at any of the three points in simulation time. There is one file per process, and one line per cell, with the first N columns representing the coordinates of that cell. Note that the files will not include cells that have values of 'zero'. You can edit the time tick to output at different points in the simulation. (The files are a bit large to output with every tick.)

Some notes about this. First, because we are initializing the values in the ValueLayer to the current rank, rank zero has lots of cells with a value of zero and that therefore are omitted in the array. The cells that are written in the first file for rank zero (the files have a suffix that indicates which rank they include) are only those in the buffer areas from the adjacent processes.

Second, you can inspect specific cells with 'grep', making sure to use the '^' symbol to indicate that you want to match a pattern with the start of the line. Some cells will show up in all output files because they are on the corners of the wrap-around space:

$ grep "^0,0,0," ValueLayer_AFTER_INIT_0.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_AFTER_INIT_1.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_AFTER_INIT_2.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_AFTER_INIT_3.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_AFTER_INIT_4.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_AFTER_INIT_5.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_AFTER_INIT_6.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_AFTER_INIT_7.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_TEST_BEFORE_0.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_TEST_BEFORE_1.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_TEST_BEFORE_2.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_TEST_BEFORE_3.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_TEST_BEFORE_4.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_TEST_BEFORE_5.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_TEST_BEFORE_6.csv 
0,0,0,7
$ grep "^0,0,0," ValueLayer_TEST_BEFORE_7.csv 
$ grep "^0,0,0," ValueLayer_TEST_AFTER_0.csv 
$ grep "^0,0,0," ValueLayer_TEST_AFTER_1.csv 
$ grep "^0,0,0," ValueLayer_TEST_AFTER_2.csv 
$ grep "^0,0,0," ValueLayer_TEST_AFTER_3.csv 
$ grep "^0,0,0," ValueLayer_TEST_AFTER_4.csv 
$ grep "^0,0,0," ValueLayer_TEST_AFTER_5.csv 
$ grep "^0,0,0," ValueLayer_TEST_AFTER_6.csv 
$ grep "^0,0,0," ValueLayer_TEST_AFTER_7.csv 

In this example, the value at point '0,0,0'. which is visible to all eight processes but is local only on process 7, is initialized to '7'. The initialization includes synchronization, so the value of '7' is seen on all processes in the 'AFTER_INIT' files. At tick 2, process 7 has changed the value to zero (because there was an agent there that 'processed' it). In the 'TEST_BEFORE' files, the value is still '7' on all other processes except 7 (where the search through the file finds nothing because the value is zero and the cell is not recorded). After synchronization, in the 'TEST_AFTER' files, none of the processes have an entry (meaning that they all are now aware that the value is zero.