Building Your First Agent

gkmurthy10 edited this page May 22, 2018 · 27 revisions

In this tutorial, we will go over how to make a POLARIS simulation from scratch.

Begin by opening the Visual Studio solution produced at Getting Started

Switch your configuration from Debug to Release

Next, under the "apps" filter - go to the Test_Application and open Test.cpp, you should see the following

#include "Core\Core.h"

using namespace polaris;

struct MasterType
{
};

int main()
{
}

####Defining The Agent So, what we intend to accomplish is to create 2 simple agents which says their name and the current iteration.

The first step is to define the agent like so:

using namespace polaris;

// you can also just use the macro "implementation" before struct to get the same text
template<typename MasterType,typename InheritanceList = NULLTYPELIST>
struct Agent : public Polaris_Component<MasterType,INHERIT(Agent),Execution_Object>
{

};

struct MasterType
{
};

int main()
{
}

There are already some unconventional things which we will cover briefly:

  • MasterType is a directory of sorts which gives all Polaris Components to all other Polaris Components at compile time
  • In order to get the event scheduling mechanisms, you need to inherit from Polaris_Component
  • Polaris_Component is a template class which wants to know:
  • What the MasterType is
  • Polaris objects know their full identity at compile time, this is tracked in the type "InheritanceList" and communicated to the Polaris_Component by using the INHERIT() macro
  • What type of POLARIS object, in this case an Execution_Object because we want it to be able to schedule events

####Adding Behavior So, the next step is to add our behavior

#include "Core\Core.h"

using namespace polaris;

template<typename MasterType,typename InheritanceList = NULLTYPELIST>
struct Agent : public Polaris_Component<MasterType,INHERIT(Agent),Execution_Object>
{
	// you can also use declare_event(EVENT_NAME) if you forget the signature
	static void Talk(Agent* _this,Event_Response& response)
	{
		cout << "My name is: " << _this->_name << "; the current iteration is: " << iteration() << endl;

		response.next._iteration = iteration() + 1;
		response.next._sub_iteration = 0;
	}

	void Setup(string& name)
	{
		_name = name;

		Load_Event(&Talk,0,0);
	}

	// we prefer member variables to be prefixed with an underscore
	string _name;
};

struct MasterType
{
};

int main()
{

}

Here we have a setup method and an event function. All events use the same signature - they return void and take as parameters pointer to the class and a reference to the specialized structure Event_Response.

The setup function loads the event into the execution engine and specifies the first iteration and sub-iteration it will be executed. Polaris commonly uses iteration to denote a clock ticking and sub-iteration to differentiate the different events which may occur during that clock tick. In this case the agent will go on the first iteration and the first sub-iteration (0,0).

Our event function does what we said we wanted it to do, the person will say their name and what the current iteration is. Notice that since this is a static function we don't have an implicit "this", however POLARIS makes sure to supply that information in the _this variable. The iteration is one of several global variables available, accessible through getter functions.

The primary way for an agent to schedule themselves for the future is through the use of the Event_Response structure. This structure is set to go next at iteration = iteration()+1 and sub_iteration = sub_iteration().

####Set Up the Simulation Now that we have set up our agent, it is time to set up the simulation.

#include "Core\Core.h"

using namespace polaris;

template<typename MasterType,typename InheritanceList = NULLTYPELIST>
struct Agent : public Polaris_Component<MasterType,INHERIT(Agent),Execution_Object>
{
	static void Talk(Agent* _this,Event_Response& response)
	{
		cout << "My name is: " << _this->_name << "; the current iteration is: " << iteration() << endl;

		response.next._iteration = iteration() + 1;
		response.next._sub_iteration = sub_iteration();
	}

	void Setup(string& name)
	{
		_name = name;

		Load_Event(&Talk,0,0);
	}

	string _name;
};

struct MasterType
{
};

int main()
{
	// Simulation configuration is used to set the run parameters for the program
	Simulation_Configuration cfg;
	// use this to set up a single-threaded run, specifying the number of iterations and threads to use
	cfg.Single_Threaded_Setup(10);
	// initialize the run environment
	INITIALIZE_SIMULATION(cfg);

}

Setting up the simulation is fairly straightforward; before the simulation can be set up, Core needs to know a few details. The main relevant ones are how many threads to use and how many iterations to perform. Here we will use a default single threaded configuration which will run for 10 iterations. The configuration information is passed along through the use of the INITIALIZE_SIMULATION() macro.

####Creating Agents Next, we should make our two agents, Bob and Sally.

#include "Core\Core.h"

using namespace polaris;

template<typename MasterType,typename InheritanceList = NULLTYPELIST>
struct Agent : public Polaris_Component<MasterType,INHERIT(Agent),Execution_Object>
{
	static void Talk(Agent* _this,Event_Response& response)
	{
		cout << "My name is: " << _this->_name << "; the current iteration is: " << iteration() << endl;

		response.next._iteration = iteration() + 1;
		response.next._sub_iteration = sub_iteration();


		// If we wanted to look up Sally for instance, we can use her uuid
		if(_this->uuid() == 1)
		{
			Agent* sally = Object_Lookup<Agent>(2);
			cout << "Found: " << sally->_name << endl;
		}
	}

