예제 #1
0
def morse_reduce_nanda(morse_complex, return_matching=False):
    """ Finds an acyclic matching and reduces on the way

    Implements [MN13] p. 344 MorseReduce verbatim

    :param morse_complex: A morse complex
    :param return_matching: Return a tuple of the reduced complex and the matching used to reduce it
    :return: Pair of a reduced morse complex and the matches used to reduce it.
    The latter is returned for testing reasons.
    """
    morse_complex = morse_complex.sort_by_filtration()
    gradients = np.full((morse_complex.size, morse_complex.size), False)
    akq = np.zeros(
        morse_complex.size)  # Array to mark cells as aces and k-q matches
    matching = Matching(morse_complex)
    match_counter = 0
    for filtr_val in np.unique(morse_complex.filtration):
        cell_ixs = np.where(morse_complex.filtration == filtr_val)[0]
        unreduced_cell_ctr = len(cell_ixs)
        queue = list()
        while unreduced_cell_ctr > 0:
            ace_ix = cell_ixs[np.where(
                akq[cell_ixs] == 0)[0][0]]  # np.where returns tuple
            akq[ace_ix] = -1  # Mark the cell as ace, prior to update_gradients
            gradients = update_gradients(morse_complex, gradients, akq, ace_ix)
            unreduced_cell_ctr -= 1
            queue.extend(unreduced_coboundary(morse_complex, akq, ace_ix))
            while queue:
                cell_ix = queue.pop(0)
                if cell_ix not in cell_ixs:
                    continue
                unreduced_cell_bd = unreduced_boundary(morse_complex, akq,
                                                       cell_ix)
                if len(unreduced_cell_bd) == 0:
                    queue.extend(
                        unreduced_coboundary(morse_complex, akq, cell_ix))
                elif len(unreduced_cell_bd) == 1:
                    match_counter += 1  # Found a match, cell_ix is in K
                    q_ix = unreduced_cell_bd[0]
                    matching.append((q_ix, cell_ix))
                    akq[cell_ix] = match_counter
                    queue.extend(unreduced_coboundary(morse_complex, akq,
                                                      q_ix))
                    if morse_complex.cell_dimensions[
                            q_ix] == morse_complex.cell_dimensions[ace_ix]:
                        gradients[:, q_ix] = gradients[:, cell_ix]
                        gradients = update_gradients(morse_complex, gradients,
                                                     akq, q_ix)
                    akq[q_ix] = match_counter
                    unreduced_cell_ctr -= 2
    ace_ixs = np.where(akq == -1)[0]
    reduced_cplx = morse_complex.copy(boundary_matrix=gradients)[ace_ixs]
    if return_matching:
        return reduced_cplx, matching
    return reduced_cplx
예제 #2
0
def test_find_discretization_on_real_acyclic_matching(matches, filtration,
                                                      delta, expected_matches):
    from dmt.matching import Matching
    from dmt.binning import find_discretization
    from dmt.binning import ceil
    matching = Matching(matches=matches)
    bins = find_discretization(matching, filtration, delta)
    ceiled_filtration = ceil(filtration, bins)
    assert np.all(ceiled_filtration >= filtration)
    assert ceiled_filtration.delta <= delta
    assert len(
        matching.induce_filtered_matching(ceil(
            filtration, bins)).matches) == expected_matches
예제 #3
0
def test_find_discretization_is_good():
    from dmt.matching import Matching
    from dmt.binning import find_discretization
    from dmt.binning import ceil
    acyclic_matching = Matching(matches=[(0, 1), (2, 3), (4, 5)])
    filtration = np.array([0, 1, 1, 2, 1, 2])
    delta = 1.2
    bins = find_discretization(acyclic_matching, filtration, delta)
    # For a good binning of the given matching the following should hold
    assert len(bins) >= 2
    assert bins[0] <= 1
    assert bins[-1] >= 2
    assert len(
        acyclic_matching.induce_filtered_matching(ceil(filtration,
                                                       bins)).matches) == 2
예제 #4
0
def construct_acyclic_matching(morse_complex, delta=np.inf):
    """ Finds an acyclic matching

    Inspired by [MN13] p. 344 MorseReduce

    :param morse_complex: A morse complex
    :param delta: Only construct matches with a filtration difference smaller than delta
    :return: Matching on morse_complex
    """
    morse_complex = morse_complex.copy().sort_by_filtration()
    reduced = np.full(morse_complex.size,
                      False)  # Array to mark cells as reduced
    matching = Matching(morse_complex)
    while not np.all(reduced):
        ace_ix = new_ace_ix(reduced)
        queue = unreduced_coboundary(morse_complex, reduced, ace_ix).tolist()
        while queue:
            cell_ix = queue.pop(0)
            unreduced_cell_bd = unreduced_boundary(morse_complex, reduced,
                                                   cell_ix)
            if len(unreduced_cell_bd) == 0:
                queue.extend(
                    unreduced_coboundary(morse_complex, reduced, cell_ix))
            elif len(unreduced_cell_bd) == 1:
                q_ix = unreduced_cell_bd[0]
                if morse_complex.filtration[
                        cell_ix] - morse_complex.filtration[q_ix] > delta:
                    continue
                add_matching((q_ix, cell_ix), matching, reduced)
                queue.extend(unreduced_coboundary(morse_complex, reduced,
                                                  q_ix))
    return matching
