예제 #1
0
 def adjacent(self, i, j):
     _min_i, _max_i = self.unit_bounding_box(i, True)
     _min_j, _max_j = self.unit_bounding_box(j, True)
     if _min_i is None or _min_j is None:
         _ri, _rj = self.unit_polytope(i), self.unit_polytope(j)
         return pt.is_adjacent(_ri, _rj)
     else:
         return np.all(_min_i <= _max_j) and np.all(_min_j <= _max_i)
예제 #2
0
def find_adjacent_regions(partition):
    """Return region pairs that are spatially adjacent.

    @type partition: iterable container of L{Region}

    @rtype: lil_matrix
    """
    n = len(partition)
    adj = sp.lil_matrix((n, n), dtype=np.int8)
    s = partition.regions

    for i, a in enumerate(s):
        adj[i, i] = 1

        for j, b in enumerate(s[0:i]):
            adj[i, j] = adj[j, i] = pc.is_adjacent(a, b)

    return adj
예제 #3
0
def find_adjacent_regions(partition):
    """Return region pairs that are spatially adjacent.

    @type partition: iterable container of L{Region}

    @rtype: lil_matrix
    """
    n = len(partition)
    adj = sp.lil_matrix((n, n), dtype=np.int8)
    s = partition.regions

    for i, a in enumerate(s):
        adj[i, i] = 1

        for j, b in enumerate(s[0:i]):
            adj[i, j] = adj[j, i] = pc.is_adjacent(a, b)

    return adj
예제 #4
0
def add_grid(ppp, grid_size=None, num_grid_pnts=None, abs_tol=1e-10):
    """ This function takes a proposition preserving partition ppp and the size
    of the grid or the number of grids, and returns a refined proposition
    preserving partition with grids.

    Input:

      - `ppp`: a L{PropPreservingPartition} object
      - `grid_size`: the size of the grid,
          type: float or list of float
      - `num_grid_pnts`: the number of grids for each dimension,
          type: integer or list of integer

    Output:

      - A L{PropPreservingPartition} object with grids

    Note: There could be numerical instabilities when the continuous
    propositions in ppp do not align well with the grid resulting in very small
    regions. Performace significantly degrades without glpk.
    """
    if (grid_size != None) & (num_grid_pnts != None):
        raise Exception("add_grid: Only one of the grid size or number of \
                        grid points parameters is allowed to be given.")
    if (grid_size == None) & (num_grid_pnts == None):
        raise Exception("add_grid: At least one of the grid size or number of \
                         grid points parameters must be given.")

    dim = len(ppp.domain.A[0])
    domain_bb = ppp.domain.bounding_box
    size_list = list()
    if grid_size != None:
        if isinstance(grid_size, list):
            if len(grid_size) == dim:
                size_list = grid_size
            else:
                raise Exception(
                    "add_grid: grid_size isn't given in a correct format.")
        elif isinstance(grid_size, float):
            for i in xrange(dim):
                size_list.append(grid_size)
        else:
            raise Exception("add_grid: "
                            "grid_size isn't given in a correct format.")
    else:
        if isinstance(num_grid_pnts, list):
            if len(num_grid_pnts) == dim:
                for i in xrange(dim):
                    if isinstance(num_grid_pnts[i], int):
                        grid_size = (float(domain_bb[1][i]) -
                                     float(domain_bb[0][i])) / num_grid_pnts[i]
                        size_list.append(grid_size)
                    else:
                        raise Exception(
                            "add_grid: "
                            "num_grid_pnts isn't given in a correct format.")
            else:
                raise Exception(
                    "add_grid: "
                    "num_grid_pnts isn't given in a correct format.")
        elif isinstance(num_grid_pnts, int):
            for i in xrange(dim):
                grid_size = (float(domain_bb[1][i]) -
                             float(domain_bb[0][i])) / num_grid_pnts
                size_list.append(grid_size)
        else:
            raise Exception("add_grid: "
                            "num_grid_pnts isn't given in a correct format.")

    j = 0
    list_grid = dict()

    while j < dim:
        list_grid[j] = compute_interval(float(domain_bb[0][j]),
                                        float(domain_bb[1][j]), size_list[j],
                                        abs_tol)
        if j > 0:
            if j == 1:
                re_list = list_grid[j - 1]
                re_list = product_interval(re_list, list_grid[j])
            else:
                re_list = product_interval(re_list, list_grid[j])
        j += 1

    new_list = []
    parent = []
    for i in xrange(len(re_list)):
        temp_list = list()
        j = 0
        while j < dim * 2:
            temp_list.append([re_list[i][j], re_list[i][j + 1]])
            j = j + 2
        for j in xrange(len(ppp.regions)):
            tmp = pc.box2poly(temp_list)
            isect = tmp.intersect(ppp.regions[j], abs_tol)

            #if pc.is_fulldim(isect):
            rc, xc = pc.cheby_ball(isect)
            if rc > abs_tol / 2:
                if rc < abs_tol:
                    print("Warning: "
                          "One of the regions in the refined PPP is too small"
                          ", this may cause numerical problems")
                if len(isect) == 0:
                    isect = pc.Region([isect], [])
                isect.props = ppp.regions[j].props.copy()
                new_list.append(isect)
                parent.append(j)

    adj = sp.lil_matrix((len(new_list), len(new_list)), dtype=np.int8)
    for i in xrange(len(new_list)):
        adj[i, i] = 1
        for j in xrange(i + 1, len(new_list)):
            if (ppp.adj[parent[i], parent[j]] == 1) or \
                    (parent[i] == parent[j]):
                if pc.is_adjacent(new_list[i], new_list[j]):
                    adj[i, j] = 1
                    adj[j, i] = 1

    return PropPreservingPartition(domain=ppp.domain,
                                   regions=new_list,
                                   adj=adj,
                                   prop_regions=ppp.prop_regions)
