graphs.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  1. # -*- coding: utf-8 -*-
  2. """
  3. .. _graphs:
  4. graphs.py
  5. =========
  6. This module implements essential graph classes for representing
  7. vertices (nodes), edges (links), and graphs.
  8. """
  9. # This code is part of Grandalf
  10. # Copyright (C) 2008-2011 Axel Tillequin (bdcht3@gmail.com)
  11. # published under GPLv2 license or EPLv1 license
  12. from mantis.grandalf.utils import Poset
  13. # ------------------------------------------------------------------------------
  14. class vertex_core(object):
  15. """ The Vertex essentials attributes and methods.
  16. Attributes:
  17. e (list[Edge]): list of edges associated with this vertex.
  18. Methods:
  19. deg() : degree of the vertex (number of edges).
  20. e_in() : list of edges directed toward this vertex.
  21. e_out(): list of edges directed outward this vertex.
  22. e_dir(int): either e_in, e_out or all edges depending on
  23. provided direction parameter (>0 means outward).
  24. N(f_io=0): list of neighbor vertices in all directions (default)
  25. or in filtered f_io direction (>0 means outward).
  26. e_to(v): returns the Edge from this vertex directed toward vertex v.
  27. e_from(v): returns the Edge from vertex v directed toward this vertex.
  28. e_with(v): return the Edge with both this vertex and vertex v
  29. detach(): removes this vertex from all its edges and returns this list
  30. of edges.
  31. """
  32. def __init__(self):
  33. # will hold list of edges for this vertex (adjacency list)
  34. self.e = []
  35. def deg(self):
  36. return len(self.e)
  37. def e_in(self):
  38. return list(filter((lambda e: e.v[1] == self), self.e))
  39. def e_out(self):
  40. return list(filter((lambda e: e.v[0] == self), self.e))
  41. def e_dir(self, dir):
  42. if dir > 0:
  43. return self.e_out()
  44. if dir < 0:
  45. return self.e_in()
  46. return self.e
  47. def N(self, f_io=0):
  48. N = []
  49. if f_io <= 0:
  50. N += [e.v[0] for e in self.e_in()]
  51. if f_io >= 0:
  52. N += [e.v[1] for e in self.e_out()]
  53. return N
  54. def e_to(self, y):
  55. for e in self.e_out():
  56. if e.v[1] == y:
  57. return e
  58. return None
  59. def e_from(self, x):
  60. for e in self.e_in():
  61. if e.v[0] == x:
  62. return e
  63. return None
  64. def e_with(self, v):
  65. for e in self.e:
  66. if v in e.v:
  67. return e
  68. return None
  69. def detach(self):
  70. E = self.e[:]
  71. for e in E:
  72. e.detach()
  73. assert self.deg() == 0
  74. return E
  75. # ------------------------------------------------------------------------------
  76. class edge_core(object):
  77. """The Edge essentials attributes.
  78. Attributes:
  79. v (list[Vertex]): list of vertices associated with this edge.
  80. deg (int): degree of the edge (number of unique vertices).
  81. """
  82. def __init__(self, x, y):
  83. self.deg = 0 if x == y else 1
  84. self.v = (x, y)
  85. # ------------------------------------------------------------------------------
  86. class Vertex(vertex_core):
  87. """Vertex class enhancing a vertex_core with graph-related features.
  88. Attributes:
  89. c (graph_core): the component of connected vertices that contains this vertex.
  90. By default a vertex belongs no component but when it is added in a
  91. graph, c points to the connected component in this graph.
  92. data (object) : an object associated with the vertex.
  93. """
  94. def __init__(self, data=None):
  95. super().__init__()
  96. # by default, a new vertex belongs to its own component
  97. # but when the vertex is added to a graph, c points to the
  98. # connected component where it belongs.
  99. self.c = None
  100. self.data = data
  101. self.__index = None
  102. @property
  103. def index(self):
  104. if self.__index:
  105. return self.__index
  106. elif isinstance(self.c, graph_core):
  107. self.__index = self.c.sV.index(self)
  108. return self.__index
  109. else:
  110. return None
  111. def __lt__(self, v):
  112. return 0
  113. def __gt__(self, v):
  114. return 0
  115. def __le__(self, v):
  116. return 0
  117. def __ge__(self, v):
  118. return 0
  119. def __getstate__(self):
  120. return (self.index, self.data)
  121. def __setstate__(self, state):
  122. self.__index, self.data = state
  123. self.c = None
  124. self.e = []
  125. # ------------------------------------------------------------------------------
  126. class Edge(edge_core):
  127. """Edge class enhancing edge_core with attributes and methods related to the graph.
  128. Attributes:
  129. w (int): a weight associated with the edge (default 1) used by Dijkstra to
  130. find min-flow paths.
  131. data (object): an object associated with the edge.
  132. feedback (bool): indicates if the edge has been marked as a *feeback* edge
  133. by the Tarjan algorithm which means that it is part of a cycle and that
  134. inverting this edge would remove this cycle.
  135. Methods:
  136. attach(): add this edge in its vertices edge lists.
  137. detach(): remove this edge from its vertices edge lists.
  138. """
  139. def __init__(self, x, y, w=1, data=None, connect=False):
  140. super().__init__(x, y)
  141. # w is an optional weight associated with the edge.
  142. self.w = w
  143. self.data = data
  144. self.feedback = False
  145. if connect and (x.c is None or y.c is None):
  146. c = x.c or y.c
  147. c.add_edge(self)
  148. def attach(self):
  149. if not self in self.v[0].e:
  150. self.v[0].e.append(self)
  151. if not self in self.v[1].e:
  152. self.v[1].e.append(self)
  153. def detach(self):
  154. if self.deg == 1:
  155. assert self in self.v[0].e
  156. assert self in self.v[1].e
  157. self.v[0].e.remove(self)
  158. self.v[1].e.remove(self)
  159. else:
  160. if self in self.v[0].e:
  161. self.v[0].e.remove(self)
  162. assert self not in self.v[0].e
  163. return [self]
  164. def __lt__(self, v):
  165. return 0
  166. def __gt__(self, v):
  167. return 0
  168. def __le__(self, v):
  169. return 0
  170. def __ge__(self, v):
  171. return 0
  172. def __getstate__(self):
  173. xi, yi = (self.v[0].index, self.v[1].index)
  174. return (xi, yi, self.w, self.data, self.feedback)
  175. def __setstate__(self, state):
  176. xi, yi, self.w, self.data, self.feedback = state
  177. self._v = [xi, yi]
  178. self.deg = 0 if xi == yi else 1
  179. # ------------------------------------------------------------------------------
  180. class graph_core(object):
  181. """A connected graph of Vertex/Edge objects. A graph_core is a *component*
  182. of a Graph that contains a connected set of Vertex and Edges.
  183. Attributes:
  184. sV (poset[Vertex]): the partially ordered set of vertices of the graph.
  185. sE (poset[Edge]): the partially ordered set of edges of the graph.
  186. degenerated_edges (set[Edge]): the set of *degenerated* edges (of degree 0).
  187. directed (bool): indicates if the graph is considered *oriented* or not.
  188. Methods:
  189. V(cond=None): generates an iterator over vertices, with optional filter
  190. E(cond=None): generates an iterator over edges, with optional filter
  191. M(cond=None): returns the associativity matrix of the graph component
  192. order(): the order of the graph (number of vertices)
  193. norm(): the norm of the graph (number of edges)
  194. deg_min(): the minimum degree of vertices
  195. deg_max(): the maximum degree of vertices
  196. deg_avg(): the average degree of vertices
  197. eps(): the graph epsilon value (norm/order), average number of edges per vertex.
  198. path(x,y,f_io=0,hook=None): shortest path between vertices x and y by breadth-first descent,
  199. contrained by f_io direction if provided. The path is returned as a list of Vertex objects.
  200. If a *hook* function is provided, it is called at every vertex added to the path, passing
  201. the vertex object as argument.
  202. roots(): returns the list of *roots* (vertices with no inward edges).
  203. leaves(): returns the list of *leaves* (vertices with no outward edges).
  204. add_single_vertex(v): allow a graph_core to hold a single vertex.
  205. add_edge(e): add edge e. At least one of its vertex must belong to the graph,
  206. the other being added automatically.
  207. remove_edge(e): remove Edge e, asserting that the resulting graph is still connex.
  208. remove_vertex(x): remove Vertex x and all associated edges.
  209. dijkstra(x,f_io=0,hook=None): shortest weighted-edges paths between x and all other vertices
  210. by dijkstra's algorithm with heap used as priority queue.
  211. get_scs_with_feedback(): returns the set of strongly connected components
  212. ("scs") by using Tarjan algorithm.
  213. These are maximal sets of vertices such that there is a path from each
  214. vertex to every other vertex.
  215. The algorithm performs a DFS from the provided list of root vertices.
  216. A cycle is of course a strongly connected component,
  217. but a strongly connected component can include several cycles.
  218. The Feedback Acyclic Set of edge to be removed/reversed is provided by
  219. marking the edges with a "feedback" flag.
  220. Complexity is O(V+E).
  221. partition(): returns a *partition* of the connected graph as a list of lists.
  222. N(v): returns neighbours of a vertex v.
  223. """
  224. def __init__(self, V=None, E=None, directed=True):
  225. if V is None:
  226. V = []
  227. if E is None:
  228. E = []
  229. self.directed = directed
  230. self.sV = Poset(V)
  231. self.sE = Poset([])
  232. self.degenerated_edges = set()
  233. if len(self.sV) == 1:
  234. v = self.sV[0]
  235. v.c = self
  236. for e in v.e:
  237. e.detach()
  238. return
  239. for e in E:
  240. x = self.sV.get(e.v[0])
  241. y = self.sV.get(e.v[1])
  242. if x is None or y is None:
  243. raise ValueError("unknown Vertex (%s or %s)" % e.v)
  244. e.v = (x, y)
  245. if e.deg == 0:
  246. self.degenerated_edges.add(e)
  247. e = self.sE.add(e)
  248. e.attach()
  249. if x.c is None:
  250. x.c = Poset([x])
  251. if y.c is None:
  252. y.c = Poset([y])
  253. if id(x.c) != id(y.c):
  254. x, y = (x, y) if len(x.c) > len(y.c) else (y, x)
  255. x.c.update(y.c)
  256. for v in y.c:
  257. v.c = x.c
  258. s = x.c
  259. # check if graph is connected:
  260. for v in self.V():
  261. if v.c is None or (v.c != s):
  262. raise ValueError("unconnected Vertex %s" % v.data)
  263. else:
  264. v.c = self
  265. def roots(self):
  266. return list(filter(lambda v: len(v.e_in()) == 0, self.sV))
  267. def leaves(self):
  268. return list(filter(lambda v: len(v.e_out()) == 0, self.sV))
  269. def add_single_vertex(self, v):
  270. if len(self.sE) == 0 and len(self.sV) == 0:
  271. v = self.sV.add(v)
  272. v.c = self
  273. return v
  274. return None
  275. def add_edge(self, e):
  276. if e in self.sE:
  277. return self.sE.get(e)
  278. x = e.v[0]
  279. y = e.v[1]
  280. if not ((x in self.sV) or (y in self.sV)):
  281. raise ValueError("unconnected edge")
  282. x = self.sV.add(x)
  283. y = self.sV.add(y)
  284. e.v = (x, y)
  285. e.attach()
  286. e = self.sE.add(e)
  287. x.c = self
  288. y.c = self
  289. if e.deg == 0:
  290. self.degenerated_edges.add(e)
  291. return e
  292. def remove_edge(self, e):
  293. if not e in self.sE:
  294. return
  295. e.detach()
  296. # check if still connected (path is not oriented here):
  297. if e.deg == 1 and not self.path(e.v[0], e.v[1]):
  298. # return to inital state by reconnecting everything:
  299. e.attach()
  300. # exit with exception!
  301. raise ValueError(e)
  302. else:
  303. e = self.sE.remove(e)
  304. if e in self.degenerated_edges:
  305. self.degenerated_edges.remove(e)
  306. return e
  307. def remove_vertex(self, x):
  308. if x not in self.sV:
  309. return
  310. V = x.N() # get all neighbor vertices to check paths
  311. E = x.detach() # remove the edges from x and neighbors list
  312. # now we need to check if all neighbors are still connected,
  313. # and it is sufficient to check if one of them is connected to
  314. # all others:
  315. v0 = V.pop(0)
  316. for v in V:
  317. if not self.path(v0, v):
  318. # repair everything and raise exception if not connected:
  319. for e in E:
  320. e.attach()
  321. raise ValueError(x)
  322. # remove edges and vertex from internal sets:
  323. for e in E:
  324. self.sE.remove(e)
  325. x = self.sV.remove(x)
  326. x.c = None
  327. return x
  328. def V(self, cond=None):
  329. V = self.sV
  330. if cond is None:
  331. cond = lambda x: True
  332. for v in V:
  333. if cond(v):
  334. yield v
  335. def E(self, cond=None):
  336. E = self.sE
  337. if cond is None:
  338. cond = lambda x: True
  339. for e in E:
  340. if cond(e):
  341. yield e
  342. def M(self, cond=None):
  343. from array import array
  344. mat = []
  345. for v in self.V(cond):
  346. vec = array("b", [0] * self.order())
  347. mat.append(vec)
  348. for e in v.e_in():
  349. v0 = e.v[0]
  350. if v0.index == v.index:
  351. continue
  352. vec[v0.index] = -e.w
  353. for e in v.e_out():
  354. v1 = e.v[1]
  355. vec[v1.index] = e.w
  356. return mat
  357. def order(self):
  358. return len(self.sV)
  359. def norm(self):
  360. return len(self.sE)
  361. def deg_min(self):
  362. return min([v.deg() for v in self.sV])
  363. def deg_max(self):
  364. return max([v.deg() for v in self.sV])
  365. def deg_avg(self):
  366. return sum([v.deg() for v in self.sV]) / float(self.order())
  367. def eps(self):
  368. return float(self.norm()) / self.order()
  369. def path(self, x, y, f_io=0, hook=None):
  370. assert x in self.sV
  371. assert y in self.sV
  372. x = self.sV.get(x)
  373. y = self.sV.get(y)
  374. if x == y:
  375. return []
  376. if f_io != 0:
  377. assert self.directed == True
  378. # path:
  379. p = None
  380. if hook is None:
  381. hook = lambda x: False
  382. # apply hook:
  383. hook(x)
  384. # visisted:
  385. v = {x: None}
  386. # queue:
  387. q = [x]
  388. while (not p) and len(q) > 0:
  389. c = q.pop(0)
  390. for n in c.N(f_io):
  391. if not n in v:
  392. hook(n)
  393. v[n] = c
  394. if n == y:
  395. p = [n]
  396. q.append(n)
  397. if p:
  398. break
  399. # now we fill the path p backward from y to x:
  400. while p and p[0] != x:
  401. p.insert(0, v[p[0]])
  402. return p
  403. def dijkstra(self, x, f_io=0, hook=None):
  404. from collections import defaultdict
  405. from heapq import heappop, heappush
  406. if x not in self.sV:
  407. return None
  408. if f_io != 0:
  409. assert self.directed == True
  410. # initiate with path to itself...
  411. v = self.sV.get(x)
  412. # D is the returned vector of distances:
  413. D = defaultdict(lambda: None)
  414. D[v] = 0.0
  415. L = [(D[v], v)]
  416. while len(L) > 0:
  417. l, u = heappop(L)
  418. for e in u.e_dir(f_io):
  419. v = e.v[0] if (u is e.v[1]) else e.v[1]
  420. Dv = l + e.w
  421. if D[v] != None:
  422. # check if heap/D needs updating:
  423. # ignore if a shorter path was found already...
  424. if Dv < D[v]:
  425. for i, t in enumerate(L):
  426. if t[1] is v:
  427. L.pop(i)
  428. break
  429. D[v] = Dv
  430. heappush(L, (Dv, v))
  431. else:
  432. D[v] = Dv
  433. heappush(L, (Dv, v))
  434. return D
  435. def get_scs_with_feedback(self, roots=None):
  436. from sys import getrecursionlimit, setrecursionlimit
  437. limit = getrecursionlimit()
  438. N = self.norm() + 10
  439. if N > limit:
  440. setrecursionlimit(N)
  441. def _visit(v, L):
  442. v.ind = v.ncur
  443. v.lowlink = v.ncur
  444. Vertex.ncur += 1
  445. self.tstack.append(v)
  446. v.mark = True
  447. for e in v.e_out():
  448. w = e.v[1]
  449. if w.ind == 0:
  450. _visit(w, L)
  451. v.lowlink = min(v.lowlink, w.lowlink)
  452. elif w.mark:
  453. e.feedback = True
  454. if w in self.tstack:
  455. v.lowlink = min(v.lowlink, w.ind)
  456. if v.lowlink == v.ind:
  457. l = [self.tstack.pop()]
  458. while l[0] != v:
  459. l.insert(0, self.tstack.pop())
  460. # print "unstacked %s"%('-'.join([x.data[1:13] for x in l]))
  461. L.append(l)
  462. v.mark = False
  463. if roots is None:
  464. roots = self.roots()
  465. self.tstack = []
  466. scs = []
  467. Vertex.ncur = 1
  468. for v in self.sV:
  469. v.ind = 0
  470. # start exploring tree from roots:
  471. for v in roots:
  472. v = self.sV.get(v)
  473. if v.ind == 0:
  474. _visit(v, scs)
  475. # now possibly unvisited vertices:
  476. for v in self.sV:
  477. if v.ind == 0:
  478. _visit(v, scs)
  479. # clean up Tarjan-specific data:
  480. for v in self.sV:
  481. del v.ind
  482. del v.lowlink
  483. del v.mark
  484. del Vertex.ncur
  485. del self.tstack
  486. setrecursionlimit(limit)
  487. return scs
  488. def partition(self):
  489. V = self.sV.copy()
  490. R = self.roots()
  491. for r in R:
  492. V.remove(r)
  493. parts = []
  494. while len(R) > 0:
  495. v = R.pop(0)
  496. p = Poset([v])
  497. l = v.N(+1)
  498. while len(l) > 0:
  499. x = l.pop(0)
  500. if x in p:
  501. continue
  502. if all([(y in p) for y in x.N(-1)]):
  503. p.add(x)
  504. if x in R:
  505. R.remove(x)
  506. else:
  507. V.remove(x)
  508. l.extend(x.N(+1))
  509. else:
  510. if x in V:
  511. V.remove(x)
  512. R.append(x)
  513. parts.append(list(p))
  514. return parts
  515. def N(self, v, f_io=0):
  516. return v.N(f_io)
  517. # general graph properties:
  518. # -------------------------
  519. # returns True iff
  520. # - o is a subgraph of self, or
  521. # - o is a vertex in self, or
  522. # - o is an edge in self
  523. def __contains__(self, o):
  524. try:
  525. return o.sV.issubset(self.sV) and o.sE.issubset(self.sE)
  526. except AttributeError:
  527. return (o in self.sV) or (o in self.sE)
  528. # merge graph_core G into self
  529. def union_update(self, G):
  530. for v in G.sV:
  531. v.c = self
  532. self.sV.update(G.sV)
  533. self.sE.update(G.sE)
  534. # derivated graphs:
  535. # -----------------
  536. # returns subgraph spanned by vertices V
  537. def spans(self, V):
  538. raise NotImplementedError
  539. # returns join of G (if disjoint)
  540. def __mul__(self, G):
  541. raise NotImplementedError
  542. # returns complement of a graph G
  543. def complement(self, G):
  544. raise NotImplementedError
  545. # contraction G\e
  546. def contract(self, e):
  547. raise NotImplementedError
  548. def __getstate__(self):
  549. V = [v for v in self.sV]
  550. E = [e for e in self.sE]
  551. return (V, E, self.directed)
  552. def __setstate__(self, state):
  553. V, E, directed = state
  554. for e in E:
  555. e.v = [V[x] for x in e._v]
  556. del e._v
  557. graph_core.__init__(self, V, E, directed)
  558. # ------------------------------------------------------------------------------
  559. class Graph(object):
  560. """Disjoint-set Graph.
  561. The graph is stored in disjoint-sets holding each connex component
  562. in self.C as a list of graph_core objects.
  563. Attributes:
  564. C (list[graph_core]): list of graph_core components.
  565. Methods:
  566. add_vertex(v): add vertex v into the Graph as a new component
  567. add_edge(e): add edge e and its vertices into the Graph possibly merging the
  568. associated graph_core components
  569. get_vertices_count(): see order()
  570. V(): see graph_core
  571. E(): see graph_core
  572. remove_edge(e): remove edge e possibly spawning two new cores
  573. if the graph_core that contained e gets disconnected.
  574. remove_vertex(v): remove vertex v and all its edges.
  575. order(): the order of the graph (number of vertices)
  576. norm(): the norm of the graph (number of edges)
  577. deg_min(): the minimum degree of vertices
  578. deg_max(): the maximum degree of vertices
  579. deg_avg(): the average degree of vertices
  580. eps(): the graph epsilon value (norm/order), average number of edges per vertex.
  581. connected(): returns True if the graph is connected (i.e. it has only one component).
  582. components(): returns self.C
  583. """
  584. component_class = graph_core
  585. def __init__(self, V=None, E=None, directed=True):
  586. if V is None:
  587. V = []
  588. if E is None:
  589. E = []
  590. self.directed = directed
  591. # tag connex set of vertices:
  592. # at first, every vertex is its own component
  593. for v in V:
  594. v.c = Poset([v])
  595. CV = [v.c for v in V]
  596. # then pass through edges and union associated vertices such that
  597. # CV finally holds only connected sets:
  598. for e in E:
  599. x = e.v[0]
  600. y = e.v[1]
  601. assert x in V
  602. assert y in V
  603. assert x.c in CV
  604. assert y.c in CV
  605. e.attach()
  606. if x.c != y.c:
  607. # merge y.c into x.c :
  608. x.c.update(y.c)
  609. # update set list (MUST BE DONE BEFORE UPDATING REFS!)
  610. CV.remove(y.c)
  611. # update reference:
  612. for z in y.c:
  613. z.c = x.c
  614. # now create edge sets from connected vertex sets and
  615. # make the graph_core connected graphs for this component :
  616. self.C = []
  617. for c in CV:
  618. s = set()
  619. for v in c:
  620. s.update(v.e)
  621. self.C.append(self.component_class(c, s, directed))
  622. def add_vertex(self, v):
  623. for c in self.C:
  624. if v in c.sV:
  625. return c.sV.get(v)
  626. g = self.component_class(directed=self.directed)
  627. v = g.add_single_vertex(v)
  628. self.C.append(g)
  629. return v
  630. def add_edge(self, e):
  631. # take vertices:
  632. x = e.v[0]
  633. y = e.v[1]
  634. x = self.add_vertex(x)
  635. y = self.add_vertex(y)
  636. # take respective graph_cores:
  637. cx = x.c
  638. cy = y.c
  639. # add edge:
  640. e = cy.add_edge(e)
  641. # connect (union) the graphs:
  642. if cx != cy:
  643. cx.union_update(cy)
  644. self.C.remove(cy)
  645. return e
  646. def get_vertices_count(self):
  647. return sum([c.order() for c in self.C])
  648. def V(self):
  649. for c in self.C:
  650. V = c.sV
  651. for v in V:
  652. yield v
  653. def E(self):
  654. for c in self.C:
  655. E = c.sE
  656. for e in E:
  657. yield e
  658. def remove_edge(self, e):
  659. # get the graph_core:
  660. c = e.v[0].c
  661. assert c == e.v[1].c
  662. if not c in self.C:
  663. return None
  664. # remove edge in graph_core and replace it with two new cores
  665. # if removing edge disconnects the graph_core:
  666. try:
  667. e = c.remove_edge(e)
  668. except ValueError:
  669. e = c.sE.remove(e)
  670. e.detach()
  671. self.C.remove(c)
  672. tmpg = type(self)(c.sV, c.sE, self.directed)
  673. assert len(tmpg.C) == 2
  674. self.C.extend(tmpg.C)
  675. return e
  676. def remove_vertex(self, x):
  677. # get the graph_core:
  678. c = x.c
  679. if not c in self.C:
  680. return None
  681. try:
  682. x = c.remove_vertex(x)
  683. if c.order() == 0:
  684. self.C.remove(c)
  685. except ValueError:
  686. for e in x.detach():
  687. c.sE.remove(e)
  688. x = c.sV.remove(x)
  689. self.C.remove(c)
  690. tmpg = type(self)(c.sV, c.sE, self.directed)
  691. assert len(tmpg.C) == 2
  692. self.C.extend(tmpg.C)
  693. return x
  694. def order(self):
  695. return sum([c.order() for c in self.C])
  696. def norm(self):
  697. return sum([c.norm() for c in self.C])
  698. def deg_min(self):
  699. return min([c.deg_min() for c in self.C])
  700. def deg_max(self):
  701. return max([c.deg_max() for c in self.C])
  702. def deg_avg(self):
  703. t = 0.0
  704. for c in self.C:
  705. t += sum([v.deg() for v in c.sV])
  706. return t / float(self.order())
  707. def eps(self):
  708. return float(self.norm()) / self.order()
  709. def path(self, x, y, f_io=0, hook=None):
  710. if x == y:
  711. return []
  712. if x.c != y.c:
  713. return None
  714. # path:
  715. return x.c.path(x, y, f_io, hook)
  716. def N(self, v, f_io=0):
  717. return v.N(f_io)
  718. def __contains__(self, G):
  719. r = False
  720. for c in self.C:
  721. r |= G in c
  722. return r
  723. def connected(self):
  724. return len(self.C) == 1
  725. # returns connectivity (kappa)
  726. def connectivity(self):
  727. raise NotImplementedError
  728. # returns edge-connectivity (lambda)
  729. def e_connectivity(self):
  730. raise NotImplementedError
  731. # returns the list of graphs components
  732. def components(self):
  733. return self.C
  734. # derivated graphs:
  735. # -----------------
  736. # returns subgraph spanned by vertices V
  737. def spans(self, V):
  738. raise NotImplementedError
  739. # returns join of G (if disjoint)
  740. def __mul__(self, G):
  741. raise NotImplementedError
  742. # returns complement of a graph G
  743. def complement(self, G):
  744. raise NotImplementedError
  745. # contraction G\e
  746. def contract(self, e):
  747. raise NotImplementedError