summaryrefslogtreecommitdiff
path: root/networkx/algorithms/operators/all.py
blob: 0ff170943ad64597a17268a8660ef9f02f25779e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
"""Operations on many graphs.
"""
from itertools import zip_longest
import networkx as nx

__all__ = ["union_all", "compose_all", "disjoint_union_all", "intersection_all"]


def union_all(graphs, rename=(None,)):
    """Returns the union of all graphs.

    The graphs must be disjoint, otherwise an exception is raised.

    Parameters
    ----------
    graphs : list of graphs
       List of NetworkX graphs

    rename : bool , default=(None, None)
       Node names of G and H can be changed by specifying the tuple
       rename=('G-','H-') (for example).  Node "u" in G is then renamed
       "G-u" and "v" in H is renamed "H-v".

    Returns
    -------
    U : a graph with the same type as the first graph in list

    Raises
    ------
    ValueError
       If `graphs` is an empty list.

    Notes
    -----
    To force a disjoint union with node relabeling, use
    disjoint_union_all(G,H) or convert_node_labels_to integers().

    Graph, edge, and node attributes are propagated to the union graph.
    If a graph attribute is present in multiple graphs, then the value
    from the last graph in the list with that attribute is used.

    See Also
    --------
    union
    disjoint_union_all
    """
    # collect the graphs in case an iterator was passed
    graphs = list(graphs)

    if not graphs:
        raise ValueError("cannot apply union_all to an empty list")

    U = graphs[0]

    if any(G.is_multigraph() != U.is_multigraph() for G in graphs):
        raise nx.NetworkXError("All graphs must be graphs or multigraphs.")

    # rename graph to obtain disjoint node labels
    def add_prefix(graph, prefix):
        if prefix is None:
            return graph

        def label(x):
            if isinstance(x, str):
                name = prefix + x
            else:
                name = prefix + repr(x)
            return name

        return nx.relabel_nodes(graph, label)

    graphs = [add_prefix(G, name) for G, name in zip_longest(graphs, rename)]

    if sum(len(G) for G in graphs) != len(set().union(*graphs)):
        raise nx.NetworkXError(
            "The node sets of the graphs are not disjoint.",
            "Use appropriate rename"
            "=(G1prefix,G2prefix,...,GNprefix)"
            "or use disjoint_union(G1,G2,...,GN).",
        )

    # Union is the same type as first graph
    R = U.__class__()

    # add graph attributes, later attributes take precedent over earlier ones
    for G in graphs:
        R.graph.update(G.graph)

    # add nodes and attributes
    for G in graphs:
        R.add_nodes_from(G.nodes(data=True))

    if U.is_multigraph():
        for G in graphs:
            R.add_edges_from(G.edges(keys=True, data=True))
    else:
        for G in graphs:
            R.add_edges_from(G.edges(data=True))

    return R


def disjoint_union_all(graphs):
    """Returns the disjoint union of all graphs.

    This operation forces distinct integer node labels starting with 0
    for the first graph in the list and numbering consecutively.

    Parameters
    ----------
    graphs : list
       List of NetworkX graphs

    Returns
    -------
    U : A graph with the same type as the first graph in list

    Raises
    ------
    ValueError
       If `graphs` is an empty list.

    Notes
    -----
    It is recommended that the graphs be either all directed or all undirected.

    Graph, edge, and node attributes are propagated to the union graph.
    If a graph attribute is present in multiple graphs, then the value
    from the last graph in the list with that attribute is used.
    """
    graphs = list(graphs)

    if not graphs:
        raise ValueError("cannot apply disjoint_union_all to an empty list")

    first_labels = [0]
    for G in graphs[:-1]:
        first_labels.append(len(G) + first_labels[-1])

    relabeled = [
        nx.convert_node_labels_to_integers(G, first_label=first_label)
        for G, first_label in zip(graphs, first_labels)
    ]
    R = union_all(relabeled)
    for G in graphs:
        R.graph.update(G.graph)
    return R


def compose_all(graphs):
    """Returns the composition of all graphs.

    Composition is the simple union of the node sets and edge sets.
    The node sets of the supplied graphs need not be disjoint.

    Parameters
    ----------
    graphs : list
       List of NetworkX graphs

    Returns
    -------
    C : A graph with the same type as the first graph in list

    Raises
    ------
    ValueError
       If `graphs` is an empty list.

    Notes
    -----
    It is recommended that the supplied graphs be either all directed or all
    undirected.

    Graph, edge, and node attributes are propagated to the union graph.
    If a graph attribute is present in multiple graphs, then the value
    from the last graph in the list with that attribute is used.
    """
    graphs = list(graphs)

    if not graphs:
        raise ValueError("cannot apply compose_all to an empty list")

    U = graphs[0]

    if any(G.is_multigraph() != U.is_multigraph() for G in graphs):
        raise nx.NetworkXError("All graphs must be graphs or multigraphs.")

    R = U.__class__()
    # add graph attributes, H attributes take precedent over G attributes
    for G in graphs:
        R.graph.update(G.graph)

    for G in graphs:
        R.add_nodes_from(G.nodes(data=True))

    if U.is_multigraph():
        for G in graphs:
            R.add_edges_from(G.edges(keys=True, data=True))
    else:
        for G in graphs:
            R.add_edges_from(G.edges(data=True))
    return R


def intersection_all(graphs):
    """Returns a new graph that contains only the nodes and the edges that exist in
    all graphs.

    Parameters
    ----------
    graphs : list
       List of NetworkX graphs

    Returns
    -------
    R : A new graph with the same type as the first graph in list

    Raises
    ------
    ValueError
       If `graphs` is an empty list.

    Notes
    -----
    Attributes from the graph, nodes, and edges are not copied to the new
    graph.
    """
    graphs = list(graphs)

    if not graphs:
        raise ValueError("cannot apply intersection_all to an empty list")

    U = graphs[0]

    if any(G.is_multigraph() != U.is_multigraph() for G in graphs):
        raise nx.NetworkXError("All graphs must be graphs or multigraphs.")

    # create new graph
    node_intersection = set.intersection(*[set(G.nodes) for G in graphs])
    R = U.__class__()
    R.add_nodes_from(node_intersection)

    if U.is_multigraph():
        edge_sets = [set(G.edges(keys=True)) for G in graphs]
    else:
        edge_sets = [set(G.edges()) for G in graphs]

    edge_intersection = set.intersection(*edge_sets)
    R.add_edges_from(edge_intersection)

    return R