예제 #5
0
def pwa_partition(pwa_sys, ppp, abs_tol=1e-5):
    """This function takes:

      - a piecewise affine system C{pwa_sys} and
      - a proposition-preserving partition C{ppp}
          whose domain is a subset of the domain of C{pwa_sys}

    and returns a *refined* proposition preserving partition
    where in each region a unique subsystem of pwa_sys is active.

    Reference
    =========
    Modified from Petter Nilsson's code
    implementing merge algorithm in:

    Nilsson et al.
    `Temporal Logic Control of Switched Affine Systems with an
    Application in Fuel Balancing`, ACC 2012.

    See Also
    ========
    L{discretize}

    @type pwa_sys: L{hybrid.PwaSysDyn}
    @type ppp: L{PropPreservingPartition}

    @return: new partition and associated maps:

        - new partition C{new_ppp}
        - map of C{new_ppp.regions} to C{pwa_sys.list_subsys}
        - map of C{new_ppp.regions} to C{ppp.regions}

    @rtype: C{(L{PropPreservingPartition}, list, list)}
    """
    if pc.is_fulldim(ppp.domain.diff(pwa_sys.domain)):
        raise Exception('pwa system is not defined everywhere ' +
                        'in state space')

    # for each subsystem's domain, cut it into pieces
    # each piece is the intersection with
    # a unique Region in ppp.regions
    new_list = []
    subsys_list = []
    parents = []
    for i, subsys in enumerate(pwa_sys.list_subsys):
        for j, region in enumerate(ppp.regions):
            isect = region.intersect(subsys.domain)

            if pc.is_fulldim(isect):
                rc, xc = pc.cheby_ball(isect)

                if rc < abs_tol:
                    msg = 'One of the regions in the refined PPP is '
                    msg += 'too small, this may cause numerical problems'
                    warnings.warn(msg)

                # not Region yet, but Polytope ?
                if len(isect) == 0:
                    isect = pc.Region([isect])

                # label with AP
                isect.props = region.props.copy()

                # store new Region
                new_list.append(isect)

                # keep track of original Region in ppp.regions
                parents.append(j)

                # index of subsystem active within isect
                subsys_list.append(i)

    # compute spatial adjacency matrix
    n = len(new_list)
    adj = sp.lil_matrix((n, n), dtype=np.int8)
    for i, ri in enumerate(new_list):
        pi = parents[i]
        for j, rj in enumerate(new_list[0:i]):
            pj = parents[j]

            if (ppp.adj[pi, pj] == 1) or (pi == pj):
                if pc.is_adjacent(ri, rj):
                    adj[i, j] = 1
                    adj[j, i] = 1
        adj[i, i] = 1

    new_ppp = PropPreservingPartition(domain=ppp.domain,
                                      regions=new_list,
                                      adj=adj,
                                      prop_regions=ppp.prop_regions)
    return (new_ppp, subsys_list, parents)
예제 #6
0
 def add_collection(self, unit_regions, label=None):
     # check if already existing
     if label in self.index:
         if 1 < len(self.index):
             raise RuntimeError(
                 'cannot overwrite a collection if other collections have already been defined'
             )
         self.__reset__()
     #
     i0 = len(self.unit_region)
     self.unit_region += list(unit_regions)
     # first group overlapping unit regions in the collection
     current_index = max(self.group.keys()) + 1 if self.group else 0
     not_an_index = -1
     n = len(unit_regions)
     assignment = np.full(n, not_an_index, dtype=int)
     groups = dict()
     for i in range(n):
         region_i = unit_regions[i]
         if isinstance(region_i, pt.Polytope):
             region_i = pt.Region([region_i])
             _min_i = _max_i = None
         elif isinstance(region_i, pt.Region):
             _min_i = _max_i = None
         else:  #if isinstance(region_i, (tuple, list)):
             _min_i, _max_i = region_i
             region_i = None
         group_with = set()
         if assignment[i] == not_an_index:
             group_index = current_index
             group = set([i0 + i])
         else:
             group_index = assignment[i]
             group_with.add(group_index)
             group = groups[group_index]
             assert i0 + i in group
         for j in range(i + 1, n):
             if i0 + j in group:
                 continue
             region_j = unit_regions[j]
             if isinstance(region_j, (pt.Polytope, pt.Region)):
                 if region_i is None:
                     region_i = pt.box2poly(list(zip(_min_i, _max_i)))
                     self._unit_polytope[i0 + i] = region_i
                 i_and_j_are_adjacent = pt.is_adjacent(region_i, region_j)
             else:  #if isinstance(region_j, (tuple, list)):
                 _min_j, _max_j = region_j
                 if _min_i is None:
                     region_j = pt.box2poly(list(zip(_min_j, _max_j)))
                     self._unit_polytope[i0 + j] = region_j
                     i_and_j_are_adjacent = pt.is_adjacent(
                         region_i, region_j)
                 else:
                     i_and_j_are_adjacent = np.all(
                         _min_i <= _max_j) and np.all(_min_j <= _max_i)
             if i_and_j_are_adjacent:
                 if assignment[j] == not_an_index:
                     group.add(i0 + j)
                 else:
                     other_group_index = assignment[j]
                     group_with.add(other_group_index)
                     group |= groups.pop(other_group_index)
         if group_with:
             group_index = min(group_with)
         else:
             current_index += 1
         groups[group_index] = group
         group = np.array(list(group)) - i0  # indices in `assignment`
         assignment[group] = group_index
     #
     # merge the new and existing groups together
     for g in list(groups.keys()):
         adjacent = set()
         for h in self.group:
             g_and_h_are_adjacent = False
             for i in groups[g]:
                 for j in self.group[h]:
                     if self.adjacent(i, j):
                         g_and_h_are_adjacent = True
                         break
                 if g_and_h_are_adjacent:
                     adjacent.add(h)
                     break
         if adjacent:
             h = min(adjacent)
             for i in adjacent - {h}:
                 self.group[h] |= self.group.pop(i)
             self.group[h] |= groups.pop(g)
             assignment[assignment == g] = h
     if groups:
         self.group.update(groups)
     #
     if label is None:
         label = ''
     self.index[label] = assignment
     #
     self.reverse_index = np.c_[
         np.repeat(np.arange(len(self.index)
                             ), [len(self.index[s]) for s in self.index]),
         np.concatenate([np.arange(len(self.index[s]))
                         for s in self.index])]
