16.07.2013
В Аргоннской национальной лаборатории разработано программное обеспечение для проектирования агент-ориентированных моделей с целью последующего запуска на суперкомпьютерах, — Repast for High Performance Computing (RepastHPC). Данный пакет реализован с использованием языка C++ и MPI — программного интерфейса для обмена сообщениями между процессами, выполняющими задачу в параллельном режиме, а также библиотеки Boost, расширяющей C++.
В рамках RepastHPC реализован динамический дискретно-событийный планировщик выполнения программных инструкций с консервативными алгоритмами синхронизации, предусматривающими задержку процессов для соблюдения определенной очередности их выполнения.

Рис. 1. RepastHPC многократно тестировался в Аргоннской национальной лаборатории на суперкомпьютере IBM Blue Gene/P
В RepastHPC агенты распределяются между процессами, и каждый процесс связан с агентом, являющимся локальным по отношению к данному процессу. В свою очередь агент локален к процессу, выполняющему программный код, описывающий поведение данного агента. При этом копии остальных — нелокальных — агентов могут присутствовать в любом процессе, что позволяет агентам всей модели взаимодействовать с этими копиями. К примеру, пусть пользователь в своей модели, предполагающей параллельные вычисления, использует два процесса — P1 и P2, каждый из которых создает определенное количество агентов и имеет собственный планировщик выполнения программных инструкций. Агенты, поведение которых рассчитывается на процессе P1, являются локальными по отношению к данному процессу, и только в рамках данного процесса программный код может изменить их состояние (аналогично и для процесса P2). Предположим, процесс P1 запрашивает копию агента A2 из процесса P2. Агент A2 не является локальным по отношению к процессу P1, и соответственно программный код, выполняемый в рамках процесса P1, не может изменить состояние агента A2. При этом агенты, реализуемые в рамках процесса P1, при необходимости могут запросить состояние агента A2, но копия A2 останется неизменной. Изменение оригинального A2 возможно только в рамках процесса P2, но в этом случае RepastHPC синхронизирует изменения состояния агента между всеми процессами.
Пример агентной модели в RepastHPC
Как правило, для реализации агентной модели в рамках RepastHPC требуются: 1) классы объектов типа «Агент» (листинг 1); 2) класс верхнего уровня — Model; 3) функция main.
… class Agent
{
public :virtual ~Agent () {}virtual AgentId & getId () = 0;virtual const AgentId & getId () const = 0;
}; …
Листинг 1. Пример интерфейса класса «Агент»
В листинге 2 создается конструктор класса с единственной переменной _state типа int, которая определяет состояние агента. Естественно, в моделях агенты обычно имеют более сложную структуру.
… class ModelAgent : public repast :: Agent
{
private :
repast :: AgentId _id ;int _state ;
public :
ModelAgent ( repast :: AgentId id , int state );virtual ~ ModelAgent ();int state () const{
return _state ;
}void state (int val ){
_state = val ;
}repast :: AgentId & getId (){
return _id ;
}const repast :: AgentId & getId () const{
return _id ;
}void flipState ();
}
…
Листинг 2. Конструктор класса, определяющего состояние агента
Далее создается структура, позволяющая скопировать агент от одного процесса к другому (листинг 3).
…
struct ModelAgentPackage
{
friend class boost :: serialization :: access ;template < class Archive >void serialize ( Archive & ar , const unsigned int version ){
ar & id;
ar & state;
}repast :: AgentId id;int state;repast :: AgentId getId () const{
return id;
}
}
…
Листинг 3. Пример структуры, осуществляющей копирования агента между процессами
Класс верхнего уровня Model нужен для начальной инициализации модели и для старта симуляции. Как правило, в рамках данного класса осуществляется распределение агентов по процессам, а также создается среда для функционирования агентов (решетка или непрерывное пространство). Кроме того, происходит инициализация данных для модели и планирование расписания для синхронизации. В листинге 4 приведен пример определения класса Model.
…
class Model
{
private :
int rank ;
public :
repast :: SharedContext < ModelAgent > agents ;repast :: SharedNetwork < ModelAgent , ModelEdge >* net ;repast :: SharedGrids < ModelAgent >:: SharedWrappedGrid * grid ;repast :: DataSet * dataSet ;Model ();virtual ~ Model ();void initSchedule ();void step ();
}
…
Листинг 4. Инициализация модели (пример класса Model)
В листинге 5 создается конструктор класса Model. Сначала определяется ранг процессов, в рамках которых выполняются симуляции, а затем в качестве примера создаются 4 агента с уникальным номером и определением обслуживающего процесса. Также определяется среда для взаимодействия агентов — решетка размерностью 40×60 и назначается источник данных.
…
const MODEL_AGENT_TYPE = 0;
Model :: Model()
{
rank = RepastProcess :: instance() ->rank();for (int i = 0; i < 4; i++){
AgentId id(i, rank , MODEL_AGENT_TYPE);
agents.addAgent (new ModelAgent (id, rank));
}net = new SharedNetwork < ModelAgent , ModelEdge > ("network", true);agents.addProjection(net);grid = new SharedGrids < ModelAgent >:: SharedWrappedGrid ("grid", GridDimensions (Point(40, 60)), std :: vector(2, 2), 2);
agents.addProjection(grid); NCDataSetBuilder builder ("./output/data.ncf", RepastProcess :: instance () -> getScheduleRunner(). schedule()); StateSum * sum = new StateSum (this); builder . addDataSource ( repast :: createNCDataSource ("state_sum", sum, std :: plus ())); dataSet = builder . createDataSet (); Provider provider (this); AgentsCreator creator (this); grid -> synchBuffer < ModelPackage >(agents, provider, creator);
ScheduleRunner & runner = RepastProcess :: instance () -> getScheduleRunner (); runner.scheduleStop (2000); runner.scheduleEvent (1, 1, Schedule :: FunctorPtr (new MethodFunctor (this , & Model :: step))); runner.scheduleEvent (1.1 , 1, Schedule :: FunctorPtr (new MethodFunctor < DataSet > (dataSet , & DataSet :: record))); Schedule :: FunctorPtr dsWrite = Schedule :: FunctorPtr (new MethodFunctor < DataSet > (dataSet , & DataSet :: write)); runner.scheduleEvent (25.2, 25, dsWrite); runner.scheduleEndEvent (dsWrite);
…
mpi :: environment env(argc, argv );
std :: string config = argv [1]; std :: string propsfile = argv [2]; try {
RepastProcess :: init (config); Properties props (propsfile); repast :: initializeRandom (props); Model model (); model.initSchedule (); ScheduleRunner & runner = RepastProcess :: instance () -> getScheduleRunner (); runner.run ();
} catch (std :: exception & ex) {
std :: cerr << "Error while running the rumor model : " << ex. what () << std :: endl ;
throw ex;
} RepastProcess :: instance () ->done (); return 0;