-
Notifications
You must be signed in to change notification settings - Fork 8
Data Extraction
Next thing to show is how to extract data from a simulation. So far,
only processors were used that have all their private storage, and
each of them is executed once per simulation round. For data
collection, however, a simulation task should be used, because then
there is only one instance that has access to the whole
simulation. When iterating through the nodes to collect the wanted
information, the task has particularly access to the
shawn::Node
which in turn owns a collection of processors (at
least one). To avoid that the task must access the processor directly,
it is possible (for the processor) to add a so called tag
(see XXX) to the corresponding node. This
tag
can in turn be easily read by the task.
At first, the processor must
add a tag to the node, and write data that can be collected by the
task. Therefore the header of SimpleAppProcessor
is
extended by the following lines
protected:
void write_int_tag( shawn::Node& node,
const std::string& tag_name,
int value ) throw();
to declare a new method that writes an integer value to a given tag name that in turn is added to the given node.
The source file must include the integer tag which is a basic one and
can be found in basic_tags.h
:
#include "sys/taggings/basic_tags.h"
Then, the implementation of the declared method is added
// ----------------------------------------------------------------------
void
SimpleAppProcessor::
write_int_tag( shawn::Node& node, const std::string& tag_name, int value )
throw()
{
shawn::TagHandle tag = node.find_tag_w( tag_name );
if ( tag.is_not_null() )
{
shawn::IntegerTag* intt = dynamic_cast<shawn::IntegerTag*>( tag.get() );
if ( intt != NULL )
{
intt->set_value( value );
}
}
else
{
shawn::IntegerTag *intt = new shawn::IntegerTag( tag_name, value );
node.add_tag( intt );
}
}
It first checks, if a tag of the given name already exists. In the
consequence that we will potentially alter this value, we use
find_tag_w
that returns a writable handle (if
there exists any).
If the given name has been found (and thus tag
is not null),
it is checked whether the found tag is an integer one. If so
(intt
is not null), the stored value is updated with the new
one. Otherwise, if the tag is not an integer one, we can not write an
integer to an unknown tag type, and thus exit without doing
anything. Here you see the type-safety of the implemented tagging in
Shawn.
If the given tagname could not be found, a new tag is created and attached to the node. The name for the tag as well as the value are given to the constructor. Then, the tag is added to the node.
Next, the method must be called from somewhere. For simplicity
reasons, we write the actual number of known neighbors to the
tag. Alter the method process_message
as follows.
if( owner() != msg->source() )
{
neighbours_.insert( &msg->source() );
INFO( logger(), "Received message from '"
<< msg->source().label()
<< "'" );
}
// write actual number of known neighbors to tag
write_int_tag( owner_w(), "neighbours", neighbours_.size() );
return true;
The only new code is the line containing write_int_tag
(including the corresponding commment), whereas the rest of the shown
code is already known. When running the simulation again, each
processor will add a tag which contains the actual number of neighbors
to the corresponding node.
However, if a node has no neighbors, it would not receive any message and thus would not create any tag named neighbors. Therefore we add
// write actual number of known neighbors to tag
write_int_tag( owner_w(), "neighbours", 0 );
to method boot
, so that the tag will be initialized in any
case.
There is already an example task that is able to iterate through the
nodes, and prints all present tags (including value, name of tag, and
type). It is part of the examples module in apps
(and must be
enabled when calling ccmake ../src
). It is called
tagtest
. Hence, when appending tagtest
to the above
generated configuration file, and running Shawn again with this task
executed at last results in Figure
XXX.
For each node, it prints the id and the corresponding tags. The appearence of the lines containing tags follows a certain rule:
tag_name = value [tag_type]
For example, there is one tag attached to node 99 (the one we created). Its name is neighbours, its value is 10, and it is of type integer.
Since there is one integer tag that contains the actual number of neighbors attached to each node, this data must be collected. The usual way is to implement a simulation task, and start it once after the simulation finished. The task runs through all nodes and reads the appropriate information.
First, the header collect_simple_app_task.h must be created that contains the following code.
#ifndef __SHAWN_LEGACYAPPS_COLLECT_SIMPLE_APP_TASK_H
#define __SHAWN_LEGACYAPPS_COLLECT_SIMPLE_APP_TASK_H
#include "_legacyapps_enable_cmake.h"
#ifdef ENABLE_SIMPLE_APP
#include "sys/simulation/simulation_controller.h"
#include "sys/simulation/simulation_environment.h"
#include "sys/simulation/simulation_task.h"
#include "sys/node.h"
namespace simple_app
{
class CollectSimpleAppTask
: public shawn::SimulationTask
{
public:
CollectSimpleAppTask();
virtual ~CollectSimpleAppTask();
virtual std::string name( void ) const throw();
virtual std::string description( void ) const throw();
virtual void run( shawn::SimulationController& sc )
throw( std::runtime_error );
protected:
bool read_int_tag( const shawn::Node& node,
const std::string& tag_name,
int& value ) const throw();
};
}
#endif
#endif
The task CollectSimpleAppTask
inherits from
shawn::SimulationTask
, and thus must implement the methods
name()
, description()
, and run()
. As
already described earlier, name()
defines the command which
starts the task when running Shawn. description()
can be used
to get the purpose of the task. run()
is called when the task
is started.
There is also the additional method read_int_tag
that can
read a given integer tag (identified by its name) from the given node,
and stores the value. If successful, the method returns
true
. Otherwise, for example, if the tagname does not exist,
it returns false
.
The implementation of the task is shown next.
#include "legacyapps/simple_app/collect_simple_app_task.h"
#ifdef ENABLE_SIMPLE_APP
#include "sys/taggings/basic_tags.h"
#include "sys/world.h"
namespace simple_app
{
CollectSimpleAppTask::
CollectSimpleAppTask()
{}
// ----------------------------------------------------------------------
CollectSimpleAppTask::
~CollectSimpleAppTask()
{}
// ----------------------------------------------------------------------
std::string
CollectSimpleAppTask::
name( void )
const throw()
{
return "collect_simple_app";
}
// ----------------------------------------------------------------------
std::string
CollectSimpleAppTask::
description( void )
const throw()
{
return "Collect information from SimpleAppProcessor";
}
// ----------------------------------------------------------------------
void
CollectSimpleAppTask::
run( shawn::SimulationController& sc )
throw( std::runtime_error )
{
require_world( sc );
int neighbors = 0, count = 0;
for( shawn::World::const_node_iterator
it = sc.world().begin_nodes();
it != sc.world().end_nodes();
++it )
{
int tag_value;
if ( read_int_tag( *it, "neighbors", tag_value ) )
{
neighbors += tag_value;
count++;
}
}
if ( count > 0 )
{
double connectivity = (double)neighbors / (double)count;
INFO( logger(), "Connectivity: " << connectivity );
}
else
{
INFO( logger(), "No tags found." );
}
}
// ----------------------------------------------------------------------
bool
CollectSimpleAppTask::
read_int_tag( const shawn::Node& node,
const std::string& tag_name,
int& value )
const throw()
{
shawn::ConstTagHandle tag = node.find_tag( tag_name );
if ( tag.is_not_null() )
{
const shawn::IntegerTag* intt =
dynamic_cast<const shawn::IntegerTag*>( tag.get() );
if (!intt)
return false;
value = intt->value();
return true;
}
return false;
}
}
#endif
The read_int_method
gets the node from which it should read
the tag, the name of the tag, and a reference to a variable in which
it stores the read value. First, it tries to find the tag by its
name. If not found, the method returns false
. Otherwise, it
checks whether the found tag is an integer one. If so, it assigns the
value and returns true
. If not, false
is returned.
The run()
method works as follows. First, it is checked if
the world already exists by the method require_world
. If not
(for example, if the task is called in the first line of the
configuration file), an exception is thrown, and the simulation is
aborted. Then, it is iterated through all nodes in the world. For each
node, it is tried to read the previously written tag (remember method
write_tag
in the processor). If successful, the read value
is added to the sum of all neighbors, and a counter is
incremented. These two values are used after the loop to calculate the
average connectivity in the network. If not possible (no tags could be
read), an appropriate message is printed.
The next step is to add the task to Shawn, so that it can be found if
called in a configuration file. That means, the task must be added to
the simulation task keeper that is part of the
SimulationController
. The usual location for doing so is the
init method. Hence, simple_app_init.cpp
is altered as
follows.
#include "legacyapps/simple_app/simple_app_processor_factory.h"
#include "legacyapps/simple_app/collect_simple_app_task.h"
#include "sys/simulation/simulation_controller.h"
#include "sys/simulation/simulation_task_keeper.h"
extern "C" void init_simple_app( shawn::SimulationController& sc )
{
simple_app::SimpleAppProcessorFactory::register_factory( sc );
sc.simulation_task_keeper_w().add( new simple_app::CollectSimpleAppTask );
}
In the consequence that a new file has been added (the new task), that
file must be made public to CMake. So either rerun ccmake
, or
alternatively edit (add and remove a space) and save
module.cmake
. Then recompile.
Before running the simulation again, change the configuration file as follows.
prepare_world edge_model=simple comm_model=disk_graph range=2
rect_world width=10 height=10 count=100 processors=simple_app
send_round=8
simulation max_iterations=10
collect_simple_app
connectivity
In the end of the task there were two tasks added. The newly
implemented collect_ simple_app
, and connectivity
to compare the results. An example is shown in Figure
XXX.
Both tasks show an average connectivity of 10.64. The former computed the value by exchanging messages (and thus the transmission model). The latter used the internal communication model for acquiring the information.