Semantic attributes are handled by the Graph class through
void *
pointers, and so any built-in or user defined type
can be used for node or edge attributes.
The use of pointers has the advantage of making possible to manage the attributes efficiently, avoiding unnecessary copies, and allowing different graphs to share the memory areas used for the attributes. On the other hand, especially in a language like C++ that does not support automatic garbage collection, there is the problem of the ownership of the pointed data structure: who is responsible for allocating the attribute, for cloning it (i.e. allocating a new copy of it) when necessary, and for deallocating it?
The easiest answer for the user would be: the Graph class. But this choice would have implied excessive limitations to the flexibility of the attribute management.
For example, in many situations it would be reasonable to have the attributes dynamically allocated on the heap, with a separated attribute instance for each node/edge. However there are cases where only a very limited set of attribute values are possible, and so a great memory saving can be obtained by preallocating these values (possibly with static allocation) and sharing the pointers among all the nodes/edges of the graphs. Giving the Graph class the responsibility for the allocation/deallocation of the attributes would imply that only one allocation policy must be chosen, at the price of an efficiency loss in the cases where this policy is not the best one.
So, the rules we have followed in the design of the library are:
ARGLoader
objects are responsible for allocating the
attributes when the graph data is generated or read; the user that
writes his/her own ARGLoader
is the most indicated person to
decide which allocation policy is the best. ARGLoader
objects
should not be concerned with attribute clonation or deallocation; they
simply pass the pointers to the Graph
object that is built.
Note that the ARGEdit
class does not perform attribute allocation;
it simply receives the attribute pointers from the caller.Graph
objects by default do not deal with attribute
allocation, clonation or deallocation. However, it is possible to
register a node destroyer and an edge destroyer
(i.e. an object that knows
how to deallocate a node/edge attribute)
within each graph.ARGLoader
, it is usually necessary to clone the node/edge
attributes.In order to avoid the need of pointer casting when dealing with
attributes, the library provides a template class ARGraph<N,E>
,
derived from Graph
, for graphs with node attributes of type
N
and edge attributes of type E
.
How are attributes employed in the matching process? The user can
provide an object of the graph class with a node comparator
and an edge comparator. These are objects implementing
the AttrComparator
abstract class, which has a compatible
method
taking two attribute pointers, and returning a bool
value that
is false
if the corresponding nodes or edges are to be
considered incompatible and must not be paired in the matching process.
In this way, the search space can be profitably pruned removing
semantically undesirable matchings.
Notice that the matching algorithm uses the comparators
of the first of the two graphs used to construct the initial state.
Now let us turn to a practical example. Suppose that our nodes
must represent points in a plane; we will associate with each node
an attribute holding its cartesian coordinates. For simplicity,
the edges will have no attributes. Suppose we have
a class Point
to represent the node attributes:
class Point
{ public:
float x, y;
Point(float x, float y)
{ this->x=x;
this->y=y;
}
};
Now, if we want to allocate the attributes on heap, we need
a destroyer class, which is an implementation of the abstract
class AttrDestroyer
. In our example, the destroyer could
be:
class PointDestroyer: public AttrDestroyer
{ public:
virtual void destroy(void *p)
{ delete p;
}
};
We will also need a comparator class for testing two points
for compatibility during the matching process. A comparator
is an implementation of the abstract class AttrComparator
.
Suppose that we consider two points to be compatible if their euclidean
distance is less than a threshold:
class PointComparator: public AttrComparator
{ private:
double threshold;
public:
PointComparator(double thr)
{ threshold=thr;
}
virtual bool compatible(void *pa, void *pb)
{ Point *a = (Point *)pa;
Point *b = (Point *)pb;
double dist = hypot(a->x - b->x, a->y - b->y);
// Function hypot is declared in <math.h>
return dist < threshold;
}
};
We can build two graphs with these attributes using the ARGEdit
class:
int main()
{ ARGEdit ed1, ed2;
ed1.InsertNode( new Point(10.0, 7.5) );
ed1.InsertNode( new Point(2.7, -1.9) );
ed1.InsertEdge(1, 0, NULL);
// ... and so on ...
ARGraph<Point, void> g1(&ed1);
ARGraph<Point, void> g2(&ed2);
// Install the attribute destroyers
g1.SetNodeDestroyer(new PointDestroyer());
g2.SetNodeDestroyer(new PointDestroyer());
// Install the attribute comparator
// This needs to be done only on graph g1.
double my_threshold=0.1;
g1.SetNodeComparator(new PointComparator(my_threshold));
VFSubState s0(&g1, &g2);
// Now matching can begin...
Notice that the attribute destroyers and comparators
have to be allocated on heap with new
; once they are installed
they are owned by the graph, which will delete
them when they are no longer needed. So it is an
error to share a destroyer or a comparator across
graphs, as is to use a static or automatic variable
for this purpose.
Historical note:
Previous versions of the library (before 2.0.5) used simple
functions instead of full objects for deallocating or comparing
attributes. These functions were installed using the
SetNodeDestroy
, SetEdgeDestroy
, SetNodeCompat
and SetEdgeCompat
methods.
While these methods are still supported for backward compatibility,
we warmly recommend to use the new object-oriented approach, which
provides greater flexibility. For example, with the old
approach it would have been
quite difficult to obtain something equivalent to a point
comparator; the threshold would have had to be either a
compile-time costant or a global variable, with obvious drawbacks.