├── .github └── workflows │ ├── python-app.yml │ └── python-publish.yml ├── .gitignore ├── .readthedocs.yml ├── CHANGELOG.md ├── DEPLOY.md ├── LICENSE ├── README.md ├── cechmate ├── __init__.py ├── _version.py ├── filtrations │ ├── __init__.py │ ├── alpha.py │ ├── base.py │ ├── cech.py │ ├── custom.py │ ├── extended.py │ ├── miniball.py │ └── rips.py ├── solver.py └── utils.py ├── docs ├── .gitignore ├── Makefile ├── conf.py ├── index.rst ├── logo.png ├── notebooks │ ├── BasicUsage.ipynb │ └── CustomExample.svg ├── reference │ └── index.rst └── requirements.txt ├── setup.py └── test ├── __init__.py ├── test_alpha.py ├── test_cech.py ├── test_extended.py ├── test_filtrations.py ├── test_miniball.py ├── test_rips.py ├── test_solver.py └── test_utils.py /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | python-version: [3.6, 3.7, 3.8] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip==19.3.1 28 | pip install flake8 pytest pybind11 29 | pip install -e ".[testing]" 30 | - name: Lint with flake8 31 | run: | 32 | # stop the build if there are Python syntax errors or undefined names 33 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 34 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 35 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 36 | - name: Test with pytest 37 | run: | 38 | pytest --cov cechmate 39 | - name: Upload coverage results 40 | run: | 41 | bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Python 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: '3.x' 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip==19.3.1 23 | pip install setuptools wheel twine 24 | - name: Build and publish 25 | env: 26 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 27 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 28 | run: | 29 | python setup.py sdist bdist_wheel 30 | twine upload dist/* 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | .pytest_cache 3 | __pycache__ 4 | cechmate.egg-info 5 | .ipynb_checkpoints 6 | .DS_Store 7 | 8 | stubs -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | python: 4 | version: 3.7 5 | install: 6 | - requirements: docs/requirements.txt 7 | - method: pip 8 | path: . 9 | extra_requirements: 10 | - docs 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## 0.0.10 5 | - Adding a change log 6 | - Reformatting to appease linters 7 | - Migration to gh-actions 8 | -------------------------------------------------------------------------------- /DEPLOY.md: -------------------------------------------------------------------------------- 1 | # Deployment procedures 2 | 3 | Follow these steps when deploying a new version to Pypi 4 | 5 | 1. Remove `.dev` tag from version number in `setup.py` 6 | 2. Add release notes for the new version in `RELEASE.txt` 7 | 3. Run the following commands to upload the new version to pypi 8 | 9 | ``` 10 | pip install -U twine 11 | python setup.py sdist 12 | pip install wheel 13 | python setup.py bdist_wheel 14 | ``` 15 | 16 | ``` 17 | twine upload dist/* 18 | ``` 19 | 20 | 4. Check [pypi.python.org](pypi.python.org) that the new version is present. 21 | 5. Increment version number and give `.dev` tag. 22 | 23 | 24 | # Notes 25 | 26 | We use semver for versioning as best as we know how. The current working development should be labeled with a `.dev` tag. 27 | 28 | 29 | Helpful instructions can be found [here](https://github.com/fhamborg/news-please/wiki/PyPI---How-to-upload-a-new-version) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Nathaniel Saul - nat@saulgill.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PyPI version](https://badge.fury.io/py/cechmate.svg)](https://badge.fury.io/py/cechmate) 2 | [![Downloads](https://pypip.in/download/cechmate/badge.svg)](https://pypi.python.org/pypi/cechmate/) 3 | [![Build Status](https://travis-ci.org/scikit-tda/cechmate.svg?branch=master)](https://travis-ci.org/scikit-tda/cechmate) 4 | [![codecov](https://codecov.io/gh/scikit-tda/cechmate/branch/master/graph/badge.svg)](https://codecov.io/gh/scikit-tda/cechmate) 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 6 | 7 | This library provides easy to use constructors for custom filtrations that are suitable for use with [Phat](https://github.com/xoltar/phat). 8 | Phat currently provides a clean interface for persistence reduction algorithms for boundary matrices. 9 | This tool helps bridge the gap between data and boundary matrices. 10 | Currently, we support construction of Alpha, Rips, and Cech filtrations, and provide an easy interface for Phat. 11 | 12 | If you have a particular filtration you would like implemented, please feel free to reach out and we can work on helping with implementation and integration, so others can use it. 13 | 14 | # Setup 15 | 16 | We use the following dependencies in this library 17 | 18 | * Numpy 19 | * Scipy 20 | * Matplotlib 21 | * Phat 22 | 23 | 24 | The latest version of Cechmate can be found on Pypi and installed with pip: 25 | 26 | ``` 27 | pip install cechmate 28 | ``` 29 | 30 | # Contributions 31 | 32 | 33 | We welcome contributions of all shapes and sizes. There are lots of opportunities for potential projects, so please get in touch if you would like to help out. Everything from an implementation of your favorite distance, notebooks, examples, and documentation are all equally valuable so please don't feel you can't contribute. 34 | 35 | To contribute please fork the project make your changes and submit a pull request. We will do our best to work through any issues with you and get your code merged into the main branch. 36 | 37 | 38 | ## Documentation 39 | 40 | Check out complete documentation at [cechmate.scikit-tda.org](https://cechmate.scikit-tda.org/) -------------------------------------------------------------------------------- /cechmate/__init__.py: -------------------------------------------------------------------------------- 1 | from .filtrations import * 2 | from .solver import * 3 | from .utils import * 4 | 5 | from ._version import __version__ 6 | -------------------------------------------------------------------------------- /cechmate/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.0" 2 | -------------------------------------------------------------------------------- /cechmate/filtrations/__init__.py: -------------------------------------------------------------------------------- 1 | from .alpha import * 2 | from .rips import * 3 | from .cech import * 4 | from .extended import * 5 | from .miniball import get_boundary 6 | -------------------------------------------------------------------------------- /cechmate/filtrations/alpha.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import time 3 | import warnings 4 | 5 | import numpy as np 6 | from numpy import linalg 7 | from scipy import spatial 8 | 9 | from .base import BaseFiltration 10 | 11 | __all__ = ["Alpha"] 12 | 13 | 14 | class Alpha(BaseFiltration): 15 | """ Construct an Alpha filtration from the given data. 16 | 17 | Note 18 | ===== 19 | 20 | Alpha filtrations use radius instead of diameter. Multiply results or X by 2 when comparing the filtration to Rips or Cech. 21 | 22 | Examples 23 | ======== 24 | 25 | >>> r = Alpha() 26 | >>> simplices = r.build(X) 27 | >>> diagrams = r.diagrams(simplices) 28 | 29 | """ 30 | 31 | MIN_DET = 1e-10 32 | 33 | def build(self, X): 34 | """ 35 | Do the Alpha filtration of a Euclidean point set (requires scipy) 36 | 37 | Parameters 38 | =========== 39 | X: Nxd array 40 | Array of N Euclidean vectors in d dimensions 41 | """ 42 | 43 | if X.shape[0] < X.shape[1]: 44 | warnings.warn( 45 | "The input point cloud has more columns than rows; " 46 | + "did you mean to transpose?" 47 | ) 48 | maxdim = self.maxdim 49 | if not self.maxdim: 50 | maxdim = X.shape[1] - 1 51 | 52 | ## Step 1: Figure out the filtration 53 | if self.verbose: 54 | print("Doing spatial.Delaunay triangulation...") 55 | tic = time.time() 56 | 57 | delaunay_faces = spatial.Delaunay(X).simplices 58 | 59 | if self.verbose: 60 | print( 61 | "Finished spatial.Delaunay triangulation (Elapsed Time %.3g)" 62 | % (time.time() - tic) 63 | ) 64 | print("Building alpha filtration...") 65 | tic = time.time() 66 | 67 | filtration = {} 68 | for dim in range(maxdim + 2, 1, -1): 69 | for s in range(delaunay_faces.shape[0]): 70 | simplex = delaunay_faces[s, :] 71 | for sigma in itertools.combinations(simplex, dim): 72 | sigma = tuple(sorted(sigma)) 73 | if not sigma in filtration: 74 | rSqr = self._get_circumcenter(X[sigma, :])[1] 75 | if np.isfinite(rSqr): 76 | filtration[sigma] = rSqr 77 | if sigma in filtration: 78 | for i in range(dim): # Propagate alpha filtration value 79 | tau = sigma[0:i] + sigma[i + 1 : :] 80 | if tau in filtration: 81 | filtration[tau] = min( 82 | filtration[tau], filtration[sigma] 83 | ) 84 | elif len(tau) > 1 and sigma in filtration: 85 | # If Tau is not empty 86 | xtau, rtauSqr = self._get_circumcenter(X[tau, :]) 87 | if np.sum((X[sigma[i], :] - xtau) ** 2) < rtauSqr: 88 | filtration[tau] = filtration[sigma] 89 | # Convert from squared radii to radii 90 | for sigma in filtration: 91 | filtration[sigma] = np.sqrt(filtration[sigma]) 92 | 93 | ## Step 2: Take care of numerical artifacts that may result 94 | ## in simplices with greater filtration values than their co-faces 95 | simplices_bydim = [set([]) for i in range(maxdim + 2)] 96 | for simplex in filtration.keys(): 97 | simplices_bydim[len(simplex) - 1].add(simplex) 98 | simplices_bydim = simplices_bydim[2::] 99 | simplices_bydim.reverse() 100 | for simplices_dim in simplices_bydim: 101 | for sigma in simplices_dim: 102 | for i in range(len(sigma)): 103 | tau = sigma[0:i] + sigma[i + 1 : :] 104 | if filtration[tau] > filtration[sigma]: 105 | filtration[tau] = filtration[sigma] 106 | 107 | if self.verbose: 108 | print( 109 | "Finished building alpha filtration (Elapsed Time %.3g)" 110 | % (time.time() - tic) 111 | ) 112 | 113 | simplices = [([i], 0) for i in range(X.shape[0])] 114 | simplices.extend(filtration.items()) 115 | 116 | self.simplices_ = simplices 117 | 118 | return simplices 119 | 120 | def _get_circumcenter(self, X): 121 | """ 122 | Compute the circumcenter and circumradius of a simplex 123 | 124 | Parameters 125 | ---------- 126 | X : ndarray (N, d) 127 | Coordinates of points on an N-simplex in d dimensions 128 | 129 | Returns 130 | ------- 131 | (circumcenter, circumradius) 132 | A tuple of the circumcenter and squared circumradius. 133 | (SC1) If there are fewer points than the ambient dimension plus one, 134 | then return the circumcenter corresponding to the smallest 135 | possible squared circumradius 136 | (SC2) If the points are not in general position, 137 | it returns (np.inf, np.inf) 138 | (SC3) If there are more points than the ambient dimension plus one 139 | it returns (np.nan, np.nan) 140 | """ 141 | X0 = np.array(X) 142 | if X.shape[0] == 2: 143 | # Special case of an edge, which is very simple 144 | dX = X[1, :] - X[0, :] 145 | rSqr = 0.25 * np.sum(dX ** 2) 146 | x = X[0, :] + 0.5 * dX 147 | return (x, rSqr) 148 | if X.shape[0] > X.shape[1] + 1: # SC3 (too many points) 149 | warnings.warn( 150 | "Trying to compute circumsphere for " 151 | + "%i points in %i dimensions" % (X.shape[0], X.shape[1]) 152 | ) 153 | return (np.nan, np.nan) 154 | # Transform arrays for PCA for SC1 (points in higher ambient dimension) 155 | muV = np.array([]) 156 | V = np.array([]) 157 | if X.shape[0] < X.shape[1] + 1: # SC1: Do PCA down to NPoints-1 158 | muV = np.mean(X, 0) 159 | XCenter = X - muV 160 | _, V = linalg.eigh((XCenter.T).dot(XCenter)) 161 | V = V[:, (X.shape[1] - X.shape[0] + 1) : :] # Put dimension NPoints-1 162 | X = XCenter.dot(V) 163 | muX = np.mean(X, 0) 164 | D = np.ones((X.shape[0], X.shape[0] + 1)) 165 | # Subtract off centroid and scale down for numerical stability 166 | Y = X - muX 167 | scaleSqr = np.max(np.sum(Y ** 2, 1)) 168 | scaleSqr = 1 169 | scale = np.sqrt(scaleSqr) 170 | Y /= scale 171 | 172 | D[:, 1:-1] = Y 173 | D[:, 0] = np.sum(D[:, 1:-1] ** 2, 1) 174 | minor = lambda A, j: A[ 175 | :, np.concatenate((np.arange(j), np.arange(j + 1, A.shape[1]))) 176 | ] 177 | dxs = np.array([linalg.det(minor(D, i)) for i in range(1, D.shape[1] - 1)]) 178 | alpha = linalg.det(minor(D, 0)) 179 | if np.abs(alpha) > Alpha.MIN_DET: 180 | signs = (-1) ** np.arange(len(dxs)) 181 | x = dxs * signs / (2 * alpha) + muX # Add back centroid 182 | gamma = ((-1) ** len(dxs)) * linalg.det(minor(D, D.shape[1] - 1)) 183 | rSqr = (np.sum(dxs ** 2) + 4 * alpha * gamma) / (4 * alpha * alpha) 184 | x *= scale 185 | rSqr *= scaleSqr 186 | if V.size > 0: 187 | # Transform back to ambient if SC1 188 | x = x.dot(V.T) + muV 189 | return (x, rSqr) 190 | return (np.inf, np.inf) # SC2 (Points not in general position) 191 | -------------------------------------------------------------------------------- /cechmate/filtrations/base.py: -------------------------------------------------------------------------------- 1 | """All filtrations should have a base interface. 2 | 3 | """ 4 | 5 | from ..solver import phat_diagrams 6 | 7 | 8 | class BaseFiltration: 9 | """Base filtration that implements constructor and `diagrams` method. 10 | """ 11 | 12 | def __init__(self, maxdim=None, verbose=True): 13 | """Default constructor 14 | 15 | Parameters 16 | ---------- 17 | 18 | maxdim: int 19 | Maximum dimension of homology to compute 20 | verbose: boolean 21 | If True, then print logging statements. 22 | 23 | """ 24 | 25 | self.maxdim = maxdim 26 | self.verbose = verbose 27 | 28 | self.simplices_ = None 29 | self.diagrams_ = None 30 | 31 | def diagrams(self, simplices=None, show_inf=False): 32 | """Compute persistence diagrams for the simplices. 33 | 34 | Parameters 35 | ----------- 36 | simplices: 37 | simplices or filtration built from :code:`build` method. 38 | 39 | show_inf: Boolean 40 | Determines whether or not to return points that never die. 41 | 42 | Returns 43 | --------- 44 | dgms: list of diagrams 45 | the persistence diagram for Hk 46 | 47 | """ 48 | simplices = simplices or self.simplices_ 49 | self.diagrams_ = phat_diagrams(simplices, show_inf) 50 | 51 | return self.diagrams_ 52 | -------------------------------------------------------------------------------- /cechmate/filtrations/cech.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import numpy as np 3 | 4 | from .base import BaseFiltration 5 | from .miniball import miniball_cache 6 | 7 | 8 | __all__ = ["Cech"] 9 | 10 | 11 | class Cech(BaseFiltration): 12 | """Compute the Cech filtration of a Euclidean point set for simplices up to order :code:`self.max_dim`. 13 | 14 | Examples 15 | ======== 16 | 17 | >>> r = Cech(maxdim=1) 18 | >>> simplices = r.build(X) 19 | >>> diagrams = r.diagrams(simplices) 20 | 21 | """ 22 | 23 | def build(self, X): 24 | """Compute the Cech filtration of a Euclidean point set for simplices up to order :code:`self.max_dim`. 25 | 26 | Parameters 27 | =========== 28 | 29 | X: Nxd array 30 | N Euclidean vectors in d dimensions 31 | 32 | Returns 33 | ========== 34 | 35 | simplices: 36 | Cech filtration for the data X 37 | """ 38 | 39 | N = X.shape[0] 40 | xr = np.arange(N) 41 | xrl = xr.tolist() 42 | maxdim = self.maxdim 43 | if not self.maxdim: 44 | maxdim = X.shape[1] - 1 45 | 46 | miniball = miniball_cache(X) 47 | 48 | # start with vertices 49 | simplices = [([i], 0) for i in range(N)] 50 | 51 | # then higher order simplices 52 | for k in range(maxdim + 1): 53 | for idxs in itertools.combinations(xrl, k + 2): 54 | C, r2 = miniball(frozenset(idxs), frozenset([])) 55 | simplices.append((list(idxs), np.sqrt(r2))) 56 | 57 | self.simplices_ = simplices 58 | 59 | return simplices 60 | -------------------------------------------------------------------------------- /cechmate/filtrations/custom.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import time 3 | 4 | 5 | from .base import BaseFiltration 6 | 7 | __all__ = ["Custom"] 8 | 9 | 10 | class Custom(BaseFiltration): 11 | def __init__(self): 12 | self.simplices_ = None 13 | 14 | def build(self, simplices): 15 | """ 16 | OOP interface for custom filtration construction. Supply the filtration in the form of a list of simplices. Then construct diagrams with :code:`.diagrams` method. 17 | 18 | Parameters 19 | =========== 20 | simplices: List[tuple(float, List)] 21 | List of simplices as pairs of 22 | 23 | """ 24 | 25 | self.simplices_ = simplices 26 | -------------------------------------------------------------------------------- /cechmate/filtrations/extended.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import phat 3 | 4 | from .base import BaseFiltration 5 | 6 | from ..solver import _simplices_to_sparse_pivot_column 7 | 8 | __all__ = ["Extended"] 9 | 10 | 11 | class Extended(BaseFiltration): 12 | """ 13 | This class computed the extended persistence of a simplicial complex. It requires input as a simplicial complex and a mapping on each vertex in the complex. It returns a dictionary storing the associated diagrams in each homology class. 14 | 15 | The basic steps are to: 16 | - convert an abstract simplicial complex to the correct boundary matrix, using the lower-star up pass and upper-star down pass 17 | - read the reduced boundary matrix into birth-death pairs. 18 | - partition pairs into respective Ordinary/Extended/Relative diagrams. 19 | 20 | 21 | References 22 | =========== 23 | 24 | Cohen-Steiner, David, Herbert Edelsbrunner, and John Harer. "Extending persistence using Poincaré and Lefschetz duality." Foundations of Computational Mathematics 9.1 (2009): 79-103. 25 | 26 | """ 27 | 28 | def __init__(self, simplices, f): 29 | """Initialize Extended persistence class. 30 | 31 | Parameters 32 | ============ 33 | 34 | simplices: List[List] 35 | Simplices 36 | 37 | f: dictionary mapping name of vertex to value. 38 | """ 39 | 40 | self.simplices = simplices 41 | self.f = f 42 | 43 | self._boundary_matrix = None 44 | self._mapping = None 45 | self._reduced_boundary_matrix = None 46 | self._pairs = None 47 | self.diagrams_ = None 48 | 49 | @classmethod 50 | def from_kmapper(cls, graph, f): 51 | """Construct :code:`Extended` object from a Kepler Mapper graph output 52 | 53 | Parameters 54 | =========== 55 | 56 | graph: dictionary 57 | Output of the Kepler Mapper :code:`map` method. 58 | f: List or Dict 59 | Array with values for each member (like :code:`color_function`), or dictionary mapping for each node name. 60 | """ 61 | 62 | # Construct simplices from graph 63 | nodes_map = {v: k for k, v in enumerate(graph["nodes"])} 64 | simplices = [[nodes_map[s] for s in simplex] for simplex in graph["simplices"]] 65 | 66 | # Construct mapping from f 67 | if not isinstance(f, dict): 68 | f = np.array(f) 69 | mapping = {v: np.mean(f[graph["nodes"][n]]) for n, v in nodes_map.items()} 70 | else: 71 | assert len(f) == len(nodes_map), "Each node should have a value in f." 72 | mapping = {nodes_map[k]: v for k, v in f.items()} 73 | 74 | return Extended(simplices, mapping) 75 | 76 | @classmethod 77 | def from_nx(cls, graph, f): 78 | """Construct :code:`Extended` object from an nx.Graph object. 79 | 80 | Parameters 81 | =========== 82 | 83 | graph: nx.Graph 84 | Graph to compute extended persistence on. 85 | f: Dict or String 86 | Dictionary mapping node to value or string corresponding to node attribute that should be used for mapping. 87 | """ 88 | 89 | assert isinstance(f, dict) or isinstance( 90 | f, str 91 | ), "f must be of type dict or str. It is type {}".format(type(f)) 92 | 93 | try: 94 | import networkx as nx # internal import so that network isn't always required 95 | except ImportError as e: 96 | import sys 97 | 98 | raise type(e)( 99 | str(e) 100 | + "Networkx package is required for `from_nx` constructor. Please install with `pip install networkx`" 101 | ).with_traceback(sys.exc_info()[2]) 102 | 103 | simplices = list(graph.nodes) 104 | simplices.extend(list(graph.edges)) 105 | 106 | if isinstance(f, str): 107 | f = nx.get_node_attributes(graph, f) 108 | 109 | return Extended(simplices, f) 110 | 111 | def diagrams(self): 112 | """ Compute diagrams of extended persistent homology for a simplicial complex :code:`simplices` and function :code:`f`. 113 | 114 | Returns 115 | ========= 116 | 117 | diagrams: 118 | Extended persistence diagrams 119 | 120 | """ 121 | 122 | # Only compute once 123 | if self.diagrams_: 124 | return self.diagrams_ 125 | 126 | _, _ = self._up_down_boundary_matrix(self.simplices, self.f) 127 | pairs = self._compute_persistence_pairs() 128 | diagrams = self._process_pairs(pairs) 129 | 130 | self.diagrams_ = diagrams 131 | return self.diagrams_ 132 | 133 | def _process_pairs(self, pairs): 134 | """Split the persistence pairs out into their respective quadrants, adding them to their associated diagrams. 135 | 136 | """ 137 | n = len(self._boundary_matrix) / 2 138 | ordinary_pairs = [(b, d) for (b, d) in pairs if b < n and d < n] 139 | extended_pairs = [(b, d) for (b, d) in pairs if b < n and d >= n] 140 | relative_pairs = [(b, d) for (b, d) in pairs if b >= n and d >= n] 141 | 142 | diagrams = {} 143 | self._extract_diagram( 144 | diagrams, 145 | ordinary_pairs, 146 | "ordinary", 147 | lambda b, d: len(self._mapping[b][0]) - 1, 148 | ) 149 | self._extract_diagram( 150 | diagrams, 151 | extended_pairs, 152 | "extended", 153 | lambda b, d: len(self._mapping[b][0]) - 1, 154 | ) 155 | self._extract_diagram( 156 | diagrams, 157 | relative_pairs, 158 | "relative", 159 | lambda b, d: len(self._mapping[d][0]) - 1, 160 | ) 161 | 162 | diagrams = { 163 | h: {s: [[b, d] for b, d in ls if b != d] for s, ls in d.items()} 164 | for h, d in diagrams.items() 165 | } 166 | 167 | return diagrams 168 | 169 | def _extract_diagram(self, diagrams, pairs, pairs_str, order_f): 170 | """Operate on diagrams in place. Add pairs to diagram according to the order_f and self._mapping values. 171 | """ 172 | for b, d in pairs: 173 | order = order_f(b, d) 174 | diagrams.setdefault(order, {}).setdefault(pairs_str, []).append( 175 | (self._mapping[b][1], self._mapping[d][1]) 176 | ) 177 | 178 | def _up_down_boundary_matrix(self, X, f): 179 | """ 180 | Let A be the boundary matrix for the ascending pass, storing the simplices in blocks that correspond to the lower stars of v1 to vn, in this order. 181 | 182 | All simplices in the same block are assigned the same value, namely the height of the vertex defining the lower star. 183 | 184 | Returns 185 | ======== 186 | 187 | boundary matrix: sparse pivot column boundary matrix 188 | f: mapping of simplices to function values 189 | """ 190 | 191 | vs = [x[0] for x in X if len(x) == 1] 192 | fvs = sorted(vs, key=lambda v: f[v]) 193 | 194 | lstars = [(_lower_star(X, v, f), f[v]) for v in fvs] 195 | kappas = [ 196 | (kappa, fv) for lstar, fv in lstars for kappa in sorted(lstar, key=len) 197 | ] 198 | 199 | ustars = [(_upper_star(X, v, f), f[v]) for v in fvs[::-1]] 200 | lambdas = [(lam, fv) for ustar, fv in ustars for lam in sorted(ustar, key=len)] 201 | 202 | A = _simplices_to_sparse_pivot_column(kappas) 203 | D = _simplices_to_sparse_pivot_column(lambdas) 204 | 205 | # Augment D by lowering it it by m and coning. 206 | M = list(A) 207 | 208 | kap_sims = [l[0] for l in kappas] 209 | for (k, ds), lam in zip(D, lambdas): 210 | # find index of lam in A (or kappas) 211 | idx = kap_sims.index(lam[0]) 212 | M.append(((k + 1), [idx] + [len(A) + d for d in ds])) 213 | 214 | self._boundary_matrix = M 215 | self._mapping = dict(enumerate(kappas + lambdas)) 216 | return self._boundary_matrix, self._mapping 217 | 218 | def _compute_persistence_pairs(self, boundary_matrix=None): 219 | boundary_matrix = boundary_matrix or self._boundary_matrix 220 | 221 | self._reduced_boundary_matrix = phat.boundary_matrix( 222 | columns=boundary_matrix, 223 | representation=phat.representations.sparse_pivot_column, 224 | ) 225 | 226 | pairs = self._reduced_boundary_matrix.compute_persistence_pairs() 227 | pairs.sort() 228 | self._pairs = list(pairs) 229 | return self._pairs 230 | 231 | 232 | def _star(X, v): 233 | """Compute star of v 234 | """ 235 | st = [x for x in X if v in x] 236 | return st 237 | 238 | 239 | def _lower_star(X, v, f): 240 | st = _star(X, v) 241 | lst = [x for x in st if max([f[y] for y in x]) == f[v]] 242 | return lst 243 | 244 | 245 | def _upper_star(X, v, f): 246 | st = _star(X, v) 247 | lst = [x for x in st if min([f[y] for y in x]) == f[v]] 248 | return lst 249 | -------------------------------------------------------------------------------- /cechmate/filtrations/miniball.py: -------------------------------------------------------------------------------- 1 | # 2 | # This code was adapted from miniball v1.0.2 (https://github.com/marmakoide/miniball) 3 | # to accommodate lru_caching 4 | # 5 | # Modifications contained herein are copyright under same MIT license 6 | # Modifications contained herein under Copyright (c) 2019 Nathaniel Saul 7 | # 8 | # Original Copyright and license: 9 | # Copyright (c) 2019 Alexandre Devert 10 | # 11 | # Permission is hereby granted, free of charge, to any person obtaining a copy 12 | # of this software and associated documentation files (the "Software"), to deal 13 | # in the Software without restriction, including without limitation the rights 14 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | # copies of the Software, and to permit persons to whom the Software is 16 | # furnished to do so, subject to the following conditions: 17 | # 18 | # The above copyright notice and this permission notice shall be included in all 19 | # copies or substantial portions of the Software. 20 | # 21 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | # SOFTWARE. 28 | # 29 | 30 | import numpy as np 31 | import random 32 | import functools 33 | 34 | 35 | def get_circumsphere(S): 36 | """ 37 | Computes the circumsphere of a set of points 38 | Parameters 39 | ---------- 40 | S : (M, N) ndarray, where 1 <= M <= N + 1 41 | The input points 42 | Returns 43 | ------- 44 | C, r2 : ((2) ndarray, float) 45 | The center and the squared radius of the circumsphere 46 | """ 47 | 48 | U = S[1:] - S[0] 49 | B = np.sqrt(np.sum(U ** 2, axis=1)) 50 | U /= B[:, None] 51 | C = np.dot(np.linalg.solve(np.inner(U, U), 0.5 * B), U) 52 | return C + S[0], np.sum(C ** 2) 53 | 54 | 55 | def circle_contains(D, p): 56 | c, r2 = D 57 | return np.sum((p - c) ** 2) <= r2 58 | 59 | 60 | def get_boundary(data, v): 61 | if len(v) == 0: 62 | return np.zeros(data.shape[1]), 0.0 63 | 64 | if len(v) <= data.shape[1] + 1: 65 | return get_circumsphere(data[v]) 66 | 67 | c, r2 = get_circumsphere(data[v[: data.shape[1] + 1]]) 68 | 69 | # TODO: epsilon is not defined, so not sure how this ever worked? 70 | # if np.all(np.fabs(np.sum((data[v] - c) ** 2, axis = 1) - r2) < epsilon): 71 | return c, r2 72 | 73 | 74 | def miniball_cache(data): 75 | """ This miniball function is exposed so that the cache can be maintained 76 | between subsequent calls. 77 | 78 | Please see the included `miniball` function to see how the interface should be used. 79 | """ 80 | 81 | @functools.lru_cache(maxsize=1000) 82 | def miniball_rec(tau, v): 83 | # don't modify tau and v 84 | tau, v = list(tau), list(v) 85 | 86 | if len(tau) == 0: 87 | C, r2 = get_boundary(data, v) 88 | else: 89 | u = tau.pop() 90 | C, r2 = miniball_rec(frozenset(tau), frozenset(v)) 91 | if not circle_contains((C, r2), data[u]): 92 | C, r2 = miniball_rec(frozenset(tau), frozenset(v + [u])) 93 | 94 | return C, r2 95 | 96 | return miniball_rec 97 | 98 | 99 | def miniball(data): 100 | """ Miniball algorithm with no caching between runs 101 | 102 | """ 103 | mb = miniball_cache(data) 104 | 105 | C, r2 = mb(frozenset(list(range(data.shape[0]))), frozenset([])) 106 | return C, np.sqrt(r2) 107 | -------------------------------------------------------------------------------- /cechmate/filtrations/rips.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import numpy as np 3 | 4 | from .base import BaseFiltration 5 | 6 | __all__ = ["Rips"] 7 | 8 | 9 | class Rips(BaseFiltration): 10 | """Construct a Rips filtration and the associated diagrams. 11 | 12 | Examples 13 | ======== 14 | 15 | >>> r = Rips(maxdim=1) 16 | >>> simplices = r.build(X) 17 | >>> diagrams = r.diagrams(simplices) 18 | 19 | """ 20 | 21 | def build(self, X): 22 | """Compute the rips filtration of a Euclidean point set. 23 | 24 | Parameters 25 | =========== 26 | X: An Nxd array 27 | An Nxd array of N Euclidean vectors in d dimensions. 28 | 29 | Returns 30 | ======== 31 | simplices: list of tuples 32 | List of simplices with birth time representing Rips filtration for the data X. 33 | """ 34 | D = self._getSSM(X) 35 | N = D.shape[0] 36 | xr = np.arange(N) 37 | xrl = xr.tolist() 38 | maxdim = self.maxdim 39 | if not maxdim: 40 | maxdim = 1 41 | # First add all 0 simplices 42 | simplices = [([i], 0) for i in range(N)] 43 | for k in range(maxdim + 1): 44 | # Add all (k+1)-simplices, which have (k+2) vertices 45 | for idxs in itertools.combinations(xrl, k + 2): 46 | idxs = list(idxs) 47 | d = 0.0 48 | for i in range(len(idxs)): 49 | for j in range(i + 1, len(idxs)): 50 | d = max(d, D[idxs[i], idxs[j]]) 51 | simplices.append((idxs, d)) 52 | 53 | self.simplices_ = simplices 54 | 55 | return simplices 56 | 57 | def _getSSM(self, X): 58 | """ 59 | Given a set of Euclidean vectors, return a pairwise distance matrix 60 | :param X: An Nxd array of N Euclidean vectors in d dimensions 61 | :returns D: An NxN array of all pairwise distances 62 | """ 63 | XSqr = np.sum(X ** 2, 1) 64 | D = XSqr[:, None] + XSqr[None, :] - 2 * X.dot(X.T) 65 | D[D < 0] = 0 # Numerical precision 66 | D = np.sqrt(D) 67 | return D 68 | -------------------------------------------------------------------------------- /cechmate/solver.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import time 3 | 4 | import numpy as np 5 | import phat 6 | 7 | 8 | def phat_diagrams(simplices, show_inf=False, verbose=True): 9 | """ 10 | Compute the persistence diagram for :code:`simplices` using Phat. 11 | 12 | Parameters 13 | ----------- 14 | simplices: A list of lists of simplices and their distances 15 | the kth element is itself a list of tuples ([idx1, ..., idxk], dist) 16 | where [idx1, ..., idxk] is a list of vertices involved in the simplex 17 | and "dist" is the distance at which the simplex is added 18 | 19 | show_inf: Boolean 20 | Determines whether or not to return points that never die. 21 | 22 | Returns 23 | -------- 24 | dgms: list of diagrams 25 | the persistence diagram for Hk 26 | """ 27 | 28 | ## Convert simplices representation to sparse pivot column 29 | # -- sort by birth time, if tie, use order of simplex 30 | ordered_simplices = sorted(simplices, key=lambda x: (x[1], len(x[0]))) 31 | columns = _simplices_to_sparse_pivot_column(ordered_simplices, verbose) 32 | 33 | ## Setup boundary matrix and reduce 34 | if verbose: 35 | print("Computing persistence pairs...") 36 | tic = time.time() 37 | 38 | boundary_matrix = phat.boundary_matrix( 39 | columns=columns, representation=phat.representations.sparse_pivot_column 40 | ) 41 | pairs = boundary_matrix.compute_persistence_pairs() 42 | pairs.sort() 43 | 44 | if verbose: 45 | print( 46 | "Finished computing persistence pairs (Elapsed Time %.3g)" 47 | % (time.time() - tic) 48 | ) 49 | 50 | ## Setup persistence diagrams by reading off distances 51 | dgms = _process_distances(pairs, ordered_simplices) 52 | 53 | ## Add all unpaired simplices as infinite points 54 | if show_inf: 55 | dgms = _add_unpaired(dgms, pairs, simplices) 56 | 57 | ## Convert to arrays: 58 | dgms = [np.array(dgm) for dgm in dgms.values()] 59 | 60 | return dgms 61 | 62 | 63 | def _simplices_to_sparse_pivot_column(ordered_simplices, verbose=False): 64 | """ 65 | 66 | """ 67 | 68 | idx = 0 69 | columns = [] 70 | idxs2order = {} 71 | 72 | if verbose: 73 | print("Constructing boundary matrix...") 74 | tic = time.time() 75 | 76 | for idxs, dist in ordered_simplices: 77 | k = len(idxs) 78 | idxs = sorted(idxs) 79 | idxs2order[tuple(idxs)] = idx 80 | idxs = np.array(idxs) 81 | if len(idxs) == 1: 82 | columns.append((0, [])) 83 | else: 84 | # Get all faces with k-1 vertices 85 | collist = [] 86 | for fidxs in itertools.combinations(range(k), k - 1): 87 | fidxs = np.array(list(fidxs)) 88 | fidxs = tuple(idxs[fidxs]) 89 | if not fidxs in idxs2order: 90 | raise Exception( 91 | "Error: Not a proper filtration: %s added before %s" 92 | % (idxs, fidxs) 93 | ) 94 | return None 95 | collist.append(idxs2order[fidxs]) 96 | collist = sorted(collist) 97 | columns.append((k - 1, collist)) 98 | 99 | idx += 1 100 | 101 | if verbose: 102 | print( 103 | "Finished constructing boundary matrix (Elapsed Time %.3g)" 104 | % (time.time() - tic) 105 | ) 106 | 107 | return columns 108 | 109 | 110 | def _process_distances(pairs, ordered_simplices): 111 | """ Setup persistence diagrams by reading off distances 112 | """ 113 | 114 | dgms = {} 115 | posneg = np.zeros(len(ordered_simplices)) 116 | 117 | for [bi, di] in pairs: 118 | bidxs, bd = ordered_simplices[bi] 119 | didxs, dd = ordered_simplices[di] 120 | 121 | assert posneg[bi] == 0 and posneg[di] == 0 122 | posneg[bi], posneg[di] = 1, -1 123 | 124 | assert dd >= bd 125 | # assert len(bidxs) == len(didxs) - 1 126 | 127 | p = len(bidxs) - 1 128 | 129 | # Don't add zero persistence pairs 130 | if bd != dd: 131 | dgms.setdefault(p, []).append([bd, dd]) 132 | 133 | return dgms 134 | 135 | 136 | def _add_unpaired(dgms, pairs, simplices): 137 | posneg = np.zeros(len(simplices)) 138 | for [bi, di] in pairs: 139 | assert posneg[bi] == 0 140 | assert posneg[di] == 0 141 | posneg[bi] = 1 142 | posneg[di] = -1 143 | 144 | for i in range(len(posneg)): 145 | if posneg[i] == 0: 146 | (idxs, dist) = simplices[i] 147 | p = len(idxs) - 1 148 | if not p in dgms: 149 | dgms[p] = [] 150 | dgms[p].append([dist, np.inf]) 151 | 152 | return dgms 153 | -------------------------------------------------------------------------------- /cechmate/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | __all__ = ["sparse_to_dense"] 4 | 5 | 6 | def sparse_to_dense(sparse_bm): 7 | """Converts a sparse boundary matrix to a dense boundary matrix. 8 | 9 | This is used for visualization and debugging as dense boundary matrices can be easier to read to small matrices. Dense boundary matrices are the default filtration format used in cechmate filtrations. 10 | 11 | Parameters 12 | ============ 13 | 14 | sparse_bm: 15 | Sparse boundary matrix. 16 | 17 | Returns 18 | ========= 19 | 20 | dense: np.array 21 | Square matrix representation of boundary matrix. 22 | 23 | """ 24 | n = len(sparse_bm) 25 | dense = np.zeros((n, n)) 26 | 27 | for i, (_, c) in enumerate(sparse_bm): 28 | dense[c, i] = 1 29 | 30 | return dense 31 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | .cache/ 3 | persim/ 4 | persim.egg-info 5 | venv/ 6 | 7 | .vscode/ 8 | *.egg-info/ 9 | __pycache__ 10 | .pytest_cache 11 | .DS_Store 12 | .coverage 13 | .ipynb_checkpoints 14 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = Persim 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.insert(0, os.path.abspath('.')) 4 | from cechmate import __version__ 5 | from sktda_docs_config import * 6 | 7 | 8 | project = u'Cechmate' 9 | copyright = u'2019, Chris Tralie and Nathaniel Saul' 10 | author = u'Chris Tralie and Nathaniel Saul' 11 | 12 | version = __version__ 13 | release = __version__ 14 | 15 | html_theme_options.update({ 16 | # Google Analytics info 17 | 'ga_ua': 'UA-124965309-5', 18 | 'ga_domain': '', 19 | 'gh_url': 'scikit-tda/cechmate' 20 | }) 21 | html_short_title = project 22 | htmlhelp_basename = 'cechmatedoc' -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | |PyPI version| |Downloads| |Build Status| |Codecov| |License: MIT| 2 | 3 | 4 | This library provides easy to use constructors for custom filtrations that are suitable for use with `Phat `_. 5 | Phat currently provides a clean interface for persistence reduction algorithms for boundary matrices. 6 | This tool helps bridge the gap between data and boundary matrices. 7 | Currently, we support construction of Alpha, Rips, and Cech filtrations, and provide an easy interface for Phat. 8 | 9 | If you have a particular filtration you would like implemented, please feel free to reach out and we can work on helping with implementation and integration, so others can use it. 10 | 11 | 12 | Setup 13 | ======= 14 | 15 | The latest version of cechmate can be found on Pypi and installed with pip: 16 | 17 | .. code:: Bash 18 | 19 | pip install cechmate 20 | 21 | 22 | Contributions 23 | =============== 24 | 25 | We welcome contributions of all shapes and sizes. There are lots of opportunities for potential projects, so please get in touch if you would like to help out. Everything from an implementation of your favorite distance, notebooks, examples, and documentation are all equally valuable so please don't feel you can't contribute. 26 | 27 | To contribute please fork the project make your changes and submit a pull request. We will do our best to work through any issues with you and get your code merged into the main branch. 28 | 29 | 30 | .. toctree:: 31 | :maxdepth: 1 32 | :hidden: 33 | :caption: User Guide 34 | 35 | notebooks/BasicUsage 36 | reference/index 37 | 38 | 39 | 40 | .. |Downloads| image:: https://pypip.in/download/cechmate/badge.svg 41 | :target: https://pypi.python.org/pypi/cechmate/ 42 | .. |PyPI version| image:: https://badge.fury.io/py/cechmate.svg 43 | :target: https://badge.fury.io/py/cechmate 44 | .. |Build Status| image:: https://travis-ci.org/scikit-tda/cechmate.svg?branch=master 45 | :target: https://travis-ci.org/scikit-tda/cechmate 46 | .. |Codecov| image:: https://codecov.io/gh/scikit-tda/cechmate/branch/master/graph/badge.svg 47 | :target: https://codecov.io/gh/scikit-tda/cechmate 48 | .. |License: MIT| image:: https://img.shields.io/badge/License-MIT-yellow.svg 49 | :target: https://opensource.org/licenses/MIT) 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-tda/cechmate/eb2075302ea56394fa656d225dbb6d96b723d917/docs/logo.png -------------------------------------------------------------------------------- /docs/notebooks/BasicUsage.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Basic Usage\n", 8 | "\n", 9 | "In this notebook, we will show some typical use cases of the API First, we import the necessary libraries." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import numpy as np\n", 19 | "import matplotlib.pyplot as plt\n", 20 | "import cechmate as cm\n", 21 | "from persim import plot_diagrams\n", 22 | "import tadasets" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "metadata": {}, 28 | "source": [ 29 | "## Rips Filtrations" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "First, we show how to do a rips filtration (NOTE: The [ripser.py](https://github.com/scikit-tda/ripser.py) library is strongly recommended in this case, so this is mainly to show syntax)" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 2, 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "# Initialize a noisy circle\n", 46 | "X = tadasets.dsphere(n=100, d=1, r=1, noise=0.2)" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 3, 52 | "metadata": {}, 53 | "outputs": [ 54 | { 55 | "name": "stdout", 56 | "output_type": "stream", 57 | "text": [ 58 | "Constructing boundary matrix...\n", 59 | "Finished constructing boundary matrix (Elapsed Time 1.58)\n", 60 | "Computing persistence pairs...\n", 61 | "Finished computing persistence pairs (Elapsed Time 0.559)\n" 62 | ] 63 | } 64 | ], 65 | "source": [ 66 | "# Instantiate and build a rips filtration\n", 67 | "rips = cm.Rips(maxdim=1) #Go up to 1D homology\n", 68 | "rips.build(X)\n", 69 | "dgmsrips = rips.diagrams()" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": 4, 75 | "metadata": {}, 76 | "outputs": [ 77 | { 78 | "data": { 79 | "image/png": "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\n", 80 | "text/plain": [ 81 | "
" 82 | ] 83 | }, 84 | "metadata": { 85 | "needs_background": "light" 86 | }, 87 | "output_type": "display_data" 88 | } 89 | ], 90 | "source": [ 91 | "plt.figure()\n", 92 | "plt.subplot(121)\n", 93 | "plt.scatter(X[:, 0], X[:, 1])\n", 94 | "plt.axis('square')\n", 95 | "plt.title(\"Point Cloud\")\n", 96 | "plt.subplot(122)\n", 97 | "plot_diagrams(dgmsrips)\n", 98 | "plt.title(\"Rips Persistence Diagrams\")\n", 99 | "plt.tight_layout()\n", 100 | "plt.show()" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "## Cech Filtrations" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "Let's try computing Cech filtrations." 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 5, 120 | "metadata": {}, 121 | "outputs": [ 122 | { 123 | "name": "stdout", 124 | "output_type": "stream", 125 | "text": [ 126 | "Constructing boundary matrix...\n", 127 | "Finished constructing boundary matrix (Elapsed Time 1.53)\n", 128 | "Computing persistence pairs...\n", 129 | "Finished computing persistence pairs (Elapsed Time 0.924)\n" 130 | ] 131 | } 132 | ], 133 | "source": [ 134 | "cech = cm.Cech(maxdim=1) #Go up to 1D homology\n", 135 | "cech.build(X)\n", 136 | "dgmscech = cech.diagrams() * 2" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": 6, 142 | "metadata": {}, 143 | "outputs": [ 144 | { 145 | "data": { 146 | "image/png": "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\n", 147 | "text/plain": [ 148 | "
" 149 | ] 150 | }, 151 | "metadata": {}, 152 | "output_type": "display_data" 153 | } 154 | ], 155 | "source": [ 156 | "plot_diagrams(dgmsrips + dgmscech, labels = ['$H_0$ Rips', '$H_1$ Rips', '$H_0$ Cech', '$H_1$ Cech'])\n", 157 | "plt.show()" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "metadata": {}, 163 | "source": [ 164 | "## Alpha Filtrations" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "Now we will perform an alpha filtration on the exact same point cloud." 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": 7, 177 | "metadata": {}, 178 | "outputs": [ 179 | { 180 | "name": "stdout", 181 | "output_type": "stream", 182 | "text": [ 183 | "Doing spatial.Delaunay triangulation...\n", 184 | "Finished spatial.Delaunay triangulation (Elapsed Time 0.000787)\n", 185 | "Building alpha filtration...\n", 186 | "Finished building alpha filtration (Elapsed Time 0.0354)\n", 187 | "Constructing boundary matrix...\n", 188 | "Finished constructing boundary matrix (Elapsed Time 0.00469)\n", 189 | "Computing persistence pairs...\n", 190 | "Finished computing persistence pairs (Elapsed Time 0.00063)\n" 191 | ] 192 | } 193 | ], 194 | "source": [ 195 | "alpha = cm.Alpha()\n", 196 | "filtration = alpha.build(2*X) # Alpha goes by radius instead of diameter\n", 197 | "dgmsalpha = alpha.diagrams(filtration)" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 8, 203 | "metadata": {}, 204 | "outputs": [ 205 | { 206 | "data": { 207 | "image/png": "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\n", 208 | "text/plain": [ 209 | "
" 210 | ] 211 | }, 212 | "metadata": {}, 213 | "output_type": "display_data" 214 | } 215 | ], 216 | "source": [ 217 | "plot_diagrams(dgmsrips + dgmsalpha, labels = ['$H_0$ Rips', '$H_1$ Rips', '$H_0$ Alpha', '$H_1$ Alpha'])\n", 218 | "plt.show()" 219 | ] 220 | }, 221 | { 222 | "cell_type": "markdown", 223 | "metadata": {}, 224 | "source": [ 225 | "Note that the alpha filtration is substantially faster than the Rips filtration, and it is also more geometrically accurate. In rips, we add a triangle the moment its edges are added, but growing balls around their vertices do not necessarily cover the triangle at that point, as they are in the Cech filtration. Alpha is the intersection of Cech balls with Voronoi regions, so it is a strict subset of Cech. Hence, it takes a larger scale to add triangles, so the classes die slightly later.\n", 226 | "\n", 227 | "Now let's try an example with a 400 points sampled from a 4-sphere in 5 dimensions." 228 | ] 229 | }, 230 | { 231 | "cell_type": "code", 232 | "execution_count": 9, 233 | "metadata": {}, 234 | "outputs": [ 235 | { 236 | "name": "stdout", 237 | "output_type": "stream", 238 | "text": [ 239 | "Doing spatial.Delaunay triangulation...\n", 240 | "Finished spatial.Delaunay triangulation (Elapsed Time 0.775)\n", 241 | "Building alpha filtration...\n", 242 | "Finished building alpha filtration (Elapsed Time 78.5)\n", 243 | "Constructing boundary matrix...\n", 244 | "Finished constructing boundary matrix (Elapsed Time 1.51)\n", 245 | "Computing persistence pairs...\n", 246 | "Finished computing persistence pairs (Elapsed Time 0.161)\n" 247 | ] 248 | }, 249 | { 250 | "data": { 251 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAcMAAAG2CAYAAAADae2sAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAPYQAAD2EBqD+naQAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvOIA7rQAAIABJREFUeJzs3Xl4U2XaBvA7W5uW7nShdIGyiFQsSxm1qEgV0cKAMC44IouCygAqoH5sLgPKdBwVcNSCKCPDDDIosolVBARZXUgLKAICpSaUlkq3dKFplvP90TY0TbonOVnu33X1GvPmJOcJ0/bue867SARBEEBEROTFpGIXQEREJDaGIREReT2GIREReT2GIREReT2GIREReT2GIREReT2GIREReT2GIREReT2GIREReT2GIREReT1Rw3D//v0YPXo0unbtColEgq1btzZ7/ObNm3H33XcjIiICQUFBSElJwc6dO51ULREReSpRw7CyshL9+/fHu+++26rj9+/fj7vvvhuZmZlQqVRITU3F6NGjkZ2d7eBKiYjIk0lcZaFuiUSCLVu2YOzYsW163Q033IDx48fj5ZdfdlBlRETk6eRiF9ARJpMJ5eXlCAsLa/IYnU4HnU5n8Zri4mJ07twZEonEGWUSEZEdCYKA8vJydO3aFVKpfS5wunUYvvXWW6isrMRDDz3U5DHp6elYvHixE6siIiJn0Gg0iI2Ntct7ue1l0g0bNmDatGnYtm0bhg8f3uRxjXuGZWVliI+Ph0ajQVBQUIfrJiIix7t48SJGjRqF3NxcxMfHQ61Wo7S0FMHBwXZ5f7fsGW7cuBFTp07Fp59+2mwQAoCvry98fX2t2oOCghiGRERuwsfHB3q9Hj169MDnn3+OG264wa63utwuDDds2IDHH38cGzZswKhRo8Quh4iInKBXr17Yt28ffH197dYbbEjUMKyoqMC5c+fMjy9cuIBjx44hLCwM8fHxWLBgAfLy8rBu3ToAtUE4adIkvP3227jllltQUFAAAPDz83PIPw4REYlHo9Hg9OnTuPvuuwHUBiIAaLVau59L1HmGR48excCBAzFw4EAAwNy5czFw4EDzNIn8/Hyo1Wrz8e+//z4MBgNmzpyJ6Oho89ezzz4rSv1EROQYGo0Gw4YNwx//+Efs2rXL4edzmQE0zqLVahEcHIyysjLeMyQickH1QZiTk4MePXpg3759iIuLMz/viN/jXJuUiIhcRktB6CgMQyIicgliBSHAMCQiIhdQWFgoWhACbji1goiIPE/nzp0xZMgQAHB6EAIMQyIicgEymQxr165FUVERIiMjnX5+XiYlIiJRaDQazJs3D0ajEUBtIIoRhAB7hkREJIKGg2UA4PXXXxe1HvYMiYjIqRqPGp01a5bYJTEMiYjIecScPtEchiERETmFqwYhwDAkIiInMBgMuPfee10yCAGGIREROYFcLsebb76JxMRElwtCgAt1i10OEZFXMRgMkMs7NpGBC3UTEZHbqL9H2HDf2o4GoaMwDImIyO7qg/Dbb7/FtGnTxC6nRQxDIiKyq8ajRv/zn/+IXVKLGIZERGQ3rjx9ojkMQyIisgt3DUKAYUhERHYyZ84ctwxCgAt1ExGRnaxevRoAsHz5crcKQoBhSEREHVBVVQV/f38AQFhYGDZt2iRyRe3Dy6RERNQuarUaSUlJeO+998QupcMYhkRE1GZqtRqpqak4f/48li9fjqqqKrFL6hCGIRERtUl9ENYPltm7d6/5Uqm7YhgSEVGrNQ5Cdxs12hSGIRERtYqnBiHAMCQiolbatm2bRwYhwKkVRETUSk8//TQAYOzYsR4VhADDkIiImpGXl4fg4GAEBAQAuBaInoaXSYmIyCaNRoOhQ4di5MiRqKioELsch2LPkIiIrDRcdBsAysrKzL1DT8SeIRERWbC1+0RMTIzYZTkUw5CIiMzceRumjmAYEhERAO8NQoBhSEREdUpKSlBaWup1QQhwAA0REdVJSkrC3r17ERoa6lVBCDAMiYi8mkajwcWLF5GSkgKgNhC9ES+TEhF5qfp7hHfffTeOHDkidjmiYhgSEXmhhoNloqKiEBsbK3ZJomIYEhF5GW8eNdoUhiERkRdhENrGMCQi8hL5+fkMwiZwNCkRkZfo3LkzbrjhBgBgEDbCMCQi8hI+Pj7YtGkTioqKEB0dLXY5LoWXSYmIPJhGo8HSpUshCAKA2kBkEFpjz5CIyEM13oZp0aJFIlfkukTtGe7fvx+jR49G165dIZFIsHXr1hZf8+233yI5ORlKpRI9evTAqlWrnFApEQGAUW9CZakOgkkQuxRqwnt/3oL3HtlqNWp00qRJYpfm0kTtGVZWVqJ///547LHHcP/997d4/IULFzBy5Eg88cQT+O9//4tDhw5hxowZiIiIaNXriaj9sr9WQ/VVLnRVBgSFKzH04T7o1q+z2GVRnfce2QoEBgJBQSipKMSgG27GlfJ8jhptJVHDMC0tDWlpaa0+ftWqVYiPj8eKFSsAAH379sXRo0fx5ptvMgyJHCjn2O84vPmc+bH2SjW+fP8nTHw1BZ1CfEWsjMwCAwGJBCUVhXh7x3O4Up6P8MBoBmErudUAmiNHjmDEiBEWbffccw+OHj0KvV5v8zU6nQ5ardbii4ja5tcfLlu1GfUmnMsqFKEaauy9cRsAAAajHu/seAFXtPkID4rGs6PfwvZ5KpGrcw9uFYYFBQWIioqyaIuKioLBYMCVK1dsviY9PR3BwcHmL/6FRNR2EklT7U08QaKQyxQYOXgyIoNj8ezoZQgNiAQE3t9tDbcKQ8D6h69+uHBTP5QLFixAWVmZ+Uuj0Ti8RiJP0+eWLlZtch8peg6KEKEaamzmlj+b/3twrzux8MEPa4MQwMwN48Qqy624VRh26dIFBQUFFm2FhYWQy+Xo3Nn2jXxfX18EBQVZfBFR23S/MRy3j78OfoEKAEBoF3+MnJGETsG8Xyg2jUaDkSNHojQ/p7YXKAiQS+W1/11eLnZ5bsOt5hmmpKTg888/t2j7+uuvMXjwYCgUCpGqIvIOSamx6HdHDGquGqDsxJ83V2AxjzANyHw/E+/dtx6QSi16i9QyUcOwoqIC585dG6F24cIFHDt2DGFhYYiPj8eCBQuQl5eHdevWAQCmT5+Od999F3PnzsUTTzyBI0eOYM2aNdiwYYNYH4HIq0ilEgahi2g8j/D9998HAMzcNkHkytyTqGF49OhRpKammh/PnTsXADB58mSsXbsW+fn5UKvV5ucTEhKQmZmJOXPm4L333kPXrl3xz3/+k9MqiMircBsm+5MIgncNNdJqtQgODkZZWRnvHxKR22EQOub3uFsNoCEi8nZPPfWUVwehozAMiYjcyIcffoi0tDQGoZ251WhSIiJvpNPp4OtbO42la9euyMzMFLkiz8OeIRGRC9NoNLjxxhuxfv16sUvxaAxDIiIXVT9Y5uzZs1i8eDF0Op3YJXkshiERkQtqPGp0z5495kulZH8MQyIiF8PpE87HMCQiciEMQnEwDImIXMi6desYhCLg1AoiIheycOFCAMCkSZMYhE7E5diIiESWn5+PsLAwDpBpJS7HRkTkYdRqNW677TY88MADnDohIl4mJSISiVqtRmpqau1+hACKi4sRHR0tclXeiT1DIiIRNAzC+sEyDELxMAyJiJzMVhBysIy4GIZERE7EIHRNDEMiIifKy8tDYWEhg9DFcAANEZETpaSk4Ouvv0ZsbCyD0IUwDImIHEyj0aCkpARJSUkAagORXAsvkxIROVD9WqOpqak4ceKE2OVQExiGREQO0nDR7ZCQEISGhopdEjWBYUhE5ADcfcK9MAyJiOyMQeh+GIZERHaUl5fHIHRDHE1KRGRHISEhiI2NBQAGoRthGBIR2VGnTp2QmZmJ0tJSxMTEiF0OtRIvkxIRdZBGo8E777xjftypUycGoZthz5CIqAMaDpYBgKefflrkiqg92DMkImqnxqNGx44dK3ZJ1E4MQyKiduD0Cc/CMCQiaiMGoedhGBIRtUFVVRX3I/RADEMiojbw9/fHnDlz0LNnTwahB5EIgiCIXYQzabVaBAcHo6ysDEFBQWKXQ0RuqqqqCv7+/mKX4ZUc8XucPUMiohZoNBrcf//9KCoqMrcxCD0L5xkSETWj8TzCzz77TOSKyBHYMyQiakLjUaMrVqwQuyRyEIYhEZENnD7hXRiGRESNMAi9D8OQiKiRiRMnMgi9DMOQiKiRDz/8EHfccQeD0ItwNCkREQCDwQC5vPZXYq9evbBv3z5xCyKnYs+QiLyeWq1GUlISvvjiC7FLIZEwDInIq6nVaqSmpuLUqVN44YUXYDAYxC6JRMAwJCKvVR+E9YNldu7cab5USt6FYUhEXqlxEHKwjHcTPQwzMjKQkJAApVKJ5ORkHDhwoNnjV6xYgT59+sDPzw9xcXGYM2cOqqurnVQtEXkCBiE1JmoYbty4EbNnz8aiRYuQnZ2N22+/HWlpaVCr1TaPX79+PebPn49XXnkFp06dwpo1a7Bx40YsWLDAyZUTkTvLyMhgEJIFUbdwuvnmmzFo0CCsXLnS3Na3b1+MHTsW6enpVsfPmjULp06dwp49e8xtzz33HH744YcWe5T1uIUTERmNRixatAgzZ85kELohj9rCqaamBiqVCiNGjLBoHzFiBA4fPmzzNbfddhtUKhV++OEHAEBOTg4yMzMxatSoJs+j0+mg1WotvojI+xQWFsJoNAIAZDIZ/v73vzMIyUy0MLxy5QqMRiOioqIs2qOiolBQUGDzNQ8//DBeffVV3HbbbVAoFOjZsydSU1Mxf/78Js+Tnp6O4OBg8xe/+Ym8j0ajQUpKCqZMmWIORKKGRB9AI5FILB4LgmDVVm/fvn1YunQpMjIykJWVhc2bN2PHjh149dVXm3z/BQsWoKyszPyl0WjsWj8RubaGi24fPnzYYoNeonqiTagJDw+HTCaz6gUWFhZa9RbrvfTSS5g4cSKmTZsGALjxxhtRWVmJJ598EosWLYJUap3tvr6+8PX1tf8HICKXZ2v3icjISLHLIhckWs/Qx8cHycnJ2LVrl0X7rl27MGTIEJuvqaqqsgo8mUwGQRAg4jggInJB3IaJ2kLUpRbmzp2LiRMnYvDgwUhJScHq1auhVqsxffp0AMCkSZMQExNjHlk6evRoLFu2DAMHDsTNN9+Mc+fO4aWXXsKYMWMgk8nE/ChE5EIYhNRWoobh+PHjUVRUhCVLliA/Px/9+vVDZmYmunXrBqB2YmzDnuCLL74IiUSCF198EXl5eYiIiMDo0aOxdOlSsT4CEbmgM2fO4OLFiwxCajVR5xmKgfMMibzD7t270adPHwahB3LE73GuSEtEHkGj0UCn06FXr14AgOHDh4tcEbkT0adWEBF1VP09wmHDhuHcuXNil0NuiGFIRG6t4WAZTqWi9mIYEpHb4qhRsheGIRG5JQYh2RPDkIjczsWLFxmEZFcMQyJyO/7+/ggODmYQkt1wagURuZ2wsDDs3r0bVVVViI2NFbsc8gDsGRKRW9BoNFi7dq35cVhYGIOQ7IY9QyJyeQ0HywDAlClTxC2IPA57hkTk0hqPGr3rrrvELok8EMOQiFwWp0+QszAMicglMQjJmRiGRORytFotg5CcimFIRC4nKCgIkydPZhCS03A/QyJyWVqtlj+nZMURv8fZMyQil6BWqzFp0iRUVFSY2xiE5CycZ0hEolOr1UhNTTXPI1y3bp3IFZG3Yc+QiETVMAh79OiBpUuXil0SeSGGIRGJpnEQcrAMiYVhSESiYBCSK2EYEpHTCYKABx98kEFILoNhSEROJ5FIsHr1atx0000MQnIJHE1KRE5jMpkgldb+Dd6/f3989913kEgkIldFxJ4hETmJRqPBoEGDcPDgQXMbg5BcBcOQiByuftHt48ePY9asWTCZTGKXRGSBYUhEDtV494nPP//cfKmUyFXwO5KIHIbbMJG7YBgSkUMwCMmdMAyJyCFef/11BiG5DU6tICKHWLZsGQBg3rx5DEJyeQxDIrKboqIihIWFQSKRwMfHB++++67YJRG1Ci+TEpFdaDQa3HTTTZg9eza8bM9w8gAMQyLqsIaDZXbs2IHi4mKxSyJqE4YhEXWIrVGjnTt3FrssojZhGBJRu3H6BHkKhiERtQuDkDwJw5CI2uXo0aPIzc1lEJJH4NQKImqXcePGYdOmTRg8eDCDkNwew5CIWk2j0UAqlSImJgZAbSASeQJeJiWiVqm/Rzhs2DDk5eWJXQ6RXTEMiahFDQfLmEwm7kdIHoeXSYkIACCYTCjZsAHaLzIhGAwwarUwFhejMDAQk0+fwoVLlzhYhjwWw5CIAACFr7+O4n+vs2jL1+sx5Vg2NHo9EmJiGITksXiZlIhgLC9HyYb/WbTl6/WYolFDo9cjTqHAuhuTGITksRiGRISKQ4cg1NRYtMkktb8g4hQKrI2LR2RFhTjFETmB6GGYkZGBhIQEKJVKJCcn48CBA80eX1paipkzZyI6OhpKpRJ9+/ZFZmamk6ol8iwln27Cr0NuxaXZc6yei5Qr8O/4eKyNi0e0QgG/pBtFqJDIOdp1z/Dy5ct4/vnnsWfPHhQWFlpt12I0Glv1Phs3bsTs2bORkZGBW2+9Fe+//z7S0tLwyy+/ID4+3ur4mpoa3H333YiMjMSmTZsQGxsLjUaDwMDA9nwMIq8h1NSg7ItMXM1SQREfj5AHHkDpxo34fcXbFsdd0utxsroad9f9TEXKFQAAaadOiH7tNafXTeQsEqEdG4+lpaVBrVZj1qxZiI6OhkQisXj+vvvua9X73HzzzRg0aBBWrlxpbuvbty/Gjh2L9PR0q+NXrVqFN954A6dPn4ZCoWhr2QAArVaL4OBglJWVISgoqF3vQeROBJMJmieeROWhQ+Y2WZcuECorYSovN7dd0uvxmEaNPL0ey7vG4O7ISATcOgTKvokIf+pJSOQcb0euwRG/x9v13X3w4EEcOHAAAwYMaPeJa2pqoFKpMH/+fIv2ESNG4PDhwzZfs337dqSkpGDmzJnYtm0bIiIi8Mgjj2DevHmQyWQ2X6PT6aDT6cyPtVptu2smckeVBw9aBCEAGAsKLB7XB2H9YJl+SiV0CV3QlzvVk5do1z3DuLi4Du9kfeXKFRiNRkRFRVm0R0VFoaDRD2q9nJwcbNq0CUajEZmZmXjxxRfx1ltvYenSpU2eJz09HcHBweYvjoYjd2AoKUHZ9u3Q7toFU6OBLW1VfeaMzfardRdXGgdh/T1CyVMTOnReInfSrjBcsWIF5s+fj9zc3A4X0PgSqyAIVm31TCYTIiMjsXr1aiQnJ+Phhx/GokWLLC6zNrZgwQKUlZWZvzQaTYdrJnKk8m/24uywVFz6v3nIe/oZ/JoyBFUqVbvfT5mYaLNdamo6CA8OC0f/exiG5D1afZk0NDTUIqQqKyvRs2dP+Pv7W92/Ky4ubvH9wsPDIZPJrHqBhYWFVr3FetHR0VAoFBaXRPv27YuCggLU1NTAx8fH6jW+vr7w9fVtsR4iVyDU1ODSokVAg0v7QmUl1I9PRe9v90EWEmL9Gr0ekmbuoXcaMgQBqamo2LvXov1qjdFmEOYO7IJH3tlhvw9F5AZaHYYrVqyw64l9fHyQnJyMXbt2Wax8v2vXriYH4Nx66634+OOPYTKZIJXWdmp//fVXREdH2wxCIndT/etZmEpKrNoFnQ55zz2Prm/8A/KwsNpjT59GwWuv4epRFeSRkej81JMIm2Ddm5NIJIh99x2oP16Lir+9CWndHY5gqRTDAgKwr6LCHITSmwdhxDsrIVN0cujnJHI17RpNai8bN27ExIkTsWrVKqSkpGD16tX44IMPcPLkSXTr1g2TJk1CTEyMeWSpRqNBYmIipkyZgqeffhpnz57F448/jmeeeQaLFi1q1Tk5mpRcmb6wEOeG3tHk84qYGHTf9ClMFRW4MO5PMDWaCB+zYgWC7r3H6nXnVHuhmzDD6r6IIAgoM5kgRPpj8Ibt8LExpYnI1bjMaFKZTIb8/HxERkZatBcVFSEyMrLV8wzHjx+PoqIiLFmyBPn5+ejXrx8yMzPRrVs3AIBarTb3AIHagTtff/015syZg6SkJMTExODZZ5/FvHnz2vMxiFyOIjIS8tgYGC7a3iJJn5eHizNn4erx44CNn7PSzz6zCsPKo0dR82htEObr9fiwuAjzIiLhI5VCIpEgRCZDWXISg5C8Wrt6hlKpFAUFBVZheOnSJfTs2RNXr161W4H2xp4hubLKH36A+qnpQDt/hvxvvhnd/r3W/Dhnw0eoXvwPSGC51uifQ0LwUlQXAIBRCvQ4chD+wZ3t8AmIHE/0nuE///lPALX3ID788EMEBASYnzMajdi/fz+uv/56uxRG5G2K1qxB4Rtvdug9gtLSzP99dv8X0NsIwjiFAtPCaoPPoJAi/ssvGITk9doUhsuXLwdQe59h1apVFqM6fXx80L17d6xatcq+FRJ5AWNFBX5/L6P9byCRIGzSJISMfwgAULB/N/RPPm8zCOsHyxilQOLh7yDjcoZEbQvDCxcuAABSU1OxefNmhIaGOqQoIm9T89tvEKqq2v163z59ULJxI4rXrQMEAQLQbBAKAPrsP8ggJKrTrgE0exvNVyKi9hFMJpRt2w7tzq8AmcxqUIy8SxeYrl6Fqays2ffRnT5t8VgCwCQImJl30SoIDTKg9xdfQhHOS6NE9dq98u7Fixexfft2qNVq1DRaLmrZsmUdLozIEwkmE0o+3oDyr76CRKkEBMFq3VAzhQKGwkLAZGrXuaQSCV6MjMJrhZfxXkwsohUKVCol6LV/H5RBkS2/AZEXaVcY7tmzB2PGjEFCQgLOnDmDfv36ITc3F4IgYNCgQfaukchjXH5tKUo+/rjZY2Th4Qh58AGYKipQ8p//tvkcDZc0HOTvj03dukMqkcAEIGnnN/BhEBJZadfapAsWLMBzzz2Hn3/+GUqlEp999hk0Gg3uuOMOPPjgg/aukcgjGEpKUPLppy0fqNcj8tlnIQu2XnqtJfl6PR5W/4bT1dXmNqlEAgFA1LZP4FM3nYKILLUrDE+dOoXJkycDAORyOa5evYqAgAAsWbIEr7/+ul0LJPIUht9/B/T6Fo/zq7u6EjRqJNDEovW21A+W+am6Gq9cLjDvLCMA6L1vL8L7cKd6oqa0Kww7depk3iOwa9euOH/+vPm5K1eu2KcyIg/j26MH5JHNX6KUhYUh8rm5tccnJMC3lfN2G48aXdE1BpK6HmGPr76Eogt7hETNadc9w1tuuQWHDh1CYmIiRo0aheeeew4//fQTNm/ejFtuucXeNRJ5BIlcjujXXsXFZ2dDqFthRh4RgS5LFqMmJwfSwEAEpaWZpztUfPcddKdOtfi+zU2fiNjxGZTduzvwUxF5hnYtx5aTk4OKigokJSWhqqoKzz//PA4ePIhevXph+fLl5rVFXRGXYyOxGUtLUb5vH6RKPwQMuwNSpdLmcedH/RE1Da662NJcEHbbthWd+vRxwCcgEpcjfo+LumuFGBiG5A6qfvwRv02c1OJxC/MvYatWaxWE3Td9Av9+vEdInskRv8fbdc8QAEpLS/Hhhx9iwYIF5s18s7KykJdne7V9Imq91u5s/2JUF4wNCrIIQt/lrzEIidqoXfcMT5w4geHDhyM4OBi5ubl44oknEBYWhi1btuC3337DunXr7F0nkcerOHQIZdu342pWNvQaTZPHlRuNCKxbF9hfKsXforsCqB01qr7/Vtybdr8zyiXyKO3qGc6dOxdTpkzB2bNnoWxwvyMtLQ379++3W3FEnkgwGFB9+jT0hYXmtqKP1kIzdRq027Y3G4T5ej0e+C0X71753fI9ASjfTse9Sz90VNlEHq1dYfjjjz/iqaeesmqPiYlBQUFBh4si8lSVhw/j3F3DcWHsOJxLvROX5s2HsbwcVzJa3rGi4WCZz7ValNetYyoA0C16Ej3uGevg6ok8V7sukyqVSmi1Wqv2M2fOICIiosNFEXkiY0UlLj7zLEwVFXUNRpRt2wZZWBhM5eXNvtbWqNFAmax2d4q/zsXAh59w/Acg8mDt6hned999WLJkCfR1q2lIJBKo1WrMnz8f99/P+xVEtpze+b9rQdiAdvduSMOa3g6tuekT8jnT0ZdBSNRh7QrDN998E7///jsiIyNx9epV3HHHHejVqxcCAwOxdOlSe9dI5DTf5X+HCV9MwE3rb8LkLyfjWOGxDr+nSTBhwYEFSD9uezcXvUaD//Utha29KZoLQsm9Q3HdU892uD4iaudl0qCgIBw8eBB79+6FSqWCyWTCoEGDMHz4cHvXR+Q0OaU5+Muuv8AgGAAAWYVZmPb1NHw+9nNEB0S3+313/bYLO3J2QJogQZkfEHzV8nkJgAcPCbC1CukPVVU2g/Dc1KEY88L77a6JiCy1OQxNJhPWrl2LzZs3Izc3FxKJBAkJCejSpYvF1jFE7uazs5+Zg7CezqjDCtUKJEUm4WzJWfQN64sxvcbAT+4HAMgpy8H7x9/HryW/ok9YHzyV9BQSghMs3uNfP/0LCr2AP5wVcCEKGJBrfe6mfmruCw4GANzk728OwoKnRmPMnH908NMSUUNtWoFGEASMHj0amZmZ6N+/P66//noIgoBTp07hp59+wpgxY7B161ZH1tthXIGGmvLU10/hcP7hFo/rFtQNm0ZvgrZGiz9t/xPKdNd2oQ/yCcKTSU/i6OWjyC3LRbegbsj56SAWrtehc/NjZMzy9Xr4SaUIqZtLWE8AcHnpTKTeP6stH4vI4zji93ibeoZr167F/v37sWfPHqSmplo8980332Ds2LFYt24dJk1qeRkpIlcTGxgL5Ld83G/a3/DXw3/FBe0FiyAEAG2NFm8efdP8OFebixf2GFsdhJf0ejymUaOTVIp/xcWbA1EAoJkwBPcwCIkcok0DaDZs2ICFCxdaBSEA3HnnnZg/fz7Wr19vt+KInOnP1/+51cd+ceEL/FL0S6uOvUHduosv9UGo0etRaTLhqql2SI0A4OTdPXHPS2taXR8RtU2bwvDEiRO49957m3w+LS0Nx48f73BRRM721YWvsEy1DKHKpqc4tIfEJMDYitvoDYMyHzdyAAAgAElEQVSw8WCZouReePCdHXati4gstekyaXFxMaKiopp8PioqCiUlJR0uisiZ/vXzv7Bctdwh7z32iAmB1c0f01wQXrgpFqPWfe6Q2ojomjaFodFohFze9EtkMhkMBkOTzxO5Gr1Jj3/9/C+7v69EEPDY1ybck9X8JdLmgvDUTVG4f90uu9dGRNbaFIaCIGDKlCnw9fW1+bxOp7NLUUTOUllTaTUIxh5GqATc20IQAoBBEKAXBKsgPDLAF1PX7bN7XURkW5vCcPLkyS0ew5Gk5E5ClCHoGdIT50ub31G+rVJO21pPxlq8jw/WxsVDIZGYg/CX6wIw9X8/2rUeImpem8Lwo48+clQdRKIJV4bjPOwbhjVyCWrHgVrL1+txoaYGQzp1AlAbiKg7+mwPfzywnUFI5Gzt3umeyBMUVBbgh4If7PqeEaUCgqqaDsIpGjX+kncRhysrze0CgJ8HheO+zNbtcE9E9tWutUmJPEXR1SIITfTg2uPObCMe3yXAx2j9XONFtxMa9Ah/igfGf3zAbnUQUduwZ0he7bqw6xDuF26X97r3RyOmf9W6IGw4WKYgLhTjvz5llxqIqH0YhuSVqg21k/8UUgWW3rYUCqmi1a9NKBDwwAET7lGZEHfZhG6XBUgEAY982/ylUVtBeLwLcOeultdDJSLH4mVS8irf5X+H9O/TkVOWA4VUgT5hfbDgpgW4LvQ6nCw62eLr7ztiwoR91iNFy/wAX7318UUGQ5NBmN0ZmLCPPUIiV8AwJK9RUFmAmbtnosZUA6B2wv3PV37G5C8n44bON7T4+uBKAeP3254y0XiPwnohMhkG+dVu99QwCNWdgAmHGIREroJhSF7ji5wvzEHYkEEw4HTJ6RZfn1AgQN666YNmMokEr3WJRqnRiM5yOQQAxxKAR75kEBK5Et4zJK9hMDW9VKDO2PLqSZfCJGhNFubr9Xjr90IY67YKlUkk5iA8HMsgJHJFDEPyGvd0vwfSJr7l5ZKWL5IUhkrwzYDmt6CoHyyzprgYK678bm4XAOy/Dpi2m0FI5IoYhuQ1ugd3x9+H/h3+cn+L9hvDb8QjMcNa9R4f3CttclZi41Gjj4TUbgclAPi+FzB9O4OQyFXxniF5lbSENAzvNhzZl7NxQXsBcYFxSLmgQuXul7CuWywgab7nF3AV0MkAZaO5hM1NnzgbBDy2g0FI5MoYhuR1FFIFboq+CX3D+mLtidXYdvx9HI7r2mIQDjhvxDPbhTYF4dEwYNJhBiGRq2MYklf6VvMtntn7DEyCCehke0uyelKTgBc2GZFsYy1vgyDgyYsam0H4U2dgEqdPELkF3jMkr2MwGTBv/7zaIGyFW38RbAYhAMglErwQEYmedVsxmbdhCgDGMwiJ3AZ7huT6TEbg16+AvCwgsi/Qdwwg92n3250pOYNKQ2XLB9YZe9g6NAVBgKTusurQgAAM6dQJcokEAoDzSuCBowxCInfiEj3DjIwMJCQkQKlUIjk5GQcOtG71/v/973+QSCQYO3asgysk0RgNwMcPAf97BDjwJvDZVOCjNKCmqn3v98MH+HnTo4DQup0qbj5lRGyRZdslvR6TNWrk1lybwF8fhD/2AEYfYxASuRvRw3Djxo2YPXs2Fi1ahOzsbNx+++1IS0uDWq1u9nW//fYbnn/+edx+++1OqpREcXoHcG63ZVveUeDY+ja/lfG7Vdj+7ctI99W3OFim3sQ9AhoeeUmvx2MaNY5evYqXC/Ih1IWqAOBIDDA5k0FI5I5ED8Nly5Zh6tSpmDZtGvr27YsVK1YgLi4OK1eubPI1RqMREyZMwOLFi9GjRw8nVktOl3fUdvvFo0B1GfDdSmDHXODYBsBgvdRaQwt+XolFkeEwtjIIE/JNiCy/9rg+COsHy7we3RWSuh7hoXhg6h4GIZG7EjUMa2pqoFKpMGLECIv2ESNG4PDhpre1WbJkCSIiIjB16lRHl0hiC+9juz0kFlg9DPhqPnB0DbB1OrD+/tqAzPw/4M3rgLf7AweXAyYTfin6BV8qWr+w6E2njUhfe+34xkHYcLDM7kTgCe5HSOTWRB1Ac+XKFRiNRkRFRVm0R0VFoaCgwOZrDh06hDVr1uDYsWOtOodOp4NOd23dSa1W2/6CyflufAD4fhVw+edrbSHxtYNqinMsj72wH/jPnyx7k7v/Chj1OBvTRKjakJBnwNwt1/5SbC4It9wGLPqQQUjk7lxiNKmk0WWrhiP1GiovL8ejjz6KDz74AOHhrdudPD09HYsXL7ZLnSQChR/w2JeAai1wKQuITAQGPw58/qzt421cVv0u+0N8L32gVaebs9mIW87A4j7hPwoLbQbhZ6P88NJbWW3+SETkekQNw/DwcMhkMqteYGFhoVVvEQDOnz+P3NxcjB492txmMtVeypLL5Thz5gx69uxp8ZoFCxZg7ty55sdarRZxcXH2/BjUXiYTIG3FlXplEHDrM5ZtXW6sHVxjwx5/P3zdyR9KQYBOAnwR4AvkfN7iaUYcNSLljPUo07926QIUAPMiI81BeKILGIREHkTUMPTx8UFycjJ27dqFcePGmdt37dqF++67z+r466+/Hj/99JNF24svvojy8nK8/fbbNkPO19cXvr7NrzBCdmYyAqe/uNaT6zEMqNYCYT1qw+/kFmDPq0DxeSDqRiDtdaD7rS2/r64CUH0EXDgAXPzR+vnuQ/FejRqrfBvcG2zlFApFjQmP7bp27FWTCX51QR0ik2FFTEzt2wFYNxz4+7u8NErkSUS/TDp37lxMnDgRgwcPRkpKClavXg21Wo3p06cDACZNmoSYmBikp6dDqVSiX79+Fq8PCQkBAKt2EonRAHz8IHD+mwaNEgBC7b2+lFnAl/NqHwPA5Z+AtaOAaXuA2OSm39dQA/z7j8ClbNvPS2TQjnoda7+cADRcWaYVI0eHq4x47GsBsrrH9fcIJ4WGYUJoqPk4AcB/xwfg74ttBDERuTXRw3D8+PEoKirCkiVLkJ+fj379+iEzMxPdunUDAKjVakhbcymNxGfQAVtnNApCwBx8pWrgqwXXHjd8/qN7gP/7DfDtZPu9T+9oOggBQDAif81wVEd3blPJN58y4smvr9XTcLDMupJi/Ck4GH7S2m2b1o9U4G8MQiKPJBGEVl5H8hBarRbBwcEoKytDUFCQ2OV4lk8mAb9sa//rO0UCD38MxP2h9n5i8XmgUzjgFwqsHQ3k7m/25dUSCYbHdUWZTNbscQ39L93QqlGjn6ZK8MrKX9r/2YjIbhzxe1z0niF5iKLzHQtCAKgsBNYMBzpFAPqrQE0FIFUASeOB3IMtvlwpCHihuBQvh4fBVHd5VCoI5v9uyE8nID3D2Kog3J0MBiGRh2MYkn1oL9nvvSp/v/bfJj1w7L+tful9FZW4UafDHn9/aCUSrA2x/qsxuELAO+8Yoax73FwQfpkagOdW8tIokadjGJJ9dB0I+AYBOnEXNfhVocAnQQEokkrxvZ/S5gCav62+FoQAsLeiwmYQfvXnSDz3yrdOq52IxMMwJPvwDQBGvw1smQ4YdS0f7wBZvr54okskaqS2R5BKTQLeW25EWKMlTOtHjN4ZEGAOwsPjIjGXQUjkNThMk+yn35+AOSdrB72IYFVoUJNBCADLVxjRuaZ2osdlvR6VpmtTMCaEhl4bLDPGH9PSGYRE3oRhSPYVEAFE9LX/+0oVLR5yQdH0Ma/+y4AudR3WfL0ekzRqTL+osQhEAcC3ozrjlX+oOlotEbkZhiHZX6I9N1uWAL1GANKWr+h3r9HbbH/1QwOuu1zbI8zX6zGlbrBMocGACqMRQG0QbkkLwF/eannUKhF5Ht4zJPuLtGfPUADOfd2qI31tTJn92yoDepZYB2H9YJmoukujayaG461FB+xYN1H7GY1G6PW2/7jzBgqFArI2zBe2B4Yh2V/sHwDfYEBX5tTTXm20UtH4Xc0HYf09wkMjIvHWIt4jJPEJgoCCggKUlpaKXYroQkJC0KVLF5s7GDkCw5DsT+4D3PUykPmcU05XJJViSedQ/OjrY277wykD/nS05SD85pF4zHp5p1PqJGpJfRBGRkbC39/faUHgSgRBQFVVFQoLCwEA0dHRTjkvw5DsL/cgsHOh0073RJcInK3bmURiEvDCRiOSc6/tSVhuMqLcZLKeRzg+FnMZhOQijEajOQg7d27bGruexs/PD0Dtdn6RkZFOuWTKMCT7qtY6da7hTz4+5iCUGQW8vsaI+CLLY67zVWJtXBwCpbJr0yfGBuCVxbucUiNRa9TfI/T39xe5EtdQ/++g1+sZhuRmTEZg3X1AmcZppzzsVxuEib+ZMOczE4IbTJ+4bDBgQN1fmNf51q45IwBY+4gf/vEyl1gj1+SNl0Ztcfa/A8OQ7Ofc7toNfZ3kR18frAwOxrNbjbj11LWRpPX3CK8YDFgTF28ORAHAtqlR+McL+5xWIxG5B4Yh2U9JrlNOUwMgIyQYa0KCcOdxwWYQ1g+WiZLXfoubAHz5XG8seGK7U2okIvfCSfdkP91udfgp1DIZxsVEY01oMCCR4I8/XFtBpqlRowYJ8NXs6/E8g5DIYYYOHYqpU6datWdkZMDf3x/GugUuXBXDkOynSz9gyDMOeesqiQRzI8MxKq4r1D61y66N+MGAmLrBMk0F4beJwM75ffDc9C0OqYuIaqdDHDt2DMnJyVbPqVQq9O/f3+mT6NuKYUj2NeJVYPpBIOlhu77t8rAQ7Orkb96SKemsAVP31E6f+N1gsBmEl0KAThOH4fnJW+1aCxFZOnv2LMrLy5sMQ1vtroZhSPbX5UZAX2nXt9wW0Mn832FaAfM2X5tHGCyToZePr0UQ6qXAuWeSMXHcSrvWQeQOcn6vwN7ThSiurGn5YDtQqVSQyWRISkqyaNfpdDh58qRbhCEH0JBjKDq1fEwrHVL64Gpdj1AiCFi40QjFtVuF8JFIsDwmBmVGIyLkchgkwM8LbsFTj3xktxqI3IHBaMJznx7HtmOXAAA+cikWpF2Px25NcOh5s7KyYDQam5wjWR+GO3bswHPPPQeTyYR58+Zh2rRpDq2rLdgzJMdIngxI7PPt9VJEOCCRoNNVAfM/MSL+CnBJr8eqoisQ6hbn9pFIECGXo9wHUP/1TjwykUFI3ud/P2rMQQgANQYTluz4BecKKxx6XpVKhXHjxiE7O9via+HChVAqlUhMTITBYMDcuXPxzTffICsrC6+//jqKi4sdWldbMAwdqMZgwvrvf8OM9Sq8uuMX5F6x76VDl6Y+Agimlo9rQalUit/rbrzP2WrCwJzaIHxMo8Y/r1zB+8XXlpsxAbjw4u0YNf69Dp+XyB19c7rQqk0QgH1nrNvtKTs7G8OGDcOAAQMsvoqLi5GUlAS5XI4ffvgBN9xwA2JiYhAYGIiRI0di507XWQ6Rl0kd6C//VWFPg2/OjT9q8NlfhqBPl0ARq3KCqyXAt290+G1+8vHBwogwQCJBVLGApFzBHIT1g2XuCwoGAJT5AlV/m4jxo5y3JiqRqwn192lTuz3k5OSgtLQUgwYNsnouKyvLfIn00qVLiImJMT8XGxuLvLw8h9XVVuwZOojqtxKLIASACp0Bq749L1JFTlR0HjBc7dBbfBIYgEe6RkFXrsD/fWrEG2uMVkG4Ni4eXRQKHOkDFC+8FXcyCMnLTUzpBrnUchmz6GAl0m7s4rBzqlQqSKVSDBgwwKLdYDDgxIkT5jAUbOw36kpLzzEMHeT877av0TfV7lE69wKkina/vEgqwWudQ6EwAC9vMGLwOQHFV62DMLouCBOeewwjx39oxw9A5J4GxIXg34/fhJQendE1WIkx/bti45Mp8Pdx3EXArKws9O7dGwEBARbtJ0+eRHV1tbnHGBMTY9ETvHjxotO2Z2oNXiZ1kEHxITbbB8bZbvcoNRWAydDul8+IDIcgkWDwORM6lwM1JhOm2gjC492BxFnTkDLUOfsmErmDW3uF49Ze4U47X3p6OtLT063a+/fvb9EbvOmmm/Dzzz8jLy8PQUFByMzMxMsvv+y0OlvCnqGD9IoMxOONhjPHhvphRmovkSpyotyDqF0Wu+12K33xi7J2hwll3RQpH6kUs8Ij0F3hg7Vx8aiIU2DWdCkGzL4PKXczCIncgVwux1tvvYXU1FQMHDgQL7zwgkvt28ieoQO9PDoRI2/sgq9PXkb3zv4YOyjGoZcrRKPNB5RBgE/d3MKgmOaPt0EA8GJ4GLYHXrvUktVTAr0MUBiBUUFBuDswED4SCf7VT4I1I15H98Q/2ukDEJEzjBkzBmPGjBG7DJs88Dez68hWl+ClbSdxKl+LQF85LpfrMOfu68Quy34uHQO2zQQu/wzI/YDBjwMjXgOiBwCdewNFZ1v9Vl/5+1kEYU1RDY6vvYS/D4vG7EMyBFYDcqkE39wowSMPTmIQEpFdMQwdpKrGgMfX/oiSqtrdq8t1Bry95yy6higx/g/xIldnBwYd8PFDQMXlusdXge/eA858CZTmtnqOYZVEglfDw7Cj07WVK2qKanDh7xeg/12Pz5GP0892Q/fLwJVAAWvueg29E8c54AMRkTfjPUMH+eZ0oTkIG9qc5TrzajokZ9+1IGyoJKdNk+3fDAvBjoBO5gW4GwahIkKBrlO6Qi+X4GxXID3lGQYhETkEe4ZOZrIx18Yt2WGptasSCbY0ujTaMAgT5ifAp7MPIAj4T7+5GDD48Q6fk4jIFoahgwzuFmqzvWuIn5MrcZCEO2oHymjb19NVy2SY0DUKhiZ6hA2DcM+wVYjsfps9qycissDLpA7y/QXbC9BeKu3YyiwuQ+4DTNgExN7U5peuCg7CqLiuKJVf+1vs0r8v2QzCH259h0FIRA7HMHQQH5ntf1pFE+1uKSoRmLYL6H1Pqw4vl0jwVGQ43gsNNt8jrBfzWAwCkgIsgnBbtwnw653qiMqJiCzwMqmDpF4fCV+5FDqD5WCSEYlRIlXkQEWtW291RlQEjvkpzY9NehOkito/DhShCnSf2x0A4Gs0YufNr6LzDffbvVQiIls8qJviWs5errAKQgD4349qEaqxM5MJqCqu/V9dOVB8rsWX/OTjYxGENUU1OPfiOZQeLrU4TiII2HnvegYhETkVw9BBXtx6wmb76QI3X6j7xCfAin7APxKAf/YHzu0BAlpeEf/vYdfWZK0fLFNzuQaF2wph0psAQUCg0YhtAxehc9eBjvwERERWGIYOcvKSVuwS7C/nW2DzE9dGkJaqgc+mAoMmN/uyAz4+OKH0BWA9arT7/3WHVCHF5NIyHH70RyT0/7OjPwURkRWGoYMYO77Ju+vZ/KR1m8kAlGmafMkVmRQzukYBEkmT0ycCDQY8+/j319Y2JSK3M3ToUEydOtWqPSMjA/7+/jAajSJU1XoMQwfxkKn11+RlARUFtp/LPdTky1aEhDQbhFKTCV+P2QJFUFcHFU5EjiYIAo4dO2beyLchlUqF/v37QyaTiVBZ6zEMHcTj/mGripp+Tmh670KVX+3l0dLDpVZBqDCZsHPoOwiIuN7e1RKRE509exbl5eVNhqGtdlfDqRUOIpcCNZ50qTQ+BfAJqN24t7FmVqHpajDiokKBiD9GAABChoTU9ggFAd/flwlFWDdHVUzknU7tAL59HSjJBeJuBka8CkT2degpVSoVZDIZkpKSLNp1Oh1OnjyJZ5991qHntweP68C4Co8KQgDwDahdgq0N8stNmFhUBggCJBIJIkdHwqezD3xMJnx173oGIZG9/XYY+GQiUHAC0GmBc7uAtX8Eqh07oC8rKwtGoxH+/v6QSCTmL6VSCYPBYO4Zjhs3DqGhoXjggQccWk97MAyp9XSt/4HSlJlw20eVeGtdCVZdvIy7KqvQv1qHmcWlODTyE0R36e/AQom81NGPrHeNqboC/LLNoadVqVQYN24csrOzLb4WLlwIpVKJxMREAMAzzzyDdevWObSW9uJlUmq9yEQg90CLh2nKTBj270rklAgAjOhRUo0VhpraJ2dlAeE9HVsnkbfSlTfR7tieYXZ2NhYvXowBAwZYtL///vtISkqCvG4d4tTUVOzbt8+htbSXS/QMMzIykJCQAKVSieTkZBw40PQv3A8++AC33347QkNDERoaiuHDh+OHH35wYrVeyqBr1Q9UwyDsESrBvsmdEB0oBZRhwII8BiGRI10/yrpNIgX6jHTYKXNyclBaWopBgwZZPZeVleUWg2cAFwjDjRs3Yvbs2Vi0aBGys7Nx++23Iy0tDWq17WXL9u3bhz//+c/Yu3cvjhw5gvj4eIwYMQJ5eR6yaa6r2r0YOL6h2UNsBWFccN232PNnau87EpHjDJgADJ4KSOqmMfgGAWPeAcISHHZKlUoFqVRq1Ss0GAw4ceIEw7C1li1bhqlTp2LatGno27cvVqxYgbi4OKxcudLm8evXr8eMGTMwYMAAXH/99fjggw9gMpmwZ88eJ1fuZbL/2+zTTQah3A+Ypard8omIHEsqBf64DJhzEnh8JzD3FDDwUYeeMisrC71790ZAgOUfuydPnkR1dbXNHqMrEjUMa2pqoFKpMGLECIv2ESNG4PDhw616j6qqKuj1eoSFhdl8XqfTQavVWnxRGxj1wC/bAX1ls4fllZtQWNkoCAdMBF4sAMJ7OalYIgIABEUD8bc45WpMeno6Tp8+bdXev39/CIKAgQPdY61hUQfQXLlyBUajEVFRltsaRUVFoaCgidVOGpk/fz5iYmIwfPhwm8+np6dj8eLFHa7VK1VeAT4aCVw50+Kht8TKsWuiP2ICpbVB6B8OjH3XCUUSkbu45557kJWVhcrKSsTGxmLLli34wx/+IHZZAFxkNKmk0UavQt28tJb84x//wIYNG7Bv3z4olUqbxyxYsABz5841P9ZqtYiLi+tYwd7iwLJmg1BdZkLJVQH9u9Ten7gltu7bKSYZmLzDGRUSkRvZuXOn2CU0SdQwDA8Ph0wms+oFFhYWWvUWG3vzzTfxt7/9Dbt377Za9aAhX19f+Pr62qVer/PL1iafUpeZkPrvSpRWA99M8jcHIvqOAcb/x0kFEhHZh6j3DH18fJCcnIxdu3ZZtO/atQtDhgxp8nVvvPEGXn31VXz11VcYPHiwo8v0TiYTUPm7zafqgzCnRECIEgjzq+vFy32BMf90YpFERPYh+mXSuXPnYuLEiRg8eDBSUlKwevVqqNVqTJ8+HQAwadIkxMTEID09HUDtpdGXXnoJH3/8Mbp3727uVQYEBFiNZqJ2MpmAbTMBY4256bxCjnKpFEGF1bjb1qhRhR8w9zTgF9LMGxMRuSbRw3D8+PEoKirCkiVLkJ+fj379+iEzMxPdutWuW6lWqyGVXuvAZmRkoKamxmptu1deeQV//etfnVm657laCmStA059DlysXcigTCrB7MgIHPVToqaoBur1F1DdOAhTngaGvwLIFCJ/ACKi9hE9DAFgxowZmDFjhs3nGi/dk5ub6/iCvFFVMfDeTVaXRpeHhuKonxL6Er15P0JlhAK7J/jWBmG/B4B7XhOpaCIi+3CJMCSRafOBfw4EDFetntrbyQ8AIPOXQRFW2/OLn5+AMl0xEDEAeGCNU0slInIEhiEBGx+1GYQAEGAyoVgmg9RXiu5zu8NYZYQiVIHA2L8AdyxycqFERI4h+nJsJDJdBZB31OZTmjITwr64Nu1F6iuFIlSBPwT1Qs+7ltSOHiUi8gDsGXo7qRyQ+ViMHAUs1xp9FEDJqC4ol0qRKgnCnJFrRSmViMhRGIbeTqEEIq6v3Rm7TuNFt//W3Yi4i/lAcBzw9LfsERKRx+FlUm9X8Ttw+RfzwyZ3nwjvA/zlMIOQiGwaOnQopk6datWekZEBf39/GI1GEapqPYaht7vyKyAYADQThP0fBqYfBJRBIhdLRK5IEAQcO3bM5t6FKpUK/fv3h0wmE6Gy1uNlUm9mMgH6q4BMgarqGvMSa+YgDFEAz54AQmLFrpSIXNjZs2dRXl7eZBjedtttIlTVNuwZeqvC08C7ycD6+wGjHv4KCebc4oueFivLzGQQErmZ0upS/Ovnf2HxkcXYfn479Ca9w8+pUqkgk8msNk3Q6XQ4efKkW+x2z56ht9ryFFCcY9E08844PDayC/zD44Ckh4DE+0Qqjoja4/eq3zEhcwLyK/MBAJt+3YTMC5lYedfKVm2L115ZWVkwGo3w9/e3+XxycjI0Gg0mTpyIwsJCyOVyvPTSS3jwwQcdVlNbsWfojbSXgPxj0JSZcP8nVSiqMtW2VxTC/8kvgYfXMwiJ3NB/T/3XHIT1DuUdwuFLhx16XpVKhXHjxiE7O9via+HChVAqlUhMTIRcLseKFSvwyy+/YPfu3ZgzZw4qKysdWldbMAy9kU8naMqlGPbvSmw+ZcCTO6rN7ZD5iFsbEbXbmRLbm3H/WvKrQ8+bnZ2NYcOGYcCAARZfxcXFSEpKglwuR3R0NAYMGAAAiIyMRFhYGIqLix1aV1swDB1E4cL/sprftRi23mQeLLPiHmXtE8lTuPMEkRvrG9bXZvv1Ydc77Jw5OTkoLS3FoEGDrJ7Lysqyeb/w6NGjMJlMiIuLc1hdbeXCv7Ldm1zmuOvzHaHRaDBs2DDkXNaiR3Qo9s3shbhuCcCwhcDwxWKXR0QdMKHvBMQExFi0DYsdhluib3HYOVUqFaRSqbnXV89gMODEiRNWYVhUVIRJkyZh9erVDqupPTiAxkFkUgkAQewyLJiDMCcHPXr0wL59+1zqLzMi6phwv3B8MvoTbD+3HepyNQZFDcLw+OEOHzzTu3dvq83VT548ierqaoseo06nw7hx47BgwQIMGTLEYTW1B8PQQWr0JrFLsDJp0iQGIZGHC/IJwqOJjzrtfOnp6UhPT7dq79+/PwThWodAEARMmTIFd955JyZOnOi0+lqLl0kdxOB6WYgPP/wQw4YNYxASkdMdOnQIGzduxNatW80DbH766RIlMC4AABZLSURBVCexyzJjz9BBZFIJTCbxL5MaDAbI5bX/N/fs2RN79+4VuSIi8ka33XYbTCYX7CXUYc/QQSIDxZ+ioNFokJSUhC+++ELsUoiIXBrD0EF6RQaKev76wTKnTp3CCy+8AIPBIGo9RESujGHoIKfztaKdu/Go0Z07d5ovlRIRkTWGoYNcqaxp+SAH4PQJIqK2Yxh6EAYhEVH7MAw9SEZGBoOQiKgdeCPJg7z22msAgBkzZjAIiYjagGHoIIG+UpRWO35OzeXLlxEeHg6ZTAaZTGZzJQgiImoeL5M6iI/C8X9nqNVqDBkyBJMnT4bRaHT4+YiIPBV7hg5SVePYcFKr1UhNTUVOTu1u9VeuXEFUVJRDz0lE5KnYM3SQyEBfh713wyCsHyzDICQiMQ0dOhRTp061as/IyIC/v7/LX71iGDrI+D/YHsAi7+BOKraCkINliEhMgiDg2LFjNjfyValU6N+/P2QymQiVtR7D0EF0BtuLdMs6sOkvg5CIXNHZs2dRXl7eZBjaanc1DEMHOXL+is32miZCsjV+/fVXXLx4kUFIRM2quZiHyu++h1HrnGUhVSoVZDIZkpKSLNp1Oh1OnjzpFmHIATQO4u9r+5+2IxtODx8+HF988QX69OnDICQiK4LRiPyXX0bZ5i2AIEDi54eo+fMROv4hh543KysLRqMR/v7+Np9PTk5GeXk57rzzTuj1ehiNRjzzzDN44oknHFpXWzAMHWRKSnfsOVVo1d4zIqBN76PRaFBdXY3evXsDqA1EIiJbSjdvRtlnm82PhatXUbB4MTrdcjN8unVz2HlVKhXGjRuHl19+2aL9008/xbJly5CYmAiJRIJvv/0W/v7+qKqqQr9+/fCnP/0JnTt3dlhdbcHLpA5y+3URuDEmyKJNJpFg+fgBrX6P+rVGhw0bhrNnz9q7RCLyMBV7vrFuNJlQ7uBNvbOzszFs2DDzDvb1X8XFxUhKSoJcLodMJjP3HKurq2E0GiEI4m+AXo89Qwf6dPoQrDuSi4PnitA1WIkpt3bH9V2CWnwdYL3otlKpdGyxROT2ZMG2f7/IAlv3e6c9cnJyUFpaikGDBlk9l5WVZXG/sLS0FHfccQfOnj2LN954A+Hh4Q6rq63YM3QgpUKGJ4f2xLrHb8Lf709qdxBysAwRtUbI+IeBRlMYZBHhCLznHoedU6VSQSqVYsAAy6teBoMBJ06csAjDkJAQHD9+HBcuXMDHH3+My5cvO6yutmIYuhgGIRG1l/+ggYjLeA9+/ftDFhqKgOF3odu/10EW0Mlh58zKykLv3r0REGA5HuLkyZOorq622WOMiopCUlIS9u/f77C62koiuNJFWyfQarUIDg5GWVkZgoIcd+mgPS5evIg77riDQUjkhaqrq3HhwgUkJCR45G2Ry5cvw8/PD0FBQdBqtUhJScGGDRuspmPUa+7fwxG/x3nP0IX4+/sjODiYQUhEHufixYuYOnUqBEGAIAiYNWtWk0EoBoahCwkLC8Pu3btRVVWF2NhYscshIrKb5ORkHDt2TOwymsR7hiLTaDRYu3at+XFYWBiDkIjIydgzFFHDwTIAMGXKFHELIiLyUuwZiqTxqNG77rpL7JKIiLwWw1AEnD5BRORaGIZOxiAkInI9LhGGGRkZ5rkkycnJOHDgQLPHf/bZZ0hMTISvry8SExOxZcsWJ1XaMVqtlkFIROSCRA/DjRs3Yvbs2Vi0aBGys7Nx++23Iy0tDWq12ubxR44cwfjx4zFx4kQcP34cEydOxEMPPYTvv//eyZW3XVBQEB577DEGIRGRixF9BZqbb74ZgwYNwsqVK81tffv2xdixY5Genm51/Pjx46HVavHll1+a2+69916EhoZiw4YNLZ7PFVag0Wq1Lrf6DRGJy9NXoGkrZ69AI2rPsKamBiqVCiNGjLBoHzFiBA4fPmzzNf/f3r0HRVX3fwB/L3uBuHsDNkRNvCCaofBIKN76KZJm5jSiZSiNjllYqJVipoIFaprTTKKN5IxdFBw08zKMihXmNRNBSshLiOBPGMQrSCKX7/OHD/s8y0X3LHuD837N7Iwcvmf389nVfXvOfs9+T5482WT8uHHjWhxfXV2Ne/fu6d0sqbi4GDNmzEBlZaVuG4OQiMi2WPU6w/LyctTV1cHT01Nvu6enJ0pLS5vdp7S0VNL4VatWIT4+3jQFS9T4OsJvv/3WKnUQEdHjWf0zQwBQKBR6PwshmmwzdvySJUtw9+5d3a24uLj1BRug8azRhIQEizwuERFJZ9Uw7Ny5M5RKZZOjurKysiZHfw28vLwkjbe3t4erq6vezdx4+QQRyc2IESMwa9asJts3btwIR0dH1NXVWaEqw1k1DDUaDQIDA5GRkaG3PSMjA0OHDm12n5CQkCbjDx061OJ4S2MQEpHcCCGQk5Ojt5Bvg6ysLDz33HNQNlp02NZY/btJFy5ciMjISAQFBSEkJASbN29GUVER5s6dCwCYMWMGvL29dTNLY2JiMGLECKxZswaTJk3Cnj17cPjwYRw7dsyabQB49BdiypQpDEIikpVLly6hoqKixTAMDQ21QlXSWD0Mp06dips3b2LlypUoKSnBgAEDkJ6eju7duwMAioqKYGf33wPYoUOHIjU1FR9//DGWLVsGX19f7NixA8HBwdZqQUehUGDz5s2YM2cO0tLSGIREZHFX/7yJM+mFuHujCtpe7gh5xRfuno5mfcysrCwolcom6xNWV1fj/PnziImJMevjm4LVrzO0NHNcn1JfX68X2E+aAERE1JgprjMsLbiLH9adhaj/79u6o5sG0+Ofh8bBfMc+H374IdatW9fi78+dO6cLyqqqKvTr1w9Tpkx57D6yus6wPSgqKsKgQYP0vkKOQUhE1vDnkf/XC0IAqLr7EH+fvWHWx83KysLkyZORnZ2td/voo4/g4OAAf39/3diEhASbOJPXGMOwFYqKijB69Gjk5ubi3XffRX19vbVLIiIZq66qkbTdVLKzszFq1CgEBATo3W7duoWBAwdCpXp0VHrp0iX89ddfGD9+vFnrMQbD0EgNQdgwWWbfvn16p0qJiCytx8DOTTcqgB7PNrPdRAoKCnDnzh0MHjy4ye/Onj2rN6nmgw8+aPZrNm0B372N0DgIOWuUiGxBv2FPwy/EC/jPJzUqtR1GvtbXrBNosrKyYGdnh4CAAL3ttbW1yM3N1YXhnj170KdPH/Tp08dstbSG1WeTtjUMQiKyVXZ2CvzfTH/8a8IzuFv+Dzy6ucDeUW3Wxzx79ix69+4NZ2dnve3nz5/HgwcPdEeMp06dQmpqKtLS0lBZWYmamhq4urpi+fLlZq3PUJxNKtG8efOQlJTEICQik5LTqhVbt27Fn3/+aVOzSXlkKNH69esBAIsXL2YQEhG1EwxDA9y8eRMdO3aEQqGARqPBhg0brF0SEVGbFRUVZe0SmuAEmicoLi7GkCFD8N5770FmZ5SJiGSDYfgY//ul2+np6bh165a1SyIiIjNgGLagudUnOnXqZO2yiIjIDBiGzeAyTERE8sIwbIRBSETWxLkJj1j6eWAYNnLmzBkUFhYyCInIotTqRxfHV1VVWbkS29DwPDQ8L+bGSysamTx5Mnbu3ImgoCAGIRFZjFKphLu7O8rKygAAjo6OslwBRwiBqqoqlJWVwd3dHUql0iKPyzDEo1OjdnZ28Pb2BvAoEImILM3LywsAdIEoZ+7u7rrnwxJkH4YNnxHa2dkhMzNTF4hERJamUCig1Wrh4eGBmhrzLrtky9RqtcWOCBvIOgwbT5bheoREZAuUSqXFw0DuZDuB5tq1a5w1SkREAGR8ZDhhwgTOGiUiIgAyPjJkEBIRUQPZHRk2XMjZrVs37Nu3D25ubrh3756VqyIiIkM1vGeb8sJ82YVhRUUFgEcr1vfv39/K1RARkbFu3rwJNzc3k9yX7Fa6r6+vx/Xr1+Hi4mKxC1rv3bsHHx8fFBcXm2xVZlvT3nts7/0B7LG9kEOPd+/eRbdu3XD79m24u7ub5D5ld2RoZ2eHrl27WuWxXV1d2+1fzgbtvcf23h/AHtsLOfRoZ2e6aS+ynUBDRETUgGFIRESyp4yLi4uzdhFyoFQqMWrUKKhU7ffMdHvvsb33B7DH9oI9Sie7CTRERESN8TQpERHJHsOQiIhkj2FIRESyxzAkIiLZYxiayMaNG/HMM8/AwcEBgYGBOHr06GPH79q1C/7+/rC3t4e/vz92795toUqNJ6XH5ORkDB8+HB06dECHDh0wZswYnD592oLVSif1NWyQmpoKhUKBV155xcwVtp7UHu/cuYPo6GhotVo4ODigX79+SE9Pt1C1xpHa4xdffIG+ffviqaeego+PDxYsWIAHDx5YqFrpfv31V0ycOBFPP/00FAoFfvzxxyfuc+TIEQQGBsLBwQE9e/bEV199ZYFKjSO1vx9++AFjx45Fly5d4OrqipCQEBw8eFD6AwtqtdTUVKFWq0VycrLIy8sTMTExwsnJSVy9erXZ8SdOnBBKpVIkJiaK/Px8kZiYKFQqlTh16pSFKzec1B5ff/11kZSUJLKzs0V+fr548803hZubm7h27ZqFKzeM1P4aFBYWCm9vbzF8+HAxadIkC1VrHKk9VldXi6CgIDF+/Hhx7NgxUVhYKI4ePSpycnIsXLnhpPb4/fffC3t7e7Ft2zZx5coVcfDgQaHVasX8+fMtXLnh0tPTxdKlS8WuXbsEALF79+7Hji8oKBCOjo4iJiZG5OXlieTkZKFWq8XOnTstVLE0UvuLiYkRa9asEadPnxYXL14US5YsEWq1Wpw9e1bS4zIMTWDIkCFi7ty5etv8/PxEbGxss+MjIiJEeHi43rZx48aJadOmma3G1pLaY2O1tbXCxcVFfPPNN+Yor9WM6a+2tlYMGzZMfP3112LmzJk2H4ZSe9y0aZPo2bOnePjwoSXKMwmpPUZHR4sXXnhBb9vChQtFaGio2Wo0JUPCYtGiRcLPz09v21tvvSWef/55c5ZmEob01xx/f38RHx8vaR+eJm2lhw8fIisrC2FhYXrbw8LCcOLEiWb3OXnyZJPx48aNa3G8tRnTY2NVVVWoqalBx44dzVFiqxjb38qVK9GlSxfMmjXL3CW2mjE97t27FyEhIYiOjoanpycGDBiAxMRE1NXVWaJkyYzpMTQ0FFlZWbpT+AUFBUhPT8eECRPMXq+ltPR+c+bMGdTU1FipKvOpr69HRUWF5Pea9vv1BBZSXl6Ouro6eHp66m339PREaWlps/uUlpZKGm9txvTYWGxsLLy9vTFmzBhzlNgqxvR3/PhxbNmyBTk5OZYosdWM6bGgoAA///wzpk+fjvT0dFy6dAnR0dGora3F8uXLLVG2JMb0OG3aNNy4cQOhoaEQQqC2thZvv/02YmNjLVGyRbT0flNbW4vy8nJotVorVWYen3/+Oe7fv4+IiAhJ+zEMTaTxclBCiMcuESV1vC0wtubPPvsMKSkpyMzMhIODg7nKazVD+6uoqMAbb7yB5ORkdO7c2VLlmYSU17C+vh4eHh7YvHkzlEolAgMDcf36daxdu9Ymw7CBlB4zMzORkJCAjRs3Ijg4GJcvX0ZMTAy0Wi2WLVtmiXItornnpLntbV1KSgri4uKwZ88eeHh4SNqXYdhKnTt3hlKpbPI/z7Kysib/G2vg5eUlaby1GdNjg3Xr1iExMRGHDx/GwIEDzVmm0aT29/fff6OwsBATJ07UbauvrwcAqFQqXLhwAb6+vuYtWiJjXkOtVgu1Wg2lUqnb1q9fP5SWluLhw4fQaDRmrVkqY3pctmwZIiMjMXv2bADAs88+i/v372POnDlYunSpSZcIspaW3m9UKhU6depkpapMb8eOHZg1axbS0tKMOgPV9l9pK9NoNAgMDERGRobe9oyMDAwdOrTZfUJCQpqMP3ToUIvjrc2YHgFg7dq1+OSTT3DgwAEEBQWZu0yjSe3Pz88Pf/zxB3JycnS3l19+GaNHj0ZOTg58fHwsVbrBjHkNhw0bhsuXL+uCHgAuXrwIrVZrc0EIGNdjVVVVk8BTKpUQjyYXmq1WS2rp/SYoKAhqtdpKVZlWSkoKoqKisH37duM/75U8TYeaaJjOvWXLFpGXlyfmz58vnJycRGFhoRBCiMjISL3ZbMePHxdKpVKsXr1a5Ofni9WrV7eZSysM7XHNmjVCo9GInTt3ipKSEt2toqLCWi08ltT+GmsLs0ml9lhUVCScnZ3FvHnzxIULF8T+/fuFh4eH+PTTT63VwhNJ7XHFihXCxcVFpKSkiIKCAnHo0CHh6+srIiIirNXCE1VUVIjs7GyRnZ0tAIj169eL7Oxs3eUjsbGxIjIyUje+4dKKBQsWiLy8PLFlyxabvrRCan/bt28XKpVKJCUl6b3X3LlzR9LjMgxNJCkpSXTv3l1oNBoxePBgceTIEd3vRo4cKWbOnKk3Pi0tTfTt21eo1Wrh5+cndu3aZeGKpZPSY/fu3QWAJrcVK1ZYvnADSX0N/1dbCEMhpPd44sQJERwcLOzt7UXPnj1FQkKCqK2ttXDV0kjpsaamRsTFxQlfX1/h4OAgfHx8xDvvvCNu375thcoN88svvzT7b6uhr5kzZ4qRI0fq7ZOZmSkGDRokNBqN6NGjh9i0aZPlCzeQ1P5Gjhz52PGG4hJOREQke/zMkIiIZI9hSEREsscwJCIi2WMYEhGR7DEMiYhI9hiGREQkewxDIiKSPYYhURtVWFgIhUJh9MoZhq6STiQHDEMiGxUVFQWFQqG7derUCeHh4cjNzQUA+Pj4oKSkBAMGDHjs/cTFxSEgIMASJRO1WQxDIhsWHh6OkpISlJSU4KeffoJKpcJLL70E4NEXSnt5eUGlan7xGfGf9fmI6MkYhkQ2zN7eHl5eXvDy8kJAQAAWL16M4uJi3Lhxo8lp0szMTCgUChw8eBBBQUGwt7fHd999h/j4eJw7d053hLl161bd/ZeXl2Py5MlwdHRE7969sXfvXit1SmRdDEOiNqKyshLbtm1Dr169HrsO3aJFi7Bq1Srk5+cjLCwM77//Pvr37687wpw6dapubHx8PCIiIpCbm4vx48dj+vTpuHXrliXaIbIpXNyXyIbt378fzs7OAID79+9Dq9Vi//79j110duXKlRg7dqzuZ2dnZ6hUKnh5eTUZGxUVhddeew0AkJiYiC+//BKnT59GeHi4iTshsm08MiSyYQ0LBufk5OC3335DWFgYXnzxRVy9erXFfaQspDxw4EDdn52cnODi4oKysrJW1UzUFvHIkMiGOTk5oVevXrqfAwMD4ebmhuTkZMyePbvFfQzVeKVzhUKht7I9kVzwyJCoDVEoFLCzs8M///xj8D4ajQZ1dXVmrIqo7eORIZENq66uRmlpKQDg9u3b2LBhAyorKzFx4kSD76NHjx64cuUKcnJy0LVrV7i4uMDe3t5cJRO1STwyJLJhBw4cgFarhVarRXBwMH7//XekpaVh1KhRBt/Hq6++ivDwcIwePRpdunRBSkqK+QomaqMUQghh7SKIiIisiUeGREQkewxDIiKSPYYhERHJHsOQiIhkj2FIRESyxzAkIiLZYxgSEZHsMQyJiEj2GIZERCR7DEMiIpI9hiEREckew5CIiGTv3zZzDVfe4CD1AAAAAElFTkSuQmCC\n", 252 | "text/plain": [ 253 | "
" 254 | ] 255 | }, 256 | "metadata": {}, 257 | "output_type": "display_data" 258 | } 259 | ], 260 | "source": [ 261 | "X = tadasets.dsphere(n=400, r=1, d=4)\n", 262 | "\n", 263 | "alpha = cm.Alpha()\n", 264 | "filtration = alpha.build(X)\n", 265 | "dgms = alpha.diagrams(filtration)\n", 266 | "plot_diagrams(dgms)\n", 267 | "plt.show()" 268 | ] 269 | }, 270 | { 271 | "cell_type": "markdown", 272 | "metadata": {}, 273 | "source": [ 274 | "As expected, the only nontrivial homology is in $H_4$. \n", 275 | "\n", 276 | "Normally computing $H_4$ with that number of points would grind Rips to a halt, but it runs in a reasonable amount of time with Alpha. The bottleneck with Alpha is constructing the filtration and computing many circumcenters. Note that computing the persistence pairs takes even less time than H1 for Rips with only 100 points shown above." 277 | ] 278 | }, 279 | { 280 | "cell_type": "markdown", 281 | "metadata": {}, 282 | "source": [ 283 | "## Custom filtration\n", 284 | "\n", 285 | "If you have a point cloud and a set of simplices with times at which they are added, you can compute the persistence diagrams associated to the custom filtration you've defined. For instance, assume we want to compute a filtration where 4 vertices enter at time 0 and the edges and triangles are added in the pattern below (note how the triangles are not added the moment all of their edges are added, unlike Rips):\n", 286 | "\n", 287 | "\n", 288 | "\n", 289 | "Then we can execute the following code:" 290 | ] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": 10, 295 | "metadata": {}, 296 | "outputs": [ 297 | { 298 | "name": "stdout", 299 | "output_type": "stream", 300 | "text": [ 301 | "Constructing boundary matrix...\n", 302 | "Finished constructing boundary matrix (Elapsed Time 8.75e-05)\n", 303 | "Computing persistence pairs...\n", 304 | "Finished computing persistence pairs (Elapsed Time 5.08e-05)\n", 305 | "H0:\n", 306 | " [[ 0. 1.]\n", 307 | " [ 0. 1.]\n", 308 | " [ 0. 2.]\n", 309 | " [ 0. inf]]\n", 310 | "H1:\n", 311 | " [[2 4]\n", 312 | " [3 6]]\n" 313 | ] 314 | } 315 | ], 316 | "source": [ 317 | "filtration = [([0], 0), \n", 318 | " ([1], 0), \n", 319 | " ([2], 0), \n", 320 | " ([3], 0), \n", 321 | " ([0, 1], 1),\n", 322 | " ([0, 2], 1),\n", 323 | " ([1, 2], 2),\n", 324 | " ([0, 1, 2], 4),\n", 325 | " ([0, 3], 2),\n", 326 | " ([2, 3], 3),\n", 327 | " ([0, 2, 3], 6)]\n", 328 | "#Compute persistence diagrams\n", 329 | "dgms = cm.phat_diagrams(filtration, show_inf = True)\n", 330 | "print(\"H0:\\n\", dgms[0])\n", 331 | "print(\"H1:\\n\", dgms[1])" 332 | ] 333 | }, 334 | { 335 | "cell_type": "code", 336 | "execution_count": null, 337 | "metadata": {}, 338 | "outputs": [], 339 | "source": [] 340 | } 341 | ], 342 | "metadata": { 343 | "kernelspec": { 344 | "display_name": "Python 3", 345 | "language": "python", 346 | "name": "python3" 347 | }, 348 | "language_info": { 349 | "codemirror_mode": { 350 | "name": "ipython", 351 | "version": 3 352 | }, 353 | "file_extension": ".py", 354 | "mimetype": "text/x-python", 355 | "name": "python", 356 | "nbconvert_exporter": "python", 357 | "pygments_lexer": "ipython3", 358 | "version": "3.7.1" 359 | } 360 | }, 361 | "nbformat": 4, 362 | "nbformat_minor": 2 363 | } 364 | -------------------------------------------------------------------------------- /docs/notebooks/CustomExample.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 48 | 49 | 51 | 52 | 54 | image/svg+xml 55 | 57 | 58 | 59 | 60 | 61 | 66 | 72 | 78 | 84 | 90 | 95 | 100 | 105 | 110 | 0 122 | 1 134 | 2 146 | 3 158 | 1 170 | 1 182 | 2 194 | 2 206 | 3 218 | 4 230 | 6 242 | 243 | 244 | -------------------------------------------------------------------------------- /docs/reference/index.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | -------------- 3 | 4 | .. currentmodule:: cechmate 5 | 6 | Filtrations 7 | =================== 8 | 9 | .. autosummary:: 10 | :toctree: stubs 11 | :nosignatures: 12 | 13 | filtrations.Alpha 14 | filtrations.Cech 15 | filtrations.Extended 16 | filtrations.Rips 17 | 18 | 19 | Solvers 20 | ================ 21 | 22 | .. autosummary:: 23 | :toctree: stubs 24 | :nosignatures: 25 | 26 | solver.phat_diagrams 27 | 28 | Utilities 29 | ============= 30 | 31 | .. autosummary:: 32 | :toctree: stubs 33 | :nosignatures: 34 | 35 | utils.sparse_to_dense 36 | 37 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | numpydoc 4 | ipython 5 | nbsphinx -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | 6 | import re 7 | VERSIONFILE="cechmate/_version.py" 8 | verstrline = open(VERSIONFILE, "rt").read() 9 | VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" 10 | mo = re.search(VSRE, verstrline, re.M) 11 | if mo: 12 | verstr = mo.group(1) 13 | else: 14 | raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,)) 15 | 16 | with open('README.md') as f: 17 | long_description = f.read() 18 | 19 | setup(name='cechmate', 20 | version=verstr, 21 | description='Custom filtration constructors for Python', 22 | long_description=long_description, 23 | long_description_content_type="text/markdown", 24 | author='Christopher Tralie, Nathaniel Saul', 25 | author_email='chris.tralie@gmail.com, nat@saulgill.com', 26 | url='https://cechmate.scikit-tda.org', 27 | license='MIT', 28 | packages=find_packages(), 29 | include_package_data=True, 30 | install_requires=[ 31 | 'scipy', 32 | 'numpy', 33 | 'matplotlib', 34 | 'phat==1.5.0a0', 35 | 'persim' 36 | ], 37 | extras_require={ # use `pip install -e ".[testing]"` 38 | 'testing': [ 39 | 'pytest-cov', 40 | 'mock', 41 | 'kmapper', 42 | 'networkx', 43 | ], 44 | 'docs': [ # `pip install -e ".[docs]"` 45 | 'sktda_docs_config' 46 | ] 47 | }, 48 | python_requires='>=3.6', 49 | classifiers=[ 50 | 'Development Status :: 3 - Alpha', 51 | 'Intended Audience :: Science/Research', 52 | 'Intended Audience :: Education', 53 | 'Intended Audience :: Financial and Insurance Industry', 54 | 'Intended Audience :: Healthcare Industry', 55 | 'Topic :: Scientific/Engineering :: Information Analysis', 56 | 'Topic :: Scientific/Engineering :: Mathematics', 57 | 'License :: OSI Approved :: MIT License', 58 | 'Programming Language :: Python :: 3.6', 59 | 'Programming Language :: Python :: 3.7', 60 | 'Programming Language :: Python :: 3.8' 61 | ], 62 | keywords='persistent homology, persistence images, persistence diagrams, topology data analysis, algebraic topology, unsupervised learning, filtrations, Cech, Alpha, Rips' 63 | ) 64 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scikit-tda/cechmate/eb2075302ea56394fa656d225dbb6d96b723d917/test/__init__.py -------------------------------------------------------------------------------- /test/test_alpha.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import numpy as np 4 | from cechmate import Alpha 5 | 6 | @pytest.fixture 7 | def triangle(): 8 | x = np.array([ 9 | [0, 0.0], 10 | [1, 1.0], 11 | [0, 1.0], 12 | ]) 13 | 14 | return x 15 | 16 | def test_triangle(triangle): 17 | """ Expect 3 vertices, 3 edges, and a triangle 18 | 19 | """ 20 | a = Alpha(2).build(triangle) 21 | 22 | assert len(a) == 7 23 | 24 | vertices = [s for s in a if len(s[0]) == 1] 25 | edges = [s for s in a if len(s[0]) == 2] 26 | triangles = [s for s in a if len(s[0]) == 3] 27 | 28 | assert len(vertices) == 3 29 | assert len(edges) == 3 30 | assert len(triangles) == 1 31 | 32 | def test_precision(): 33 | X = np.array([4.148168134442850770e-16,2.579509799999999853e+00,3.403597400000000217e+00,1.846206783921459376e-15,1.148050980000000010e+01,1.237459740000000075e+01,6.751000000000002110e+00,1.148050980000000010e+01,3.403597400000001105e+00,6.751000000000000334e+00,2.579509799999999853e+00,1.237459740000000075e+01,2.447963127510063373e-15,1.522249019999999931e+01,3.403597400000001105e+00,1.016573157032889123e-15,6.321490199999999504e+00,1.237459740000000075e+01,6.751000000000001222e+00,6.321490199999999504e+00,3.403597400000001105e+00,6.751000000000002998e+00,1.522249019999999931e+01,1.237459740000000075e+01,2.447963127510063373e-15,1.522249019999999931e+01,1.453840260000000129e+01,1.016573157032889123e-15,6.321490199999999504e+00,5.567402600000001200e+00,6.751000000000001222e+00,6.321490199999999504e+00,1.453840260000000129e+01,6.751000000000002998e+00,1.522249019999999931e+01,5.567402600000002089e+00,4.148168134442850770e-16,2.579509799999999853e+00,1.453840260000000129e+01,1.846206783921459376e-15,1.148050980000000010e+01,5.567402600000001200e+00,6.751000000000002110e+00,1.148050980000000010e+01,1.453840260000000129e+01,6.751000000000000334e+00,2.579509799999999853e+00,5.567402600000001200e+00,1.806567600000000384e+00,1.313787599999999944e+00,4.837163199999999996e+00,1.806567600000001717e+00,1.021478759999999930e+01,1.380816320000000275e+01,8.557567600000002273e+00,1.021478759999999930e+01,4.837163200000000884e+00,8.557567600000000496e+00,1.313787599999999944e+00,1.380816320000000097e+01,1.169543239999999962e+01,1.313787599999999944e+00,4.837163200000000884e+00,1.169543240000000139e+01,1.021478759999999930e+01,1.380816320000000275e+01,4.944432400000001948e+00,1.021478759999999930e+01,4.837163200000000884e+00,4.944432400000000172e+00,1.313787599999999944e+00,1.380816320000000097e+01,1.806567600000002827e+00,1.648821239999999833e+01,4.837163200000000884e+00,1.806567600000001272e+00,7.587212400000000301e+00,1.380816320000000097e+01,8.557567600000002273e+00,7.587212400000000301e+00,4.837163200000000884e+00,8.557567600000002273e+00,1.648821239999999833e+01,1.380816320000000275e+01,1.169543240000000139e+01,1.648821239999999833e+01,4.837163200000001773e+00,1.169543240000000139e+01,7.587212400000000301e+00,1.380816320000000275e+01,4.944432400000001060e+00,7.587212400000000301e+00,4.837163200000000884e+00,4.944432400000002836e+00,1.648821239999999833e+01,1.380816320000000275e+01,1.169543240000000139e+01,1.648821239999999833e+01,1.310483680000000106e+01,1.169543240000000139e+01,7.587212400000000301e+00,4.133836800000000977e+00,4.944432400000001060e+00,7.587212400000000301e+00,1.310483679999999929e+01,4.944432400000002836e+00,1.648821239999999833e+01,4.133836800000000977e+00,1.806567600000002827e+00,1.648821239999999833e+01,1.310483679999999929e+01,1.806567600000001272e+00,7.587212400000000301e+00,4.133836800000000089e+00,8.557567600000002273e+00,7.587212400000000301e+00,1.310483679999999929e+01,8.557567600000002273e+00,1.648821239999999833e+01,4.133836800000001865e+00,1.169543239999999962e+01,1.313787599999999944e+00,1.310483679999999929e+01,1.169543240000000139e+01,1.021478759999999930e+01,4.133836800000000977e+00,4.944432400000001948e+00,1.021478759999999930e+01,1.310483679999999929e+01,4.944432400000000172e+00,1.313787599999999944e+00,4.133836800000000089e+00,1.806567600000000384e+00,1.313787599999999944e+00,1.310483679999999929e+01,1.806567600000001717e+00,1.021478759999999930e+01,4.133836800000000977e+00,8.557567600000002273e+00,1.021478759999999930e+01,1.310483679999999929e+01,8.557567600000000496e+00,1.313787599999999944e+00,4.133836800000000089e+00,2.446562400000000359e+00,3.521235599999999799e+00,3.556104400000000165e+00,2.446562400000001691e+00,1.242223559999999871e+01,1.252710439999999892e+01,9.197562400000002469e+00,1.242223559999999871e+01,3.556104400000001053e+00,9.197562400000000693e+00,3.521235599999999799e+00,1.252710439999999892e+01,1.105543759999999942e+01,3.521235599999999799e+00,3.556104400000000609e+00,1.105543760000000120e+01,1.242223559999999871e+01,1.252710440000000069e+01,4.304437600000001751e+00,1.242223559999999871e+01,3.556104400000000609e+00,4.304437600000000863e+00,3.521235599999999799e+00,1.252710439999999892e+01,2.446562400000002135e+00,1.428076440000000069e+01,3.556104400000000609e+00,2.446562400000000803e+00,5.379764400000000002e+00,1.252710439999999892e+01,9.197562400000000693e+00,5.379764400000000002e+00,3.556104400000000609e+00,9.197562400000002469e+00,1.428076440000000069e+01,1.252710440000000069e+01,1.105543760000000120e+01,1.428076440000000069e+01,3.556104400000001498e+00,1.105543759999999942e+01,5.379764400000000002e+00,1.252710440000000069e+01,4.304437600000000863e+00,5.379764400000000002e+00,3.556104400000000609e+00,4.304437600000002639e+00,1.428076440000000069e+01,1.252710440000000069e+01,1.105543760000000120e+01,1.428076440000000069e+01,1.438589560000000311e+01,1.105543759999999942e+01,5.379764400000000002e+00,5.414895600000001252e+00,4.304437600000000863e+00,5.379764400000000002e+00,1.438589560000000134e+01,4.304437600000002639e+00,1.428076440000000069e+01,5.414895600000001252e+00,2.446562400000002135e+00,1.428076440000000069e+01,1.438589560000000311e+01,2.446562400000000803e+00,5.379764400000000002e+00,5.414895600000000364e+00,9.197562400000000693e+00,5.379764400000000002e+00,1.438589560000000311e+01,9.197562400000002469e+00,1.428076440000000069e+01,5.414895600000002140e+00,1.105543759999999942e+01,3.521235599999999799e+00,1.438589560000000311e+01,1.105543760000000120e+01,1.242223559999999871e+01,5.414895600000002140e+00,4.304437600000001751e+00,1.242223559999999871e+01,1.438589560000000311e+01,4.304437600000000863e+00,3.521235599999999799e+00,5.414895600000000364e+00,2.446562400000000359e+00,3.521235599999999799e+00,1.438589560000000134e+01,2.446562400000001691e+00,1.242223559999999871e+01,5.414895600000001252e+00,9.197562400000002469e+00,1.242223559999999871e+01,1.438589560000000311e+01,9.197562400000000693e+00,3.521235599999999799e+00,5.414895600000001252e+00,1.972642200000000345e+00,1.313787599999999944e+00,2.212248600000000565e+00,1.972642200000001678e+00,1.021478759999999930e+01,1.118324860000000065e+01,8.723642200000002234e+00,1.021478759999999930e+01,2.212248600000001453e+00,8.723642200000000457e+00,1.313787599999999944e+00,1.118324860000000065e+01,1.152935779999999966e+01,1.313787599999999944e+00,2.212248600000001009e+00,1.152935780000000143e+01,1.021478759999999930e+01,1.118324860000000065e+01,4.778357800000001987e+00,1.021478759999999930e+01,2.212248600000001009e+00,4.778357800000000211e+00,1.313787599999999944e+00,1.118324860000000065e+01,1.972642200000002788e+00,1.648821239999999833e+01,2.212248600000001453e+00,1.972642200000001234e+00,7.587212400000000301e+00,1.118324860000000065e+01,8.723642200000002234e+00,7.587212400000000301e+00,2.212248600000001009e+00,8.723642200000002234e+00,1.648821239999999833e+01,1.118324860000000065e+01,1.152935780000000143e+01,1.648821239999999833e+01,2.212248600000001897e+00,1.152935780000000143e+01,7.587212400000000301e+00,1.118324860000000065e+01,4.778357800000001099e+00,7.587212400000000301e+00,2.212248600000001009e+00,4.778357800000002875e+00,1.648821239999999833e+01,1.118324860000000065e+01,1.152935780000000143e+01,1.648821239999999833e+01,1.572975140000000316e+01,1.152935780000000143e+01,7.587212400000000301e+00,6.758751400000001297e+00,4.778357800000001099e+00,7.587212400000000301e+00,1.572975140000000138e+01,4.778357800000002875e+00,1.648821239999999833e+01,6.758751400000001297e+00,1.972642200000002788e+00,1.648821239999999833e+01,1.572975140000000138e+01,1.972642200000001234e+00,7.587212400000000301e+00,6.758751400000000409e+00,8.723642200000002234e+00,7.587212400000000301e+00,1.572975140000000138e+01,8.723642200000002234e+00,1.648821239999999833e+01,6.758751400000001297e+00,1.152935779999999966e+01,1.313787599999999944e+00,1.572975140000000138e+01,1.152935780000000143e+01,1.021478759999999930e+01,6.758751400000001297e+00,4.778357800000001987e+00,1.021478759999999930e+01,1.572975140000000138e+01,4.778357800000000211e+00,1.313787599999999944e+00,6.758751400000000409e+00,1.972642200000000345e+00,1.313787599999999944e+00,1.572975140000000138e+01,1.972642200000001678e+00,1.021478759999999930e+01,6.758751400000000409e+00,8.723642200000002234e+00,1.021478759999999930e+01,1.572975140000000138e+01,8.723642200000000457e+00,1.313787599999999944e+00,6.758751400000000409e+00,4.236927600000000460e+00,0.000000000000000000e+00,1.971825800000000184e+00,4.236927600000002236e+00,8.900999999999999801e+00,1.094282580000000138e+01,1.098792760000000257e+01,8.900999999999999801e+00,1.971825800000001294e+00,1.098792760000000079e+01,0.000000000000000000e+00,1.094282580000000138e+01,9.265072399999999320e+00,0.000000000000000000e+00,1.971825800000000628e+00,9.265072400000001096e+00,8.900999999999999801e+00,1.094282580000000138e+01,2.514072400000001206e+00,8.900999999999999801e+00,1.971825800000000628e+00,2.514072399999999874e+00,0.000000000000000000e+00,1.094282579999999960e+01,9.265072399999999320e+00,0.000000000000000000e+00,1.597017420000000065e+01,9.265072400000001096e+00,8.900999999999999801e+00,6.999174200000001456e+00,2.514072400000001206e+00,8.900999999999999801e+00,1.597017420000000065e+01,2.514072399999999874e+00,0.000000000000000000e+00,6.999174200000000567e+00,4.236927600000000460e+00,0.000000000000000000e+00,1.597017420000000065e+01,4.236927600000002236e+00,8.900999999999999801e+00,6.999174200000000567e+00,1.098792760000000257e+01,8.900999999999999801e+00,1.597017420000000065e+01,1.098792760000000079e+01,0.000000000000000000e+00,6.999174200000000567e+00,2.492469199999999940e+00,0.000000000000000000e+00,1.526197213876682126e-16,2.492469200000001273e+00,8.900999999999999801e+00,8.971000000000000085e+00,9.243469200000001607e+00,8.900999999999999801e+00,1.111028306400386806e-15,9.243469199999999830e+00,0.000000000000000000e+00,8.971000000000000085e+00,1.100953080000000028e+01,0.000000000000000000e+00,6.741393327167100174e-16,1.100953080000000206e+01,8.900999999999999801e+00,8.971000000000001862e+00,4.258530800000002614e+00,8.900999999999999801e+00,8.057888636250504791e-16,4.258530800000000838e+00,0.000000000000000000e+00,8.971000000000000085e+00,0.000000000000000000e+00,0.000000000000000000e+00,6.224079800000000162e+00,1.431389970477174250e-15,8.900999999999999801e+00,1.519507980000000025e+01,6.751000000000002110e+00,8.900999999999999801e+00,6.224079800000001050e+00,6.751000000000000334e+00,0.000000000000000000e+00,1.519507980000000025e+01,0.000000000000000000e+00,0.000000000000000000e+00,1.171792020000000001e+01,1.431389970477174250e-15,8.900999999999999801e+00,2.746920200000000811e+00,6.751000000000002110e+00,8.900999999999999801e+00,1.171792020000000178e+01,6.751000000000000334e+00,0.000000000000000000e+00,2.746920200000000811e+00,1.555430400000000546e+00,2.180744999999999933e+00,3.504072600000000204e+00,1.555430400000001878e+00,1.108174500000000151e+01,1.247507260000000073e+01,8.306430400000001768e+00,1.108174500000000151e+01,3.504072600000001092e+00,8.306430399999999992e+00,2.180744999999999933e+00,1.247507260000000073e+01,1.194656960000000012e+01,2.180744999999999933e+00,3.504072600000001092e+00,1.194656960000000190e+01,1.108174500000000151e+01,1.247507260000000251e+01,5.195569600000002453e+00,1.108174500000000151e+01,3.504072600000001092e+00,5.195569600000000676e+00,2.180744999999999933e+00,1.247507260000000073e+01,1.555430400000002544e+00,1.562125499999999789e+01,3.504072600000001092e+00,1.555430400000001212e+00,6.720254999999999868e+00,1.247507260000000073e+01,8.306430400000001768e+00,6.720254999999999868e+00,3.504072600000001092e+00,8.306430400000001768e+00,1.562125499999999789e+01,1.247507260000000251e+01,1.194656960000000190e+01,1.562125499999999789e+01,3.504072600000001536e+00,1.194656960000000190e+01,6.720254999999999868e+00,1.247507260000000251e+01,5.195569600000001564e+00,6.720254999999999868e+00,3.504072600000000648e+00,5.195569600000003341e+00,1.562125499999999789e+01,1.247507260000000251e+01,1.194656960000000190e+01,1.562125499999999789e+01,1.443792740000000130e+01,1.194656960000000190e+01,6.720254999999999868e+00,5.466927400000000326e+00,5.195569600000001564e+00,6.720254999999999868e+00,1.443792739999999952e+01,5.195569600000003341e+00,1.562125499999999789e+01,5.466927400000001214e+00,1.555430400000002544e+00,1.562125499999999789e+01,1.443792740000000130e+01,1.555430400000001212e+00,6.720254999999999868e+00,5.466927400000000326e+00,8.306430400000001768e+00,6.720254999999999868e+00,1.443792740000000130e+01,8.306430400000001768e+00,1.562125499999999789e+01,5.466927400000001214e+00,1.194656960000000012e+01,2.180744999999999933e+00,1.443792740000000130e+01,1.194656960000000190e+01,1.108174500000000151e+01,5.466927400000001214e+00,5.195569600000002453e+00,1.108174500000000151e+01,1.443792740000000130e+01,5.195569600000000676e+00,2.180744999999999933e+00,5.466927400000000326e+00,1.555430400000000546e+00,2.180744999999999933e+00,1.443792739999999952e+01,1.555430400000001878e+00,1.108174500000000151e+01,5.466927400000000326e+00,8.306430400000001768e+00,1.108174500000000151e+01,1.443792740000000130e+01,8.306430399999999992e+00,2.180744999999999933e+00,5.466927400000000326e+00,2.670695600000000169e+00,0.000000000000000000e+00,1.596838000000000202e+00,2.670695600000001502e+00,8.900999999999999801e+00,1.056783800000000006e+01,9.421695600000001392e+00,8.900999999999999801e+00,1.596838000000001090e+00,9.421695599999999615e+00,0.000000000000000000e+00,1.056783800000000006e+01,1.083130440000000050e+01,0.000000000000000000e+00,1.596838000000000646e+00,1.083130440000000227e+01,8.900999999999999801e+00,1.056783800000000006e+01,4.080304400000001941e+00,8.900999999999999801e+00,1.596838000000000646e+00,4.080304400000000165e+00,0.000000000000000000e+00,1.056783800000000006e+01,1.083130440000000050e+01,0.000000000000000000e+00,1.634516200000000197e+01,1.083130440000000227e+01,8.900999999999999801e+00,7.374162000000001882e+00,4.080304400000001941e+00,8.900999999999999801e+00,1.634516200000000197e+01,4.080304400000000165e+00,0.000000000000000000e+00,7.374162000000000994e+00,2.670695600000000169e+00,0.000000000000000000e+00,1.634516200000000197e+01,2.670695600000001502e+00,8.900999999999999801e+00,7.374162000000000994e+00,9.421695600000001392e+00,8.900999999999999801e+00,1.634516200000000197e+01,9.421695599999999615e+00,0.000000000000000000e+00,7.374162000000000994e+00,1.531126800000000010e+00,0.000000000000000000e+00,5.723498000000000197e+00,1.531126800000001342e+00,8.900999999999999801e+00,1.469449799999999939e+01,8.282126800000002120e+00,8.900999999999999801e+00,5.723498000000001085e+00,8.282126800000000344e+00,0.000000000000000000e+00,1.469449799999999939e+01,1.197087320000000155e+01,0.000000000000000000e+00,5.723498000000001085e+00,1.197087320000000332e+01,8.900999999999999801e+00,1.469449800000000117e+01,5.219873200000002100e+00,8.900999999999999801e+00,5.723498000000001085e+00,5.219873200000000324e+00,0.000000000000000000e+00,1.469449799999999939e+01,1.197087320000000155e+01,0.000000000000000000e+00,1.221850200000000086e+01,1.197087320000000332e+01,8.900999999999999801e+00,3.247502000000001221e+00,5.219873200000002100e+00,8.900999999999999801e+00,1.221850200000000264e+01,5.219873200000000324e+00,0.000000000000000000e+00,3.247502000000000333e+00,1.531126800000000010e+00,0.000000000000000000e+00,1.221850200000000086e+01,1.531126800000001342e+00,8.900999999999999801e+00,3.247502000000000777e+00,8.282126800000002120e+00,8.900999999999999801e+00,1.221850200000000264e+01,8.282126800000000344e+00,0.000000000000000000e+00,3.247502000000000333e+00,3.375500000000001055e+00,4.450499999999999901e+00,4.485500000000000931e+00,3.375500000000002387e+00,1.335149999999999970e+01,1.345650000000000190e+01,1.012650000000000183e+01,1.335149999999999970e+01,4.485500000000001819e+00,1.012650000000000006e+01,4.450499999999999901e+00,1.345650000000000190e+01,1.012650000000000006e+01,4.450499999999999901e+00,4.485500000000000931e+00,1.012650000000000183e+01,1.335149999999999970e+01,1.345650000000000190e+01,3.375500000000002387e+00,1.335149999999999970e+01,4.485500000000000931e+00,3.375500000000001055e+00,4.450499999999999901e+00,1.345650000000000013e+01]) 34 | X = np.reshape(X, (int(X.size/3), 3)) 35 | alpha = Alpha() 36 | alpha_filtration = alpha.build(X) 37 | dgms = alpha.diagrams(alpha_filtration) 38 | assert(len(dgms) == 3) 39 | -------------------------------------------------------------------------------- /test/test_cech.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import numpy as np 4 | from cechmate import Cech 5 | 6 | @pytest.fixture 7 | def triangle(): 8 | x = np.array([ 9 | [0, 0.0], 10 | [1, 1.0], 11 | [0, 1.0], 12 | ]) 13 | 14 | return x 15 | 16 | def test_triangle(triangle): 17 | """ Expect 3 vertices, 3 edges, and a triangle 18 | 19 | """ 20 | c = Cech(2).build(triangle) 21 | 22 | assert len(c) == 7 23 | 24 | vertices = [s for s in c if len(s[0]) == 1] 25 | edges = [s for s in c if len(s[0]) == 2] 26 | triangles = [s for s in c if len(s[0]) == 3] 27 | 28 | assert len(vertices) == 3 29 | assert len(edges) == 3 30 | assert len(triangles) == 1 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/test_extended.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | import kmapper 5 | import networkx as nx 6 | 7 | from cechmate import Extended 8 | from cechmate.filtrations.extended import _lower_star 9 | 10 | @pytest.fixture 11 | def reeb(): 12 | """ This example taken from the reeb graph in Carri`ere 2017 of Figure 4""" 13 | 14 | f = { 15 | 1: 0.0, 16 | 2: 0.5, 17 | 3: 1.0, 18 | 4: 1.5, 19 | 5: 1.5, 20 | 6: 2.0, 21 | 7: 2.0, 22 | 8: 2.5, 23 | 9: 3.0, 24 | 10: 3.5 25 | } 26 | 27 | X = [ 28 | [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], 29 | [1,3], [2,4], [3,4], [3,5], [4,6], [5,7], [7,9], [7,8], [6,8], [8,10] 30 | ] 31 | 32 | expected = { 33 | 0: { 34 | "ordinary": [[0.5, 1.5]], 35 | "extended": [[0.0, 3.5]], 36 | }, 37 | 1: { 38 | "extended": [[2.5, 1.0]], 39 | "relative": [[3.0, 2.0]] 40 | } 41 | } 42 | 43 | return X, f, expected 44 | 45 | @pytest.fixture 46 | def X(): 47 | f = { 48 | 0: 0, 49 | 1: 1, 50 | 2: 2, 51 | 3: 3, 52 | 4: 4, 53 | 5: 5, 54 | 6: 6, 55 | } 56 | 57 | X = [ 58 | [0], [1], [2], [3], [4], [5], [6], 59 | [0,2], [1,3], [2,4], [3,4], [4,5], [4,6] 60 | ] 61 | 62 | return X, f 63 | 64 | @pytest.fixture 65 | def triangle(): 66 | X = [ 67 | [0],[1],[2], 68 | [0,1],[1,2],[0,2], 69 | [0,1,2] 70 | ] 71 | f = {0:4.1, 1:1.1, 2:0.1} 72 | 73 | return X, f 74 | 75 | def test_reeb_known(reeb): 76 | X, f, expected = reeb 77 | 78 | diagrams = Extended(X, f).diagrams() 79 | assert expected == diagrams 80 | 81 | def test_lower_star(triangle): 82 | X, f = triangle 83 | lstar = _lower_star(X, 0, f) 84 | 85 | assert len(lstar) == 4 86 | assert [0,1,2] in lstar 87 | 88 | f = {0:4, 1:1, 2:1} 89 | lstar = _lower_star(X, 1, f) 90 | 91 | assert len(lstar) == 2 92 | assert [0,1,2] not in lstar 93 | 94 | def test_lower_boundary_matrix(triangle): 95 | X, f = triangle 96 | 97 | bm, _ = Extended(X, f)._up_down_boundary_matrix(X, f) 98 | assert bm == [ # this was manually computed by @sauln 99 | (0, []), 100 | (0, []), 101 | (1, [0, 1]), 102 | (0, []), 103 | (1, [1, 3]), 104 | (1, [0, 3]), 105 | (2, [2,4,5]), 106 | (1, [3]), 107 | (1, [1]), 108 | (2, [4, 7, 8]), 109 | (1, [0]), 110 | (2, [2, 8, 10]), 111 | (2, [5, 7, 10]), 112 | (3, [6, 9, 11, 12]) 113 | ] 114 | 115 | def test_reduction(triangle): 116 | X, f = triangle 117 | e = Extended(X, f) 118 | bm, _ = e._up_down_boundary_matrix(X, f) 119 | 120 | pairs = e._compute_persistence_pairs(bm) 121 | assert pairs == [ 122 | (0, 7), (1, 2), (3, 4), (5, 6), (8, 9), (10, 11), (12, 13) 123 | ] 124 | 125 | 126 | 127 | class TestConstructors: 128 | def test_from_kmapper_simplices(self): 129 | km = kmapper.KeplerMapper() 130 | data = np.random.random((300, 5)) 131 | lens = km.project(data) 132 | graph = km.map(lens, data) 133 | 134 | e = Extended.from_kmapper(graph, lens) 135 | 136 | vs = [s for s in e.simplices if len(s) == 1] 137 | ls = [s for s in e.simplices if len(s) == 2] 138 | assert len(vs) == len(graph['nodes']) 139 | assert len(ls) == sum(len(v) for v in graph['links'].values()) 140 | assert len(e.simplices) == len(graph['simplices']) 141 | 142 | def test_from_kmapper_mapping_lens(self): 143 | km = kmapper.KeplerMapper() 144 | np.random.seed(0) 145 | data = np.random.random((300, 5)) 146 | lens = km.project(data) 147 | graph = km.map(lens, data) 148 | 149 | e = Extended.from_kmapper(graph, lens) 150 | 151 | assert len(graph['nodes']) == len(e.f) 152 | assert set(e.f.values()) == set(np.mean(lens[v]) for v in graph['nodes'].values()) 153 | 154 | def test_from_kmapper_mapping_nodes(self): 155 | km = kmapper.KeplerMapper() 156 | np.random.seed(0) 157 | data = np.random.random((300, 5)) 158 | lens = km.project(data) 159 | graph = km.map(lens, data) 160 | 161 | f = {k: np.random.random() for k in graph['nodes']} 162 | e = Extended.from_kmapper(graph, f) 163 | 164 | assert len(f) == len(e.f) 165 | assert set(e.f.values()) == set(f.values()) 166 | 167 | def test_from_nx_weights(self): 168 | g = nx.tutte_graph() 169 | vals = {n: np.random.random() for n in g.nodes} 170 | nx.set_node_attributes(g, vals, "weight_str") 171 | e = Extended.from_nx(g, "weight_str") 172 | 173 | assert len(e.simplices) == len(g.nodes) + len(g.edges) 174 | assert set(vals.values()) == set(e.f.values()) 175 | 176 | def test_from_nx_map(self): 177 | g = nx.tutte_graph() 178 | vals = {n: np.random.random() for n in g.nodes} 179 | e = Extended.from_nx(g, vals) 180 | 181 | assert len(e.simplices) == len(g.nodes) + len(g.edges) 182 | assert set(vals.values()) == set(e.f.values()) 183 | -------------------------------------------------------------------------------- /test/test_filtrations.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import numpy as np 4 | 5 | from cechmate import phat_diagrams, Alpha, Rips 6 | 7 | 8 | def test_phat_diagrams(): 9 | 10 | t = np.linspace(0, 2 * np.pi, 40) 11 | X = np.zeros((len(t), 2)) 12 | X[:, 0] = np.cos(t) 13 | X[:, 1] = np.sin(t) 14 | np.random.seed(10) 15 | X += 0.2 * np.random.randn(len(t), 2) 16 | rips = Rips(1).build(X) 17 | 18 | dgms = phat_diagrams(rips) 19 | 20 | 21 | def test_rips(): 22 | """ 23 | A test with a noisy circle, comparing H1 to GUDHI 24 | """ 25 | t = np.linspace(0, 2 * np.pi, 40) 26 | X = np.zeros((len(t), 2)) 27 | X[:, 0] = np.cos(t) 28 | X[:, 1] = np.sin(t) 29 | np.random.seed(10) 30 | X += 0.2 * np.random.randn(len(t), 2) 31 | rips = Rips(1).build(X) 32 | 33 | 34 | def test_alpha(): 35 | 36 | # Make a 3-sphere in 4 dimensions 37 | X = np.random.randn(15, 4) 38 | X = X / np.sqrt(np.sum(X ** 2, 1)[:, None]) 39 | tic = time.time() 40 | diagrams = Alpha().build(X) 41 | phattime = time.time() - tic 42 | -------------------------------------------------------------------------------- /test/test_miniball.py: -------------------------------------------------------------------------------- 1 | # 2 | # This code was adapted from miniball v1.0.2 (https://github.com/marmakoide/miniball) 3 | # to accommodate lru_caching 4 | # 5 | # Modifications contained herein are copyright under same MIT license 6 | # Modifications contained herein under Copyright (c) 2019 Nathaniel Saul 7 | # 8 | # Original Copyright and license: 9 | # Copyright (c) 2019 Alexandre Devert 10 | # 11 | # Permission is hereby granted, free of charge, to any person obtaining a copy 12 | # of this software and associated documentation files (the "Software"), to deal 13 | # in the Software without restriction, including without limitation the rights 14 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | # copies of the Software, and to permit persons to whom the Software is 16 | # furnished to do so, subject to the following conditions: 17 | # 18 | # The above copyright notice and this permission notice shall be included in all 19 | # copies or substantial portions of the Software. 20 | # 21 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | # SOFTWARE. 28 | 29 | from mock import patch 30 | 31 | import numpy as np 32 | 33 | import cechmate 34 | from cechmate.filtrations.miniball import miniball_cache, miniball 35 | 36 | 37 | @patch('cechmate.filtrations.miniball.get_boundary') 38 | def test_caching(mock_get_boundary): 39 | 40 | mock_get_boundary.side_effect = cechmate.filtrations.get_boundary 41 | points = np.array([ 42 | [0.0,0.1], [0.5, 0.5], [0.0, 1.0], [0.7, 0.7] 43 | ]) 44 | 45 | mb = miniball_cache(points) 46 | C, r = mb(frozenset(list(range(3))), frozenset([])) 47 | 48 | # import pdb; pdb.set_trace() 49 | count = mock_get_boundary.call_count 50 | assert count == 8 51 | # compute a subset 52 | C, r = mb(frozenset(list(range(2))), frozenset([])) 53 | assert mock_get_boundary.call_count == count 54 | 55 | # compute a superset 56 | C, r = mb(frozenset(list(range(4))), frozenset([])) 57 | assert mock_get_boundary.call_count == 12 58 | 59 | def test_vertex(): 60 | points = np.array([ 61 | [2.45, 0.5] 62 | ]) 63 | C, r = miniball(points) 64 | assert r == 0.0 65 | assert np.array_equal(C, points[0]) 66 | 67 | def test_simple_case(): 68 | points = np.array([ 69 | [0.0,0.0], [0.0, 0.5], [0.0, 1.0] 70 | ]) 71 | 72 | C, r = miniball(points) 73 | assert r == 0.5 74 | 75 | def test_bounding_ball_contains_point_set(): 76 | # Check that the computed bounding ball contains all the input points 77 | for n in range(1, 10): 78 | for count in range(2, n + 10): 79 | # Generate points 80 | S = np.random.randn(count, n) 81 | 82 | # Get the bounding sphere 83 | C, r2 = miniball(S) 84 | 85 | # Check that all points are inside the bounding sphere up to machine precision 86 | assert np.all(np.sum((S - C) ** 2, axis = 1) - r2**2 < 1e-12) 87 | 88 | 89 | 90 | def test_bounding_ball_optimality(): 91 | # Check that the bounding ball are optimal 92 | for n in range(2, 10): 93 | for count in range(n + 2, n + 30): 94 | # Generate a support sphere from n+1 points 95 | S_support = np.random.randn(n + 1, n) 96 | C_support, r2_support = miniball(S_support) 97 | 98 | # Generate points inside the support sphere 99 | S = np.random.randn(count - S_support.shape[0], n) 100 | S /= np.sqrt(np.sum(S ** 2, axis = 1))[:,None] 101 | S *= ((.9 * r2_support) * np.random.rand(count - S_support.shape[0], 1)) 102 | S = S + C_support 103 | 104 | # Get the bounding sphere 105 | C, r2 = miniball(np.concatenate([S, S_support], axis = 0)) 106 | 107 | # Check that the bounding sphere and the support sphere are equivalent 108 | # up to machine precision. 109 | assert np.allclose(r2, r2_support) 110 | assert np.allclose(C, C_support) 111 | 112 | -------------------------------------------------------------------------------- /test/test_rips.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import numpy as np 4 | from cechmate import Rips 5 | 6 | 7 | @pytest.fixture 8 | def two_points(): 9 | x = np.array([[0, 0.0], [1, 1.0]]) 10 | 11 | return x 12 | 13 | 14 | def test_two_points(two_points): 15 | r = Rips(2).build(two_points) 16 | 17 | assert len(r) == 3 18 | 19 | vertices = [s for s in r if len(s[0]) == 1] 20 | edges = [s for s in r if len(s[0]) == 2] 21 | 22 | assert len(vertices) == 2 23 | assert len(edges) == 1 24 | 25 | 26 | def test_correct_edge_length(two_points): 27 | r = Rips(2).build(two_points) 28 | 29 | vertices = [s for s in r if len(s[0]) == 1] 30 | edges = [s for s in r if len(s[0]) == 2] 31 | 32 | assert vertices[0][1] == 0.0 33 | assert edges[0][1] == np.sqrt(2) 34 | -------------------------------------------------------------------------------- /test/test_solver.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from cechmate import phat_diagrams 4 | 5 | 6 | def test_1d_solver(): 7 | 8 | filt = [ 9 | ([0], 0.0), 10 | ([1], 0.0), 11 | ([2], 0.0), 12 | ([3], 0.0), 13 | ([0, 1], 0.5), 14 | ([1, 2], 0.6), 15 | ] 16 | 17 | dgms = phat_diagrams(filt) 18 | 19 | assert len(dgms[0]) == 2 20 | assert [0, 0.5] in dgms[0].tolist() 21 | assert [0.0, 0.6] in dgms[0].tolist() 22 | 23 | 24 | def test_infs(): 25 | filt = [ 26 | ([0], 0.0), 27 | ([1], 0.0), 28 | ([2], 0.0), 29 | ([3], 0.0), 30 | ([0, 1], 0.5), 31 | ([1, 2], 0.6), 32 | ([0, 2], 0.7), 33 | ] 34 | 35 | dgms = phat_diagrams(filt, show_inf=True) 36 | 37 | assert len(dgms) == 2 38 | 39 | assert [0.7, np.inf] in dgms[1].tolist() 40 | -------------------------------------------------------------------------------- /test/test_utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from cechmate.utils import sparse_to_dense 4 | 5 | 6 | def test_sparse_bm_to_dense(): 7 | sparse = [ 8 | (0, []), 9 | (0, []), 10 | (1, [0, 1]), 11 | (0, []), 12 | (1, [1, 3]) 13 | ] 14 | expected = np.array([ 15 | [0,0,1,0,0], 16 | [0,0,1,0,1], 17 | [0,0,0,0,0], 18 | [0,0,0,0,1], 19 | [0,0,0,0,0] 20 | ], np.float32) 21 | 22 | dense = sparse_to_dense(sparse) 23 | np.testing.assert_array_equal(dense, expected) 24 | --------------------------------------------------------------------------------