graph_union: propagate both external and internal property maps
This is an enhancement proposal that allows to propagate internal and external property maps conveniently. Currently, internal property maps are propagated only if props=None is set (or done by the user). The proposal does not break the current API's behavior, but introduces a new parameter force_propagate_internal_properties. There is also a new example in the doc string that uses the proposal.
#!python
def graph_union(g1, g2, intersection=None, props=None, include=False, force_propagate_internal_properties=False):
"""Return the union of graphs g1 and g2, composed of all edges and vertices
of g1 and g2, without overlap.
Parameters
----------
g1 : :class:`~graph_tool.Graph`
First graph in the union.
g2 : :class:`~graph_tool.Graph`
Second graph in the union.
intersection : :class:`~graph_tool.PropertyMap` (optional, default: ``None``)
Vertex property map owned by `g1` which maps each of its vertices
to vertex indexes belonging to `g2`. Negative values mean no mapping
exists, and thus both vertices in `g1` and `g2` will be present in the
union graph.
props : list of tuples of :class:`~graph_tool.PropertyMap` (optional, default: ``None``)
Each element in this list must be a tuple of two PropertyMap objects. The
first element must be a property of `g1`, and the second of `g2`. If either
value is ``None``, an empty map is created. The values of the property
maps are propagated into the union graph, and returned. If this value is
``None`` all internal property maps are propagated.
include : bool (optional, default: ``False``)
If true, graph `g2` is inserted into `g1` which is modified. If false, a
new graph is created, and both graphs remain unmodified.
force_propagate_internal_properties : bool (optional, default: ``False``)
If true, propagate internal property maps even if `props` is not ``None``.
Returns
-------
ug : :class:`~graph_tool.Graph`
The union graph
props : list of :class:`~graph_tool.PropertyMap` objects
List of propagated properties. This is only returned if `props` is not
empty.
Examples
--------
.. testcode::
:hide:
from numpy.random import random, seed
from pylab import *
seed(42)
gt.seed_rng(42)
>>> g = gt.triangulation(random((300,2)))[0]
>>> ug = gt.graph_union(g, g)
>>> uug = gt.graph_union(g, ug)
>>> pos = gt.sfdp_layout(g)
>>> gt.graph_draw(g, pos=pos, output_size=(300,300), output="graph_original.pdf")
<...>
.. testcode::
:hide:
gt.graph_draw(g, pos=pos, output_size=(300,300), output="graph_original.png")
>>> pos = gt.sfdp_layout(ug)
>>> gt.graph_draw(ug, pos=pos, output_size=(300,300), output="graph_union.pdf")
<...>
.. testcode::
:hide:
gt.graph_draw(ug, pos=pos, output_size=(300,300), output="graph_union.png")
>>> pos = gt.sfdp_layout(uug)
>>> gt.graph_draw(uug, pos=pos, output_size=(300,300), output="graph_union2.pdf")
<...>
.. testcode::
:hide:
gt.graph_draw(uug, pos=pos, output_size=(300,300), output="graph_union2.png")
.. image:: graph_original.*
.. image:: graph_union.*
.. image:: graph_union2.*
.. testcode::
:hide:
from numpy.random import random, seed
from pylab import *
seed(42)
gt.seed_rng(42)
>>> g1 = gt.triangulation(random((300,2)))[0]
>>> g2 = gt.triangulation(random((300,2)))[0]
>>> p1 = g1.new_vertex_property("bool")
>>> p1.a = True
>>> p2 = g2.new_vertex_property("bool")
>>> p2.a = True
>>> vcolor = g1.new_vertex_property("string")
>>> for v in g1.vertices(): vcolor[v] = "red"
>>> vcolor = g2.new_vertex_property("string")
>>> for v in g2.vertices(): vcolor[v] = "blue"
>>> ug, (pu1,pu2) = gt.graph_union(g1, g2, props=[(p1,None),(None,p2)], force_propagate_internal_properties=True)
>>> g1v = gt.GraphView(ug,vfilt=pu1)
>>> g2v = gt.GraphView(ug,vfilt=pu2)
>>> pos = gt.sfdp_layout(ug)
>>> gt.graph_draw(g, pos=pos, vprops=dict(fill_color=ug.vp['vcolor']), output_size=(300,300), output="graph_union3.pdf")
<...>
.. image:: graph_union3.*
"""
pnames = None
if (props is None) or (force_propagate_internal_properties):
if props is None:
props = []
pnames = []
else:
pnames = [None for x in props]
for (k, name), p1 in g1.properties.items():
if k == 'g':
continue
p2 = g2.properties.get((k, name), None)
props.append((p1, p2))
pnames.append(name)
for (k, name), p2 in g2.properties.items():
if k == 'g' or (k, name) in g1.properties:
continue
props.append((None, p2))
pnames.append(name)
gprops = [[(name, g1.properties[('g', name)]) for name in g1.graph_properties.keys()],
[(name, g2.properties[('g', name)]) for name in g2.graph_properties.keys()]]
if not include:
g1 = GraphView(g1, skip_properties=True)
p1s = []
for i, (p1, p2) in enumerate(props):
if p1 is None:
continue
if p1.key_type() == "v":
g1.vp[str(i)] = p1
elif p1.key_type() == "e":
g1.ep[str(i)] = p1
g1 = Graph(g1, prune=True)
for i, (p1, p2) in enumerate(props):
if p1 is None:
continue
if str(i) in g1.vp:
props[i] = (g1.vp[str(i)], p2)
del g1.vp[str(i)]
else:
props[i] = (g1.ep[str(i)], p2)
del g1.ep[str(i)]
else:
emask, emask_flip = g1.get_edge_filter()
emask_flipped = False
if emask is not None and not emask_flip:
emask.a = not emask.a
emask_flipped = True
g1.set_edge_filter(emask, True)
vmask, vmask_flip = g1.get_vertex_filter()
vmask_flipped = False
if vmask is not None and not vmask_flip:
vmask.a = not vmask.a
g1.set_vertex_filter(vmask, True)
vmask_flipped = True
if intersection is None:
intersection = g1.new_vertex_property("int32_t")
intersection.a = 0
else:
intersection = intersection.copy("int32_t")
intersection.a[intersection.a >= 0] += 1
intersection.a[intersection.a < 0] = 0
u1 = GraphView(g1, directed=True, skip_properties=True)
u2 = GraphView(g2, directed=True, skip_properties=True)
vmap, emap = libgraph_tool_generation.graph_union(u1._Graph__graph,
u2._Graph__graph,
_prop("v", g1,
intersection))
if include:
emask, emask_flip = g1.get_edge_filter()
if emask is not None and emask_flipped:
emask.a = not emask.a
g1.set_edge_filter(emask, False)
vmask, vmask_flip = g1.get_vertex_filter()
if vmask is not None and vmask_flipped:
vmask.a = not vmask.a
g1.set_vertex_filter(vmask, False)
n_props = []
for p1, p2 in props:
if p1 is None:
p1 = g1.new_property(p2.key_type(), p2.value_type())
if p2 is None:
p2 = g2.new_property(p1.key_type(), p1.value_type())
if not include:
p1 = g1.copy_property(p1)
if p2.value_type() != p1.value_type():
p2 = g2.copy_property(p2, value_type=p1.value_type())
if p1.key_type() == 'v':
libgraph_tool_generation.\
vertex_property_union(u1._Graph__graph, u2._Graph__graph,
vmap, emap,
_prop(p1.key_type(), g1, p1),
_prop(p2.key_type(), g2, p2))
else:
libgraph_tool_generation.\
edge_property_union(u1._Graph__graph, u2._Graph__graph,
vmap, emap,
_prop(p1.key_type(), g1, p1),
_prop(p2.key_type(), g2, p2))
n_props.append(p1)
if pnames is not None:
n_n_props = []
for name, p in zip(pnames, n_props):
if name is None:
n_n_props.append(p)
else:
g1.properties[(p.key_type(), name)] = p
if not include:
for name, p in gprops[0]:
g1.graph_properties[name] = p.copy()
for name, p in gprops[1]:
if name not in g1.graph_properties:
g1.graph_properties[name] = p.copy()
n_props = n_n_props
if len(n_props) > 0:
return g1, n_props
else:
return g1