예제 #5
0
def test_get_ace_ixs():
    from dmt.morse_complex import MorseComplex
    from dmt.matching import Matching
    qk = np.random.choice(range(100), 10, replace=False)
    morse_complex = MorseComplex(boundary_matrix=np.empty((100, 100)),
                                 cell_dimensions=np.empty(100))
    matching = Matching(morse_complex=morse_complex,
                        matches=list(
                            zip(qk[range(0, 10, 2)], qk[range(1, 10, 2)])))
    assert len(matching.matches) == 5
    assert len(matching.unmatched_ixs) == 90
예제 #6
0
def concentric_annuli_matching(n=3):
    max_filtration = n + 1
    points, boundary_matrix, cell_dimensions, filtration, base, matches = central_square(
        max_filtration)
    for i in range(n):
        points, boundary_matrix, cell_dimensions, filtration, base, matches = add_annulus(
            points, boundary_matrix, cell_dimensions, filtration, base,
            matches)
    gradient_advantage_cplx = MorseComplex(boundary_matrix=boundary_matrix,
                                           cell_dimensions=cell_dimensions,
                                           filtration=filtration,
                                           points=points,
                                           sort_by_filtration=False)
    assert gradient_advantage_cplx.valid_boundary()
    matching = Matching(morse_complex=gradient_advantage_cplx, matches=matches)
    return matching
예제 #7
0
def construct_acyclic_matching_along_gradients(morse_complex, delta=np.inf):
    """ Finds an acyclic matching in filtration order along gradients

    Inspired by [MN13] p. 344 MorseReduce

    :param morse_complex: A morse complex
    :param delta: Only construct matches with a filtration difference smaller than delta
    :return: Matching on morse_complex with approximate filtration.
    The approximate filtration will be less or equal than the old filtration.
    """
    new_filtration = ApproxFiltration(morse_complex.filtration.copy(),
                                      exact=morse_complex.filtration)
    morse_complex = morse_complex.copy(
        filtration=new_filtration).sort_by_filtration()
    matching = Matching(morse_complex)
    reduced = np.full(morse_complex.size,
                      False)  # Array to mark cells as reduced
    while not np.all(reduced):
        grow_gradient_path(matching, reduced, delta)
    return matching
예제 #8
0
def test_reduce_by_acyclic_matching_gradient_path_multiplicity(reduction_algo):
    """ Minimal example where there is a gradient path multiplicity of two

    Four triangles like this that are collapsed from left to right, with only the leftmost cell remaining
      ______
     / | >  \
    .  >  |––|
     \_|_>__/
    """
    from dmt import MorseComplex
    from dmt.matching import Matching
    from dmt.dmt import reduce_by_acyclic_matching, reduce_by_acyclic_matching_dense
    from dmt.pers_hom import filter_zero_persistence_classes
    algo_map = {"sparse": reduce_by_acyclic_matching,
                "dense": reduce_by_acyclic_matching_dense}
    edges = np.array([[1, 1, 0, 1, 0, 0, 1, 0],
                      [1, 0, 1, 0, 0, 1, 0, 1],
                      [0, 1, 1, 0, 1, 0, 0, 0],
                      [0, 0, 0, 1, 1, 1, 0, 0],
                      [0, 0, 0, 0, 0, 0, 1, 1]])
    triangles = np.array([[1, 1, 0, 0],
                          [0, 1, 1, 0],
                          [0, 1, 0, 1],
                          [0, 0, 1, 0],
                          [0, 0, 1, 1],
                          [0, 0, 0, 1],
                          [1, 0, 0, 0],
                          [1, 0, 0, 0]])
    boundary = np.block([
        [np.zeros((edges.shape[0], edges.shape[0])), edges, np.zeros((edges.shape[0], triangles.shape[1]))],
        [np.zeros((triangles.shape[0], edges.shape[0])), np.zeros((triangles.shape[0], edges.shape[1])), triangles],
        [np.zeros((triangles.shape[1], 17))]])
    cell_dimensions = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2]
    filtration =      [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1]
    cplx = MorseComplex(boundary.astype(bool), cell_dimensions, filtration=filtration, sort_by_filtration=False)
    matching = Matching(cplx, matches=[(5, 14), (6, 15), (7, 16)])
    """
matrix([[0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

    """
    assert cplx.valid_boundary()

    reduced_cplx = algo_map[reduction_algo](matching)
    assert reduced_cplx.size < cplx.size
    assert reduced_cplx.valid_boundary()
    dgm = filter_zero_persistence_classes(cplx.persistence_diagram())
    reduced_dgm = filter_zero_persistence_classes(reduced_cplx.persistence_diagram())
    for i in [0, 1]:
        assert np.all(dgm[i] == reduced_dgm[i])