예제 #7
0
def add_grid(ppp, grid_size=None, num_grid_pnts=None, abs_tol=1e-10):
    """ This function takes a proposition preserving partition ppp and the size 
    of the grid or the number of grids, and returns a refined proposition 
    preserving partition with grids.
     
    Input:
    
      - `ppp`: a L{PropPreservingPartition} object
      - `grid_size`: the size of the grid,
          type: float or list of float
      - `num_grid_pnts`: the number of grids for each dimension,
          type: integer or list of integer
    
    Output:
    
      - A L{PropPreservingPartition} object with grids
        
    Note: There could be numerical instabilities when the continuous 
    propositions in ppp do not align well with the grid resulting in very small 
    regions. Performace significantly degrades without glpk.
    """
    if (grid_size!=None)&(num_grid_pnts!=None):
        raise Exception("add_grid: Only one of the grid size or number of \
                        grid points parameters is allowed to be given.")
    if (grid_size==None)&(num_grid_pnts==None):
        raise Exception("add_grid: At least one of the grid size or number of \
                         grid points parameters must be given.")
 
    dim=len(ppp.domain.A[0])
    domain_bb = ppp.domain.bounding_box
    size_list=list()
    if grid_size!=None:
        if isinstance( grid_size, list ):
            if len(grid_size) == dim:
                size_list=grid_size
            else:
                raise Exception(
                    "add_grid: grid_size isn't given in a correct format."
                )
        elif isinstance( grid_size, float ):
            for i in xrange(dim):
                size_list.append(grid_size)
        else:
            raise Exception("add_grid: "
                "grid_size isn't given in a correct format.")
    else:
        if isinstance( num_grid_pnts, list ):
            if len(num_grid_pnts) == dim:
                for i in xrange(dim):
                    if isinstance( num_grid_pnts[i], int ):
                        grid_size=(
                                float(domain_bb[1][i]) -float(domain_bb[0][i])
                            ) /num_grid_pnts[i]
                        size_list.append(grid_size)
                    else:
                        raise Exception("add_grid: "
                            "num_grid_pnts isn't given in a correct format.")
            else:
                raise Exception("add_grid: "
                    "num_grid_pnts isn't given in a correct format.")
        elif isinstance( num_grid_pnts, int ):
            for i in xrange(dim):
                grid_size=(
                        float(domain_bb[1][i])-float(domain_bb[0][i])
                    ) /num_grid_pnts
                size_list.append(grid_size)
        else:
            raise Exception("add_grid: "
                "num_grid_pnts isn't given in a correct format.")
    
    j=0
    list_grid=dict()
    
    while j<dim:
        list_grid[j] = compute_interval(
            float(domain_bb[0][j]),
            float(domain_bb[1][j]),
            size_list[j],
            abs_tol
        )
        if j>0:
            if j==1:
                re_list=list_grid[j-1]
                re_list=product_interval(re_list, list_grid[j])
            else:
                re_list=product_interval(re_list, list_grid[j])
        j+=1
        
    new_list = []
    parent = []
    for i in xrange(len(re_list)):
        temp_list=list()
        j=0
        while j<dim*2:
            temp_list.append([re_list[i][j],re_list[i][j+1]])
            j=j+2
        for j in xrange(len(ppp.regions)):
            tmp = pc.box2poly(temp_list)
            isect = tmp.intersect(ppp.regions[j], abs_tol)
            
            #if pc.is_fulldim(isect):
            rc, xc = pc.cheby_ball(isect)
            if rc > abs_tol/2:
                if rc < abs_tol:
                    print("Warning: "
                        "One of the regions in the refined PPP is too small"
                        ", this may cause numerical problems")
                if len(isect) == 0:
                    isect = pc.Region([isect], [])
                isect.props = ppp.regions[j].props.copy()
                new_list.append(isect)
                parent.append(j)   
    
    adj = sp.lil_matrix((len(new_list), len(new_list)), dtype=np.int8)
    for i in xrange(len(new_list)):
        adj[i,i] = 1
        for j in xrange(i+1, len(new_list)):
            if (ppp.adj[parent[i], parent[j]] == 1) or \
                    (parent[i] == parent[j]):
                if pc.is_adjacent(new_list[i], new_list[j]):
                    adj[i,j] = 1
                    adj[j,i] = 1
            
    return PropPreservingPartition(
        domain = ppp.domain,
        regions = new_list,
        adj = adj,
        prop_regions = ppp.prop_regions
    )
예제 #8
0
def pwa_partition(pwa_sys, ppp, abs_tol=1e-5):
    """This function takes:
    
      - a piecewise affine system C{pwa_sys} and
      - a proposition-preserving partition C{ppp}
          whose domain is a subset of the domain of C{pwa_sys}
    
    and returns a *refined* proposition preserving partition
    where in each region a unique subsystem of pwa_sys is active.
    
    Reference
    =========
    Modified from Petter Nilsson's code
    implementing merge algorithm in:
    
    Nilsson et al.
    `Temporal Logic Control of Switched Affine Systems with an
    Application in Fuel Balancing`, ACC 2012.

    See Also
    ========
    L{discretize}
    
    @type pwa_sys: L{hybrid.PwaSysDyn}
    @type ppp: L{PropPreservingPartition}
    
    @return: new partition and associated maps:
        
        - new partition C{new_ppp}
        - map of C{new_ppp.regions} to C{pwa_sys.list_subsys}
        - map of C{new_ppp.regions} to C{ppp.regions}
    
    @rtype: C{(L{PropPreservingPartition}, list, list)}
    """
    if pc.is_fulldim(ppp.domain.diff(pwa_sys.domain) ):
        raise Exception('pwa system is not defined everywhere ' +
                        'in state space')
    
    # for each subsystem's domain, cut it into pieces
    # each piece is the intersection with
    # a unique Region in ppp.regions
    new_list = []
    subsys_list = []
    parents = []
    for i, subsys in enumerate(pwa_sys.list_subsys):
        for j, region in enumerate(ppp.regions):
            isect = region.intersect(subsys.domain)
            
            if pc.is_fulldim(isect):
                rc, xc = pc.cheby_ball(isect)
                
                if rc < abs_tol:
                    msg = 'One of the regions in the refined PPP is '
                    msg += 'too small, this may cause numerical problems'
                    warnings.warn(msg)
                
                # not Region yet, but Polytope ?
                if len(isect) == 0:
                    isect = pc.Region([isect])
                
                # label with AP
                isect.props = region.props.copy()
                
                # store new Region
                new_list.append(isect)
                
                # keep track of original Region in ppp.regions
                parents.append(j)
                
                # index of subsystem active within isect
                subsys_list.append(i)
    
    # compute spatial adjacency matrix
    n = len(new_list)
    adj = sp.lil_matrix((n, n), dtype=np.int8)
    for i, ri in enumerate(new_list):
        pi = parents[i]
        for j, rj in enumerate(new_list[0:i]):
            pj = parents[j]
            
            if (ppp.adj[pi, pj] == 1) or (pi == pj):
                if pc.is_adjacent(ri, rj):
                    adj[i, j] = 1
                    adj[j, i] = 1
        adj[i, i] = 1
            
    new_ppp = PropPreservingPartition(
        domain = ppp.domain,
        regions = new_list,
        adj = adj,
        prop_regions = ppp.prop_regions
    )
    return (new_ppp, subsys_list, parents)
