Example #1
0
def index_map_to_region_map( hom_mat, reg2gen ):
    """
    hom_mat : numpy matrix representing a map on generators (index map).

    reg2gen : dictionary mapping region -> generator(s). (I.e, which
    regions support which generators)

    Returns a DiGraph object of the map on regions in phase space.
    """
    H = hom_mat
    R = reg2gen
    Rinv = invert_dictionary( R )
    G = DiGraph()
    
    # find where region k maps to based the index map
    for k in R.keys():
        # find generator connections
        if hasattr( H, 'nnz' ):
            if len( R[k] ) == 0:
                continue
            gen_conns, _J, _V = sparse.find( H[:,R[k]] )
        else:
            # dense matrix case
            gen_conns = np.where( H[:,R[k]] != 0 )[0]
            gen_conns = gen_conns.tolist()[0] # fix matrix formatting
        for edge in gen_conns:
            for glist in Rinv.keys():
                if edge in glist:
                    G.add_edge( k, Rinv[glist][0] )

    # return the graph so that we have access to the nodes labels that
    # correspond directly to regions with generators.
    return G
def condensation( G, components ):
	"""Return the condensation of G with respect to the given components.

	Given components C_1 .. C_k which partition the nodes of G, the
	condensation graph cG has nodes <C_1> .. <C_k> and has edge
	(<C_i>,<C_j>) iff there was an edge in G from a node in C_i to a
	node in C_j.
	
	Note: the given components can be arbitrary, but if they are the
	strongly connected components (in the graph theory sense, not the
	dynamics sense) of G, then cG corresponds to the directed acyclic
	graph between strongly connected components.

	Parameters
	----------
	G : DiGraph

	components : list of lists of component nodes.

	Returns
	-------
	cG : DiGraph
	   The condensed graph.
	"""
	mapping = dict([(n,c) for c in range(len(components)) for n in components[c]])
	cG = DiGraph()
	for u in mapping:
		cG.add_node(mapping[u])
		for _,v in G.graph.edges_iter(u, data=False):
			if v not in components[mapping[u]]:
				cG.add_edge(mapping[u], mapping[v])
	return cG
Example #3
0
def condensation( G, components, loops=False ):
    """Return the condensation of G with respect to the given components.

    Given components C_1 .. C_k which partition the nodes of G, the
    condensation graph cG has nodes <C_1> .. <C_k> and has edge
    (<C_i>,<C_j>) iff there was an edge in G from a node in C_i to a
    node in C_j.
    
    Note: the given components can be arbitrary, but if they are the
    strongly connected components (in the graph theory sense, not the
    dynamics sense) of G, then cG corresponds to the directed acyclic
    graph between strongly connected components.

    Parameters
    ----------
    G : DiGraph

    components : list of lists of component nodes, or dictionary of
    component label -> component nodes

    loops : whether to allow condensed nodes to have self loops (default: False)

    Returns
    -------
    cG : DiGraph
       The condensed graph.
    """
    # convert list to dict
    if isinstance(components,list):
        components = {c:components[c] for c in range(len(components))}
        
    mapping = {n:c for c in components for n in components[c]}
    cG = DiGraph()
    for u in mapping.keys():
        cG.add_node(mapping[u])
        for v in G.successors(u):
            # if v~u and u,v are in the same component, don't add the loop
            # (unless we allow loops)
            if loops or v not in components[mapping[u]]:
                cG.add_edge(mapping[u], mapping[v])
    return cG
