Python prime-field tutorial

This tutorial shows how to use the Python API over a prime field F_p. The current implementation supports prime moduli such as 2, 3, 5, and 7. Composite rings such as Z_4 or Z_6 are rejected because the barcode reducers assume field coefficients.

The same public objects are used as in the Z2 path:

  • FilteredComplex stores the filtered simplicial complex.

  • MorseSequence stores critical simplices and regular pairs.

  • compute_reference_map_modp computes the Morse reference map over F_p.

  • compute_coreference_map_modp computes the dual coreference map over F_p.

  • compute_morse_persistence_modp reduces the Morse complex from references.

  • compute_morse_coreference_persistence_modp reduces it from coreferences.

  • compute_standard_persistence(..., modulus=p) is the full-complex oracle.

Run the example

From the workspace root:

python3 morseframes/python/examples/prime_field_tutorial.py --modulus 3

To try another Morse sequence strategy:

python3 morseframes/python/examples/prime_field_tutorial.py \
  --modulus 5 \
  --algorithm f-max

The example constructs a small lower-star filtered complex with two triangles and a genuine plateau:

import morseframes as mp

complex_ = mp.FilteredComplex.from_lower_star(
    [(0, 1, 2), (0, 2, 3)],
    {0: 1.0, 1: 0.0, 2: 1.0, 3: 0.0},
)

A Morse sequence is independent of the coefficient field:

sequence = mp.compute_morse_sequence(
    complex_,
    algorithm=mp.SAME_LEVEL_REDUCTION_SEQUENCE,
)

The coefficient field enters when the reference and coreference maps are built. For odd primes, annotations carry signed coefficients modulo p:

references = mp.compute_reference_map_modp(
    complex_,
    sequence,
    modulus=3,
)

coreferences = mp.compute_coreference_map_modp(
    complex_,
    sequence,
    modulus=3,
)

Those maps can be passed directly to the Morse reducers:

morse = mp.compute_morse_persistence_modp(
    complex_,
    sequence=sequence,
    references=references,
    modulus=3,
)

dual_morse = mp.compute_morse_coreference_persistence_modp(
    complex_,
    sequence=sequence,
    coreferences=coreferences,
    modulus=3,
)

The standard reducer uses the same modulus keyword:

standard = mp.compute_standard_persistence(complex_, modulus=3)

assert morse.finite_barcode() == standard.finite_barcode()
assert morse.essential_barcode() == standard.essential_barcode()
assert dual_morse.finite_barcode() == standard.finite_barcode()
assert dual_morse.essential_barcode() == standard.essential_barcode()

By default, finite_barcode() omits zero-length intervals. This is the right comparison for persistence; zero-length plateau cancellations can differ between the full complex and the Morse-reduced complex. Use finite_barcode(include_zero=True) only when inspecting implementation-level tie-breaking.

Working with annotations

The Z2 reference map is a tuple of critical ids. The F_p map is a tuple of (critical_id, coefficient) pairs:

for simplex_id, annotation in enumerate(references):
    if annotation:
        simplex = complex_.vertices(simplex_id)
        print(simplex, annotation)

To display the critical ids as simplices, index through sequence.critical_simplices:

def annotation_as_simplices(annotation):
    return tuple(
        (complex_.vertices(sequence.critical_simplices[critical_id]), coefficient)
        for critical_id, coefficient in annotation
    )

Coefficients are stored in the range 1..p-1; for example, the coefficient 2 in F_3 represents -1.

Convenience entry points

If you do not need to inspect the maps, the generic persistence functions are usually enough:

morse = mp.compute_morse_persistence(
    complex_,
    algorithm="f-max",
    modulus=3,
)

dual_morse = mp.compute_morse_coreference_persistence(
    complex_,
    algorithm="f-max",
    modulus=3,
)

The explicit _modp names are useful when you want code to make the coefficient field visible:

standard = mp.compute_standard_persistence_modp(complex_, 3)

Current scope

This is prime-field persistence, not general Z_n persistence. In particular:

  • modulus=2 gives the usual Z2 behavior.

  • Odd prime moduli use oriented boundary and coboundary coefficients.

  • Composite moduli raise ValueError.

  • The Python prototype exposes this functionality now; the current GUDHI-facing patch is intentionally kept at Z2 for a smaller first contribution.