예제 #9
0
    def compute_adj(self):
        """Update the adjacency matrix by checking all region pairs.

        Uses L{polytope.is_adjacent}.
        """
        n = len(self.regions)
        adj = sp.lil_matrix((n, n))

        logger.info('computing adjacency from scratch...')
        for i, region0 in enumerate(self.regions):
            for j, region1 in enumerate(self.regions):
                if i == j:
                    adj[i, j] = 1
                    continue

                if pc.is_adjacent(region0, region1):
                    adj[i, j] = 1
                    adj[j, i] = 1

                    logger.info('regions: ' + str(i) + ', ' +
                                 str(j) + ', are adjacent.')
        logger.info('...done computing adjacency.')

        # check previous one to unmask errors
        if self.adj is not None:
            logger.info('checking previous adjacency...')

            ok = True
            row, col = adj.nonzero()

            for i, j in zip(row, col):
                assert(adj[i, j])
                if adj[i, j] != self.adj[i, j]:
                    ok = False

                    msg = 'PPP adjacency matrix is incomplete, '
                    msg += 'missing: (' + str(i) + ', ' + str(j) + ')'
                    logger.error(msg)

            row, col = self.adj.nonzero()

            for i, j in zip(row, col):
                assert(self.adj[i, j])
                if adj[i, j] != self.adj[i, j]:
                    ok = False

                    msg = 'PPP adjacency matrix is incorrect, '
                    msg += 'has 1 at: (' + str(i) + ', ' + str(j) + ')'
                    logger.error(msg)

            if not ok:
                logging.error('PPP had incorrect adjacency matrix.')

            logger.info('done checking previous adjacency.')
        else:
            ok = True
            logger.info('no previous adjacency found: ' +
                        'skip verification.')

        # update adjacency
        self.adj = adj

        return ok
예제 #10
0
    def compute_adj(self):
        """Update the adjacency matrix by checking all region pairs.

        Uses L{polytope.is_adjacent}.
        """
        n = len(self.regions)
        adj = sp.lil_matrix((n, n))

        logger.info('computing adjacency from scratch...')
        for i, region0 in enumerate(self.regions):
            for j, region1 in enumerate(self.regions):
                if i == j:
                    adj[i, j] = 1
                    continue

                if pc.is_adjacent(region0, region1):
                    adj[i, j] = 1
                    adj[j, i] = 1

                    logger.info('regions: ' + str(i) + ', ' +
                                str(j) + ', are adjacent.')
        logger.info('...done computing adjacency.')

        # check previous one to unmask errors
        if self.adj is not None:
            logger.info('checking previous adjacency...')

            ok = True
            row, col = adj.nonzero()

            for i, j in zip(row, col):
                assert(adj[i, j])
                if adj[i, j] != self.adj[i, j]:
                    ok = False

                    msg = 'PPP adjacency matrix is incomplete, '
                    msg += 'missing: (' + str(i) + ', ' + str(j) + ')'
                    logger.error(msg)

            row, col = self.adj.nonzero()

            for i, j in zip(row, col):
                assert(self.adj[i, j])
                if adj[i, j] != self.adj[i, j]:
                    ok = False

                    msg = 'PPP adjacency matrix is incorrect, '
                    msg += 'has 1 at: (' + str(i) + ', ' + str(j) + ')'
                    logger.error(msg)

            if not ok:
                logging.error('PPP had incorrect adjacency matrix.')

            logger.info('done checking previous adjacency.')
        else:
            ok = True
            logger.info('no previous adjacency found: ' +
                        'skip verification.')

        # update adjacency
        self.adj = adj

        return ok
