What Repast HPC is designed to do is share agents across processes. This is a way to deal with the problem that arises when an agent-based model is parallelized: agent interaction across process boundaries is limited. The solution to this is to copy agents that are actually on one process onto the other processes where they are needed.

A crucial aspect of this is that the copies are passive: you can read information from them, but changes made to the copies are not transmitted back to the original.

As we will see in later demos, spatial and network contexts handle the borrowing of agents automatically. First, however, we will initiate and manage the task of borrowing agents manually.

To do this requires two steps: first, a request must be created; this is basically a list of the agents on the other processes that are to be borrowed. Second, this request must be issued to the RepastProcess instance.

To do this in our demo simulation we will create a new method of the 'model' class and schedule it as the first event in the simulation. (Note that we could also borrow agents during initialization or at some other point during the simulation execution; it is convenient here to use a scheduled event.) First, add the new function to the model class (in Demo_01_Model.h):

class RepastHPCDemoModel{
	int stopAt;
	int countOfAgents;
	repast::Properties* props;
	repast::SharedContext<RepastHPCDemoAgent> context;
	
	RepastHPCDemoAgentPackageProvider* provider;
	RepastHPCDemoAgentPackageReceiver* receiver;
	
public:
	RepastHPCDemoModel(std::string propsFile, int argc, char** argv, boost::mpi::communicator* comm);
	~RepastHPCDemoModel();
	void init();
	void requestAgents();
	void doSomething();
	void initSchedule(repast::ScheduleRunner& runner);
	void recordResults();
};

Then, in the Demo_01_Model.cpp file, modify the schedule to call this method at step 1 of the simulation (not) repeating. The 'doSomething' method is now delayed until step 2:

void RepastHPCDemoModel::initSchedule(repast::ScheduleRunner& runner){
	runner.scheduleEvent(1, repast::Schedule::FunctorPtr(new repast::MethodFunctor<RepastHPCDemoModel> (this, &RepastHPCDemoModel::requestAgents)));
	runner.scheduleEvent(2, 1, repast::Schedule::FunctorPtr(new repast::MethodFunctor<RepastHPCDemoModel> (this, &RepastHPCDemoModel::doSomething)));
	runner.scheduleEndEvent(repast::Schedule::FunctorPtr(new repast::MethodFunctor<RepastHPCDemoModel> (this, &RepastHPCDemoModel::recordResults)));
	runner.scheduleStop(stopAt);
}

And, also in Demo_01_Model.cpp, the key code is here:

void RepastHPCDemoModel::requestAgents(){
	int rank = repast::RepastProcess::instance()->rank();
	int worldSize= repast::RepastProcess::instance()->worldSize();
	repast::AgentRequest req(rank);
	for(int i = 0; i < worldSize; i++){                              // For each process
		if(i != rank){                                           // ... except this one
			std::vector<RepastHPCDemoAgent*> agents;        
			context.selectAgents(5, agents);                 // Choose 5 local agents randomly
			for(size_t j = 0; j < agents.size(); j++){
				repast::AgentId local = agents[j]->getId();          // Transform each local agent's id into a matching non-local one
				repast::AgentId other(local.id(), i, 0);
				other.currentRank(i);
				req.addRequest(other);                      // Add it to the agent request
			}
		}
	}
	std::cout << " BEFORE: RANK " << rank << " has " << context.size() << " agents." << std::endl;
	repast::RepastProcess::instance()->requestAgents<RepastHPCDemoAgent, RepastHPCDemoAgentPackage, RepastHPCDemoAgentPackageProvider, RepastHPCDemoAgentPackageReceiver>(context, req, *provider, *receiver, *receiver);
	std::cout << " AFTER:  RANK " << rank << " has " << context.size() << " agents." << std::endl;
}

The first two lines simply retrieve useful values- the process's rank and the size of the world (the number of total MPI processes).

The next line creates an AgentRequest object, initialized using the current 'rank' value.

The next line begins a loop through all the processes by rank number; the subsequent line excludes the current rank (no need to borrow agents from 'self')

The next lines create a vector of agent pointers, and, using the 'selectAgent' method of the context, chooses five agents and retrieves pointers to them.

The next line begins a loop through these five agent pointers. Each agent pointed to will have an 'AgentId', which comprises four integers: an id 'number', the rank on which the agent was created, the agent 'type', and the rank on which the agent is currently found. All agents so far have type=0. IMPORTANT: The programmer is responsible for ensuring that the three values 'id, start rank, and type' should combine to form a globally unique identifier for the agents. Failure to do so can lead to a program crash (because internally Repast HPC indexes agents on these IDs and expects them to be unique).

The routine here takes the local agents' ID values and transforms them to match an ID of an agent on another process. For example, on Process '0', the first agent chosen by the selectAgents routine is Agent[4, 0, 0, 0]- that is, agent #4 created on rank 0, of type 0, and currently on rank 0. This ID would be transformed to represent an agent on process 1: [4, 1, 0, 1]. We can guarantee that this agent exists on process 1 only because we know that all processes are creating the same set of agents and numbering them equivalently. The new ID is added to the agent request, and the loops continue until 5 agents are being requested from each of the other processes.

The last three lines are our main focus. The first prints the number of agents in the context before the exchange, and the last prints the number of agents after the exchange, for illustrative purposes. The real action is in the middle line:

	repast::RepastProcess::instance()->requestAgents<RepastHPCDemoAgent, RepastHPCDemoAgentPackage, RepastHPCDemoAgentPackageProvider, RepastHPCDemoAgentPackageReceiver>(context, req, *provider, *receiver, *receiver);

This line calls the 'requestAgents' function, a member function of the RepastProcess instance. The function is templated: the types that will be used for the classes that play the roles of 'agent', 'package', 'provider', and 'receiver', must be specified between the brackets. The actual arguments passed are: the context, the request, and the provider and receiver objects.

When this line is executed, the agent request is transmitted to the RepastProcess instance; the RepastProcess instance engages in communication with the other processes, and informs them of its requests. It also receives the requests coming from other processes. It then sends out the agent information that was requested of it, and receives the information in reply to its requests. Note that the 'serialize' method of the AgentPackage is called by the Boost library as the AgentPackage is being transmitted.<.p>

When run, the output will look like:

diswl189:work murphy$ /usr/local/bin/mpirun -n 4 ./bin/Demo_01.exe ./props/config.props ./props/model.props stop.at=4 random.seed=2 count.of.agents=10
 BEFORE: RANK 0 has 10 agents.
 BEFORE: RANK 1 has 10 agents.
 BEFORE: RANK 2 has 10 agents.
 BEFORE: RANK 3 has 10 agents.
 AFTER:  RANK 0 has 25 agents.
 AFTER:  RANK 1 has 25 agents.
 AFTER:  RANK 2 has 25 agents.
 AFTER:  RANK 3 has 25 agents.

Each process originally had 10 agents; after the exchange, it has received 5 from each of the three other processes, for a new total of 25 agents.