Source code for homcloud.interface.optimal_volume

from homcloud.delegate import forwardable
import homcloud.optvol as optvol
import homcloud.plotly_3d as p3d


def validate_restore_data(cls, pd, data):
    assert pd.pdgm_id == data["pdgm_id"]
    assert cls.__name__ == data["type"]
    assert pd.degree == data["degree"]


class VolumeFailure:
    success = False

    def __init__(self, pair, message, status):
        self.pair = pair
        self.diagram = pair.diagram
        self.message = message
        self.status = status

    def birth_time(self):
        return self.pair.birth_time()

    def death_time(self):
        return self.pair.death_time()

    def dump_to_dict(self):
        assert self.diagram.pdgm_id
        return {
            "type": "VolumeFailure",
            "pdgm_id": self.diagram.pdgm_id,
            "degree": self.diagram.degree,
            "nth": self.pair.nth,
            "birth_time": self.pair.birth_time(),
            "death_time": self.pair.death_time(),
            "message": self.message,
            "status": self.status,
        }

    @classmethod
    def restore_from_dict(cls, pd, data, validate=True):
        if validate:
            validate_restore_data(cls, pd, data)
        return cls(pd.pair(data["nth"]), data["message"], data["status"])


[docs] @forwardable class Volume: """ This class represents a volume. This is the superclass of OptimalVolume, StableVolume and StableSubvolume. Notes: * point: list of float * cell: simplex or cube, simplex is used if the filtration is simplicial (alpha filtration) and cube is used if the filtration is cubical. * simplex: list of point * cube: tuple[point, list of {0, 1}], * ssimplex: list of string """ success = True def __init__(self, pair, cell_indices, result=None): self.pair = pair self.diagram = pair.diagram self.cell_indices = cell_indices self.result = result __delegator_definitions__ = {"diagram": ["get_geometry_resolver"]}
[docs] def birth_time(self): """ Returns: float: The birth time. """ return self.pair.birth_time()
[docs] def death_time(self): """ Returns: float: The death time. """ return self.pair.death_time()
[docs] def lifetime(self): """ Returns: float: The lifetime of the pair. """ return self.death_time() - self.birth_time()
[docs] def death_position(self, by="default"): """ Args: by (string): Format of return values, "default", "coordinates", "symbols", "vertex_indexes", or "vindexes" Returns: simplex: The death simplex. """ return self.get_geometry_resolver(by).resolve_cell(self.pair.death_index)
[docs] def points(self, by="default"): """ Args: by (string): Format of return values, "default", "coordinates", "symbols", "vertex_indexes", or "vindexes" Returns: list of point: All vertices in the optimal volume. """ return self.get_geometry_resolver(by).resolve_vertices(self.cell_indices)
[docs] def points_symbols(self): """ Returns: list of string: All vertices in the optimal volume in the form of the symbolic representation. Notes: This method is the same as :meth:`points` ("symbols")`. """ return self.points("symbols")
[docs] def boundary_points(self, by="default"): """ Args: by (string): Format of return values, "default", "coordinates", "symbols", "vertex_indexes", or "vindexes" Returns: list of point: All vertices in the volume optimal cycle. """ return self.get_geometry_resolver(by).resolve_boundary_vertices(self.cell_indices)
[docs] def boundary_points_symbols(self): """ Returns: list of string: All vertices in the volume optimal cycle in the form of the symbolic representation. Notes: This method is the same as :meth:`boundary_points` ("symbols"). """ return self.boundary_points("symbols")
[docs] def boundary(self, by="default", adjust_periodic_boundary=None): """ Args: by (string): Format of return values, "default", "coordinates", "symbols", "vertex_indexes", or "vindexes" adjust_periodic_boundary (Option[(float, float)]): periodic boundary treatment Returns: list of cells: All cells in the volume optimal cycle. """ return self.get_geometry_resolver(by, adjust_periodic_boundary).resolve_boundary(self.cell_indices)
[docs] def boundary_symbols(self): """ Returns: list of ssimplex: All simplices in the volume optimal cycle in the form of the symbolic representation. Notes: This method is the same as :meth:`boundary` ("symbols") """ return self.boundary("symbols")
[docs] def cells(self, by="default", adjust_periodic_boundary=None): """ Args: by (string): Format of return values, "default", "coordinates", "symbols", "vertex_indexes", or "vindexes" adjust_periodic_boundary (Option[(float, float)]): periodic boundary treatment Returns: list of cell: All cells in volume optimal cycles. """ return self.get_geometry_resolver(by, adjust_periodic_boundary).resolve_cells(self.cell_indices)
simplices = cells
[docs] def simplices_symbols(self): """ Returns: list of ssimplex: All simplices in volume optimal cycles in the form of the symbolic representation. Notes: This method is the same as :meth:`simplices` ("symbols") """ return self.simplices("symbols")
volume_simplices_symbols = simplices_symbols
[docs] def cubes(self, by="default"): """ Args: by (string): Format of return values, "default", "coordinates", "symbols", "vertex_indexes", or "vindexes" Returns: list of cube: All cubes in volume optimal cycles. """ return self.cells(by)
[docs] def children(self): """ Returns: list of :class:`Pair`: All children pairs. """ from .pd import Pair death_to_number = self.diagram.pd.death_index_to_pair_number def valid(d): return d != self.pair.death_index and d in death_to_number return [Pair(self.diagram, death_to_number[d]) for d in self.cell_indices if valid(d)]
[docs] def dump_to_dict(self): """Returns information about the optimal volume in the form of dict. Users can reconstruct :class:`OptimalVolume` and :class:`StableVolume` from the dictionary using :meth:`restore_from_dict`. The method is useful to compute large number of optimal/stable volumes. Returns: dict: The information about the optimal volume. """ assert self.diagram.pdgm_id return { "type": type(self).__name__, "pdgm_id": self.diagram.pdgm_id, "degree": self.diagram.degree, "nth": self.pair.nth, "birth_time": self.pair.birth_time(), "death_time": self.pair.death_time(), "cell_indices": self.cell_indices, }
[docs] @classmethod def restore_from_dict(cls, pd, data, validate=True): """Returns :class:`Volume` object reconstructed from pd and data. The data should be a dictionary returned by :meth:`dump_to_dict`. Args: pd (:class:`PD`): Persistence diagram object related to the volume data (dict): A dictionary which contains volume information validate (bool): Validate the information in the dictionary if True Returns: Volume: restored volume object """ if data["type"] == VolumeFailure.__name__: return VolumeFailure.restore_from_dict(pd, data, validate) elif data["type"] == OptimalVolume.__name__: return OptimalVolume.restore_from_dict(pd, data, validate) elif data["type"] == StableVolume.__name__: return StableVolume.restore_from_dict(pd, data, validate) elif data["type"] == StableSubvolume.__name__: return StableSubvolume.restore_from_dict(pd, data, validate) else: raise (ValueError("Unknown type: {}".format(data["type"])))
#: The alias of :meth:`death_position`. death_pos = death_position
[docs] def boundary_loop(self, by="default", adjust_periodic_boundary=None): """ Args: by (string): Format of return values, "default", "coordinates", "symbols", "vertex_indexes", or "vindexes" adjust_periodic_boundary (Option[(float, float)]): periodic boundary treatment Returns: Optional[list of point]: The list of points in the loop order. Return None if the boundary consists of multiple loops. Raises: ValueError: Raised if the dimension of the volume is not 2D, or if the loop is a self-loop or multi-edge loop. """ return self.get_geometry_resolver(by, adjust_periodic_boundary).resolve_boundary_loop(self.cell_indices)
[docs] def boundary_loop_symbols(self): """ Returns: Optional[List[str]]: The list of vertex symbols in the loop order. Return None if the boundary consists of multiple loops. Raises: ValueError: Raised if the dimension of the volume is not 2D, or if the loop is a self-loop or multi-edge loop. """ return self.boundary_loop("symbols")
[docs] def to_plotly3d_trace(self, color="green", width=1, name=""): """ Constructs a plotly's trace object to visualize the optimal volume Args: color (string or None): The name of the color width (int): The width of the line name (string): The name of the object Returns: plotly.graph_objects.Scatter3d: Plotly's trace object """ if self.diagram.filtration_type == "alpha": return p3d.Simplices(self.boundary(), color, width, name) elif self.diagram.filtration_type == "cubical": return p3d.Cubes(self.boundary(), color, width, name) else: raise RuntimeError(f"{self.filtration_type} cannot be renderred")
to_plotly3d = to_plotly3d_trace
[docs] def to_plotly3d_mesh(self, color="green", name=""): """ Constructs a plotly's trace object to visualize the face of an optimal volume Args: color (string or None): The name of the color name (string): The name of the object Returns: plotly.graph_objects.Mesh3d: Plotly's trace object """ if self.diagram.filtration_type == "alpha": if self.diagram.degree == 2: return p3d.SimplicesMesh(self.boundary(), color, name) elif self.diagram.degree == 1: return p3d.SimplicesMesh(self.simplices(), color, name) else: raise RuntimeError(f"dim {self.diagram.degree} volume is available for plotly") elif self.diagram.filtration_type == "cubical": if self.diagram.degree == 2: return p3d.CubesMesh(self.boundary(), color, name) else: raise RuntimeError(f"{self.filtration_type} cannot be renderred")
[docs] def to_pyvista_boundary_mesh(self, adjust_periodic_boundary=None): """ Constructs a PyVista's mesh object to visualize the boundary of an optimal/stable volume. Args: adjust_periodic_boundary (Option[(float, float)]): periodic boundary treatment Returns: pyvista.PolyData: PyVista's mesh object """ import homcloud.pyvistahelper as pvhelper if self.diagram.filtration_type == "alpha": if self.diagram.degree == 2: return pvhelper.Triangles(self.boundary("coordinates", adjust_periodic_boundary)) elif self.diagram.degree == 1: return pvhelper.Lines(self.boundary("coordinates", adjust_periodic_boundary)) else: raise RuntimeError(f"dim {self.diagram.degree} volume is available for pyvista") raise RuntimeError(f"{self.filtration_type} cannot be renderred")
[docs] def to_pyvista_volume_mesh(self, adjust_periodic_boundary=None): """ Constructs a PyVista's mesh object to visualize the internal face of a 1D optimal/stable volume. Args: adjust_periodic_boundary (Option[(float, float)]): periodic boundary treatment Returns: pyvista.PolyData: PyVista's mesh object """ import homcloud.pyvistahelper as pvhelper if self.diagram.filtration_type == "alpha": if self.diagram.degree == 1: return pvhelper.Triangles(self.cells("coordinates", adjust_periodic_boundary)) raise RuntimeError("Volume mesh can only be applied to 1d alpha volume")
[docs] class OptimalVolume(Volume): """ This class represents an optimal volume. """
[docs] def birth_position(self, by="default"): """ Args: by (string): Format of return values, "default", "coordinates", "symbols", "vertex_indexes", or "vindexes" Returns: simplex: The birth simplex. """ return self.get_geometry_resolver(by).resolve_cell(self.pair.birth_index)
#: The alias of :meth:`birth_position`. birth_pos = birth_position
[docs] def stable_subvolume(self, threshold, solver=None, solver_options=[]): """ Returns the stable subvolume of the optimal volume. Args: threshold (float): The noise bandwidth. Returns: StableSubvolume: The stable subvolume. """ lp_solver = optvol.find_lp_solver(solver, solver_options) ssvfinder = optvol.TightenedSubVolumeFinder( self.diagram.optvol_optimizer_builder(None, None, lp_solver), self.diagram.pd.index_to_level, threshold ) result = ssvfinder.find(self.pair.birth_index, self.pair.death_index, self.cell_indices) return StableSubvolume(self.pair, result.cell_indices, threshold, result)
[docs] @classmethod def restore_from_dict(cls, pd, data, validate=True): if validate: validate_restore_data(cls, pd, data) return cls(pd.pair(data["nth"]), data["cell_indices"])
tightened_subvolume = stable_subvolume
[docs] class EigenVolume(Volume): """ This class represents an "eigenvolume". It is the superclass of StableVolume and StableSubvolume. Attributes: threshold (float): The threshold used for the computation of the eigenvolume. """ def __init__(self, pair, cell_indices, threshold, result=None): super().__init__(pair, cell_indices, result) self.threshold = threshold
[docs] def dump_to_dict(self): data = super().dump_to_dict() data["threshold"] = self.threshold return data
[docs] @classmethod def restore_from_dict(cls, pd, data, validate=True): if validate: validate_restore_data(cls, pd, data) return cls(pd.pair(data["nth"]), data["cell_indices"], data["threshold"])
[docs] class StableVolume(EigenVolume): """ This class represents a stable volume. The instance is given by :meth:`Pair.stable_volume`. """ pass
[docs] class StableSubvolume(EigenVolume): """ This class represents a stable subvolume. The instance is given by :meth:`OptimalVolume.stable_subvolume`. """ pass