예제 #11
0
def pwa_shrunk_partition(pwa_sys, ppp, eps, abs_tol=1e-5):
    """This function takes:
    
      - a piecewise affine system C{pwa_sys} and
      - a proposition-preserving partition C{ppp}
          whose domain is a subset of the domain of C{pwa_sys}
      - a shrinkage factor C{eps}
    
    and returns a *refined* proposition preserving partition
    where in each region a unique subsystem of pwa_sys is active
    and pwa_sys domains are shrunk to account for estimation 
    errors.

    See Also
    ========
    L{pwa_partition}
    
    @type pwa_sys: L{hybrid.PwaSysDyn}
    @type ppp: L{PropPreservingPartition}
    
    @return: new partition and associated maps:
        
        - new partition C{new_ppp}
        - map of C{new_ppp.regions} to C{pwa_sys.list_subsys}
        - map of C{new_ppp.regions} to C{ppp.regions}
    
    @rtype: C{(L{PropPreservingPartition}, list, list)}
    """

    new_list = []
    subsys_list = []
    parents = []
    for i, subsys in enumerate(pwa_sys.list_subsys):
        dom = shrinkPoly(subsys.domain, eps)
        for j, region in enumerate(ppp.regions):
            isect = pc.reduce(region.intersect(dom))
            
            if pc.is_fulldim(isect):
                rc, xc = pc.cheby_ball(isect)
                
                if rc < abs_tol:
                    msg = 'One of the regions in the refined PPP is '
                    msg += 'too small, this may cause numerical problems'
                    warnings.warn(msg)
                
                # not Region yet, but Polytope ?
                if len(isect) == 0:
                    isect = pc.Region([isect])
                
                # label with AP
                isect.props = region.props.copy()
                
                # store new Region
                new_list.append(isect)
                
                # keep track of original Region in ppp.regions
                parents.append(j)
                
                # index of subsystem active within isect
                subsys_list.append(i)
    
    # compute spatial adjacency matrix
    n = len(new_list)
    adj = sp.lil_matrix((n, n), dtype=np.int8)
    for i, ri in enumerate(new_list):
        pi = parents[i]
        for j, rj in enumerate(new_list[0:i]):
            pj = parents[j]
            
            if (ppp.adj[pi, pj] == 1) or (pi == pj):
                # account for shrinkage in adjacency check
                if pc.is_adjacent(ri, rj, 2*eps):  
                    adj[i, j] = 1
                    adj[j, i] = 1
        adj[i, i] = 1
            
    new_ppp = PropPreservingPartition(
        domain = ppp.domain,
        regions = new_list,
        adj = adj,
        prop_regions = ppp.prop_regions
    )
    return (new_ppp, subsys_list, parents)
예제 #12
0
def pwa_shrunk_partition(pwa_sys, ppp, eps, abs_tol=1e-5):
    """This function takes:
    
      - a piecewise affine system C{pwa_sys} and
      - a proposition-preserving partition C{ppp}
          whose domain is a subset of the domain of C{pwa_sys}
      - a shrinkage factor C{eps}
    
    and returns a *refined* proposition preserving partition
    where in each region a unique subsystem of pwa_sys is active
    and pwa_sys domains are shrunk to account for estimation 
    errors.

    See Also
    ========
    L{pwa_partition}
    
    @type pwa_sys: L{hybrid.PwaSysDyn}
    @type ppp: L{PropPreservingPartition}
    
    @return: new partition and associated maps:
        
        - new partition C{new_ppp}
        - map of C{new_ppp.regions} to C{pwa_sys.list_subsys}
        - map of C{new_ppp.regions} to C{ppp.regions}
    
    @rtype: C{(L{PropPreservingPartition}, list, list)}
    """

    new_list = []
    subsys_list = []
    parents = []
    for i, subsys in enumerate(pwa_sys.list_subsys):
        dom = shrinkPoly(subsys.domain, eps)
        for j, region in enumerate(ppp.regions):
            isect = pc.reduce(region.intersect(dom))

            if pc.is_fulldim(isect):
                rc, xc = pc.cheby_ball(isect)

                if rc < abs_tol:
                    msg = 'One of the regions in the refined PPP is '
                    msg += 'too small, this may cause numerical problems'
                    warnings.warn(msg)

                # not Region yet, but Polytope ?
                if len(isect) == 0:
                    isect = pc.Region([isect])

                # label with AP
                isect.props = region.props.copy()

                # store new Region
                new_list.append(isect)

                # keep track of original Region in ppp.regions
                parents.append(j)

                # index of subsystem active within isect
                subsys_list.append(i)

    # compute spatial adjacency matrix
    n = len(new_list)
    adj = sp.lil_matrix((n, n), dtype=np.int8)
    for i, ri in enumerate(new_list):
        pi = parents[i]
        for j, rj in enumerate(new_list[0:i]):
            pj = parents[j]

            if (ppp.adj[pi, pj] == 1) or (pi == pj):
                # account for shrinkage in adjacency check
                if pc.is_adjacent(ri, rj, 2 * eps):
                    adj[i, j] = 1
                    adj[j, i] = 1
        adj[i, i] = 1

    new_ppp = PropPreservingPartition(domain=ppp.domain,
                                      regions=new_list,
                                      adj=adj,
                                      prop_regions=ppp.prop_regions)
    return (new_ppp, subsys_list, parents)
