В Аргоннской национальной лаборатории разработано программное обеспечение для проектирования агент-ориентированных моделей с целью последующего запуска на суперкомпьютерах, — 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);
}
Листинг 5. Пример конструктора класса Model
В листинге 6 приведен пример инициализации планировщика. Как видно, остановка происходит на 2000-м шаге, но при этом на каждом шаге вызывается метод step, а также осуществляется изменение состояния агентов и запись данных.
void Model::initSchedule()
{
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);
}
Листинг 6. Инициализация планировщика модели
И наконец, функция main обычно осуществляет инициализацию MPI в рамках RepastHPC, создает класс Model и запускает планировщик модели (листинг 7).
int main(int argc, char** argv)
{
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;
}
Листинг 7. Пример функции main
Более подробно с данным ПО можно познакомиться в руководстве пользователя
[Collier Nick (2012): Repast HPC Manual, February 23, 2012]
, а по этому адресу можно скачать версию пакета 1.0.1 (по состоянию на 5 марта 2012 г.).