Package: grpphati
GrPPHATIis aGroundedPipeline LoPHATImplementation.Overview•arXiv•Google ColabOverviewThe grounded pipeline, introduced in[1], is a method for building stable, topological descriptors of weighted digraphs.
GrPPHATI is a Python library for implementing descriptors derived from this pipeline.
In particular, GrPPHATI provides default, optimised implementations of grounded persistent path homology (GrPPH) and grounded persistent directed flag complex homology (GrPdFlH).
GrPPHATI builds the boundary matrix in Python, converts it into a sparse format and then employsLoPHATto perform the persistence calculation in Rust.NoteDue to its focus on GrPPH, this library only computes homology in degree 1.InstallationTo get started straight away, save a copy ofthis Google Colab notebookto your drive.To install, simply runpipinstallgrpphatiBasic UsageThe quickest way to get started is to use the default implementation of GrPPH or GrPdFlH.
All descriptors implemented ingrpphatiexpect anetworkx.DiGraphas input with the edge-weights stored in theweightattribute.# examples/readme_1.pyfromgrpphati.pipelines.groundedimportGrPPH,GrPdFlHimportnetworkxasnxG=nx.DiGraph()G.add_edges_from([(0,1),(0,2),(1,3),(2,3)],weight=3)grpph_bar=GrPPH(G).barcodegrpdflh_bar=GrPdFlH(G).barcodeprint(grpph_bar)print(grpdflh_bar)$ python example/readme_1.py
[[0, 3]]
[[0, 6]]OptimisationsBy default, all pipelines parallelise the computation over weakly connected components.In addition,GrPPHiteratively removes appendage edges before starting the computation.
The pipelineGrPdFlHdoes not use this optimisation as it may result in a different barcode (e.g.[Example A.13, 1]).Thanks to[Theorem 4.21, 1], it is possible to parallelise the computation of GrPPH over wedge components.
The pipelineGrPPH_par_wedgeimplements this optimisation.
If you expect that your input has a wedge decomposition, it is highly recommended to use this pipeline since it (a) parallelises the computation and (b) significantly reduces the number of allowed 2-paths.
For example, the following script computes the GrPPH of the wedge of two complete digraphs, each on 50 nodes.
The output is from a laptop with a Ryzen 5 3500U @2.1GHz.# examples/readme_2.pyimportnetworkxasnximporttimefromgrpphati.pipelines.groundedimport(GrPPH,GrPPH_par_wedge,)deftimed(f):tic=time.time()output=f()toc=time.time()elapsed=toc-ticreturn(output,elapsed)N=50G_1=nx.relabel_nodes(nx.complete_graph(N,create_using=nx.DiGraph),lambdax:(x,1)ifx>0elsex)G_2=nx.relabel_nodes(nx.complete_graph(N,create_using=nx.DiGraph),lambdax:(x,2)ifx>0elsex)G_wedge=nx.compose(G_1,G_2)print(f"{G_wedge.number_of_nodes()}nodes in wedge graph")(out,elap)=timed(lambda:GrPPH(G_wedge))print("Serial:")print(f"Size of barcode ={len(out.barcode)}")print(f"Time elapsed ={elap}s")print("Parallel over wedges:")(out,elap)=timed(lambda:GrPPH_par_wedge(G_wedge))print(f"Size of barcode ={len(out.barcode)}")print(f"Time elapsed ={elap}s")$ python examples/readme_2.py
99 nodes in wedge graph
Serial:
Size of barcode = 4802
Time elapsed = 10.496055603027344s
Parallel over wedges:
Size of barcode = 4802
Time elapsed = 2.702505111694336sStandard pipelinesFor ease of comparison, we also provide implementations of the standard pipelines ingrpphati.pipelines.standard.
The pipelinePPHcombines path homology with the shortest-path filtration (PPH_par_wedgeparallelises over wedges).
The pipelinePdFlHcombines directed flag complex homology with the shortest-path filtration.grpphati_rsIf you are computing GrPPH on large networks, consider usinggrpphati_rs, which computes the basis of the boundary matrix in parallel, in Rust.Advanced UsageGrPPHATI is in fact a library for implementing instances of the grounded pipeline.
The main interface for is the functionmake_grounded_pipelinein thegrpphati.pipelines.groundedmodule.
The required arguments a filtration map and a homology class, which are explained further in the following sections.
Additionally, you can specify the PH backend and provide an optimisation strategy.
The function returns a 'pipeline' which accepts anx.DiGraphand returns the barcode.The functionmake_standard_pipelineingrpphati.pipelines.standardhas the same interface and returns the standard pipeline.FiltrationsA filtration map should except anx.DiGraphand return agrpphati.filtrations.Filtration.
To specify a custom filtration, simply implement a subclass ofFiltration, implementing all abstract methods.
The main methods describing the filtration arenode_iterandedge_iterwhich return an iterator of(node/edge, entrance_time)tuples.
See the implementation ofShortestPathFiltrationfor an illustrative example.Note, iffiltrationrepresents the filtration $t\mapsto F^t G$ thenfiltration.ground(G)should return aFiltrationrepresenting the filtration $t\mapsto G \cup F^t G$.
A default implementation of this method is provided.
If $V(F^t G) \subseteq V(G)$ for all $t$ thenProperGroundedFiltrationprovides a more efficient iterator over nodes.HomologyTo specify your homology theory, implement a subclass ofgrpphati.homologies.Homology.
The key methods to implement are theget_<k>_cells(self, filtration).
For a given $k$, the method should return an iterator of columns in the boundary matrix.
For most digraph homology theories, you should only need to implementget_two_cells.In order to implement your homology theory, you may need to implement new column types.
These column types should be subclasses ofgrpphati.column.Column.
A column object should encapsulate itsentrance_time, as this will be needed for sorting the columns of the boundary matrix.
However, you should implement__eq__and__hash__so that columns representing the same basis are equal, regardless of entrance time.
This allowsconvert_to_sparseto lookup the index of each column when sparsifying the boundary matrix.OptimisationsAn optimisation should accept a pipeline (as constructed viamake_grounded_pipeline) and return a new pipeline, implementing the optimisation.
For illustrative examples, see the contents ofgrpphati.optimisations.BackendsBy default, GrPPHATI uses LoPHAT to do the core persistence computation.
An alternative backend, relying on Eirene.jl[3]is also provided.
Here is a rough guide to setting this up:Install Julia 1.6(needed for Eirene compatibility). In the following assume the binary for Julia 1.6 is at/path/to/julia- it is important that the filename isjulia.Installgrpphatiwith the optional[eirene]feature flag, ideally in a virtual environment.Install PyCall for Julia from within your virtual environment, e.g. if usinghatch$ hatch run python
>>> import julia
>>> julia.install(julia="/path/to/julia")Make your own pipelines via themake_grounded_pipelineinterface, using theEireneBackend. For example:# examples/eirene_simple_ex.pyimportnetworkxasnxfromgrpphati.pipelines.groundedimportmake_grounded_pipelinefromgrpphati.homologiesimportRegularPathHomologyfromgrpphati.filtrationsimportShortestPathFiltrationfromgrpphati.backendsimportEireneBackendfromgrpphati.optimisationsimportall_optimisations_serialfrompprintimportpprintpipe=make_grounded_pipeline(ShortestPathFiltration,RegularPathHomology,backend=EireneBackend(runtime_path="/path/to/julia"),optimisation_strat=all_optimisations_serial,)G=nx.DiGraph()G.add_edges_from([(0,1),(0,2),(1,3),(2,3),(3,4),(4,5),(5,6),(6,3,{"weight":10})])result=pipe(G)print(result.barcode)pprint(result.reps)$ python examples/eirene_simple_ex.py
[[0.0, 1.0], [0.0, 10.0]]
[[Edge (0, 2) :: 0, Edge (0, 1) :: 0, Edge (1, 3) :: 0, Edge (2, 3) :: 0],
 [Edge (5, 6) :: 0, Edge (4, 5) :: 0, Edge (3, 4) :: 0, Edge (6, 3) :: 0]]WarningJulia codecannot be called in parallelvia PyJulia.