예제 #13
0
def discretize(
    part, ssys, N=10, min_cell_volume=0.1,
    closed_loop=True, conservative=False,
    max_num_poly=5, use_all_horizon=False,
    trans_length=1, remove_trans=False,
    abs_tol=1e-7,
    plotit=False, save_img=False, cont_props=None,
    plot_every=1
):
    """Refine the partition and establish transitions
    based on reachability analysis.

    Reference
    =========
    U{[NOTM12]
    <https://tulip-control.sourceforge.io/doc/bibliography.html#notm12>}

    See Also
    ========
    L{prop2partition.pwa_partition}, L{prop2partition.part2convex}

    @param part: L{PropPreservingPartition} object
    @param ssys: L{LtiSysDyn} or L{PwaSysDyn} object
    @param N: horizon length
    @param min_cell_volume: the minimum volume of cells in the resulting
        partition.
    @param closed_loop: boolean indicating whether the `closed loop`
        algorithm should be used. default True.
    @param conservative: if true, force sequence in reachability analysis
        to stay inside starting cell. If false, safety
        is ensured by keeping the sequence inside a convexified
        version of the original proposition preserving cell.
    @param max_num_poly: maximum number of polytopes in a region to use in
        reachability analysis.
    @param use_all_horizon: in closed loop algorithm: if we should look
        for reachability also in less than N steps.
    @param trans_length: the number of polytopes allowed to cross in a
        transition.  a value of 1 checks transitions
        only between neighbors, a value of 2 checks
        neighbors of neighbors and so on.
    @param remove_trans: if True, remove found transitions between
        non-neighbors.
    @param abs_tol: maximum volume for an "empty" polytope

    @param plotit: plot partitioning as it evolves
    @type plotit: boolean,
        default = False

    @param save_img: save snapshots of partitioning to PDF files,
        requires plotit=True
    @type save_img: boolean,
        default = False

    @param cont_props: continuous propositions to plot
    @type cont_props: list of C{Polytope}

    @rtype: L{AbstractPwa}
    """
    start_time = os.times()[0]

    orig_ppp = part
    min_cell_volume = (min_cell_volume /np.finfo(np.double).eps
        *np.finfo(np.double).eps)

    ispwa = isinstance(ssys, PwaSysDyn)
    islti = isinstance(ssys, LtiSysDyn)

    if ispwa:
        (part, ppp2pwa, part2orig) = pwa_partition(ssys, part)
    else:
        part2orig = range(len(part))

    # Save original polytopes, require them to be convex
    if conservative:
        orig_list = None
        orig = [0]
    else:
        (part, new2old) = part2convex(part) # convexify
        part2orig = [part2orig[i] for i in new2old]

        # map new regions to pwa subsystems
        if ispwa:
            ppp2pwa = [ppp2pwa[i] for i in new2old]

        remove_trans = False # already allowed in nonconservative
        orig_list = []
        for poly in part:
            if len(poly) == 0:
                orig_list.append(poly.copy())
            elif len(poly) == 1:
                orig_list.append(poly[0].copy())
            else:
                raise Exception("discretize: "
                    "problem in convexification")
        orig = list(range(len(orig_list)))

    # Cheby radius of disturbance set
    # (defined within the loop for pwa systems)
    if islti:
        if len(ssys.E) > 0:
            rd = ssys.Wset.chebR
        else:
            rd = 0.

    # Initialize matrix for pairs to check
    IJ = part.adj.copy()
    IJ = IJ.todense()
    IJ = np.array(IJ)
    logger.debug("\n Starting IJ: \n" + str(IJ) )

    # next line omitted in discretize_overlap
    IJ = reachable_within(trans_length, IJ,
                          np.array(part.adj.todense()) )

    # Initialize output
    num_regions = len(part)
    transitions = np.zeros(
        [num_regions, num_regions],
        dtype = int
    )
    sol = deepcopy(part.regions)
    adj = part.adj.copy()
    adj = adj.todense()
    adj = np.array(adj)

    # next 2 lines omitted in discretize_overlap
    if ispwa:
        subsys_list = list(ppp2pwa)
    else:
        subsys_list = None
    ss = ssys

    # init graphics
    if plotit:
        try:
            import matplotlib.pyplot as plt

            plt.ion()
            fig, (ax1, ax2) = plt.subplots(1, 2)
            ax1.axis('scaled')
            ax2.axis('scaled')
            file_extension = 'pdf'
        except:
            logger.error('failed to import matplotlib')
            plt = None
    else:
    	plt = None

    iter_count = 0

    # List of how many "new" regions
    # have been created for each region
    # and a list of original number of neighbors
    #num_new_reg = np.zeros(len(orig_list))
    #num_orig_neigh = np.sum(adj, axis=1).flatten() - 1

    progress = list()

    # Do the abstraction
    while np.sum(IJ) > 0:
        ind = np.nonzero(IJ)
        # i,j swapped in discretize_overlap
        i = ind[1][0]
        j = ind[0][0]
        IJ[j, i] = 0
        si = sol[i]
        sj = sol[j]

        si_tmp = deepcopy(si)
        sj_tmp = deepcopy(sj)

        #num_new_reg[i] += 1
        #print(num_new_reg)

        if ispwa:
            ss = ssys.list_subsys[subsys_list[i]]
            if len(ss.E) > 0:
                rd, xd = pc.cheby_ball(ss.Wset)
            else:
                rd = 0.

        if conservative:
            # Don't use trans_set
            trans_set = None
        else:
            # Use original cell as trans_set
            trans_set = orig_list[orig[i]]

        S0 = solve_feasible(
            si, sj, ss, N, closed_loop,
            use_all_horizon, trans_set, max_num_poly
        )

        msg = '\n Working with partition cells: ' + str(i) + ', ' + str(j)
        logger.info(msg)

        msg = '\t' + str(i) +' (#polytopes = ' +str(len(si) ) +'), and:\n'
        msg += '\t' + str(j) +' (#polytopes = ' +str(len(sj) ) +')\n'

        if ispwa:
            msg += '\t with active subsystem: '
            msg += str(subsys_list[i]) + '\n'

        msg += '\t Computed reachable set S0 with volume: '
        msg += str(S0.volume) + '\n'

        logger.debug(msg)

        #logger.debug('si \cap s0')
        isect = si.intersect(S0)
        vol1 = isect.volume
        risect, xi = pc.cheby_ball(isect)

        #logger.debug('si \ s0')
        diff = si.diff(S0)
        vol2 = diff.volume
        rdiff, xd = pc.cheby_ball(diff)

        # if pc.is_fulldim(pc.Region([isect]).intersect(diff)):
        #     logging.getLogger('tulip.polytope').setLevel(logging.DEBUG)
        #     diff = pc.mldivide(si, S0, save=True)
        #
        #     ax = S0.plot()
        #     ax.axis([0.0, 1.0, 0.0, 2.0])
        #     ax.figure.savefig('./img/s0.pdf')
        #
        #     ax = si.plot()
        #     ax.axis([0.0, 1.0, 0.0, 2.0])
        #     ax.figure.savefig('./img/si.pdf')
        #
        #     ax = isect.plot()
        #     ax.axis([0.0, 1.0, 0.0, 2.0])
        #     ax.figure.savefig('./img/isect.pdf')
        #
        #     ax = diff.plot()
        #     ax.axis([0.0, 1.0, 0.0, 2.0])
        #     ax.figure.savefig('./img/diff.pdf')
        #
        #     ax = isect.intersect(diff).plot()
        #     ax.axis([0.0, 1.0, 0.0, 2.0])
        #     ax.figure.savefig('./img/diff_cap_isect.pdf')
        #
        #     logger.error('Intersection \cap Difference != \emptyset')
        #
        #     assert(False)

        if vol1 <= min_cell_volume:
            logger.warning('\t too small: si \cap Pre(sj), '
                           'so discard intersection')
        if vol1 <= min_cell_volume and isect:
            logger.warning('\t discarded non-empty intersection: '
                           'consider reducing min_cell_volume')
        if vol2 <= min_cell_volume:
            logger.warning('\t too small: si \ Pre(sj), so not reached it')

        # We don't want our partitions to be smaller than the disturbance set
        # Could be a problem since cheby radius is calculated for smallest
        # convex polytope, so if we have a region we might throw away a good
        # cell.
        if (vol1 > min_cell_volume) and (risect > rd) and \
           (vol2 > min_cell_volume) and (rdiff > rd):

            # Make sure new areas are Regions and add proposition lists
            if len(isect) == 0:
                isect = pc.Region([isect], si.props)
            else:
                isect.props = si.props.copy()

            if len(diff) == 0:
                diff = pc.Region([diff], si.props)
            else:
                diff.props = si.props.copy()

            # replace si by intersection (single state)
            isect_list = pc.separate(isect)
            sol[i] = isect_list[0]

            # cut difference into connected pieces
            difflist = pc.separate(diff)

            difflist += isect_list[1:]
            n_isect = len(isect_list) -1

            num_new = len(difflist)

            # add each piece, as a new state
            for region in difflist:
                sol.append(region)

                # keep track of PWA subsystems map to new states
                if ispwa:
                    subsys_list.append(subsys_list[i])
            n_cells = len(sol)
            new_idx = range(n_cells-1, n_cells-num_new-1, -1)

            """Update transition matrix"""
            transitions = np.pad(transitions, (0,num_new), 'constant')

            transitions[i, :] = np.zeros(n_cells)
            for r in new_idx:
                #transitions[:, r] = transitions[:, i]
                # All sets reachable from start are reachable from both part's
                # except possibly the new part
                transitions[i, r] = 0
                transitions[j, r] = 0

            # sol[j] is reachable from intersection of sol[i] and S0
            if i != j:
                transitions[j, i] = 1

                # sol[j] is reachable from each piece os S0 \cap sol[i]
                #for k in range(n_cells-n_isect-2, n_cells):
                #    transitions[j, k] = 1

            """Update adjacency matrix"""
            old_adj = np.nonzero(adj[i, :])[0]

            # reset new adjacencies
            adj[i, :] = np.zeros([n_cells -num_new])
            adj[:, i] = np.zeros([n_cells -num_new])
            adj[i, i] = 1

            adj = np.pad(adj, (0, num_new), 'constant')

            for r in new_idx:
                adj[i, r] = 1
                adj[r, i] = 1
                adj[r, r] = 1

                if not conservative:
                    orig = np.hstack([orig, orig[i]])

            # adjacencies between pieces of isect and diff
            for r in new_idx:
                for k in new_idx:
                    if r is k:
                        continue

                    if pc.is_adjacent(sol[r], sol[k]):
                        adj[r, k] = 1
                        adj[k, r] = 1

            msg = ''
            if logger.getEffectiveLevel() <= logging.DEBUG:
                msg += '\t\n Adding states ' + str(i) + ' and '
                for r in new_idx:
                    msg += str(r) + ' and '
                msg += '\n'
                logger.debug(msg)

            for k in np.setdiff1d(old_adj, [i,n_cells-1]):
                # Every "old" neighbor must be the neighbor
                # of at least one of the new
                if pc.is_adjacent(sol[i], sol[k]):
                    adj[i, k] = 1
                    adj[k, i] = 1
                elif remove_trans and (trans_length == 1):
                    # Actively remove transitions between non-neighbors
                    transitions[i, k] = 0
                    transitions[k, i] = 0

                for r in new_idx:
                    if pc.is_adjacent(sol[r], sol[k]):
                        adj[r, k] = 1
                        adj[k, r] = 1
                    elif remove_trans and (trans_length == 1):
                        # Actively remove transitions between non-neighbors
                        transitions[r, k] = 0
                        transitions[k, r] = 0

            """Update IJ matrix"""
            IJ = np.pad(IJ, (0,num_new), 'constant')
            adj_k = reachable_within(trans_length, adj, adj)
            sym_adj_change(IJ, adj_k, transitions, i)

            for r in new_idx:
                sym_adj_change(IJ, adj_k, transitions, r)

            if logger.getEffectiveLevel() <= logging.DEBUG:
                msg = '\n\n Updated adj: \n' + str(adj)
                msg += '\n\n Updated trans: \n' + str(transitions)
                msg += '\n\n Updated IJ: \n' + str(IJ)
                logger.debug(msg)

            logger.info('Divided region: ' + str(i) + '\n')
        elif vol2 < abs_tol:
            logger.info('Found: ' + str(i) + ' ---> ' + str(j) + '\n')
            transitions[j,i] = 1
        else:
            if logger.level <= logging.DEBUG:
                msg = '\t Unreachable: ' + str(i) + ' --X--> ' + str(j) + '\n'
                msg += '\t\t diff vol: ' + str(vol2) + '\n'
                msg += '\t\t intersect vol: ' + str(vol1) + '\n'
                logger.debug(msg)
            else:
                logger.info('\t unreachable\n')
            transitions[j,i] = 0

        # check to avoid overlapping Regions
        if debug:
            tmp_part = PropPreservingPartition(
                domain=part.domain,
                regions=sol, adj=sp.lil_matrix(adj),
                prop_regions=part.prop_regions
            )
            assert(tmp_part.is_partition() )

        n_cells = len(sol)
        progress_ratio = 1 - float(np.sum(IJ) ) /n_cells**2
        progress += [progress_ratio]

        msg = '\t total # polytopes: ' + str(n_cells) + '\n'
        msg += '\t progress ratio: ' + str(progress_ratio) + '\n'
        logger.info(msg)

        iter_count += 1

        # no plotting ?
        if not plotit:
            continue
        if plt is None or plot_partition is None:
            continue
        if iter_count % plot_every != 0:
            continue

        tmp_part = PropPreservingPartition(
            domain=part.domain,
            regions=sol, adj=sp.lil_matrix(adj),
            prop_regions=part.prop_regions
        )

        # plot pair under reachability check
        ax2.clear()
        si_tmp.plot(ax=ax2, color='green')
        sj_tmp.plot(ax2, color='red', hatch='o', alpha=0.5)
        plot_transition_arrow(si_tmp, sj_tmp, ax2)

        S0.plot(ax2, color='none', hatch='/', alpha=0.3)
        fig.canvas.draw()

        # plot partition
        ax1.clear()
        plot_partition(tmp_part, transitions.T, ax=ax1, color_seed=23)

        # plot dynamics
        ssys.plot(ax1, show_domain=False)

        # plot hatched continuous propositions
        part.plot_props(ax1)

        fig.canvas.draw()

        # scale view based on domain,
        # not only the current polytopes si, sj
        l,u = part.domain.bounding_box
        ax2.set_xlim(l[0,0], u[0,0])
        ax2.set_ylim(l[1,0], u[1,0])

        if save_img:
            fname = 'movie' +str(iter_count).zfill(3)
            fname += '.' + file_extension
            fig.savefig(fname, dpi=250)
        plt.pause(1)

    new_part = PropPreservingPartition(
        domain=part.domain,
        regions=sol, adj=sp.lil_matrix(adj),
        prop_regions=part.prop_regions
    )

    # check completeness of adjacency matrix
    if debug:
        tmp_part = deepcopy(new_part)
        tmp_part.compute_adj()

    # Generate transition system and add transitions
    ofts = trs.FTS()

    adj = sp.lil_matrix(transitions.T)
    n = adj.shape[0]
    ofts_states = range(n)

    ofts.states.add_from(ofts_states)

    ofts.transitions.add_adj(adj, ofts_states)

    # Decorate TS with state labels
    atomic_propositions = set(part.prop_regions)
    ofts.atomic_propositions.add_from(atomic_propositions)
    for state, region in zip(ofts_states, sol):
        state_prop = region.props.copy()
        ofts.states.add(state, ap=state_prop)

    param = {
        'N':N,
        'trans_length':trans_length,
        'closed_loop':closed_loop,
        'conservative':conservative,
        'use_all_horizon':use_all_horizon,
        'min_cell_volume':min_cell_volume,
        'max_num_poly':max_num_poly
    }

    ppp2orig = [part2orig[x] for x in orig]

    end_time = os.times()[0]
    msg = 'Total abstraction time: ' +\
          str(end_time - start_time) + '[sec]'
    print(msg)
    logger.info(msg)

    if save_img and plt is not None:
        fig, ax = plt.subplots(1, 1)
        plt.plot(progress)
        ax.set_xlabel('iteration')
        ax.set_ylabel('progress ratio')
        ax.figure.savefig('progress.pdf')

    return AbstractPwa(
        ppp=new_part,
        ts=ofts,
        ppp2ts=ofts_states,
        pwa=ssys,
        pwa_ppp=part,
        ppp2pwa=orig,
        ppp2sys=subsys_list,
        orig_ppp=orig_ppp,
        ppp2orig=ppp2orig,
        disc_params=param
    )
