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