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
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
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
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
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
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
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
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])