00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042 from util import *
00043 from snapkey import *
00044 import types
00045 import logging
00046
00047 class Topology(object):
00048 def __init__(self):
00049 self._vertices = TypedList(Vertex)
00050 self._edges = TypedList(Edge)
00051 self._sources = TypedList(Source)
00052 self._sinks = TypedList(Sink)
00053
00054
00055 self._hide_disconnected_snaps = False
00056
00057 @property
00058 def vertices(self):
00059 """ returns an unordered list of vertex objects in the topology """
00060 return self._vertices
00061
00062 @property
00063 def edges(self):
00064 """ returns an unordered list of edge objects in the topology """
00065 return self._edges
00066
00067 @property
00068 def blocks(self):
00069 """ Returns dictionary of all blocks who have a proper index value assigned """
00070 return dict(filter(lambda x: isinstance(x[0],int),[(v.block.index,v.block) for v in self._vertices]))
00071
00072 @property
00073 def bands(self):
00074 """ Returns dictionary of all bands, by altitude. Bands which have not
00075 been assigned altitudes are not reported. All bands that have an altitude
00076 (regardless of if they are being used (indicated by isUsed) are reported.
00077 """
00078 allBands = [band for edge in self._edges for band in [edge.posBand,edge.negBand]]
00079 if None is [band.altitude for band in allBands]:
00080 logging.warning("WARNING: There are bands lacking altitude information! Not all bands are represented")
00081 return dict([(band.altitude,band) for band in filter(lambda x: isinstance(x.altitude,int),allBands)])
00082
00083 @property
00084 def snaps(self):
00085 """ Returns dictionary of all snaps, by snapkey. Snaps which have not been
00086 assigned an order are not reported. All snaps that have an order regardless
00087 of if they are being used (indicated by isUsed) are reported.
00088 """
00089 containers = [container for block in [[v.block.emitter, v.block.collector] for v in self._vertices] for container in block]
00090 snaps = [(snap.snapkey(),snap) for snaps in [container.values() for container in containers] for snap in snaps]
00091 return dict(snaps)
00092
00093 def __get_hide_disconnected_snaps(self):
00094 return self._hide_disconnected_snaps
00095 def __set_hide_disconnected_snaps(self, state):
00096 typecheck(state, bool, "state")
00097 self._hide_disconnected_snaps = state
00098 hide_disconnected_snaps = property(__get_hide_disconnected_snaps, __set_hide_disconnected_snaps)
00099
00100
00101
00102
00103 class Vertex(object):
00104 """ A Vertex in a directional graph.
00105 A vertex can connect to multiple edges as either an input (source) or output
00106 (sink) to the edge. It is graphically represented by a Block object.
00107
00108 Sources - outgoing connections to Edges
00109 Sinks - incomming connections from Edges
00110 """
00111 def __init__(self,topology):
00112 self._topology = typecheck(topology,Topology,"topology")
00113 self._topology._vertices.append(self)
00114
00115 self._block = Block(self)
00116
00117 def release(self):
00118 logging.debug("releasing vertex %r"%self)
00119
00120 logging.debug("... removing from topology")
00121
00122
00123
00124
00125 self._topology._vertices.remove(self)
00126
00127
00128 logging.debug("... destroying connections")
00129 for connection in self._topology._sources + self._topology._sinks:
00130 if connection.vertex == self:
00131 connection.release()
00132 logging.debug("... releasing associated block")
00133
00134 self._block._release()
00135 self._block = None
00136 logging.debug("... destroying reference to topology")
00137 self._topology = None
00138
00139 @property
00140 def sources(self):
00141 """ Returns an unordered list of outgoing connections (Source objects)
00142 from this vertex.
00143 """
00144 return filter(lambda x: x.vertex == self, self._topology._sources)
00145
00146 @property
00147 def sinks(self):
00148 """ Returns an unordered list of outgoing connections (Sink objects)
00149 from this vertex.
00150 """
00151 return filter(lambda x: x.vertex == self, self._topology._sinks)
00152
00153 @property
00154 def block(self):
00155 """ Returns the relative graphical object (Block) for this Vertex.
00156 The block cannot be changed
00157 """
00158 return self._block
00159
00160 class Edge(object):
00161 """ A directional multiple-input multiGple-output edge in the graph. Inputs
00162 (sources) and outputs (sinks) are linked to vertices. An edge is represented
00163 graphically by either 1 or 2 Band objects.
00164
00165 Sources - inputs from vertices
00166 Sinks - outputs to vertices
00167 """
00168 def __init__(self,topology):
00169 self._topology = typecheck(topology,Topology,"topology")
00170 self._topology._edges.append(self)
00171
00172 self._pBand = Band(self,True)
00173 self._nBand = Band(self,False)
00174
00175 def release(self):
00176 """ Removes this edge from the topology """
00177 logging.debug("releasing edge %r"%self)
00178
00179 logging.debug("... destroying connections")
00180 for connection in self._topology._sources + self._topology._sinks:
00181 if connection.edge == self:
00182 connection.release()
00183
00184 logging.debug("... releasing associated bands")
00185 self._pBand._release()
00186 self._nBand._release()
00187
00188 self._pBand = None
00189 self._nBand = None
00190 logging.debug("... removing from topology")
00191
00192 self._topology._edges.remove(self)
00193
00194 self._topology = None
00195
00196 @property
00197 def sources(self):
00198 """ returns list of all source connections to this edge """
00199 return filter(lambda x: x.edge == self, self._topology._sources)
00200
00201 @property
00202 def sinks(self):
00203 """ returns list of all sink connections from this edge """
00204 return filter(lambda x: x.edge == self, self._topology._sinks)
00205
00206 @property
00207 def posBand(self):
00208 return self._pBand
00209
00210 @property
00211 def negBand(self):
00212 return self._nBand
00213
00214 class Connection(object):
00215 """ A base class for connecting a vertex to an edge, but without specifing
00216 the nature of the connection (input or output). Rather then using this
00217 class directly, Source or Sink objects should be used.
00218
00219 """
00220 def __init__(self,topology,vertex,edge):
00221 self._topology = typecheck(topology,Topology,"topology")
00222 self._vertex = typecheck(vertex,Vertex,"vertex")
00223 self._edge = typecheck(edge,Edge,"edge")
00224 if (not isinstance(self,Source)) and (not isinstance(self,Sink)):
00225 raise Exception("Do not create connections directly! Use Source or Sink")
00226 self._snap = Snap(self)
00227
00228 def release(self):
00229 """ Removes this connection between a vertex and an edge from the topology.
00230 This does NOT release either the vertex or the edge objects, it simply
00231 removes this particular reference to them.
00232 """
00233 logging.debug("... releasing associated snap")
00234
00235 self._snap._release()
00236 self._snap = None
00237 logging.debug("... deleting pointer to vertex and edge")
00238
00239 self._vertex = None
00240 self._edge = None
00241
00242
00243 @property
00244 def snap(self):
00245 return self._snap
00246
00247 @property
00248 def edge(self):
00249 return self._edge
00250
00251 @property
00252 def vertex(self):
00253 return self._vertex
00254
00255 @property
00256 def block(self):
00257 return self.vertex.block
00258
00259 class Source(Connection):
00260 """ A logical connection from a Vertex to an Edge. Graphically represented
00261 by a Snap object.
00262 """
00263 def __init__(self,topology,vertex,edge):
00264 super(Source,self).__init__(topology,vertex,edge)
00265
00266 for source in vertex.sources + edge.sources:
00267 if vertex == source.vertex and edge == source.edge:
00268 raise Exception("Duplicate Source!")
00269 self._topology._sources.append(self)
00270
00271 def release(self):
00272 logging.debug("Releasing Source %r"%self)
00273 super(Source,self).release()
00274
00275 logging.debug("... removing from topology")
00276 self._topology._sources.remove(self)
00277 self._topology = None
00278
00279 class Sink(Connection):
00280 """ A logical connection from an Edge to a Vertex. Graphically represented
00281 by a Snap object.
00282 """
00283 def __init__(self,topology,vertex,edge):
00284 super(Sink,self).__init__(topology,vertex,edge)
00285
00286 for sink in vertex.sinks + edge.sinks:
00287 if vertex == sink.vertex and edge == sink.edge:
00288 raise Exception("Duplicate Sink!")
00289 self._topology._sinks.append(self)
00290
00291 def release(self):
00292 logging.debug("Releasing Sink %r"%self)
00293 super(Sink,self).release()
00294
00295 logging.debug("... removing from topology")
00296 self._topology._sinks.remove(self)
00297 self._topology = None
00298
00299
00300 class Block(object):
00301 """ Visual Representation of a Vertex
00302 Visual Parameters
00303 Index - Unique int value to determine order in which to draw blocks.
00304 Lower values to the left, higher to the right. Indices do not
00305 necessarily need to be consecutive.
00306 """
00307 def __init__(self,vertex):
00308 self._vertex = typecheck(vertex,Vertex,"vertex")
00309 self._topology = vertex._topology
00310
00311 self._index = None
00312
00313
00314
00315
00316 def _release(self):
00317 """ releases this block from the topology.
00318 This should only be called by Vertex.release()
00319 """
00320 logging.debug("removing block %r"%self)
00321 logging.debug("... removing references to left and right blocks")
00322
00323
00324
00325
00326
00327
00328
00329
00330
00331
00332 logging.debug("... remove reference to vertex")
00333
00334
00335 self._vertex = None
00336 logging.debug("... removing reference to topology")
00337 self._topology = None
00338
00339
00340
00341
00342
00343 @property
00344 def vertex(self):
00345 """ Returns the logical component (Vertex) for this relative object.
00346 The vertex is bound to this block, and cannot be changed.
00347 """
00348 return self._vertex
00349
00350 @property
00351 def emitter(self):
00352 """ Dictionary of Snaps that represent source connections for this block.
00353 Only snaps which have been assigned an order value are represented, since
00354 the order is used as the dictionary key. If hide_disconnected_snaps is
00355 set in the topology, only return snaps where isLinked() is true.
00356 """
00357 snaps = [(s.snap.order, s.snap) for s in self._vertex.sources if isinstance(s.snap.order, int)]
00358 if self._topology.hide_disconnected_snaps:
00359 snaps = [tup for tup in snaps if tup[1].isLinked()]
00360 return dict(snaps)
00361
00362
00363 @property
00364 def collector(self):
00365 """ Dictionary of Snaps that represent sink connections for this block.
00366 Only snaps which have been assigned an order value are represented, since
00367 the order is used as the dictionary key. If hide_disconnected_snaps is
00368 set in the topology, only return snaps where isLinked() is true.
00369 """
00370 snaps = [(s.snap.order, s.snap) for s in self._vertex.sinks if isinstance(s.snap.order, int)]
00371 if self._topology.hide_disconnected_snaps:
00372 snaps = [tup for tup in snaps if tup[1].isLinked()]
00373 return dict(snaps)
00374
00375
00376 @property
00377 def leftBlock(self):
00378
00379
00380
00381
00382 if not isinstance(self._index,int):
00383 return None
00384 blocks = self._topology.blocks
00385 if len(blocks) == 0:
00386 return None
00387 if self._index > min(blocks.keys()):
00388 return blocks[max([b for b in blocks.keys() if b < self._index])]
00389
00390 return None
00391
00392 @property
00393 def rightBlock(self):
00394
00395
00396
00397
00398 if not isinstance(self._index,int):
00399 return None
00400 blocks = self._topology.blocks
00401 if len(blocks) == 0:
00402 return None
00403 if self._index < max(blocks.keys()):
00404 return blocks[min([b for b in blocks.keys() if b > self._index])]
00405
00406 return None
00407
00408
00409
00410
00411
00412
00413
00414
00415
00416
00417
00418
00419
00420
00421
00422
00423
00424
00425
00426
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449
00450
00451 def __get_index(self):
00452 return self._index
00453 def __set_index(self,value):
00454 """ Check to see if a block with the same index already exists """
00455 if self._index == value:
00456 return
00457 if isinstance(value,types.NoneType):
00458 self._index = value
00459
00460 return
00461 allVertices = self._topology._vertices
00462 allBlocks = [v.block for v in allVertices]
00463 if value in [b.index for b in allBlocks]:
00464 raise Exception("Block with index %r already exists!"%value)
00465 self._index = value
00466
00467
00468 index = property(__get_index,__set_index)
00469
00470 class Band(object):
00471 """ Visual Representation of an Edge.
00472 An Edge can have up to two Bands - one with positive altitude and one negative.
00473 Visual Parameters
00474 Rank - the Z drawing order (higher values closer to user)
00475 Altitude - the distance above or below the Block ribbon
00476 """
00477 def __init__(self,edge,isPositive):
00478 self._edge = typecheck(edge,Edge,"edge")
00479 self._topology = edge._topology
00480
00481 self._isPositive = isPositive
00482 self._altitude = None
00483 self._rank = None
00484
00485 def _release(self):
00486 """ Release all dependent references this object holds """
00487 logging.debug("removing band %r"%self)
00488 logging.debug("... removing edge reference")
00489 self._edge = None
00490 logging.debug("... removing reference to topology")
00491 self._topology = None
00492
00493
00494 @property
00495 def emitters(self):
00496 """ returns a list of source snaps that reach this band """
00497
00498
00499 sinkBlockIndices = [s.block.index for s in self.edge.sinks]
00500 sinkBlockIndices = filter(lambda x: isinstance(x,int), sinkBlockIndices)
00501 if len(sinkBlockIndices) < 1:
00502 return list()
00503 sources = list()
00504
00505 if self._altitude and self._altitude > 0:
00506 maxSinkIndex = max(sinkBlockIndices)
00507 sources = filter(lambda src: src.block.index < maxSinkIndex, self.edge.sources)
00508
00509 elif self._altitude and self._altitude < 0:
00510 minSinkIndex = min(sinkBlockIndices)
00511 sources = filter(lambda src: src.block.index >= minSinkIndex, self.edge.sources)
00512 return [s.snap for s in sources]
00513
00514 @property
00515 def collectors(self):
00516 """ returns list of sink snaps that reach this band """
00517 sourceBlockIndices = [s.block.index for s in self.edge.sources]
00518 sourceBlockIndices = filter(lambda x: isinstance(x,int), sourceBlockIndices)
00519 if len(sourceBlockIndices) < 1:
00520 return list()
00521 sinks = list()
00522
00523 if self._altitude and self._altitude > 0:
00524 minSourceIndex = min(sourceBlockIndices)
00525 sinks = filter(lambda sink: sink.block.index > minSourceIndex, self.edge.sinks)
00526
00527 elif self._altitude and self._altitude < 0:
00528 maxSourceIndex = max(sourceBlockIndices)
00529 sinks = filter(lambda sink: sink.block.index <= maxSourceIndex, self.edge.sinks)
00530 return [s.snap for s in sinks]
00531
00532 def isUsed(self):
00533 """ returns true if this band is needed to represent connections on
00534 its edge, else false. This is determined by checking if any sources
00535 reach this band.
00536 """
00537
00538
00539
00540
00541 sinkBlockIndices = [s.block.index for s in self.collectors]
00542 sourceBlockIndices = [s.block.index for s in self.emitters]
00543 if len(sinkBlockIndices) == 0 or len(sourceBlockIndices) == 0:
00544 return False
00545
00546 if self._isPositive and max(sinkBlockIndices) > min(sourceBlockIndices):
00547 return True
00548 elif (not self._isPositive) and min(sinkBlockIndices) <= max(sourceBlockIndices):
00549 return True
00550 else:
00551 return False
00552
00553 @property
00554 def isPositive(self):
00555 return self._isPositive
00556
00557 @property
00558 def topBand(self):
00559 """ Returns the band with the next highest altitude, or None if either
00560 there is no band above this one or the block ribbon is above it.
00561 Bands for which isUsed() is false are skipped over.
00562 """
00563 if not isinstance(self._altitude,int):
00564 return None
00565 bands = self._topology.bands
00566 available = [altitude for altitude in bands.keys() if altitude > self._altitude]
00567 if self._isPositive:
00568
00569 available = [altitude for altitude in available if bands[altitude]._isPositive and bands[altitude].isUsed()]
00570 else:
00571 available = [altitude for altitude in available if (not bands[altitude]._isPositive) and bands[altitude].isUsed()]
00572 return bands[min(available)] if len(available) > 0 else None
00573
00574
00575
00576
00577
00578
00579
00580
00581 @property
00582 def bottomBand(self):
00583 """ Returns the band with the next lowest altitude, or None if either
00584 there is no band below this one or the block ribbon is below it.
00585 Bands for which isUsed() is false are skipped over.
00586 """
00587 if not isinstance(self._altitude,int):
00588 return None
00589 bands = self._topology.bands
00590 available = [altitude for altitude in bands.keys() if altitude < self._altitude]
00591 if self._isPositive:
00592 available = [altitude for altitude in available if bands[altitude]._isPositive and bands[altitude].isUsed()]
00593 else:
00594 available = [altitude for altitude in available if (not bands[altitude]._isPositive) and bands[altitude].isUsed()]
00595 return bands[max(available)] if len(available) > 0 else None
00596
00597
00598
00599
00600
00601
00602
00603
00604 def __get_edge(self):
00605 return self._edge
00606 def __get_rank(self):
00607 return self._rank
00608 def __set_rank(self,val):
00609 if self._rank == val: return
00610
00611 if val is None:
00612 self._rank = val
00613 return
00614 typecheck(val,int,"val")
00615 if val < 0:
00616 raise Exception("Rank must be >= 0, received %d"%val)
00617
00618 allBands = [edge.posBand if self.isPositive else edge.negBand for edge in self._topology._edges]
00619 if val in [b._rank for b in allBands]:
00620 raise Exception("%s Band with rank %d already exists!"%("Positive" if self._isPositive else "Negative",val))
00621 self._rank = val
00622
00623 def __get_altitude(self):
00624 return self._altitude
00625 def __set_altitude(self,value):
00626 if self._altitude == value:
00627 return
00628
00629 if value is None:
00630 self._altitude = value
00631 return
00632 if self._isPositive and value <= 0:
00633 raise Exception("Altitude must be positive")
00634 if (not self._isPositive) and value >= 0:
00635 raise Exception("Altitude must be negative")
00636
00637 allEdges = self._topology._edges
00638 allBands = filter(lambda x: isinstance(x,Band),[band for edge in allEdges for band in [edge.posBand,edge.negBand]])
00639 if value in [b.altitude for b in allBands]:
00640 raise Exception("Band with altitude %d already exists!"%value)
00641 self._altitude = value
00642
00643 edge = property(__get_edge)
00644 rank = property(__get_rank,__set_rank)
00645 altitude = property(__get_altitude,__set_altitude)
00646
00647 class Snap(object):
00648 """ Visual Representation of a Source or Sink.
00649 Snaps are layedout horizontally inside of an Emitter or Collector of a Block.
00650 A Snap provides a mapping between a Source/Sink and one or two Bands associated with a single Edge.
00651 Visual Layout Paramters
00652 Order - 0-indexed order in which to draw snaps within an Emitter or Collector
00653 """
00654 def __init__(self,connection):
00655 self._connection = typecheck(connection,Connection,"connection")
00656 self._order = None
00657
00658 def snapkey(self):
00659 """ generates the snapkey for this snap """
00660 return gen_snapkey(self.block.index, "collector" if self.isSink() else "emitter", self._order)
00661
00662 def _release(self):
00663 """ This should only be called by a Connection.release() """
00664 logging.debug("releasing snap %r"%self)
00665
00666 logging.debug("... removing reference to connection")
00667 self._connection = None
00668
00669
00670
00671 @property
00672 def posBandLink(self):
00673 """ returns the positive band connection - if it exists.
00674 Just because a positive band link exists does not mean that it should
00675 be drawn. The check for if we should draw the connection happens at drawing
00676 time when we decide if we should be using positive or negative"""
00677 pBand = self._connection.edge._pBand
00678
00679 if self.isSource():
00680 indices = [sink.block.index for sink in pBand.collectors]
00681 if len(indices) > 0 and max(indices) > self.block.index:
00682 return pBand
00683
00684 elif self.isSink():
00685 indices =[source.block.index for source in pBand.emitters]
00686 if len(indices) > 0 and min(indices) < self.block.index:
00687 return pBand
00688 return None
00689
00690 @property
00691 def negBandLink(self):
00692 """ returns the negative band connection - if it exists. See posBand for
00693 more details."""
00694 nBand = self._connection.edge._nBand
00695
00696 if self.isSource():
00697 indices = [sink.block.index for sink in nBand.collectors]
00698 if len(indices) > 0 and min(indices) <= self.block.index:
00699 return nBand
00700
00701 elif self.isSink():
00702 indices = [source.block.index for source in nBand.emitters]
00703 if len(indices) > 0 and max(indices) >= self.block.index:
00704 return nBand
00705 return None
00706
00707 @property
00708 def block(self):
00709 return self._connection.vertex.block
00710
00711 @property
00712 def connection(self):
00713 return self._connection
00714
00715 @property
00716 def bandLinks(self):
00717 return filter(lambda x: isinstance(x,Band), [self.posBandLink,self.negBandLink])
00718
00719 def isSource(self):
00720 return isinstance(self._connection,Source)
00721
00722 def isSink(self):
00723 return isinstance(self._connection,Sink)
00724
00725 def isLinked(self):
00726 """ returns true if this snap is connected to at least one sink, else false. """
00727 return True if self.posBandLink or self.negBandLink else False
00728
00729 def isUsed(self):
00730 """ returns true if topology.hide_disconnected_snaps is True and isLinked is True,
00731 or if topology.hide_disconnected_snaps is false. Otherwise, return true.
00732 """
00733 if self._connection._topology.hide_disconnected_snaps:
00734 return True if self.isLinked() else False
00735 else:
00736 return True
00737
00738 @property
00739 def leftSnap(self):
00740 """ Returns the snap directly to the left of this snap within either an
00741 emitter or collector. Returns None if this is leftmost snap.
00742 """
00743 snaps = self.block.emitter if self.isSource() else self.block.collector
00744 if isinstance(self._order,int) and self._order > min(snaps.keys()):
00745 return snaps[max([s for s in snaps.keys() if s < self._order])]
00746 else:
00747 return None
00748
00749 @property
00750 def rightSnap(self):
00751 """ Returns the snap directly to the right of this snap within either
00752 an emitter or collector. Returns None if this is rightmost snap.
00753 """
00754 snaps = self.block.emitter if self.isSource() else self.block.collector
00755 if isinstance(self._order,int) and self._order < max(snaps.keys()):
00756 return snaps[min([s for s in snaps.keys() if s > self._order])]
00757 else:
00758 return None
00759
00760 def __get_order(self):
00761 return self._order
00762 def __set_order(self,value):
00763 """ Check to see if a snap with the same order already exists """
00764 if self._order == value:
00765 return
00766
00767 if value is None:
00768 self._order = value
00769 return
00770 snaps = list()
00771
00772 if isinstance(self._connection,Source):
00773 snaps = [e.snap for e in self._connection.vertex.sources]
00774 if isinstance(self._connection,Sink):
00775 snaps = [e.snap for e in self._connection.vertex.sinks]
00776 orders = filter(lambda x: not isinstance(x,types.NoneType),[s.order for s in snaps])
00777 if value in orders:
00778 raise Exception("Order value %d already exists!"%value)
00779
00780 self._order = value
00781
00782 order = property(__get_order,__set_order)
00783