To use the class GT_Algorithm, insert the following code in
your program:
#include <gt_base/Graph.h> #include <gt_base/Algorithm.h>
The class GT_Algorithm provides a base class and a standard
interface for the implementation of graph algorithms. The key idea here is
that each algorithm is a class, and is invoked with the run
method. Noth parameters and results are stored as class member variables,
instead of parameters and/or function results as usual.
The major advantage of this approach is that all of Graphlet's interfaces provide special support for algorithms which are implemented with this class. Especially, the Tcl interface for algorithms is designed to take an algorithm that has been implemented with GT_Algorithm, and then construct a Tcl interface for it. Since algorithms already have a standardized interface, all the programmer has to supply is code for parsing parameters and error handling.
Another advantage of our approach is that algorithms may be parts of data structures. That is, a class may contain one or more algorithms, for example to customize the building of the data structure. As another example, test suites may use lists of arrays of algorithms.
All algorithms in Graphlet are derived from the class
GT_Algorithm. The following example shows a minimal algorithm
class implemented with GT_Algorithm:
class Sample : public GT_Algorithm {
public:
Sample (const string& name);
virtual ~Sample ();
virtual int run (GT_Graph& g);
virtual int check (GT_Graph& g, string& message);
}
Sample (const string& name)Sample. name is usually
the name of the Tcl command that is associated with the algorithm.
GT_Algorithm's constructor should be defined as follows:
Sample::Sample(const string& name) : GT_Algorithm (name) {
// Initialize the algorithm's parameters here
}
virtual ~ Sample()Sample
must contain a virtual destructor since GT_Algorithm already
has virtual functions.
virtual int run (GT_Graph& g)run executes the algorithm. Its parameter is the input graph.
This procedure must return GT_OK if the command could be
completed successfully, and GT_ERROR otherwise. If
GT_ERROR is returned, then the Tcl interface raises an
exception.
run without returning a value. This will result in
unpredictable Tcl runtime errors.
virtual int check(GT_Graph& g, string&
message)check should be called
before run to make sure that the current graph g is
valid input for the algorithm. This method should return GT_OK
if the graph is valid, and GT_ERROR otherwise. The parameter
message should contain a detailed error message if the check
fails. Additional error information may be returned in member variables.
GT_Algorithm does not automatically
call check before run. In contrast to that, the
Tcl interface for GT_Algorithm enforces
this.
virtual void reset ()reset reinitializes the member variables of the algorithm.
Typically, reset puts the member variables back to the
same state as after the constructor.
check, reset is
never called automatically. However, the Tcl interface has a configurable
option to to do this each time the command is executed.
reset is neccessary because of the following:
GT_Sample_Algorithm a;
// a has three parameters:
// parameter1, default value false
// parameter2, default value 0
// parameter3, default value ""
// First run: parameter1, parameter2 changed, parameter3 default.
a.parameter1 (true);
a.parameter2 (42);
a.run ();
// Second run: parameter2, parameter1 changed,
// but parameter1 does not have the default value.
a.parameter2 (21);
a.parameter3 ("xyz");
a.run ();
reset is primarily utilized by the Tcl interface. The idea
is that there is one Tcl command for each algorithm, with a single C++ object
of the corresponding class behind it. If the algorithm is executed several
times with varying parameter sets, then it is neccessary to reset the
parameters inbetween these executions.
static edge find_self_loop (GT_Graph&
g)find_self_loop searches for a
self look in g. It returns 0 if no self loop is
found.
static bool remove_all_bends (GT_Graph&
g)remove_all_bends removes all
bends in the edges of g. It returns true if bends
are found, and false otherwise.
void adjust_coordinates (GT_Graph& g,
double min_x = 0,
double min_y = 0)adjust_coordinates moves the graph so that the minimum
coordinates are at (min_x,min_y).
Note: This set of utilities is still rudimentary. Please
send suggestions for more utilities, complete with code, to
graphlet@fmi.uni-passau.de.
The following C++ header header shows how to implement an algorithm that constructs a matching:
class GT_Matching_Algorithm : public GT_Algorithm {
typedef GT_Algorithm baseclass;
// Input: edge weights
edge_map<double>* the_edge_weights;
// The result is stored in this variable.
list<edge> the_matching;
// Information on errors.
int the_error_code;
list<edge> the_self_loops;
list<edge> the_multiple_edges;
list<list<node> > the_components;
public:
// Constructor and Destructor
GT_Matching_Algorithm (const string& name);
virtual ~GT_Matching_Algorithm ();
// Standard methods
virtual void reset ();
virtual int run (GT_Graph& g);
virtual int check (GT_Graph& g, string& message);
// Accessors
void edge_weights(const edge_map<double>&) const;
inline const edge_map<double>& edge_weights() const;
inlint int error_code() const;
inline const list<edge>& matching() const;
inline const list<edge>& self_loops() const;
inline const list<edge>& multiple_edges() const;
inline const list<list<node> >& components() const;
enum {
no_error,
error_self_loops,
error_multiple_edges,
error_not_connected;
};
};
The following conventions are used in the above interface:
the_matching.
check (in this case, self
loops, multiple edges, or the graph is not connected), then the offending
object is stored in the_self_loops,
the_multiple_edges and/or the_components.
The following code implements the constructor for the matching
algorithm. There are no edge weights yet; this variable can only be
initialized when a graph is kown (e.g. in run, or by
explicitely setting this variable from edge_weights).
GT_Matching_Algorithm::GT_Matching_Algorithm (
const string& name) :
GT_Algorithm (name)
{
the_edge_weights = 0;
the_error_code = no_error; // (yet)
// All other member variables have default constructors
}
The desructor is usually quite simple; since most of the clean-up is accomplished in the destructors of the individual members, we only need to free data that has been dynamically allocated.
GT_Matching_Algorithm::~GT_Matching_Algorithm ()
{
if (the_edge_weights != 0) {
delete the_edge_weights;
}
}
If the algorithm had added user
defined attributes to the graph, then reset might also
need to remove some of these.
The method reset is optional and resets all options and
member variables. Note that since the_edge_weights os a pointer, we must
deallocate it.
void GT_Matching_Algorithm::reset ()
{
if (the_edge_weights != 0) {
delete the_edge_weights;
}
the_edge_weights = 0;
the_error_code = no_error;
the_self_loops.clear();
the_multiple_edges.clear();
the_components.clear();
}
The method check tests wether the graph is connected, searches
for self loops and multiple edges, and also checks wether the edge weights
are used consistently, e.g. there are no edge weights, or edge weights for
all edges. If it finds an error, it deposits it in the appropriate member
variable, sets message to a description of the error and
returns GT_ERROR.
int GT_Matching_Algorithm::check (GT_Graph& g, string& message)
{
if (find_multiple_edges (g, the_self_loops)) {
message = "The graph contains self a loop";
the_error_code = error_self_loops;
return GT_ERROR;
}
if (find_multiple_edges (g, the_multiple_edges)) {
message = "The graph contains multiple edges";
the_error_code = error_multiple_edges;
return GT_ERROR;
}
if (components (g, the_components) > 1) {
message = "The graph is not connected";
the_error_code = error_not_connected;
return GT_ERROR;
}
message = "";
the_error_code = no_error;
return GT_OK;
}
Note: Although checking for errors is a good idea, it is even better to design your algorithm to work with all inputs, that is
The method run is implemented in a straightforward way.
Depending on the option the_edge_weights, we either call a
matching option with or without edge weights (the latter being easier).
The method always returns GT_OK, since there will always be a
matching.
int GT_Matching_Algorithm::run (GT_Graph& g)
{
const graph& gtl = g.gtl();
the_matching.clear(); // Make sure to remove last time's edges
if (the_edge_weights != 0) {
weightless_matching (g, the_matching);
} else {
weighted_matching (g, the_edge_weight, the_matching);
}
return GT_OK;
}