	void Setup(string& name)
	{
		_name = name;

		Load_Event(&Talk,0,0);
	}

	string _name;
};

struct MasterType
{
	typedef Agent<MasterType> agent_type;
};

int main()
{
	Simulation_Configuration cfg;
	cfg.Single_Threaded_Setup(10);
	INITIALIZE_SIMULATION(cfg);

	// the MT macro is short-hand for MasterType
	MT::agent_type* bob = Allocate< MT::agent_type >(1);

	bob->Setup(string("Bob"));

	MT::agent_type* sally = Allocate< MT::agent_type >(2);
	
	sally->Setup(string("Sally"));
}

The first thing to point out is that we have started making use of MasterType. Many POLARIS objects are very interested in what types things are, to keep them all straight and to make sure agent_type means the same thing throughout the simulation we log them in the MasterType and make reference to them.

POLARIS objects should be allocated using the POLARIS-specific global Allocate function. The Allocate function mainly wants to know what type of thing to allocate (in this case MT::agent_type). Notice that Allocate can also take a universally unique id (uuid) for this object when it is allocated - Bob is 1 and Sally is 2. This enrolls the agent into a directory which allows it to be accessible from anywhere in the code through the use of its' uuid. Also note that allocate only takes one argument - constructors which are not default constructors are not supported for POLARIS objects at this time.

####Run The Simulation

Finally, we can complete our simulation code and run it.

#include "Core\Core.h"

using namespace polaris;

template<typename MasterType,typename InheritanceList = NULLTYPELIST>
struct Agent : public Polaris_Component<MasterType,INHERIT(Agent),Execution_Object>
{
	static void Talk(Agent* _this,Event_Response& response)
	{
		cout << "My name is: " << _this->_name << "; the current iteration is: " << iteration() << endl;

		response.next._iteration = iteration() + 1;
		response.next._sub_iteration = sub_iteration();

		if(_this->uuid() == 1)
		{
			Agent* sally = Object_Lookup<Agent>(2);
			cout << "Found: " << sally->_name << endl;
		}
	}

	void Setup(string& name)
	{
		_name = name;

		Load_Event(&Talk,0,0);
	}

	string _name;
};

struct MasterType
{
	typedef Agent<MasterType> agent_type;
};

int main()
{
	Simulation_Configuration cfg;
	cfg.Single_Threaded_Setup(10);
	INITIALIZE_SIMULATION(cfg);

	MT::agent_type* bob = Allocate< MT::agent_type >(1);

	bob->Setup(string("Bob"));

	MT::agent_type* sally = Allocate< MT::agent_type >(2);
	
	sally->Setup(string("Sally"));

	START();

	TERMINATE_SIMULATION();
}

Next we can compile. A couple of other projects need to be built before this one can. Fortunately, this is easy to do. First, right click on the IO library and click build; Next, right click on the Antares library and click build. Now, you should be able to right click on the Test_Application project and click build.

The output executable is located in the build\bin\CONFIGURATION within the build folder. POLARIS depends on a number of dlls which need to be known, so before we can run, it is usually handy to create a quick batch shell which shows where the dependencies are located - here is the one I use from the bin directory:

@echo off
set PATH=C:/opt/polarisdeps/glew-1.7.0/bin;%PATH%
set PATH=C:/opt/polarisdeps/plplot/bin;%PATH%
set PATH=C:/opt/polarisdeps/wxWidgets-2.9.3/lib/vc_dll;%PATH%
set PATH=C:/opt/polarisdeps/gperftools-2.1/Release;%PATH%
set PATH=C:/opt/polarisdeps/libodb-2.2.2/libodb-sqlite-2.2.0/etc/sqlite/bin64;%PATH%
set PATH=C:/opt/polarisdeps/libodb-2.2.2/libodb-sqlite-2.2.0/bin64;%PATH%
set PATH=C:/opt/polarisdeps/libodb-2.2.2/bin64;%PATH%
cd Release
Test_Application.exe
pause

Run the batch file and if everything went as planned we can see the desired output:

My name is: Bob; the current iteration is: 0
Found: Sally
My name is: Sally; the current iteration is: 0
My name is: Bob; the current iteration is: 1
Found: Sally
My name is: Sally; the current iteration is: 1
My name is: Bob; the current iteration is: 2
Found: Sally
My name is: Sally; the current iteration is: 2
My name is: Bob; the current iteration is: 3
Found: Sally
My name is: Sally; the current iteration is: 3
My name is: Bob; the current iteration is: 4
Found: Sally
My name is: Sally; the current iteration is: 4
My name is: Bob; the current iteration is: 5
Found: Sally
My name is: Sally; the current iteration is: 5
My name is: Bob; the current iteration is: 6
Found: Sally
My name is: Sally; the current iteration is: 6
My name is: Bob; the current iteration is: 7
Found: Sally
My name is: Sally; the current iteration is: 7
My name is: Bob; the current iteration is: 8
Found: Sally
My name is: Sally; the current iteration is: 8
My name is: Bob; the current iteration is: 9
Found: Sally
My name is: Sally; the current iteration is: 9
Press any key to continue . . .

####Next Visualizing Your Agent

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.