class FiniteRepresentation( object ): 
    """
    """
    def __init__( self, data, tree, noise, expansion=1. ):
        """
        Created a finite representation of the dynamics of a system
        from a collection of temporally ordered data points.

        data -- list or array of points in R^d. Index order specifies
        temporal order.

        tree -- BoxTree object initialized with the compact region
        containing the data. (See BoxTree class.)

        noise -- array of tuples or real numbers specifying the noise
        in each dimension of each data point. Hence, if data contains
        points in R^2, data[0] <--> noise[0] == ((a1,a2),(b1,b2)) or
        (a1, b1) if data[0] is centered within the error box
        determined bythe noise.  Note: This scheme assume (for the
        time being) that noise if uniform about each data point.
        """
        self.data = np.asarray( data, dtype=float )
        self.noise = noise
        self.tree = tree
        self.dim = data.ndim

        # x_i |--> {grid elements} mapping
        self.data_hash = {}
        
        self.expansion = expansion # constant expansion rate (very
                                   # crude approximation of dynamics)
  
    def _add_box( self, box ):
        """
        box -- lower left anchor in box[0], widths across dimensions
        in box[1].

        data point center in noise box, and width in j'th dimension
        specified by 2*noise[j].
        """
        # shift center to lower left corner anchor
        bx = self._make_box( box )
        self.tree.insert( bx )

    def _add_boxes_tuple( self ):
        """ TODO """
        print "NOT IMPLEMENTED!"

    def _make_box( self, b ):
        """
        b -- tuple or array of 
        """
        anchor = b[0] - (b[1] / 2.)
        if type( b ) == tuple:
            width = self.dim * [ b[1] ]
        else:
            width = b[1]
        bx = np.array( [ anchor, width ] )
        return bx
    
    def _expand_equal( self, idx ):
        """
        Expand box dimension equally by self.expansion in all
        directions. Since boxes are anchored in lower left corner,
        shift each coordinate by (self.expansion * w - w )/2, where w
        is the width of the box in each dimension.

        box : array( [[ x1,...,xd ], [ w1,...,wd ]] )

        Returns expanded and shifted box
        """
        B = self.tree.boxes()
        anchors = B.corners[ idx ]
        width = B.width
        new_width = self.expansion * width
        shift = ( new_width - width ) / 2.
        new_anchors = anchors - shift
        new_box = np.array( [ new_anchors, new_width ] )
        print new_box
        print ""
        return new_box
        
    def add_error_boxes( self ):
        """
        Intersect each noise box containing a data point with the grid
        contained in Tree. This is used to construct the grid on the
        phase space without a MVM. the MVM function perform the same
        operation during the construction of the map.
        """
        if hasattr( self.noise[0], '__len__' ):
            self._add_boxes_tuple()  # TODO
        else:
            errors = izip( self.data, self.noise )
            for box in errors:
                self._add_box( box )

    def boxes( self ):
        """ Return a list of all boxes in the tree. """
        return self.tree.boxes()

    def construct_mvm_simple( self ):
        """
        Construct a directed graph on the boxes in self.tree. Boxes
        intersecting error_box[i] are mapped to boxes intersecting
        error_box[i+1]. [No expansion estimates are used.]
        """
        # store the finite representation
        self.mvm = DiGraph()

        # generator to save memory
        error_boxes = izip( self.data, self.noise )

        # pull first time step off the top
        data_idx = 0
        pred = self._make_box( error_boxes.next() )
        pred_ids = self.tree.search( pred )

        self.data_hash[ data_idx ] = pred_ids
        
        # iteration starts at second element
        for succ in error_boxes:
            bx = self._make_box( succ )
            self.tree.insert( bx )
            succ_ids = self.tree.search( bx )
            
            # loop over boxes in predecessor region and create edge to
            # those in successor regions
            for u in pred_ids:
                for v in succ_ids:
                    self.mvm.add_edge( u, v )
                    
            # update predecessor for next time step
            pred = succ
            pred_ids = succ_ids
            data_idx += 1
            self.data_hash[ data_idx ] = pred_ids
            

    def construct_mvm_expansion( self ):
        """
        Construct a directed graph on the boxes in self.tree
        using. Boxes G_i intersecting error_box[i] are mapped to boxes
        G_{i+1} intersecting error_box[i+1] with expansion rate
        C. Thus the image boxes are expanded equally in all directions
        by a factor C > 1. This image is intersected with boxes in the
        tree and the image is updated.
        """
        # store the finite representation
        self.mvm = DiGraph()

        # generator to save memory
        error_boxes = izip( self.data, self.noise )

        # pull first time step off the top 
        pred = self._make_box( error_boxes.next() )
        pred_ids = self.tree.search( pred )

        # loop optimizations
        maker = self._make_box
        expander = self._expand_equal
        tree_insert = self.tree.insert
        tree_search = self.tree.search
        
        # iteration starts at second element
        for succ in error_boxes:
            # error in 'box' form
            bx = self._make_box( succ )
            # intersect with subdivision
            self.tree.insert( bx )
            succ_ids = self.tree.search( bx )
            
            # apply expansion to image
            ex_box = self._expand_equal( succ_ids )

            # DANGER! THIS MIGHT DOUBLE INSERT BOXES
            self.tree.insert( ex_box )       

            # loop over boxes in predecessor region and connect to
            # those in successor regions
            for u in pred_ids:
                for v in succ_ids:
                    self.mvm.add_edge( u, v )
            # update predecessor for next time step
            pred = succ
            pred_ids = succ_ids

    def graph_mis( self ):
        self.mis = alg.graph_mis( self.mvm )

    def trim_graph( self, copy=False ):
        """
        Trim the MVM graph to include only nodes from the maximal
        invariant set (i.e. the strongly connected component == SCC).

        copy : boolean. Default False. If True, copy original MVM to
        self.full_mvm. In either case, self.mvm is replaced by the
        SCC.
        """
        nbunch = set( self.mvm.nodes() )
        scc = set( self.mis )
        non_scc = nbunch - scc 

        if copy:
            self.full_mvm = DiGraph()
            # copy returns NX.DiGraph()
            self.full_mvm.graph = self.mvm.copy()
        self.mvm.remove_nodes_from( non_scc )

    def pickle_tree( self, fname, tree=None ):
        """
        Extact necessary information to create a persistent object.
        """
        if not tree:
            tree = self.tree
        b = tree.boxes()
        tree_dict = { 'corners' : b.corners,
                      'width' : b.width,
                      'dim' : b.dim,
                      'size' : b.size
                      }
        with open( fname, 'wb' ) as fh:
            pkl.dump( tree_dict, fh )

    def write_mvm( self, fname, stype='dot' ):
        """
        A wrapper around NX's graph writers.

        fname : full path to save graph to

        type : 'pkl' or 'dot' (default).  'pkl' => pickle the
        graph. 'dot' => save graph in dot format (more portable)
        """
        if stype == 'pkl':
            write_gpickle( self.mvm.graph, fname )
        else:
            write_dot( self.mvm.graph, fname )       

    def show_boxes( self, color='b', alpha=0.6 ):
        """
        """
        fig = gfx.show_uboxes( self.boxes(), col=color )
        return fig

    def show_error_boxes( self, box=None, color='r', alpha=0.6, fig=None ):
        """
        """
        error_boxes = izip( self.data, self.noise )
        # boxes = [ self._make_box( b ) for b in error_boxes ]
        # gfx.show_boxes( error_boxes, S=range( len(boxes) ), col=color,
        #                 alpha=alpha, fig=fig )
        for b in error_boxes:
            # shift center to lower left corner anchor
            bx = self._make_box( b )
            fig = gfx.show_box( bx, col=color, alpha=alpha, fig=fig )
        return fig