As such, the parallel optimisations should not be used without settingn_jobs=1.When usingEireneBackendyou may notice it takes a while for the backend to initialise.
This is becauseJuliahas to boot up and compile Eirene.
To speed this up, you can additionally provideEireneBackendwith asysimage.
This image should have Eirene pre-compiled and the necessary bootstrapping for compatibility with PyJulia.
To build such a system image:Clone this repository and installhatch.Installjuliaup.Move into thescriptsdirectory and run./build_so.sh.The system image is now in the root of the project, namedpatched_sys.so.Seeexamples/eirene_ex.pyfor example usageTODOImplement non-regular path homologyImprove performance of sparse matrix construction?Write unit testsChoose benchmark datasetsBenchmark different reductionsSwitch to and benchmarkbit_tree_pivot_columnrepresentationBenchmark optimisationsExpand hypothesis test coverage - test combinations of backends + optimisationsAdd type hintsAdd docstringsSeparate out entrance times?Rephrase optimisations with decorators?Benchmark Eirene vs PHATSetup Eirene notebookUpdate to latest LoPHAT versionReferences[1]Chaplin, T., Harrington, H.A. and Tillmann, U., 2022.
Grounded persistent path homology: a stable, topological descriptor for weighted digraphs.
arXiv preprintarXiv:2210.11274.[2]Bauer, U., Kerber, M., Reininghaus, J. and Wagner, H., 2017.
Phat–persistent homology algorithms toolbox. Journal of symbolic computation, 78, pp.76-90.
github:xoltar/phat[3]Henselman, G. and Ghrist, R., 2016.
Matroid filtrations and computational persistent homology.
arXiv preprintarXiv:1606.00199.
github:Eetion/Eirene.jl