<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def find_neighbor_sites(sites, am, flatten=True, include_input=False, logic='or'):
 r""" Given a symmetric adjacency matrix, finds all sites that are connected to the input sites. Parameters am : scipy.sparse matrix The adjacency matrix of the network. Must be symmetrical such that if sites *i* and *j* are connected, the matrix contains non-zero values at locations (i, j) and (j, i). flatten : boolean If ``True`` (default) the returned result is a compressed array of all neighbors, or a list of lists with each sub-list containing the neighbors for each input site. Note that an *unflattened* list might be slow to generate since it is a Python ``list`` rather than a Numpy array. include_input : boolean If ``False`` (default) the input sites will be removed from the result. logic : string Specifies logic to filter the resulting list. Options are: **'or'** : (default) All neighbors of the input sites. This is also known as the 'union' in set theory or 'any' in boolean logic. Both keywords are accepted and treated as 'or'. **'xor'** : Only neighbors of one and only one input site. This is useful for finding the sites that are not shared by any of the input sites. 'exclusive_or' is also accepted. **'xnor'** : Neighbors that are shared by two or more input sites. This is equivalent to finding all neighbors with 'or', minus those found with 'xor', and is useful for finding neighbors that the inputs have in common. 'nxor' is also accepted. **'and'** : Only neighbors shared by all input sites. This is also known as 'intersection' in set theory and (somtimes) as 'all' in boolean logic. Both keywords are accepted and treated as 'and'. Returns ------- An array containing the neighboring sites filtered by the given logic. If ``flatten`` is ``False`` then the result is a list of lists containing the neighbors of each input site. See Also -------- find_complement Notes ----- The ``logic`` options are applied to neighboring sites only, thus it is not possible to obtain sites that are part of the global set but not neighbors. This is because (a) the list global sites might be very large, and (b) it is not possible to return a list of neighbors for each input site if global sites are considered. """

    if am.format != 'lil':
        am = am.tolil(copy=False)
    n_sites = am.shape[0]
    rows = [am.rows[i] for i in sp.array(sites, ndmin=1)]
    if len(rows) == 0:
        return []
    neighbors = sp.hstack(rows).astype(sp.int64)  # Flatten list to apply logic
    if logic in ['or', 'union', 'any']:
        neighbors = sp.unique(neighbors)
    elif logic in ['xor', 'exclusive_or']:
        neighbors = sp.unique(sp.where(sp.bincount(neighbors) == 1)[0])
    elif logic in ['xnor', 'nxor']:
        neighbors = sp.unique(sp.where(sp.bincount(neighbors) > 1)[0])
    elif logic in ['and', 'all', 'intersection']:
        neighbors = set(neighbors)
        [neighbors.intersection_update(i) for i in rows]
        neighbors = sp.array(list(neighbors), dtype=sp.int64, ndmin=1)
    else:
        raise Exception('Specified logic is not implemented')
    # Deal with removing inputs or not
    mask = sp.zeros(shape=n_sites, dtype=bool)
    mask[neighbors] = True
    if not include_input:
        mask[sites] = False
    # Finally flatten or not
    if flatten:
        neighbors = sp.where(mask)[0]
    else:
        if (neighbors.size > 0):
            for i in range(len(rows)):
                vals = sp.array(rows[i], dtype=sp.int64)
                rows[i] = vals[mask[vals]]
            neighbors = rows
        else:
            neighbors = [sp.array([], dtype=int) for i in range(len(sites))]
    return neighbors