/* GTS - Library for the manipulation of triangulated surfaces
* Copyright (C) 1999 Stéphane Popinet
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "gts.h"
/* GtsGNodeSplit */
static void gnode_split_destroy (GtsObject * object)
{
GtsGNodeSplit * ns = GTS_GNODE_SPLIT (object);
if (gts_container_size (GTS_CONTAINER (ns->n)) == 0) {
g_assert (GTS_SLIST_CONTAINEE (ns->n)->containers == NULL);
gts_object_destroy (GTS_OBJECT (ns->n));
}
else {
GtsGNode * n1 = GTS_GNODE_SPLIT_N1 (ns);
GtsGNode * n2 = GTS_GNODE_SPLIT_N2 (ns);
g_warning ("Memory deallocation for GtsGNodeSplit not fully implemented yet: memory leak!");
}
(* GTS_OBJECT_CLASS (gts_gnode_split_class ())->parent_class->destroy)
(object);
}
static void gnode_split_class_init (GtsGNodeSplitClass * klass)
{
GTS_OBJECT_CLASS (klass)->destroy = gnode_split_destroy;
}
static void gnode_split_init (GtsGNodeSplit * ns)
{
ns->n = NULL;
ns->n1 = ns->n2 = NULL;
}
/**
* gts_gnode_split_class:
*
* Returns: the #GtsGNodeSplitClass.
*/
GtsGNodeSplitClass * gts_gnode_split_class (void)
{
static GtsGNodeSplitClass * klass = NULL;
if (klass == NULL) {
GtsObjectClassInfo gnode_split_info = {
"GtsGNodeSplit",
sizeof (GtsGNodeSplit),
sizeof (GtsGNodeSplitClass),
(GtsObjectClassInitFunc) gnode_split_class_init,
(GtsObjectInitFunc) gnode_split_init,
(GtsArgSetFunc) NULL,
(GtsArgGetFunc) NULL
};
klass = gts_object_class_new (gts_object_class (), &gnode_split_info);
}
return klass;
}
/**
* gts_gnode_split_new:
* @klass: a #GtsGNodeSplitClass.
* @n: a #GtsGNode.
* @n1: a #GtsGNodeSplit or #GtsGNode.
* @n2: a #GtsGNodeSplit or #GtsGNode.
*
* Creates a new #GtsGNodeSplit which would collapse @n1 and @n2 into
* @n. The collapse itself is not performed.
*
* Returns: the new #GtsGNodeSplit.
*/
GtsGNodeSplit * gts_gnode_split_new (GtsGNodeSplitClass * klass,
GtsGNode * n,
GtsObject * n1,
GtsObject * n2)
{
GtsGNodeSplit * ns;
g_return_val_if_fail (klass != NULL, NULL);
g_return_val_if_fail (n != NULL, NULL);
g_return_val_if_fail (GTS_IS_GNODE_SPLIT (n1) || GTS_IS_GNODE (n1), NULL);
g_return_val_if_fail (GTS_IS_GNODE_SPLIT (n2) || GTS_IS_GNODE (n2), NULL);
ns = GTS_GNODE_SPLIT (gts_object_new (GTS_OBJECT_CLASS (klass)));
ns->n = n;
ns->n1 = n1;
ns->n2 = n2;
return ns;
}
static void connect_edge (GtsGEdge * e, gpointer * data)
{
GtsGNode * n = data[0];
GtsGNode * n1 = data[1];
GtsGNode * n2 = data[2];
if (GTS_OBJECT (e)->reserved || /* edge is disconnected */
gts_gedge_connects (e, n1, n2))
return;
if (e->n1 == n1 || e->n1 == n2)
e->n1 = n;
else if (e->n2 == n1 || e->n2 == n2)
e->n2 = n;
else
g_assert_not_reached ();
gts_container_add (GTS_CONTAINER (n), GTS_CONTAINEE (e));
}
/**
* gts_gnode_split_collapse:
* @ns: a #GtsGNodeSplit.
* @g: a #GtsGraph.
* @klass: a #GtsWGEdgeClass.
*
* Collapses the node split @ns. Any new edge created during the
* process will be of class @klass.
*/
void gts_gnode_split_collapse (GtsGNodeSplit * ns,
GtsGraph * g,
GtsWGEdgeClass * klass)
{
GtsGNode * n1, * n2;
GSList * i;
gpointer data[3];
g_return_if_fail (ns != NULL);
g_return_if_fail (g != NULL);
g_return_if_fail (gts_container_size (GTS_CONTAINER (ns->n)) == 0);
n1 = GTS_GNODE_SPLIT_N1 (ns);
n2 = GTS_GNODE_SPLIT_N2 (ns);
/* look for triangles */
i = GTS_SLIST_CONTAINER (n1)->items;
while (i) {
GtsGEdge * e13 = i->data;
GtsGNode * n3 = GTS_GNODE_NEIGHBOR (n1, e13);
if (n3 != n2) {
GSList * j = GTS_SLIST_CONTAINER (n3)->items;
while (j) {
GtsGEdge * e32 = j->data;
GSList * next = j->next;
GtsGNode * n4 = GTS_GNODE_NEIGHBOR (n3, e32);
if (n4 == n2) { /* found triangle n1 (e13) n3 (e32) n2 */
gts_wgedge_new (klass, ns->n, n3,
gts_gedge_weight (e13) + gts_gedge_weight (e32));
GTS_OBJECT (e13)->reserved = n3;
GTS_OBJECT (e32)->reserved = n3;
GTS_SLIST_CONTAINER (n3)->items =
g_slist_remove (GTS_SLIST_CONTAINER (n3)->items, e32);
}
j = next;
}
if (GTS_OBJECT (e13)->reserved == n3)
GTS_SLIST_CONTAINER (n3)->items =
g_slist_remove (GTS_SLIST_CONTAINER (n3)->items, e13);
}
i = i->next;
}
/* connect edges to new node */
data[0] = ns->n;
data[1] = n1;
data[2] = n2;
gts_container_foreach (GTS_CONTAINER (n1), (GtsFunc) connect_edge, data);
gts_container_foreach (GTS_CONTAINER (n2), (GtsFunc) connect_edge, data);
gts_allow_floating_gnodes = TRUE;
gts_container_remove (GTS_CONTAINER (g), GTS_CONTAINEE (n1));
gts_container_remove (GTS_CONTAINER (g), GTS_CONTAINEE (n2));
gts_allow_floating_gnodes = FALSE;
gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (ns->n));
}
static void restore_edge (GtsGEdge * e, gpointer * data)
{
GtsGNode * n = data[0];
GtsGNode * n1 = data[1];
GtsGNode * n2 = data[2];
GtsGNode * n3 = GTS_OBJECT (e)->reserved;
if (n3) { /* e is a disconnected edge */
GTS_OBJECT (e)->reserved = NULL;
gts_container_add (GTS_CONTAINER (n3), GTS_CONTAINEE (e));
return;
}
if (gts_gedge_connects (e, n1, n2))
return;
if (e->n1 == n)
e->n1 = n1;
else if (e->n2 == n)
e->n2 = n1;
else
g_assert_not_reached ();
GTS_SLIST_CONTAINER (n)->items =
g_slist_remove (GTS_SLIST_CONTAINER (n)->items, e);
}
/**
* gts_gnode_split_expand:
* @ns: a #GtsGNodeSplit.
* @g: a #GtsGraph.
*
* Expands the node split ns adding the new nodes to @g.
*/
void gts_gnode_split_expand (GtsGNodeSplit * ns,
GtsGraph * g)
{
GtsGNode * n1, * n2;
gpointer data[3];
GSList * i;
g_return_if_fail (ns != NULL);
g_return_if_fail (g != NULL);
g_return_if_fail (gts_containee_is_contained (GTS_CONTAINEE (ns->n),
GTS_CONTAINER (g)));
n1 = GTS_GNODE_SPLIT_N1 (ns);
n2 = GTS_GNODE_SPLIT_N2 (ns);
data[0] = ns->n;
data[1] = n1;
data[2] = n2;
gts_container_foreach (GTS_CONTAINER (n1), (GtsFunc) restore_edge, data);
data[1] = n2;
data[2] = n1;
gts_container_foreach (GTS_CONTAINER (n2), (GtsFunc) restore_edge, data);
i = GTS_SLIST_CONTAINER (ns->n)->items;
while (i) {
GSList * next = i->next;
gts_container_remove (GTS_CONTAINER (ns->n), GTS_CONTAINEE (i->data));
i = next;
}
g_assert (gts_container_size (GTS_CONTAINER (ns->n)) == 0);
gts_allow_floating_gnodes = TRUE;
gts_container_remove (GTS_CONTAINER (g), GTS_CONTAINEE (ns->n));
gts_allow_floating_gnodes = FALSE;
gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (n1));
gts_container_add (GTS_CONTAINER (g), GTS_CONTAINEE (n2));
}
/* GtsPGraph */
static void pgraph_destroy (GtsObject * object)
{
GtsPGraph * pg = GTS_PGRAPH (object);
guint i;
for (i = 0; i < pg->split->len; i++)
gts_object_destroy (GTS_OBJECT (g_ptr_array_index (pg->split, i)));
g_ptr_array_free (pg->split, TRUE);
g_array_free (pg->levels, TRUE);
(* GTS_OBJECT_CLASS (gts_pgraph_class ())->parent_class->destroy) (object);
}
static void pgraph_class_init (GtsPGraphClass * klass)
{
GTS_OBJECT_CLASS (klass)->destroy = pgraph_destroy;
}
static void pgraph_init (GtsPGraph * pg)
{
pg->g = NULL;
pg->split = g_ptr_array_new ();
pg->levels = g_array_new (FALSE, FALSE, sizeof (guint));
pg->level = 0;
pg->split_class = gts_gnode_split_class ();
pg->edge_class = gts_wgedge_class ();
pg->pos = pg->min = 0;
}
/**
* gts_pgraph_class:
*
* Returns: the #GtsPGraphClass.
*/
GtsPGraphClass * gts_pgraph_class (void)
{
static GtsPGraphClass * klass = NULL;
if (klass == NULL) {
GtsObjectClassInfo pgraph_info = {
"GtsPGraph",
sizeof (GtsPGraph),
sizeof (GtsPGraphClass),
(GtsObjectClassInitFunc) pgraph_class_init,
(GtsObjectInitFunc) pgraph_init,
(GtsArgSetFunc) NULL,
(GtsArgGetFunc) NULL
};
klass = gts_object_class_new (gts_object_class (), &pgraph_info);
}
return klass;
}
static void match_neighbor (GtsGNode * n, gpointer * data)
{
if (!GTS_OBJECT (n)->reserved) {
GtsGraph * g = data[0];
GSList ** list = data[1];
GSList * i = GTS_SLIST_CONTAINER (n)->items;
gfloat wmax = - G_MAXFLOAT;
GtsGEdge * emax = NULL;
while (i) {
GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, i->data);
if (!GTS_OBJECT (n1)->reserved &&
gts_gedge_weight (i->data) > wmax &&
gts_containee_is_contained (GTS_CONTAINEE (n1), GTS_CONTAINER (g))) {
emax = i->data;
wmax = gts_gedge_weight (emax);
}
i = i->next;
}
if (emax) {
GtsGNode * n1 = GTS_GNODE_NEIGHBOR (n, emax);
GTS_OBJECT (n1)->reserved = n;
GTS_OBJECT (n)->reserved = n1;
*list = g_slist_prepend (*list, emax);
}
}
}
static GSList * maximal_matching (GtsGraph * g)
{
GSList * list = NULL;
gpointer data[2];
data[0] = g;
data[1] = &list;
gts_container_foreach (GTS_CONTAINER (g), (GtsFunc) match_neighbor, data);
gts_container_foreach (GTS_CONTAINER (g),
(GtsFunc) gts_object_reset_reserved,
NULL);
return list;
}
/**
* gts_pgraph_new:
* @klass: a #GtsPGraphClass.
* @g: a #GtsGraph.
* @split_class: a #GtsGNodeSplitClass.
* @node_class: a #GtsWGNodeClass.
* @edge_class: a #GtsWGEdgeClass.
* @min: the minimum number of nodes.
*
* Creates a new multilevel approximation of graph @g. At each level a
* maximal matching is created using the Heavy Edge Matching (HEM)
* technique of Karypis and Kumar (1997). The newly created nodes are
* of type @node_class and their weight is set to the sum of the
* weights of their children. The newly created edges are of type
* @edge_class and their weight is set to the sum of the weight of the
* collapsed edges. The last level is reached when the maximal
* matching obtained would lead to a graph with less than @min nodes.
*
* Returns: the new #GtsPGraph containing the multilevel
* representation of @g.
*/
GtsPGraph * gts_pgraph_new (GtsPGraphClass * klass,
GtsGraph * g,
GtsGNodeSplitClass * split_class,
GtsWGNodeClass * node_class,
GtsWGEdgeClass * edge_class,
guint min)
{
GtsPGraph * pg;
GSList * matching;
g_return_val_if_fail (klass != NULL, NULL);
g_return_val_if_fail (g != NULL, NULL);
g_return_val_if_fail (split_class != NULL, NULL);
g_return_val_if_fail (node_class != NULL, NULL);
g_return_val_if_fail (edge_class != NULL, NULL);
pg = GTS_PGRAPH (gts_object_new (GTS_OBJECT_CLASS (klass)));
pg->g = g;
pg->split_class = split_class;
pg->edge_class = edge_class;
while (gts_container_size (GTS_CONTAINER (g)) > min &&
(matching = maximal_matching (g))) {
GSList * i = matching;
guint size = gts_container_size (GTS_CONTAINER (g));
g_array_append_val (pg->levels, size);
while (i && gts_container_size (GTS_CONTAINER (g)) > min) {
GtsGEdge * e = i->data;
GtsGNode * n = GTS_GNODE (gts_wgnode_new (node_class,
gts_gnode_weight (e->n1) +
gts_gnode_weight (e->n2)));
GtsGNodeSplit * ns = gts_gnode_split_new (split_class, n,
GTS_OBJECT (e->n1),
GTS_OBJECT (e->n2));
gts_gnode_split_collapse (ns, g, edge_class);
g_ptr_array_add (pg->split, ns);
i = i->next;
}
g_slist_free (matching);
}
pg->pos = pg->split->len;
pg->min = gts_container_size (GTS_CONTAINER (g));
pg->level = pg->levels->len;
return pg;
}
/**
* gts_pgraph_add_node:
* @pg: a #GtsPGraph.
*
* Adds one node to the multilevel graph @pg by expanding the next
* available #GtsGNodeSplit.
*
* Returns: the expanded #GtsGNodeSplit or #NULL if all the
* #GtsGNodeSplit have already been expanded.
*/
GtsGNodeSplit * gts_pgraph_add_node (GtsPGraph * pg)
{
GtsGNodeSplit * ns;
g_return_val_if_fail (pg != NULL, NULL);
if (pg->pos == 0)
return NULL;
ns = g_ptr_array_index (pg->split, --pg->pos);
gts_gnode_split_expand (ns, pg->g);
return ns;
}
/**
* gts_pgraph_remove_node:
* @pg: a #GtsPGraph.
*
* Removes one node from the multilevel graph @pg by collapsing the
* first available #GtsGNodeSplit.
*
* Returns: the collapsed #GtsGNodeSplit or %NULL if all the
* #GtsGNodeSplit have already been collapsed.
*/
GtsGNodeSplit * gts_pgraph_remove_node (GtsPGraph * pg)
{
GtsGNodeSplit * ns;
g_return_val_if_fail (pg != NULL, NULL);
if (pg->pos == pg->split->len)
return NULL;
ns = g_ptr_array_index (pg->split, pg->pos++);
gts_gnode_split_collapse (ns, pg->g, pg->edge_class);
return ns;
}
/**
* gts_pgraph_max_node_number:
* @pg: a #GtsPGraph.
*
* Returns: the maximum number of nodes of @pg i.e. the number of
* nodes if all the #GtsGNodeSplit were expanded.
*/
guint gts_pgraph_max_node_number (GtsPGraph * pg)
{
g_return_val_if_fail (pg != NULL, 0);
return pg->min + pg->split->len;
}
/**
* gts_pgraph_min_node_number:
* @pg: a #GtsPGraph.
*
* Returns: the minimum number of nodes of @pg i.e. the number of
* nodes if all the #GtsGNodeSplit were collapsed.
*/
guint gts_pgraph_min_node_number (GtsPGraph * pg)
{
g_return_val_if_fail (pg != NULL, 0);
return pg->min;
}
/**
* gts_pgraph_set_node_number:
* @pg: a #GtsPGraph.
* @n: a number of nodes.
*
* Performs the required number of collapses or expansions to set the
* number of nodes of @pg to @n.
*/
void gts_pgraph_set_node_number (GtsPGraph * pg, guint n)
{
g_return_if_fail (pg != NULL);
n = pg->min + pg->split->len - n;
while (pg->pos > n && gts_pgraph_add_node (pg))
;
while (pg->pos < n && gts_pgraph_remove_node (pg))
;
}
/**
* gts_pgraph_get_node_number:
* @pg: a #GtsPGraph.
*
* Returns: the current number of nodes of @pg.
*/
guint gts_pgraph_get_node_number (GtsPGraph * pg)
{
g_return_val_if_fail (pg != NULL, 0);
return pg->min + pg->split->len - pg->pos;
}
/**
* gts_pgraph_down:
* @pg: a #GtsPGraph.
* @func: a #GtsFunc or %NULL.
* @data: user data to pass to @func.
*
* Performs the required number of expansions to go from the current
* level to the level immediately below.
*
* If @func is not %NULL, it is called after each #GtsGNodeSplit has
* been expanded.
*
* Returns: %FALSE if it is not possible to go down one level, %TRUE
* otherwise.
*/
gboolean gts_pgraph_down (GtsPGraph * pg,
GtsFunc func,
gpointer data)
{
guint size;
g_return_val_if_fail (pg != NULL, FALSE);
if (pg->level == 0)
return FALSE;
size = g_array_index (pg->levels, guint, --(pg->level));
while (gts_container_size (GTS_CONTAINER (pg->g)) < size) {
GtsGNodeSplit * ns = gts_pgraph_add_node (pg);
g_assert (ns);
if (func)
(* func) (ns, data);
}
return TRUE;
}
syntax highlighted by Code2HTML, v. 0.9.1