예제 #14
0
def merge_partitions(abstractions):
    """Merge multiple abstractions.

    @param abstractions: keyed by mode
    @type abstractions: dict of L{AbstractPwa}

    @return: (merged_abstraction, ap_labeling)
        where:
            - merged_abstraction: L{AbstractSwitched}
            - ap_labeling: dict
    """
    if len(abstractions) == 0:
        warnings.warn('Abstractions empty, nothing to merge.')
        return

    # consistency check
    for ab1 in abstractions.values():
        for ab2 in abstractions.values():
            p1 = ab1.ppp
            p2 = ab2.ppp

            if p1.prop_regions != p2.prop_regions:
                msg = 'merge: partitions have different sets '
                msg += 'of continuous propositions'
                raise Exception(msg)

            if not (p1.domain.A == p2.domain.A).all() or \
            not (p1.domain.b == p2.domain.b).all():
                raise Exception('merge: partitions have different domains')

            # check equality of original PPP partitions
            if ab1.orig_ppp == ab2.orig_ppp:
                logger.info('original partitions happen to be equal')

    init_mode = list(abstractions.keys())[0]
    all_modes = set(abstractions)
    remaining_modes = all_modes.difference(set([init_mode]))

    print('init mode: ' + str(init_mode))
    print('all modes: ' + str(all_modes))
    print('remaining modes: ' + str(remaining_modes))

    # initialize iteration data
    prev_modes = [init_mode]

   	# Create a list of merged-together regions
    ab0 = abstractions[init_mode]
    regions = list(ab0.ppp)
    parents = {init_mode:list(range(len(regions) ))}
    ap_labeling = {i:reg.props for i,reg in enumerate(regions)}
    for cur_mode in remaining_modes:
        ab2 = abstractions[cur_mode]
        r = merge_partition_pair(
            regions, ab2, cur_mode, prev_modes,
            parents, ap_labeling
        )
        regions, parents, ap_labeling = r
        prev_modes += [cur_mode]
    new_list = regions

    # build adjacency based on spatial adjacencies of
    # component abstractions.
    # which justifies the assumed symmetry of part1.adj, part2.adj
	# Basically, if two regions are either 1) part of the same region in one of
	# the abstractions or 2) adjacent in one of the abstractions, then the two
	# regions are adjacent in the switched dynamics.
    n_reg = len(new_list)

    adj = np.zeros([n_reg, n_reg], dtype=int)
    for i, reg_i in enumerate(new_list):
        for j, reg_j in enumerate(new_list[0:i]):
            touching = False
            for mode in abstractions:
                pi = parents[mode][i]
                pj = parents[mode][j]

                part = abstractions[mode].ppp

                if (part.adj[pi, pj] == 1) or (pi == pj):
                    touching = True
                    break

            if not touching:
                continue

            if pc.is_adjacent(reg_i, reg_j):
                adj[i,j] = 1
                adj[j,i] = 1
        adj[i,i] = 1

    ppp = PropPreservingPartition(
        domain=ab0.ppp.domain,
        regions=new_list,
        prop_regions=ab0.ppp.prop_regions,
        adj=adj
    )

    abstraction = AbstractSwitched(
        ppp=ppp,
        modes=abstractions,
        ppp2modes=parents,
    )

    return (abstraction, ap_labeling)