topology.py
Go to the documentation of this file.
00001 # [db] dan@danbrooks.net
00002 #
00003 # Diarc topology objects
00004 # 
00005 # A Diarc topology consists of two types of objects - logical objects and graphical
00006 # objects which visually represent logical objects. 
00007 # 
00008 # Logical objects:                  Graphical Objects:
00009 #   Vertex                          Block
00010 #   Edge                            Band(s)
00011 #   Connection, Source, Sink        Snap(s)
00012 # 
00013 #
00014 # TODO:
00015 # - Document how to create a topology, and how to remove objects from it.
00016 #
00017 # t = Topology()
00018 #
00019 # v1 = Vertext(t)
00020 # v2 = Vertext(t)
00021 # v3 = Vertext(t)
00022 #
00023 # Create an edge from v1 to v2 
00024 # e1 = Edge(t)
00025 # src1 = Source(t,v1,e1)
00026 # snk1 = Sink(t,v2,e1)
00027 # 
00028 # Add connect the edge to v3 as well
00029 # snk2 = Sink(t,v3,e1)
00030 #
00031 # Add an edge from v3 to v1
00032 # e2 = Edge(t)
00033 # src2 = Source(t,v3,e2)
00034 # snk3 = Sink(t,v1,e2)
00035 #
00036 # arrange the vertices in order v1 v2 v3 
00037 # v1.block.index = 0
00038 # v2.block.index = 1
00039 # v3.block.index = 2
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         # Visual Settings
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         # Visual Component
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         # Release yourself from the topology and remove the reference. This
00122         # needs to be done before destroying blocks, since we preclaculate 
00123         # block neighbors and that depends on iterating over the vertex list.
00124         # If we don't cache block neighbors, then the order no longer matters.
00125         self._topology._vertices.remove(self)
00126 
00127         # Release connections to and from the vertex
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         # Release the block object associated with this vertex 
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         # Visual Component
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         # Release connections to and from this edge
00179         logging.debug("... destroying connections")
00180         for connection in self._topology._sources + self._topology._sinks:
00181             if connection.edge == self:
00182                 connection.release()
00183         # Release each of your bands
00184         logging.debug("... releasing associated bands")
00185         self._pBand._release()
00186         self._nBand._release()
00187         # Remove references to your bands
00188         self._pBand = None
00189         self._nBand = None
00190         logging.debug("... removing from topology")
00191         # Release youself from the topology
00192         self._topology._edges.remove(self)
00193         # Remove reference to the topology
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         # Release and remove the reference to your snap
00235         self._snap._release()
00236         self._snap = None
00237         logging.debug("... deleting pointer to vertex and edge")
00238         # Remove references to vertex and edge
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         # Check to make sure there is not already a source going from this vertex to this edge
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         # Remove yourself from the topology
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         # Check to make sure there is not already a sink going from this edge to this vertex
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         # Remove youself from the topology
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         # Visual Properties
00311         self._index = None
00312         # blocks to left and right
00313 #         self._leftBlock = None
00314 #         self._rightBlock = None
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         #This needs to recalculate the left and right blocks on either side
00323         #NOTE: This does not collapse index values, so there becomes a "hole"
00324         # in the index values
00325 #         if self._leftBlock:
00326 #             self._leftBlock._updateNeighbors()
00327 #         if self._rightBlock:
00328 #             self._rightBlock._updateNeighbors()
00329         # Remove cached references to left and right blocks
00330 #         self._leftBlock = None
00331 #         self._rightBlock = None
00332         logging.debug("... remove reference to vertex")
00333         # We don't need to call release() on the vertex, it should already be
00334         # called, we just need to remove the reference
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 #         return dict(filter(lambda x: isinstance(x[0],int), [(s.snap.order, s.snap) for s in self._vertex.sources]))
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 #         return dict(filter(lambda x: isinstance(x[0],int),[(s.snap.order,s.snap) for s in self._vertex.sinks]))
00375 
00376     @property
00377     def leftBlock(self):
00378 #         """ Returns the block to the left, determined by block wich has the next
00379 #         lowest index value. This value is cached when the index is set.  
00380 #         """
00381 #         return self._leftBlock
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         # Else
00390         return None
00391 
00392     @property
00393     def rightBlock(self):
00394 #         """ returns the block to the right, determined by block which has the next
00395 #         highest index value. This  value is cached when the index is set. 
00396 #         """
00397 #         return self._rightBlock
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         # Else:
00406         return None
00407 
00408 
00409 
00410 #     def _updateNeighbors(self):
00411 #         """ Update leftIndex and rightIndex, as well as previous neighbors """
00412 #         blocks = self._topology.blocks
00413 #         # First update your former neighbor's left and right values
00414 #         # If there was an item to the left, it needs a new right hand value
00415 #         if len(blocks) > 0:
00416 #             # update old neighbors
00417 #             if not isinstance(self._leftBlock,types.NoneType):
00418 #                 if self._leftBlock.index < max(blocks.keys()):
00419 #                     self._leftBlock._rightBlock = blocks[min([b for b in blocks.keys() if b > self._leftBlock.index])]
00420 #                 else:
00421 #                     self._leftBlock._rightBlock = None
00422 # 
00423 #             if not isinstance(self._rightBlock,types.NoneType):
00424 #                 if self._rightBlock.index > min(blocks.keys()):
00425 #                     self._rightBlock._leftBlock = blocks[max([b for b in blocks.keys() if b < self._rightBlock.index])]
00426 # 
00427 #                 else: 
00428 #                     self._rightBlock._leftBlock = None
00429 # 
00430 #         # Set my current neighbors
00431 #         if isinstance(self._index,types.NoneType):
00432 #             self._leftBlock = None
00433 #             self._rightBlock = None
00434 #         else:
00435 #             # Calculate new values of left and right blocks
00436 #             # update the right value of the left block and left value of the right block
00437 #             # If you are on an edge, leave the value at None
00438 #             if self._index > min(blocks.keys()):
00439 #                 self._leftBlock = blocks[max([b for b in blocks.keys() if b < self._index])]
00440 #                 self._leftBlock._rightBlock = self
00441 #             else:
00442 #                 self._leftBlock = None
00443 # 
00444 #             if self._index < max(blocks.keys()):
00445 #                 self._rightBlock = blocks[min([b for b in blocks.keys() if b > self._index])]
00446 #                 self._rightBlock._leftBlock = self
00447 #             else:
00448 #                 self._rightBlock = None
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 #             self._updateNeighbors()
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 #         self._updateNeighbors()
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         # Visual Properties
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         # We compare the position of each source against the position of the furthest
00498         # away sink (depending on pos/neg altitude).
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         # Find Sources if this is a  Positive Bands
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         # Find Sources if this is a  Negative Bands
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         # Find Sinks if this is a  Positive Bands
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         # Find Sinks if this is a  Negative Bands
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         # This should be equivalent to checking if any sinks reach this band,
00538         # but this has not been tested or proven. 
00539 #         sinkBlockIndices = [s.block.index for s in self.edge.sinks if isinstance(s.block.index,int)]
00540 #         sourceBlockIndices = [s.block.index for s in self.edge.sources if isinstance(s.block.index,int)]
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         # If positive and there is a sink to the left of any source
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             # TODO: we probably dont need band._isPositive if altitude > self._altitude
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 #         posMax = max([band.altitude for band in bands.values() if band.isUsed()])
00575 #         negVals = [altitude for altitude in bands.keys() if altitude < 0]
00576 #         negMax = max(negVals) if len(negVals) > 0 else 0
00577 #         if (self._isPositive and self._altitude < posMax) or ((not self._isPositive) and self._altitude < negMax) :
00578 #             return bands[min([a for a in bands.keys() if a > self._altitude])]
00579 #         return None
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 #         posVals = [altitude for altitude in bands.keys() if altitude > 0]
00598 #         posMin = min(posVals) if len(posVals) > 0 else 0
00599 #         negMin = min(bands.keys())
00600 #         if (self._isPositive and self._altitude > posMin) or ((not self._isPositive) and self._altitude > negMin):
00601 #             return bands[max([a for a in bands.keys() if a < self._altitude])]
00602 #         return None
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         # Allow "unsetting" rank
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         # Make sure the rank is unique among all bands of the same altitude
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         # Always allow "unsetting" value
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         # Make sure the altitude is unique among all bands 
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         # the connection should 
00666         logging.debug("... removing reference to connection")
00667         self._connection = None
00668 #         print "... removing reference to topology"
00669 #         self._topology = None
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         # If you are a source snap and there is a sink snap to the right, you connect to this band
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         # if you are a sink snap and there is a source snap to your left, connect to this band
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         # If you are a source snap and there is a sink snap to the left, connect to this band
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         # if you are a sink snap and there is a source snap to the right, connect to this band
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         # Always allow "unsetting values"
00767         if value is None:
00768             self._order = value
00769             return
00770         snaps = list()
00771         # Check to see if the order value exists in this emitter or collector
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         # Update value
00780         self._order = value
00781 
00782     order = property(__get_order,__set_order)
00783  


rqt_graphprofiler
Author(s): Dan Brooks
autogenerated on Thu Jun 6 2019 20:29:31